Sabtu, 24 Desember 2016

Om Telolet Om: WeMos D1 OLED Shield (ESP-8266EX, NodeMCU, Lua)

Pada tulisan ini akan dibuat script untuk menampilkan pesan "Selamat Hari Natal" pada WeMos D1 OLED Shield.

Gambar 1. WeMos D1 mini OLED Shield

Untuk dapat menggunakan OLED Shield, anda perlu melakukan kustomisasi firmware NodeMCU dengan memasukan module i2c dan u8g, dan memilih ukuran OLED yang digunakan.

Gambar 8. Menentukan ukuran layar OLED

Sehingga ketika boot akan tampil module yang ditandai warna merah.
NodeMCU custom build by frightanic.com
    branch: master
    commit: 81ec3665cb5fe68eb8596612485cc206b65659c9
    SSL: true
    modules: bit,cjson,crypto,dht,file,gpio,http,i2c,net,node,rtctime,sntp,tmr,u8g,uart,websocket,wifi
 build     built on: 2016-12-23 16:47
 powered by Lua 5.1.4 on SDK 1.5.4.1(39cb9a32)
Adapun script yang digunakan untuk menampikan pesan adalah sebagai berikut:

sda = 2 -- SDA Pin
scl = 1 -- SCL Pin

function init_OLED(sda,scl) --Set up the u8glib lib
     sla = 0x3C
     i2c.setup(0, sda, scl, i2c.SLOW)
     disp = u8g.ssd1306_64x48_i2c(sla)
     disp:setFont(u8g.font_6x10)
     disp:setFontRefHeightExtendedText()
     disp:setDefaultForegroundColor()
     --disp:setRot90()           -- Rotate Display if needed
     --disp:setRot180()           -- Rotate Display if needed
     --disp:setRot270()           -- Rotate Display if needed
end

function print_OLED()
   disp:firstPage()
   --disp:setScale2x2()
   repeat
     --disp:drawFrame(0,0,64,47)
     disp:drawStr(0, 10, str1)
     disp:drawStr(0, 30, str2)
   until disp:nextPage() == false
   --disp:undoScale()
end

-- Main Program
str1="  Selamat   "
str2=" Hari Natal "
init_OLED(sda,scl)
print_OLED()

Jumat, 23 Desember 2016

Om Telolet Om: WeMos D1 mini pro as APSTATION (ESP-8266EX, NodeMCU, Lua)

Pada tulisan ini akan memberi contoh script untuk mengaktifkan WeMos D1 mini pro sebagai APSTATION yang terdiri dari tiga script utama yaitu init.lua yang bertujuan untuk menghindari terjebak reboot ulang tak terhingga akibat adanya kesalahan script pada init.lua, konfigurasi untuk APSTATION adalah disimpan pada apconfig.lua, dan konfigurasi untuk sebagai STATION adalah disimpan pada credentilas.lua. Script utama anda adalah disimpan pada file app.lua, yang secara otomatis akan diaktifkan dalam waktu tiga detik. Jika ada kesalahan pada script app.lua, maka anda dapat menjalankan perintah file.remove("app.lua") dalam waktu tiga detik sehingga terhindar dari jebakan reboot ulang tak terhingga.

Gambar 1. WeMos D1 mini pro

apconfig.lua
cfg={}
cfg.ssid="myssid"
cfg.pwd="password"
cfg.auth=wifi.WPA_WPA2_PSK

dhcp_config ={}
dhcp_config.start = "192.168.4.100"

credentials.lua
-- Credentials
SSID = "rapsberrypi"
PASSWORD = "password"

init.lua
-- aplikasi ini berfungsi menghindarkan module anda terjebak
-- pada kondisi restart tak terhingga jika terjadi bugs pada
-- init.lua.

-- aplikasi ini dapat mengaktifkan board anda sebagai access
-- point, jika anda menginginkan hal ini, maka anda perlu
-- menyediakan file apconfig.lua.

-- aplikasi dapat berjalan pada dua modus, yaitu standalone
-- atau terkoneksi ke wifi.

-- agar dapat terkoneksi ke wifi, maka username dan password
-- didefinisikan pada file credentials.lua

-- aplikasi akan menunggu selama 3 detik sebelum file app.lua
-- dieksekusi, jika terjadi bugs pada app.lua, maka anda
-- dapat menjalankan file.remove("app.lua") sehingga tidak
-- terjebak pada kondisi restart tak terhingga.

function startup()
    print("Running")
    -- aplikasi autoexec anda  sekarang adalah 'app.lua'
    if file.open("app.lua") == nil then
        print("app.lua tidak ditemukan, silakan buat app.lua sebagai auto executable.")
    else 
        file.close("app.lua")
        dofile("app.lua")
    end
end

if file.open("apconfig.lua") ~= nil then
    print("activate as access point")
    dofile("apconfig.lua")
    -- set mode as stationAP  
    wifi.setmode(wifi.STATIONAP)
    wifi.setphymode(wifi.PHYMODE_G)
    wifi.ap.config(cfg)
    print("AP IP Address:" .. wifi.ap.getip())   
    pool_ipstart, pool_ipend = wifi.ap.dhcp.config(dhcp_config)
    if wifi.ap.dhcp.start() then
        print("DHCP server started, pool addr from " .. pool_ipstart .. " to " .. pool_ipend .. "!")
    else
        print("DHCP server failed!")
    end
else
    -- default mode as station
    wifi.setmode(wifi.STATION)
    wifi.setphymode(wifi.PHYMODE_N)
end

-- periksa keberadaan file credentials.lua yang mendefinisikan
-- 'SSID' and 'PASSWORD' wifi
if file.open("credentials.lua") == nil then
    -- bagian module standalone
    print("Boot sebagai module standalone")
    print("You have 3 seconds to abort")
    print("Waiting...")
    -- jalankan fungsi startup setelah 3 detik      
    tmr.alarm(0, 3000, 0, startup)
else   
    -- bagian module koneksi wifi
    file.close("credentials.lua")
    -- muat variable SSID dan PASSWORD
    dofile("credentials.lua")
    print("Koneksi ke " .. SSID)
    station_cfg={}
    station_cfg.ssid=SSID
    station_cfg.pwd=PASSWORD
    wifi.sta.config(station_cfg, false)
   
    --wifi.sta.config(SSID, PASSWORD)
    -- wifi.sta.connect() not necessary because config() uses auto-connect=true by default

    -- mencoba koneksi setiap detik sampai tmr.stop(1)
    trycount = 1
    tmr.alarm(1, 1000, 1, function()
        if wifi.sta.getip() == nil then
            print("trying " .. trycount .. " second(s)")
            trycount = trycount + 1
        else
            -- berhasil koneksi dan mendapatkan IP Address
            tmr.stop(1)
            print("WiFi connected")
            print("Client IP Address:" .. wifi.sta.getip())
            print("You have 3 seconds to abort")
            print("Waiting...")
            -- issue file.remove("app.lua") here
            -- jalankan fungsi startup setelah 3 detik      
            tmr.alarm(0, 3000, 0, startup)
        end       
    end)
end

Om Telolet Om: WeMos DH11 Shield (ESP8266EX, NodeMCU, Lua)

Pada tulisan ini akan membahas pembuatan Lua script untuk mengukur temperatur dan humidity dengan menggunakan perangkat WeMos DH11, pemakai perangkat IoT dapat membantu para peneliti untuk merekam kondisi dilapangan dari waktu ke waktu sehingga cukup berkonsentrasi pada variabel lainnya, sedangkan variabel terkait dengan temperatur dan kelembaban dapat secara otomatis direkam oleh perangkat IoT.

Gambar 1. WeMos DHT11 Shield

Interface antara DH11 shield dengan WeMos board adalah melalui pin D1, keuntungan dari pemakaian shield adalah anda tidak membutuhkan bread-board maupun menggunakan jumper untuk menghubungkan antara WeMos board dengan sensor DH11, tetapi cukup shield DH11 ditumpukan diatas WeMos board.

pinDHT11 = 4 -- pin D4 pada board WeMos D1 mini

function readFromDHT11()
    status, temp, humi, temp_dec, humi_dec = dht.read11(pinDHT11)

    if status == dht.OK then
        wib = rtctime.epoch2cal(rtctime.get()+7*60*60) -- tambah 7 WEST INDONESIA TIME
        timeStr = string.format("%04d/%02d/%02d %02d:%02d:%02d", wib["year"], wib["mon"], wib["day"], wib["hour"], wib["min"], wib["sec"])
        print(timeStr ..";" .. temp .. ";" .. humi)
      
    elseif status == dht.ERROR_CHECKSUM then
        print( "DHT Checksum error." )
    elseif status == dht.ERROR_TIMEOUT then
        print( "DHT timed out." )
    end
end

sntp.sync("1.id.pool.ntp.org",
 function(sec,usec,server)
   print('setting time to:', sec, usec, "from: " .. server)
   rtctime.set(sec, usec)
 end,

 function()
   print('failed!')
 end
)

tmr.alarm(0,1000,1,readFromDHT11) -- setiap detik baca dari sensir DHT11

Pada script berikut ini adalah melakukan log ke file harian setiap 5 detik untuk keperluaan penelitian.

pinDHT11 = 4 -- pin D4 pada board WeMos D1 mini

function readFromDHT11()
    status, temp, humi, temp_dec, humi_dec = dht.read11(pinDHT11)

    if status == dht.OK then
        wib = rtctime.epoch2cal(rtctime.get()+7*60*60) -- tambah 7 WEST INDONESIA TIME
        timeStr = string.format("%04d/%02d/%02d %02d:%02d:%02d", wib["year"], wib["mon"], wib["day"], wib["hour"], wib["min"], wib["sec"])
        --print(timeStr ..";" .. temp .. ";" .. humi)
        payload = timeStr ..";" .. temp .. ";" .. humi
        return payload
      
    elseif status == dht.ERROR_CHECKSUM then
        print( "DHT Checksum error." )
    elseif status == dht.ERROR_TIMEOUT then
        print( "DHT timed out." )
    end
end

function logToFile()
    wib = rtctime.epoch2cal(rtctime.get()+7*60*60) -- tambah 7 WEST INDONESIA TIME
    fname = string.format("%04d%02d%02d", wib["year"], wib["mon"], wib["day"])
    -- open file in 'a+' mode   
    if file.open(fname, "a+") then
        -- write 'foo bar' to the end of the file
        file.writeline(readFromDHT11())
        print("log to " .. fname)
        file.close()
    end
end

sntp.sync("1.id.pool.ntp.org",
 function(sec,usec,server)
   print('setting time to:', sec, usec, "from: " .. server)
   rtctime.set(sec, usec)
 end,

 function()
   print('failed!')
 end
)

tmr.alarm(0,5000,1,logToFile) -- setiap detik baca dari sensir DHT11
 

Kamis, 22 Desember 2016

Om Telolet Om: Switch on/off WeMos Relay Shield (ESP8266EX, NodeMCU, Lua)

What is Om Telolet Om mean? Sir honk your horn sir.

Ketika tulisan ini dibuat, fenomena Om Telolet Om lagi menjadi fenomena trend di Indonesia, dan juga menjadi trending topic di Twitter. Hal ini menjadi penasaran bagi banyak orang apa sih artinya Om Telolet Om. Berdasarkan pembacaan di Internet dapat disimpulkan bahwa "Om Telolet Om" artinya meminta kepada pengemudi Bus angkutan untuk membunyikan klakson yang memiliki suara unik dalam melodi yang dapat dimainkan oleh supir. Awalnya Om Telolet Om menjadi hiburan bagi anak-anak dimana supir memainkan lagi anak-akan seperti unyil, happy birthday, dll.

 Gambar 1. WeMos Relay Shield

Pada tulisan ini penulis membahas tentang pengendalian switch pada WeMos Relay Shield dengan Lua. Pengendalian switch adalah mengatur GPIO pin 1 kepada kondisi HIGH (switch on), dan LOW (switch off).

relayPin = 1
interval = 2000 --pause for two seconds
mode = 0

gpio.mode(relayPin, gpio.OUTPUT);

function Switch()
  if mode==0 then 
    gpio.write(relayPin, gpio.HIGH)
  else
    gpio.write(relayPin, gpio.LOW)
  end
  mode = math.abs(mode)-1
end

tmr.alarm(0,interval,1,Switch)
Pada keadaan On, maka Led pada Shield akan hidup, dan sebaliknya adalah Off.

Senin, 19 Desember 2016

IOT Tutorial: Begining WeMos D1 Mini Pro (ESP-8266EX, NodeMCU, Lua)

Setelah menunggu hampir 21 hari, akhirnya IoT board WeMos D1 mini yang saya pesan dari Aliexpress sampai juga. Berdasarkan informasi dari wemos.cc, perbedaan utama antara WeMos D1 mini dengan WeMos D1 mini pro adalah pada ukuran flash dari 4MB menjadi 16MB. Perbedaan berikutnya adalah pada USB driver yang digunakan, jika yang biasa adalah menggunakan driver CH340G, maka yang pro adalah menggunakan CP2104.

 Gambar 0. WeMos D1 mini pro

Langkah-langkah flash firmware nodemcu:
1. Install driver CP2104.
2. Koneksikan board WeMos dengan komputer anda.
3. Catat nomor port hasil koneksi.
4. Download nodemcu-Flasher (baca)
5. Kustomisasi firmware nodemcu

Untuk melakukan kustomisasi, anda dapat menggunjungi https://nodemcu-build.com/
1. Isikan alamat email anda untuk mendapatkan konfirmasi proses builds dan hasil builds yang dapat didownload
2. Pilih module-module yang ingin anda ikut sertakan pada firmware, pilihlah module yang yang butuhkan saja, karena semakin banyak module yang dimasukan, maka binary firmware akan semakin besar. Beberapa module default yang dimasukan adalah file, GPIO, net, node, timer, UART, dan wifi, anda dapat menambahkan module seperti cjson, crypto, DHT, HTTP, I2C, RTC time, SNTP, dan websocket, masukan juga SSL support TLS. (untuk lingkungan produksi, anda dapat menkustomisasi firmware anda dengan modul-modul yang anda gunakan saja dalam implementasi, sehingga ukuran firmware menjadi compact). Kemudian klik pada Start your build.
Catatan: This project uses two main branches, master and dev. dev is actively worked on and it's also where PRs should be created against. master thus can be considered "stable" even though there are no automated regression tests.

Anda akan menerima email yang memberitahukan bahwa nodemcu custom build started, silakan menunggu untuk mendapatkan hasil build (berkisar 2 menit), anda akan menerima email nodemcu custom build finished disertai dengan link untuk mendownload hasil build.

http://nodemcu-build.com/builds/nodemcu-master-15-modules-2016-12-20-01-43-07-float.bin
http://nodemcu-build.com/builds/nodemcu-master-15-modules-2016-12-20-01-43-07-integer.bin

Catatan: There are two versions for each firmware version: the integer version which supports only integer operations and the float version which contains support for floating point calculations. From a performance point of view, the integer version should be better. It’s also smaller and most of the time, you will not need to work with floating point numbers.
Dalam hal ini ukuran binary saya yang float adalah 495412 bytes

6. Lakukan flash-firmware anda dengan menggunakan nodemcu-Flasher

6a. Aktifkan software nodemcu-Flasher

Gambar 1. NodeMCU Flasher
6b. Klik pada tab config, dan pilih firmware anda (hasil download langkah sebelumnya)
Gambar 2. Select Custom Firmware
Pilihlah file custom firmware yang didownload sebelumnya.

6c. Klik pada tab advanced, dan ubah parameter flash size menjadi 16M sesuai dengan ukuran flash D1 mini pro.

Gambar 3. Ubah Flash size 16MB (sesuai ukuran flash-size D1 mini pro)
6d. Kembali kehalaman operation, dan klik pada flash untuk memulai, jika berhasil akan mendapatkan tampilan sebagai berikut ini:

Gambar 4. Hasil Flash

7.Download software Esplor untuk sebagai lingkungan pengembangan nodemcu pada alamat https://esp8266.ru/esplorer/

8. Tutup Sofware nodemcu-flasher, dan lakukan koneksi dengan software esplor

8a. Dalam hal ini saya menggunakan port 8 dan kecepatan 115200 baud dengan hasil:
PORT OPEN 115200
Communication with MCU..
8b. Tekan tombol reset pada board
NodeMCU custom build by frightanic.com
    branch: master
    commit: 81ec3665cb5fe68eb8596612485cc206b65659c9
    SSL: true
    modules: cjson,crypto,dht,file,gpio,http,i2c,net,node,rtctime,sntp,tmr,uart,websocket,wifi
 build     built on: 2016-12-20 03:33
 powered by Lua 5.1.4 on SDK 1.5.4.1(39cb9a32)
lua: cannot open init.lua
Troubleshooting:
1. Blue led keep fast flashing and board trapped in forever loop reset condition

Connect to your board on 74880 baud , shown something like this
load 0x40100000, len 25952, room 16
tail 0
chksum 0xd7
load 0x3ffe8000, len 2260, room 8
tail 12
chksum 0xd9
ho 0 tail 12 room 4
load 0x3ffe88d4, len 8, room 12
tail 8
chksum 0xc5
csum 0xc5
rf_cal[0] !=0x05,is 0xFF

 ets Jan  8 2013,rst cause:2, boot mode:(3,7)
Solution 1: Must make sure that when flashing has set flash-size to 16MB

Gambar 5. Make sure to use 16MB

Solution 2: Check your firmware size firmware, noted that size cannot over 491 KB (the space before the system data)


Sabtu, 17 Desember 2016

Tutorial NodeMCU: Posting file to Google Drive (ESP8266, WeMos, Lua)

Pada tulisan ini akan membahas posting file dari perangkat IOT berbasis ESP8266 ke google drive. Untuk menjalankan script pada pembahasan ini, anda perlu melakukan kustomisasi firmware nodemcu dengan memasukan beberapa module yang diperlukan sebagai berikut:
NodeMCU custom build by frightanic.com
    branch: master
    commit: 81ec3665cb5fe68eb8596612485cc206b65659c9
    SSL: true
    modules: cjson,crypto,dht,file,gpio,http,net,node,rtctime,sntp,tmr,uart,websocket,wifi
 build     built on: 2016-12-10 10:19
 powered by Lua 5.1.4 on SDK 1.5.4.1(39cb9a32)
Langkah-langkah:
1. Aktifkan ke Google API console
2. Klik pada Project, dan pilih Create Project

Gambar 1. Pembuatan Project Baru
3. Tambahkan Library Drive API

Gambar 2. Tambahkan Drive API
4. Aktifkan Drive API dengan klik pada Enable


Gambar 3. Google Drive API setelah di Enable

5. Tambahkan Credentials pada Project
Gambar 4. Setting Credentials

Pastikan bahwa Authorized redirect URLS adalah: https://developers.google.com/oauthplayground

6. Setting OAuth 2.0 consent screen
Gambar 5. OAuth 2.0 Consent Screen
7. Aktifkan ke Google OAauth Playground
Gambar 6. OAuth 2.0 configuration
Check Use your own OAuth credentials, isikan OAuth Client ID, dan OAuth Client secret yang dapat diperoleh pada langkah sebelumnya (pada bagian Credentials pada Google Console API).

8. Tentukan authorize API scope, dalam hal ini adalah https://www.googleapis.com/auth/drive
Gambar 7. Tentukan Scope otorisasi yang diinginkan
9. Berikan otorisasi dengan klik pada Allow
 Gambar 8. Memperbolehkan View dan Manage file pada Google Drive
10. Buat Refresh Token dan Access Token

Gambar 9. Membuat Refresh Token dan Access Token
11. Catat Authorization code, Client_Id, Client_Secret, Refresh Token
12. Lakukan koding berikut ini:

-- by Hendra Soewarno (0119067305) based on documentation
-- https://developers.google.com/identity/protocols/OAuth2WebServer
-- https://developers.google.com/drive/v2/reference/
authorization_code = "your authorization code"
client_id = "your client id"
client_secret = "your client secret"
refresh_token = "your refresh token"
access_token = nil
id = nil
title = "init.lua"

function encode_parameter(str)
    return str:gsub('[^-%._~a-zA-Z0-9]', function(c)
        return string.format("%%%02x", c:byte()):upper()
    end)
end

function changeTitle()


    local url = "https://www.googleapis.com/drive/v2/files/" .. id

    local header = "Content-type: application/json\r\n" ..
        "Authorization: Bearer " .. access_token .. "\r\n"

    local payload = "{\"title\" : \"" .. title .. "\"}"
   
    http.request(url,
        "PATCH",
        header,
        payload,
        function(code, data)
            if (code ~= 200) then
                print("Failed : HTTP error code : " .. code, data)
            else
                print("Succeed : " .. code, data)
            end
        end)
end

function uploadFile(callback)

    local url = "https://www.googleapis.com/upload/drive/v2/files?uploadType=media&convert=true"
   
    local header = "Content-type: application/text\r\n" ..
        "Authorization: Bearer " .. access_token .. "\r\n"
      
    if file.open("init.lua", "r") then
        payload = file.read(512)
        print(payload)
        file.close()
  
        http.post(url,
            header,
            payload,
            function(code, data)
                if (code ~= 200) then
                    print("Failed : HTTP error code : " .. code, data)
                else
                    print("Succeed : " .. code, data)
                    local t = cjson.decode(data)
                    id = t["id"]
                    node.task.post(changeTitle)
                end
            end)
    end
end

function getAccessToken()

    local url = "https://www.googleapis.com/oauth2/v4/token"

    local payload =
        "code=" .. encode_parameter(authorization_code) ..
        "&redirect_uri=" .. encode_parameter("https://developers.google.com/oauthplayground") ..
        "&client_id=" .. encode_parameter(client_id) ..
        "&client_secret=" .. encode_parameter(client_secret) ..
        "&scope=" ..
        "&grant_type=authorization_code"
   
    local header = "Content-type: application/x-www-form-urlencoded\r\n"
        --"Content-length: " .. string.len(payload) .. "\r\n"
       
    http.post(url,
            header,
            payload,
            function(code, data)
                if (code ~= 200) then
                    print("Failed : HTTP error code : " .. code, data)
                else
                    print("Succeed : " .. code, data)
                end
            end)       
end

function refreshAccessToken(callback)

    local url = "https://www.googleapis.com/oauth2/v4/token"

    local payload =
        "client_secret=" .. encode_parameter(client_secret) ..
        "&grant_type=refresh_token" ..
        "&client_id=" .. encode_parameter(client_id) ..
        "&refresh_token=" .. encode_parameter(refresh_token)
   
    local header = "Content-type: application/x-www-form-urlencoded\r\n"
        --"Content-length: " .. string.len(payload) .. "\r\n"
       
    http.post(url,
            header,
            payload,
            function(code, data)
                if (code ~= 200) then
                    print("Failed : HTTP error code : " .. code, data)
                else
                    print("Succeed : " .. code, data)
                    local t = cjson.decode(data)
                    access_token = t["access_token"]
                    node.task.post(uploadFile)
                end
            end)       
end

--getAccessToken()
--refreshAccessToken()
refreshAccessToken()

Hasil Run:
Succeed : 200    {
 "kind": "drive#file",
 "id": "0B9b1RNrwu14WNmsyeV9vU1RHVEE",
 "etag": "\"m64ksZC09nG4-fB1IRhNTNcF3vg/MTQ4MTk3ODM2OTYzMw\"",
 "selfLink": "https://www.googleapis.com/drive/v2/files/0B9b1RNrwu14WNmsyeV9vU1RHVEE",
 "webContentLink": "https://drive.google.com/uc?id=0B9b1RNrwu14WNmsyeV9vU1RHVEE&export=download",
 "alternateLink": "https://drive.google.com/file/d/0B9b1RNrwu14WNmsyeV9vU1RHVEE/view?usp=drivesdk",
 "embedLink": "https://drive.google.com/file/d/0B9b1RNrwu14WNmsyeV9vU1RHVEE/preview?usp=drivesdk",
 "iconLink": "https://ssl.gstatic.com/docs/doclist/images/generic_app_icon_16.png",
 "title": "Untitled",
 "mimeType": "application/text",
 "labels": {
  "starred": false,
  "hidden": false,
  "trashed": false,
  "restricted": false,
  "viewed": true
 },
 "createdDate": "2016-12-17T12:39:29.633Z",
 "modifiedDate": "2016-12-17T12:39:29.633Z",
 "modifiedByMeDate": "2016-12-17T12:39:29.633Z",
 "lastViewedByMeDate": "2016-12-17T12:39:29.633Z",
 "markedViewedByMeDate": "1970-01-01T00:00:00.000Z",
 "version": "95279",
 "parents": [
  {
   "kind": "drive#parentReference",
   "id": "0ANb1RNrwu14WUk9PVA",
   "selfLink": "https://www.googleapis.com/drive/v2/files/0B9b1RNrwu14WNmsyeV9vU1RHVEE/parents/0ANb1RNrwu14WUk9PVA",
   "parentLink": "https://www.googleapis.com/drive/v2/files/0ANb1RNrwu14WUk9PVA",
   "isRoot": true
  }
 ],
 "downloadUrl": "https://doc-0s-0g-docs.googleusercontent.com/docs/securesc/6ef3a2thq1io4lguiv000fc3n8j157mi/hkbk6sp6ejiicbcbnmof5oobimi08o6e/1481976000000/14467718844153000671/14467718844153000671/0B9b1RNrwu14WNmsyeV9vU1RHVEE?e=download&gd=true",
 "userPermission": {
  "kind": "drive#permission",
  "etag": "\"m64ksZC09nG4-fB1IRhNTNcF3vg/oL4ijP0n8GxZ7JDTDMVZhQFn1h0\"",
  "id": "me",
  "selfLink": "https://www.googleapis.com/drive/v2/files/0B9b1RNrwu14WNmsyeV9vU1RHVEE/permissions/me",
  "role": "owner",
  "type": "user"
 },
 "originalFilename": "Untitled",
 "fileExtension": "",
 "md5Checksum": "74339827856ad32167b81a9b69c6a369",
 "fileSize": "512",
 "quotaBytesUsed": "512",
 "ownerNames": [
  "Hendra Soewarno"
 ],
 "owners": [
  {
   "kind": "drive#user",
   "displayName": "Hendra Soewarno",
   "picture": {
    "url": "https://lh3.googleusercontent.com/-zT-K5R2Kl7c/AAAAAAAAAAI/AAAAAAAAAM0/rk_CEnm7hMA/s64/photo.jpg"
   },
   "isAuthenticatedUser": true,
   "permissionId": "14467718844153000671",
   "emailAddress": "hendra.soewarno@gmail.com"
  }
 ],
 "lastModifyingUserName": "Hendra Soewarno",
 "lastModifyingUser": {
  "kind": "drive#user",
  "displayName": "Hendra Soewarno",
  "picture": {
   "url": "https://lh3.googleusercontent.com/-zT-K5R2Kl7c/AAAAAAAAAAI/AAAAAAAAAM0/rk_CEnm7hMA/s64/photo.jpg"
  },
  "isAuthenticatedUser": true,
  "permissionId": "14467718844153000671",
  "emailAddress": "hendra.soewarno@gmail.com"
 },
 "editable": true,
 "copyable": true,
 "writersCanShare": true,
 "shared": false,
 "explicitlyTrashed": false,
 "appDataContents": false,
 "headRevisionId": "0B9b1RNrwu14WK01kUUtOQTdTVlljNTlhMjN5Zml5MVFsMjNRPQ",
 "spaces": [
  "drive"
 ]
}

Succeed : 200    {
 "kind": "drive#file",
 "id": "0B9b1RNrwu14WNmsyeV9vU1RHVEE",
 "etag": "\"m64ksZC09nG4-fB1IRhNTNcF3vg/MTQ4MTk3ODM3MTYwNA\"",
 "selfLink": "https://www.googleapis.com/drive/v2/files/0B9b1RNrwu14WNmsyeV9vU1RHVEE",
 "webContentLink": "https://drive.google.com/uc?id=0B9b1RNrwu14WNmsyeV9vU1RHVEE&export=download",
 "alternateLink": "https://drive.google.com/file/d/0B9b1RNrwu14WNmsyeV9vU1RHVEE/view?usp=drivesdk",
 "embedLink": "https://drive.google.com/file/d/0B9b1RNrwu14WNmsyeV9vU1RHVEE/preview?usp=drivesdk",
 "iconLink": "https://ssl.gstatic.com/docs/doclist/images/generic_app_icon_16.png",
 "title": "init.lua",
 "mimeType": "application/text",
 "labels": {
  "starred": false,
  "hidden": false,
  "trashed": false,
  "restricted": false,
  "viewed": true
 },
 "createdDate": "2016-12-17T12:39:29.633Z",
 "modifiedDate": "2016-12-17T12:39:31.604Z",
 "modifiedByMeDate": "2016-12-17T12:39:31.604Z",
 "lastViewedByMeDate": "2016-12-17T12:39:31.604Z",
 "markedViewedByMeDate": "1970-01-01T00:00:00.000Z",
 "version": "95281",
 "parents": [
  {
   "kind": "drive#parentReference",
   "id": "0ANb1RNrwu14WUk9PVA",
   "selfLink": "https://www.googleapis.com/drive/v2/files/0B9b1RNrwu14WNmsyeV9vU1RHVEE/parents/0ANb1RNrwu14WUk9PVA",
   "parentLink": "https://www.googleapis.com/drive/v2/files/0ANb1RNrwu14WUk9PVA",
   "isRoot": true
  }
 ],
 "downloadUrl": "https://doc-0s-0g-docs.googleusercontent.com/docs/securesc/6ef3a2thq1io4lguiv000fc3n8j157mi/hkbk6sp6ejiicbcbnmof5oobimi08o6e/1481976000000/14467718844153000671/14467718844153000671/0B9b1RNrwu14WNmsyeV9vU1RHVEE?e=download&gd=true",
 "userPermission": {
  "kind": "drive#permission",
  "etag": "\"m64ksZC09nG4-fB1IRhNTNcF3vg/oL4ijP0n8GxZ7JDTDMVZhQFn1h0\"",
  "id": "me",
  "selfLink": "https://www.googleapis.com/drive/v2/files/0B9b1RNrwu14WNmsyeV9vU1RHVEE/permissions/me",
  "role": "owner",
  "type": "user"
 },
 "originalFilename": "Untitled",
 "fileExtension": "lua",
 "md5Checksum": "74339827856ad32167b81a9b69c6a369",
 "fileSize": "512",
 "quotaBytesUsed": "512",
 "ownerNames": [
  "Hendra Soewarno"
 ],
 "owners": [
  {
   "kind": "drive#user",
   "displayName": "Hendra Soewarno",
   "picture": {
    "url": "https://lh3.googleusercontent.com/-zT-K5R2Kl7c/AAAAAAAAAAI/AAAAAAAAAM0/rk_CEnm7hMA/s64/photo.jpg"
   },
   "isAuthenticatedUser": true,
   "permissionId": "14467718844153000671",
   "emailAddress": "hendra.soewarno@gmail.com"
  }
 ],
 "lastModifyingUserName": "Hendra Soewarno",
 "lastModifyingUser": {
  "kind": "drive#user",
  "displayName": "Hendra Soewarno",
  "picture": {
   "url": "https://lh3.googleusercontent.com/-zT-K5R2Kl7c/AAAAAAAAAAI/AAAAAAAAAM0/rk_CEnm7hMA/s64/photo.jpg"
  },
  "isAuthenticatedUser": true,
  "permissionId": "14467718844153000671",
  "emailAddress": "hendra.soewarno@gmail.com"
 },
 "editable": true,
 "copyable": true,
 "writersCanShare": true,
 "shared": false,
 "explicitlyTrashed": false,
 "appDataContents": false,
 "headRevisionId": "0B9b1RNrwu14WK01kUUtOQTdTVlljNTlhMjN5Zml5MVFsMjNRPQ",
 "spaces": [
  "drive"
 ]
}

Trouble Shooting:
> Failed : HTTP error code : 401    {
 "error": "invalid_client",
 "error_description": "The OAuth client was not found."

Pastikan client_id telah benar

> Failed : HTTP error code : 401    {
 "error": "invalid_client",
 "error_description": "Unauthorized"
}

Pastikan client_secret telah benar

> Failed : HTTP error code : 400    {
 "error": "invalid_grant",
 "error_description": "Bad Request"
}

Pastikan refresh_token telah benar




Jumat, 16 Desember 2016

Tutorial NodeMCU: GET Twitter account/settings Using REST API (ESP8266, WeMos, Lua)

Jika pada tulisan sebelumnya kita membahas tentang bagaimana melakukan posting Tweet dengan menggunakan REST API, maka pada tulisan ini kita akan melakukan pembacaan account/setting Twitter dengan menggunakan metode GET.

Untuk menjalankan script ini, anda perlu melakukan kustomisasi firmware nodemcu dengan memasukan beberapa module berikut ini:
NodeMCU custom build by frightanic.com
    branch: master
    commit: 81ec3665cb5fe68eb8596612485cc206b65659c9
    SSL: true
    modules: cjson,crypto,dht,file,gpio,http,net,node,rtctime,sntp,tmr,uart,websocket,wifi
 build     built on: 2016-12-10 10:19
 powered by Lua 5.1.4 on SDK 1.5.4.1(39cb9a32)
Dengan script sebagai berikut:

-- by Hendra Soewarno (0119067305) base on documentation
-- https://dev.twitter.com/rest/reference/get/account/settings
api_base = "https://api.twitter.com"
consumer_key = "7LzQ03LBNtFZ8OIsgq******"
consumer_secret = "xfyoGFz2fV9nOPAS4dx9sfiHz7PZEKeGOgZJqiXb98w******"
access_token = "2203290865-ClUx6zIDTmBWI3JjML5tZlfEXOhnzvKl******"
access_token_secret = "k09y3gSIQ4pEROSvLJewd7lG51YBXcLyno23m8JM******"

-- Calculate the sha1 hash for a given string.
local function sha1(str, key)
    return crypto.hmac("sha1",str,key)
end

-- Returns the current time as a Unix timestamp.
function oauth_timestamp()
   return tostring(rtctime.get() + 1) -- we want the next second
end

-- Generate nonce none for oauth
function oauth_nonce()  
   return oauth_timestamp()
end


function encode_parameter(str)
    return str:gsub('[^-%._~a-zA-Z0-9]', function(c)
        return string.format("%%%02x", c:byte()):upper()
    end)
end

function built_base_string(baseuri, method, param)
    local t = { }
    for key, val in pairs(param) do
        table.insert(t, {key = key,val = val})
    end
   
    -- Sort by key first, then value
    table.sort(t, function(a,b)
        if a.key < b.key then
            return true
        elseif a.key > b.key then
            return false
        else
            return a.val < b.val
        end
    end)

    local key_value_pairs = { }
    for _, rec in pairs(t) do      
        table.insert(key_value_pairs, rec.key .. "=" .. encode_parameter(rec.val))
    end
   
    local query_string_except_signature = table.concat(key_value_pairs, "&")

    return method .. '&' .. encode_parameter(baseuri) .. '&' .. encode_parameter(query_string_except_signature)  
end

function built_authorization_header(oauth, oauth_signature)
    local r = "Authorization: OAuth "
    local oauth_headers = { }
   
    for key, val in pairs(oauth) do
        if (string.sub(key,1,5)=="oauth") then
            table.insert(oauth_headers, key .. "=\"" .. encode_parameter(val) .. "\"")
        end           
    end

    table.insert(oauth_headers, "oauth_signature=\"" .. oauth_signature .. "\"")

    local authorization_header = table.concat(oauth_headers, ", ")

    return r .. authorization_header
end

function requestToGet(path)
    local oauth = {
        oauth_consumer_key = consumer_key,
        oauth_nonce = oauth_nonce(),
        oauth_signature_method = "HMAC-SHA1",
        oauth_token =  access_token,
        oauth_timestamp = oauth_timestamp(),
        oauth_version = "1.0"
        }

    local signature_base_string = built_base_string(api_base .. path, "GET", oauth)
    print(signature_base_string)

    local signature_key = encode_parameter(consumer_secret) .. "&" .. encode_parameter(access_token_secret)
    print(signature_key)

    local oauth_signature = crypto.toBase64(sha1(signature_base_string, signature_key))
    print(oauth_signature)

    local header = built_authorization_header(oauth, encode_parameter(oauth_signature))
    print(header)

    local url = api_base .. path
    print(url)
   
    http.get(url,
        header,
        function(code, data)
            if (code ~= 200) then
                print("Failed : HTTP error code : " .. code, data)
            else
                print("Succeed : " .. code, data)
            end
        end)
end

sntp.sync("1.id.pool.ntp.org",
    function(sec,usec,server)
        print('setting time to:', sec, usec, "from: " .. server)
        rtctime.set(sec, usec)
        requestToGet("/1.1/account/settings.json")
    end,

    function()
        print('failed!')
    end
)

Hasil Run:

Succeed : 200   {"protected":false,"screen_name":"hendrasoewarno","always_use_https":true,"use_cookie_personalization":true,"sleep_time":{"enabled":false,"end_time":null,"start_time":null},"geo_enabled":false,"language":"en","discoverable_by_email":false,"discoverable_by_mobile_phone":false,"display_sensitive_media":false,"allow_contributor_request":"all","allow_dms_from":"following","allow_dm_groups_from":"following","smart_mute":false,"translator_type":"none"}