ESP8266 @ NodeMCU – lua boilerplate

This time we will look into ESP 8266 at NodeMCU v2 board. It is very small board with quite big capabilities. As a brain it has ESP-12E.
I had some opportunity to play with it at work and thought that it may be good idea to play with it some more.
My goal is to use it as event broadcaster. What events and what about broadcast you may ask :). I will hook 8 buttons to board and we will program it to broadcast event attached to button to local network.
So pressing button will send event without knowing its destination, any device can read it and do something.
Our first device that would do something is Raspberry Pi with attached LCD. So it will read event and display it. Next we will use it as controls in Piader game.
But before that we need to think and create some boilerplate. Why create and use boilerplate? So we will automate common tasks like joining WiFi, aborting booting procedure, emergency reboot and keeping WiFi alive. Simple code to do basic tasks so we may focus on our goal.
To program on NodeMCU board I will use ESPlorer as IDE and Lua as language.

Download boilerplate from GitHub

Boilerplate – what we need and what we want

The plan is to have three-step start up process. NodeMCU will autorun init.lua. But just running init is quite dangerous. Imagine we do an error in code resulting in reboot, we will end up with run-reboot-run loop! And it is quite common 🙂
To protect against such case we will write a boot check. Simplest way will be to check GPIO state and if it is connected (button is pressed) we will abort launch.

Another important and common task is to connect to WiFi access point.

It is also a good idea to keep some parameters in one place (password and login).

After wifi is alive we may start a main file.

And so we end with five files:

  • init.lua – run automatically
  • parameters.lua – project configuration (AP login and password)
  • parameters-device.lua – this unit configuration (like node name)
  • wifi-init.lua – WiFi connection and keep alive timer
  • main.lua – main code of our app

Autostart file – init.lua

This one is simple. We check for D1 / GPIO5 state. If it is connected we gonna abort app and if not we start wifi-init.lua. We are using small trick there, wifi-init.py is in timer. This trick allows init.lua to end and free it’s resources, after wifi-init launch.

print ("Booting..")
gpio.mode(1, gpio.INPUT, gpio.PULLUP)
if gpio.read(1) == 0 then
    print("..aborted")
else    
    dofile("parameters.lua")
    tmr.alarm(1, 2000, 0, function()
        print ("..launch..")       
        dofile("wifi-init.lua")        
    end)
end    

Parameters

In parameters.lua we should keep project variables like access point login, some pin mapping, global names. But what if we want to have some device only config? Like node name? No problem, use parameters-device.lua for this.
parameters.lua:

NET_AP = "----"
NET_PASSWORD = "----"

dofile("parameters-device.lua")

parameters-device.lua:

NODE_ID = "node-1"

Set NET_AP to your access point and NEW_PASSWORD to its password.

Connect to access point

Idea is simple, check if we are already connected and if not initialize connection and use timer to check status. After getting valid connection we should launch main.lua and start keep-alive-timer.

I had few cases when net was gone and board didn’t reconnect until rebooted. After few tries I ended with timer that checks connection and count failures in row. After reaching 10 or more it force reboot. Normally board would reconnect automatically before time is out.
In my case it was allright to reboot the device but keep in mind that may not be in yours.
Drawback is that we are taking one timer from available.

wifi-init.lua:


wifi.setmode(wifi.STATION)
wifi.sta.config(NET_AP, NET_PASSWORD)

WIFI_FAIL_COUNTER = 0

tmr.register(2, 5000, 1, function()
    if wifi.sta.status() ~= 5 then
        WIFI_FAIL_COUNTER = WIFI_FAIL_COUNTER + 1
        print ("fail = "..wifi.sta.status())
    else
        WIFI_FAIL_COUNTER = 0
    end
    if WIFI_FAIL_COUNTER > 10 then        
        print "Node reboot"
        node.restart()
    end       
end)

if wifi.sta.getip() == nil then
    tmr.alarm(1, 2000, 1, function() 
       if wifi.sta.getip() == nil then     
          print(" Wait for IP --> "..wifi.sta.status()) 
       else 
          print("New IP address is "..wifi.sta.getip()) 
          tmr.stop(1)            
          tmr.start(2)
          dofile('main.lua')
       end 
    end)
end

Main

This file is main app start file. In boilerplate we will just turn on build in led.
main.lua:

gpio.mode(4, gpio.OUTPUT)
gpio.write(4, gpio.LOW)

print ("core ready")

Compile .lua to .lc

Our files are in lua format. This is plain text format. What we can do is to compile those files. After that we have a bytecode and .lc extension.
Problem is with dofile command. It takes filename as attribute and can not guess extension.  So we need to compile all files all the time or use non-compiled all the time. Compromise is to first check for .lc file and load it if possible and do a fallback to .lua if no .lc available.
For now it is done manually, just replaced all dofile with:

if file.exists('filename.lc') then  
    dofile("filename.lc")        
 else
    dofile("filename.lua")        
end

Summary

We have a simple boilerplate. Now we may focus on project, magic buttons box that will broadcast events into network. Any of our devices may read such message and react. Hopefully this skeleton will develop into something better.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s