Rabu, 14 Desember 2016

Tutorial NodeMCU: Posting a Tweet Using REST API (IOT, ESP8266, WeMos, Lua)

Let's Machine Tweet: Tulisan ini dibuat setelah melakukan surfing tidak menemukan contoh untuk posting Tweet langsung dari NodeMCU Lua dengan menggunakan metode REST sebagaimana dokumentasi pada https://dev.twitter.com/oauth/overview/authorizing-requests (pada umumnya contoh yang diberikan adalah melalui API pihak ketiga seperti https://ifttt.com, https://thingspeak.com). Pada tulisan ini kita akan melakukan posting Tweet secara langsung dengan menggunakan REST API yang disediakan oleh Twitter. Untuk menjalankan script pada contoh dibawah ini, anda perlu melakukan kustomisasi terhadap firmware NodeMCU dengan memasukan module-module yang ditandai warna merah 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)
Pada dasarnya Twitter menyediakan dua bentuk authentication (OAUTH), yaitu user authentication (oauth1.0a protocol) dan application-only-authentication (oauth2 specification). Untuk mengirim Tweet maka dibutuhkan otorisasi melalui user authentication.

Untuk otorisasi dengan modus user authentication, maka dibutuhkan key dan token sebagai berikut ini:
  1. Consumer Key
  2. Consumer Secret
  3. Access Token
  4. Access Token Secret
Gambar 1. Key dan Token


Selanjutnya anda dapat menggunakan koding berikut ini untuk mengirim Tweet ke Twitter:
--By Hendra Soewarno (0119067305)
--base on documentation https://dev.twitter.com/oauth/overview/authorizing-requests
api_base = "https://api.twitter.com"
consumer_key = "your consumer key"
consumer_secret = "your consumer secret"
access_token = "your access token"
access_token_secret = "your access token secret"


-- 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() --we use timestamp as nonce
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 postTweet(status)
    local path = "/1.1/statuses/update.json"

    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",
        status=status
        }

    local payload = "status=" .. encode_parameter(status)
    print(payload)

    local signature_base_string = built_base_string(api_base .. path, "POST", 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.post(url,
        "Content-Type: application/x-www-form-urlencoded\r\n" ..
        header,
        payload,
        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)
        postTweet("Hello WeMos1!")
    end,

    function()
        print('failed!')
    end
)
Hasil Run:
Succeed : 200    {"created_at":"Wed Dec 14 16:39:00 +0000 2016","id":809075225799180288,"id_str":"809075225799180288","text":"Hello WeMos1!","truncated":false,"entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]},"source":"\u003ca href=\"http:\/\/hsoewarno.blogspot.com\" rel=\"nofollow\"\u003eNodeMCU8266\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":2203290865,"id_str":"2203290865","name":"hendra soewarno","screen_name":"hendrasoewarno","location":"","description":"","url":null,"entities":{"description":{"urls":[]}},"protected":false,"followers_count":1,"friends_count":0,"listed_count":0,"created_at":"Tue Nov 19 15:27:54 +0000 2013","favourites_count":0,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":false,"statuses_count":4,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_tile":false,"profile_image_url":"http:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_6_normal.png","profile_image_url_https":"https:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_6_normal.png","profile_link_color":"1DA1F2","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"has_extended_profile":false,"default_profile":true,"default_profile_image":true,"following":false,"follow_request_sent":false,"notifications":false,"translator_type":"none"},"geo":null,"coordinates":null,"place":null,"contributors":null,"is_quote_status":false,"retweet_count":0,"favorite_count":0,"favorited":false,"retweeted":false,"lang":"en"}

Gambar 2. Hasil Tweet

Hasil Run kedua kali:
Failed : HTTP error code : 403    {"errors":[{"code":187,"message":"Status is a duplicate."}]}

Troubleshooting:
Failed : HTTP error code : 401    {"errors":[{"code":32,"message":"Could not authenticate you."}]}

Silakan periksa kembali consumer key, consumer secret, access token dan access token secret anda, atau pada header tidak terdapat "Content-Type: application/x-www-form-urlencoded\r\n" (penulis juga terjebak dengan debugging berhari-hari karena tidak memasukan Content-Type). Penyebab lain juga bisa disebabkan oleh setting permisi dari aplikasi Twitter anda, denagn memastikan sudah diset ke Read and Write.


Gambar 3. Permission pada aplikasi Twitter.

Tidak ada komentar:

Posting Komentar