Selasa, 08 November 2016

Tutorial NodeMCU: Sync software RTC to NIST time server using Day Time Protocol (ESP8266, WeMos, Lua)

Untuk menjalankan contoh pada tulisan ini, maka firmware NodeMCU harus tersedia module RTC time, jika tidak maka berpotensi memicu PANIC pada saat runtime.

Tulisan ini saya buat setelah membaca bahwa NIST menyediakan jam atom yang paling akurat didunia,  pemakaian time server NIST yang dapat diakses melalui alamat time.nist.gov dengan menggunakan Day Time Protocol (RFC-867) pada port 13, maupun melalui Network Time Protocol (RFC-1305) pada port 113.

Pada tulisan ini saya mencoba men-sinkron software RTC pada WeMos D1 mini dengan time.nist.gov melalui Day Time Protocol.

Response dari NIST time server adalah berupa suatu string sepanjang 49 karakter yang diawali dengan suatu karakter line feed (LF) dengan gambaran format sebagai berikut

--x57700 16-11-08 12:33:48 00 0 0 454.5 UTC(NIST) *
--xJJJJJ YR-MO-DA HH:MM:SS TT L H msADV UTC(NIST) *
--         1         2         3         4        
--1234567890123456789012345678901234567890123456789
--first characater x is LF

Karena response adalah berupa string sehingga kita harus memisahkan masing-masing bagian menjadi tahun (YR), bulan (MO), hari (DA), jam(HH), menit(MM), detik(SS), dan milidetik (msADV) dengan menggunakan fungsi bantu string.sub(). Sebelum melakukan pemisahan, maka adalah penting untuk memastikan respon memiliki signature NIST.

Sesuatu hal yang menjadi masalah untuk melakukan setting waktu pada software RTC pada NodeMCU LUA adalah perintah rtctimer.set(unix epoch time, mikrodetik) menggunakan unix epoch time yang merupakan jumlah detik sejak 1 Januari 1970.

Setelah melakukan pencarian diinternet, ternyata NodeMCU Lua tidak menyediakan fungsi untuk konversi datetime menjadi unix epoch time, sehingga dalam hal ini kita perlu membuat library sendiri. Hal pertama yang perlu menjadi perhatian adalah adanya perbedaan jumlah hari pada tahun kabisat dengan tahun biasa khususnya pada bulan February.

Berikut ini adalah library yang saya siapkan untuk fungsi konversi cal2epoch yang beberapa bagian saya adaptasi dari rtctime.c yang ada di source code NodeMCU dengan rincian koding berikut ini.

mylib.lua

-- berberapa bagian saya adaptasi dari rtctime.c
-- dikoding oleh: hendra soewarno (0119067305)

local moduleName = ...
local M = {}
_G[moduleName] = M

--day per month/jumlah hari perbulan?
local dpm = {
    0,
    (31),
    (31+28),
    (31+28+31),
    (31+28+31+30),
    (31+28+31+30+31),
    (31+28+31+30+31+30),
    (31+28+31+30+31+30+31),
    (31+28+31+30+31+30+31+31),
    (31+28+31+30+31+30+31+31+30),
    (31+28+31+30+31+30+31+31+30+31),
    (31+28+31+30+31+30+31+31+30+31+30),
    (31+28+31+30+31+30+31+31+30+31+30+31)
}

function mod(a, b)
    return a - (math.floor(a/b))*b
end

function isleap (year)
    -- every fourth year is a leap year except for century years that are
    -- not divisible by 400.
  return (mod(year,4)==0 and (mod(year,100)~=0 or mod(year,400)==0))
end

function M.cal2epoch(yyyy, mo, dd, hh, mm, ss)
    days = 0
    for year = 1970, yyyy-1 do
        if isleap(year) then
            days = days + 366
        else
            days = days + 365
        end
    end
    days = days + dpm[mo]
    if isleap(yyyy) and mo > 2 then days = days + 1 end
    days = days + dd - 1

    seconds = days*24*60*60 + hh*60*60+  mm*60 + ss

    return seconds
end

return M
Adapun contoh unit test untuk module mylib.lua:

require("mylib")
--unit test
print(mylib.cal2epoch(2016,11,8,21,15,25)==1478639725)
Yang harus menghasil nilai true.

Selanjutnya adalah menyiapkan koding untuk koneksi wifi, dan sikronisasi waktu.

mylib.lua
Silakan mengacu pada koding diatas
credentials.lua
 Silakan mengacu pada koding tulisan saya sebelumnya
init.lua
Silakan mengacu pada koding tulisan saya sebelumnya

app.lua

--x57700 16-11-08 12:33:48 00 0 0 454.5 UTC(NIST) *
--xJJJJJ YR-MO-DA HH:MM:SS TT L H msADV UTC(NIST) *
--         1         2         3         4        
--1234567890123456789012345678901234567890123456789
--first characater x is LF
require("mylib")

retried = 1
conn=net.createConnection(net.TCP, false)

function setTime(conn, pl)
    -- contain NIS signature
    if string.find(pl, "NIST") > 0 then
        tmr.stop(0)
        print(pl)
        year = 2000+tonumber(string.sub(pl,8,9))
        month = tonumber(string.sub(pl,11,12))
        day = tonumber(string.sub(pl,14,15))
        hour = tonumber(string.sub(pl,17,18))
        minute = tonumber(string.sub(pl,20,21))
        second = tonumber(string.sub(pl,23,24))
        mikrosecond = tonumber(string.sub(pl,33,37))*1000
        -- datetime to unix epoch time
        unixtime = mylib.cal2epoch(year, month, day, hour, minute, second)
        -- update software RTC
        rtctime.set(unixtime, mikrosecond)
        utc = rtctime.epoch2cal(rtctime.get())
        print("Waktu adalah UTC:" .. string.format("%04d/%02d/%02d %02d:%02d:%02d", utc["year"], utc["mon"], utc["day"], utc["hour"], utc["min"], utc["sec"]))
        -- WIB UTC + 7
        wib = rtctime.epoch2cal(rtctime.get()+7*60*60) -- tambah 7
        print("Waktu adalah WIB:" .. string.format("%04d/%02d/%02d %02d:%02d:%02d", wib["year"], wib["mon"], wib["day"], wib["hour"], wib["min"], wib["sec"]))          
    end
end

function syncTime()
    print("trying time.nist.gov " .. retried)
    conn:on("receive", setTime)
    conn:connect(13,"time.nist.gov")
    retried = retried + 1
    -- close connection if not got response in minute
    tmr.alarm(1,1000,0, function() conn:close() end)
end  

syncTime()
--no more frequently than once every 4 seconds
tmr.alarm(0,5000,1, syncTime)


Catatan:
  • NIST time server menyarankan agar tidak melakukan lebih dari satu koneksi dalam jangka waktu 4 detik, jika terdeteksi maka koneksi anda berpotensi untuk diblokir.
  • Pada algoritma diatas saya menggunakan strategi jika tidak ada respon dalam 1 detik, maka koneksi otomatis ditutup.
  • Algoritma diatas akan terus mencoba sinkronisasi waktu per 4 detik sampai berhasil dengan menggunakan fungsi tmr.alarm.
 Gambar 1. Hasil sikronisasi RTC software ke NIST time server

Tidak ada komentar:

Posting Komentar