Introduction: NodeMCU As Provider of NTP Date and Time Over Serial Port

I've boon looking for possibility to have current date and time on my Arduino projects. Arduino itself has limited resources and I would like to use them for the actual work. So I've decided to take NodeMCU with Lua language and use it to access date and time form the internet, format it in the way that meets my requirements and provide such result over serial port.

There are many different ways to access local date and time from the internet, but mostly those interfaces change over time and code using them has to be adopted. I was looking for something that would last for years without interaction form my side. That's why I've decided to use Network Time Protocol. There is only one catch: it's UTC time without date. It was not such a big deal, because there are lot of algorithms out there so I've just adopted one of them.Now it's possible to obtain local date and time from NodeMCU over serial port and the whole thing is reliable and will last for many years without touching it :)

Here is the source code: https://github.com/maciejmiklas/NodeMCUUtils

The whole implementation is dividend into small building blocks, putting them together will give you desired serial API. This is just short overview:

  • Date Format - calculates local date and time based on timestamp
  • WiFi Access - simple facade for connecting to WiFi
  • NTP Time - obtains UTC timestamp form give NTP server
  • NTP Clock - keeps actual UTC timestamp and synchronizes it periodically with NTP server
  • Serial API - finally this one provides API for date and time

Step 1: Date Format

Provides functionality to get local date and time from timestamp given in seconds since 1970.01.01

For such code:

collectgarbage() print("heap before", node.heap())
require "dateformat" require "dateformatEurope" local ts = 1463145687 df.setEuropeTime(ts, 3600) -- function requires GMT offset for your city

print(string.format("%04u-%02u-%02u %02u:%02u:%02d", df.year, df.month, df.day, df.hour, df.min, df.sec)) print("DayOfWeek: ", df.dayOfWeek)

collectgarbage() print("heap after", node.heap())

you will get this output:

heap before 44704
2016-05-13 15:21:27 DayOfWeek: 6 heap after 39280

In order to format date for USA you have to replace require "dateformatEurope" with require "dateformatAmerica" and call setAmericaTimeinstead of setEuropeTime. Functionality is divided into small scripts in order to save some RAM.

Step 2: WiFi Access

It's simple facade for connecting to WiFi. You have to provide connection credentials and function that will be executed after the connection has been established.

execute(...) connects to WiFi and this can take some time. You can still call this method multiple times. In such case callbacks will be stored in the queue and executed after WiFi connection has been established.

require "wlan"
wlan.debug = true

local function printAbc() print("ABC") end

wlan.setup("free wlan", "12345678") wlan.execute(printAbc)

Configuring WiFi on: free wlan
status 1 status 1 status 5 Got WiFi connection: 172.20.10.6 255.255.255.240 172.20.10.1 ABC

Step 3: NTP Time

This simple facade connects to given NTP server, request UTC time from it and once response has been received it calls given callback function.

Example below executes following chain: WiFi -> NTP -> Date Format. So in the fist step we are creating WLAN connection and registering callback function that will be executed after connection has been established. This callback function requests time from NTP server (ntp.requestTime). On the ntp object we are registering another function that will get called after NTP response has been received: printTime(ts).

collectgarbage() print("RAM init", node.heap())
require "wlan" require "ntp" require "dateformatEurope";

collectgarbage() print("RAM after require", node.heap())

ntp = NtpFactory:fromDefaultServer():withDebug() wlan.debug = true

local function printTime(ts) collectgarbage() print("RAM before printTime", node.heap()) df.setEuropeTime(ts, 3600) print("NTP Local Time:", string.format("%04u-%02u-%02u %02u:%02u:%02d", df.year, df.month, df.day, df.hour, df.min, df.sec)) print("Summer Time:", df.summerTime) print("Day of Week:", df.dayOfWeek) collectgarbage() print("RAM after printTime", node.heap()) end

ntp:registerResponseCallback(printTime)

wlan.setup("free wlan", "12345678") wlan.execute(function() ntp:requestTime() end)

collectgarbage() print("RAM callbacks", node.heap())

and console output:

RAM init 43328
RAM after require 30920 Configuring WiFi on: free wlan RAM callbacks 30688 status 1 status 1 status 5 Got WiFi connection: 172.20.10.6 255.255.255.240 172.20.10.1 NTP request: pool.ntp.org NTP request: 194.29.130.252 NTP response: 11:59:34 RAM before printTime 31120 NTP Local Time: 2016-07-12 13:59:34 Summer Time: Day of Week: 3 RAM after printTime 30928

Step 4: NTP Clock

This script provides functionality to run a clock with precision of one second and to synchronize this clock every few hours with NTP server.

In the code below we first configure WiFi access. Once the WiFi access has been established it will call ntpc.start(). This function will start clock that will get synchronized with given NTP server every minute. Now you can access actual UTC time in seconds over ntpc.current. In order to show that it's working we have registered timer that will call printTime() every second. This function reads current time asntpc.current and prints it as local time.

collectgarbage() print("RAM init", node.heap())
require "dateformatEurope"; require "ntpClock"; require "wlan";

collectgarbage() print("RAM after require", node.heap())

ntpc.debug = true wlan.debug = true

wlan.setup("free wlan", "12345678") wlan.execute(function() ntpc.start("pool.ntp.org", 60) end)

local function printTime() collectgarbage() print("RAM in printTime", node.heap()) df.setEuropeTime(ntpc.current, 3600) print("Time:", string.format("%04u-%02u-%02u %02u:%02u:%02d", df.year, df.month, df.day, df.hour, df.min, df.sec)) print("Summer Time:", df.summerTime) print("Day of Week:", df.dayOfWeek) end

tmr.alarm(2, 30000, tmr.ALARM_AUTO, printTime)

so this is the output:

RAM init 43784
RAM after require 29408 Configuring WiFi on: free wlan status 1 status 5 Got WiFi connection: 192.168.2.113 255.255.255.0 192.168.2.1

NTP request: pool.ntp.org NTP request: 195.50.171.101 NTP response: 17:09:46

RAM in printTime 29664 Time: 2016-08-08 19:10:08 Summer Time: true Day of Week: 2

RAM in printTime 29808 Time: 2016-08-08 19:10:38 Summer Time: true Day of Week: 2

NTP request: pool.ntp.org NTP request: 195.50.171.101 NTP response: 17:10:46

RAM in printTime 29680 Time: 2016-08-08 19:11:08 Summer Time: true Day of Week: 2

RAM in printTime 29808 Time: 2016-08-08 19:11:38 Summer Time: true Day of Week: 2

NTP request: pool.ntp.org NTP request: 131.188.3.221 NTP response: 17:11:46

RAM in printTime 29680 Time: 2016-08-08 19:12:08 Summer Time: true Day of Week: 2

RAM in printTime 29808 Time: 2016-08-08 19:12:38 Summer Time: true Day of Week: 2

Step 5: Serial API

Serial API exposes simple interface that provides access to diagnostic info and date so that it can be accessed outside NodeMCU - for example by Arduino.

Serial API is divided into few Lua scripts. Loading of each script will automatically add new API commands: -

  • serialAPI.lua - has to be always loaded. It initializes serial interface with few diagnostics commands.
  • serialAPIClock.lua - access to clock including date formatter.

Each script above registers set of commands as keys of scmd table - inside of each script you will find further documentation.

Example below provides date access over serial port:

require "credentials"
require "serialAPI" require "serialAPIClock"

ntpc.syncPeriodSec = 900 -- 15 min sapi.baud = 115200

-- setup wlan required by NTP clock wlan.setup("free wlan", "12345678")

-- start serial API by enabling gpio and uart sapi.start()

-- start NTP synchronization ntpc.start("pool.ntp.org")

Here are few Serial API commands and their responses:

# free ram
>GFR 10664

# WiFi status >GWS 5

# date and time (24h) in format: yyyy-mm-dd HHLmm:ss >CF1 2016-09-16 10:45:25

# date in format: yyyy-mm-dd >CH2 10:45:59

Step 6: Firmware

Executing multiple scripts can lead to out of memory issues on NodeMCU. One possibility to solve it is to build custom firmware containing only minimal set of node-mcu modules: cjson, file, gpio, net, node, tmr, uart, wifi. This blog provides detailed upgrade procedure: http://maciej-miklas.blogspot.de/2016/08/installing-nodemcu-v15-on-eps8266-esp.html