NodeMcu Lua ESP8266 ESP-12E and Elderberries

ESP8266

Out of the Banggood bag came a couple of NodeMcu Lua ESP8266 ESP-12E WiFI Development Boards, fresh from the factory in Shenzhen and raring to go. The first thing to point out is that they came with absolutely no documentation at all – just a couple boards pinned to some foam and wrapped in a ton of bubbles.

These things really are tiny, smaller than my thumb, but they pack quite a punch. Tech specs for the ESP-12E model are as follows:

  • Support STA/AP/STA+AP 3 working modes;
  • Built-in TCP/IP protocol stack, support multiple-channel TCP Client connection (max 5);
  • 0~D8, SD1~SD3: used for GPIO, PWM, IIC, ect; the driven ability can be arrived at 15mA;
  • AD0: one-way ADC;
  • Power input: 4.5V~9V(10VMAX), support USB powered and USB debug;
  • Working current: ˜70mA(200mA MAX, continue), standby<200uA;
  • Transmission data rate: 110-460800bps;
  • Support UART/GPIO data communication interface;
  • Support update firmware remotely (OTA);
  • Support Smart Link;
  • Working temperature:-40?~+125?;
  • Driven mode: double large-power H bridge driven
  • Weight: 7g.

Getting them connected is simplicity itself. Plug a USB cable into your PC and the other end into the board, and away you go. Or do you? Without the right driver and software on your PC to talk to the board, you’re not going to be doing much. Some internet sleuthing led me to the manufacturer’s site, the “Shenzhen Doctors of Intelligence and Technology” at www.doit.am – if only I’d turned over the board a half hour earlier, I’d have seen their URL underneath.

On Windows 10, the CP2102 USB to UART Bridge Virtual COM Port driver automatically installs itself when you connect the device – plug it in, wait a few seconds, and you’ll see it install. Apparently on older versions of Windows or other OS’s you’ll need to do that manually. Silicon Labs have drivers available for Windows XP through to 8.1, WinCE, OSX, Linux and Android, so head there if you need them.

Alongside the driver, you’ll also want a way to program your board. Go get the ESP8266 Lua Loader which makes uploading files and working with the Lua interface possible from Windows. Once you’ve downloaded the .zip, install it somewhere sensible and run the LuaLoader executable – you’ll see something like this:

LuaLoader
LuaLoader 0.87 in action

Open the Settings menu and select Comm Port Settings from the dropdown, then choose the COM port that your board is connected to in order to make the connection. There’s a helpful “Why don’t I see my port listed?” button on the Comm Port Settings menu that you can refer to if your COM port’s not available for selection.

So now you have your driver installed, your ESP8266 connected and powered up, and LuaLoader installed and configured to talk to the device – but before trying to program it, why not see what it’s already running? The ESP-12 boards execute a Lua script on boot to do something interesting, and the good people at doit.am have programmed theirs to act as a WiFi access point and run a web server at startup. Rather than futz with the network settings on your PC, pull out your phone and open your wifi settings to look for a network called “DoItWiFi” – and join it. Once connected, visit the web server that’s running on that tiny chip – point your browser at http://192.168.1.1 and you’ll find the output of a script that produces this:

DoItWiFi  A Lua web server

This demo shows some of the power of this little gizmo – flipping the toggles changes the state of a couple GPIO pins on the board, one of which is one of the blue LED lights. Entertain yourself by turning it on and off a few times, then take a sip of your ale and start to figure out what’s going on here.

Putting your phone to one side, go back to LuaLoader and press the file.list button on the right of the screen to see what scripts are there. Fresh from the packing, you’ll should see something like this:

> ‚n?†$“R¶£ÿ4ž‚”*’¶£ÿ$Ž’2:’ÁºR%,‡ùþFüÿè

NodeMcu 0.9.6 (Doit.am Version) build 20150701 powered by Lua 5.1.4
ESP8266 Started

Hard Restart Friday, October 23, 2015 22:31:25

Start soft AP
> for k,v in pairs(file.list()) do l = string.format(“%-15s”,k) print(l..” “..v..” bytes”) end

init.lua 731 bytes
header.htm 2161 bytes
webserver.lc 2644 bytes
>

The junk at the start is some garbage text displayed at startup, so ignore that. The next couple lines tell us what version of the NodeMcu firmware and Lua interpreter that we’re running, followed by the time that I started the board up. Start soft AP is the output of the init.lua script that runs on boot – so we know that things are alive.

Following that, we see the command line generated and sent to the NodeMcu by LuaLoader when we pressed the file.list button – for k,v in pairs(file.list()) do l = string.format(“%-15s”,k) print(l..” “..v..” bytes”) end – and the three files that are available on our device:

  • init.lua
  • header.htm
  • webserver.lc

I’ve yet to figure out how to easily copy files down from the device, but some more internet sleuthing led me to https://smartarduino.gitbooks.io/user-manual-for-esp-12e-devkit/content/source_code.html which lists the content of the files as follows:

File 1: Init.lua

--Doit WiFi Robo Car Ctronl Demo
--ap mode
--Created @ 2015-05-13 by Doit Studio
--Modified: null
--Global Site: http://doit.am/
--China Site: http://cn.doit.am/
--Global Shop: http://www.smartarduino.com/
--China Shop: http://szdoit.taobao.com/
--Chinese BBS: bbs.iot.fm

print("\n")
print("ESP8266 Started")

local exefile="webserver"
local luaFile = {exefile..".lua"}
for i, f in ipairs(luaFile) do
if file.open(f) then
file.close()
print("Compile File:"..f)
node.compile(f)
print("Remove File:"..f)
file.remove(f)
end
end

if file.open(exefile..".lc") then
dofile(exefile..".lc")
else
print(exefile..".lc not exist")
end
exefile=nil;luaFile = nil
collectgarbage()

File2: WebServer.lua

--Doit WiFi Robo Car Ctronl Demo
--ap mode
--Created @ 2015-05-13 by Doit Studio
--Modified: null
--Global Site: http://doit.am/
--China Site: http://cn.doit.am/
--Global Shop: http://www.smartarduino.com/
--China Shop: http://szdoit.taobao.com/
--Chinese BBS: bbs.iot.fm
--[ is used to replace 《

print("Start soft AP")

wifi.setmode(wifi.SOFTAP)
local cfg={}
cfg.ssid="DoitWiFi";
cfg.pwd="12345678"
wifi.ap.config(cfg)

cfg={}
cfg.ip="192.168.1.1"
cfg.netmask="255.255.255.0"
cfg.gateway="192.168.1.1"
wifi.ap.setip(cfg)

start_init = function()
gpio.mode(0, gpio.OUTPUT);
gpio.mode(1, gpio.OUTPUT);
gpio.write(0,gpio.HIGH);
gpio.write(1,gpio.HIGH);
D1_state=0;
D0_state=0;
end

sendFileContents = function(conn, filename)
if file.open(filename, "r") then
--conn:send(responseHeader("200 OK","text/html"));
repeat
local line=file.readline()
if line then
conn:send(line);
end
until not line
file.close();
else
conn:send(responseHeader("404 Not Found","text/html"));
conn:send("Page not found");
end
end

responseHeader = function(code, type)
return "HTTP/1.1 " .. code .. "\r\nConnection: close\r\nServer: nunu-Luaweb\r\nContent-Type: " ..
type .. "\r\n\r\n";
end

httpserver = function ()
start_init();
srv=net.createServer(net.TCP)
srv:listen(80,function(conn)
conn:on("receive",function(conn,request)
conn:send(responseHeader("200 OK","text/html"));
if string.find(request,"gpio=0") then
if D0_state==0 then
D0_state=1;gpio.write(0,gpio.LOW);
else
D0_state=0;gpio.write(0,gpio.HIGH);
end
elseif string.find(request,"gpio=1") then
if D1_state==0 then
D1_state=1;gpio.write(1,gpio.LOW);
else
D1_state=0;gpio.write(1,gpio.HIGH);
end
else
if D0_state==0 then
preset0_on="";
end
if D0_state==1 then
preset0_on="checked=\"checked\"";
end
if D1_state==0 then
preset1_on="";
end
if D1_state==1 then
preset1_on="checked=\"checked\"";
end

sendFileContents(conn,"header.htm");
conn:send("[div&gt;[input type=\"checkbox\" id=\"checkbox0\" name=\"checkbox0\" class=\"switch\" onclick=\"loadXMLDoc(0)\" "..preset0_on.." /&gt;");
conn:send("[label for=\"checkbox0\"&gt;D0[/label&gt;[/div&gt;");
conn:send("[div&gt;[input type=\"checkbox\" id=\"checkbox1\" name=\"checkbox1\" class=\"switch\" onclick=\"loadXMLDoc(1)\" "..preset1_on.." /&gt;");
conn:send("[label for=\"checkbox1\"&gt;D1[/label&gt;[/div&gt;");
conn:send("[/div&gt;");
end
print(request);
end)
conn:on("sent",function(conn)
conn:close();
conn = nil;
end)
end)
end

httpserver()

File 3: header.htm

<html>
<head>
<title>doit</title>
<style>
body
{
font-family: sans-serif;
font-weight: normal;
margin: 10px;
color: #555;
background-color: #eee;
}
form
{
margin: 40px 0;
}
div
{
clear: both;
margin: 0 50px;
}
input.switch:empty
{
margin-left: -999px;
}
input.switch:empty ~ label
{
position: relative;
float: left;
line-height: 1.6em;
text-indent: 4em;
margin: 2em 0;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
input.switch:empty ~ label:before,
input.switch:empty ~ label:after
{
position: absolute;
display: block;
top: 0;
bottom: 0;
left: 0;
content: ‘\2718’;
width: 3.6em;
text-indent: 2.4em;
color: #900;
background-color: #c33;
border-radius: 0.3em;
box-shadow: inset 0 0.2em 0 rgba(0,0,0,0.3);
}
input.switch:empty ~ label:after
{
content: ‘ ‘;
width: 1.4em;
top: 0.1em;
bottom: 0.1em;
text-align: center;
text-indent: 0;
margin-left: 0.1em;
color: #f88;
background-color: #fff;
border-radius: 0.15em;
box-shadow: inset 0 -0.2em 0 rgba(0,0,0,0.2);
-webkit-transition: all 100ms ease-in;
transition: all 100ms ease-in;
}
/* toggle on */
input.switch:checked ~ label:before
{
content: ‘\2714’;
text-indent: 0.5em;
color: #6f6;
background-color: #393;
}
input.switch:checked ~ label:after
{
margin-left: 2.1em;
color: #6c6;
}
/* focus styles */
input.switch:focus ~ label
{
color: #000;
}
input.switch:focus ~ label:before
{
box-shadow: 0 0 0 3px #999;
}
</style>
<script language=”javascript”>
function loadXMLDoc(gpio)
{
var xmlhttp;
if (window.XMLHttpRequest)
{
xmlhttp=new XMLHttpRequest();
}
else
{
xmlhttp=new ActiveXObject(“Microsoft.XMLHTTP”);
}
xmlhttp.open(“GET”,”gpio=” + gpio+”.pht”,true); xmlhttp.send();
}
</script>
</head>
<body>
<div>
<h1>NodeMCU Doit Version GPIO DEMO</h1>

You can ignore the comment that claims that this is a “Doit WiFi Robo Car Ctronly Demo”, because I don’t think it is. What’s really going on here is that init.lua loads on boot, prints a message to tell us that it’s starting

print("\n")

print("ESP8266 Started")

looks for a file called webserver in either executable or uncompiled form:

local exefile="webserver"
local luaFile = {exefile..".lua"}
for i, f in ipairs(luaFile) do
if file.open(f) then
file.close()
print("Compile File:"..f)
node.compile(f)
print("Remove File:"..f)
file.remove(f)
end
end

If it doesn’t exist as a .lc file, it compiles it and removes the .lua script, then executes it before cleaning up after itself:

if file.open(exefile..".lc") then
dofile(exefile..".lc")
else
print(exefile..".lc not exist")
end
exefile=nil;luaFile = nil
collectgarbage()

Given the very limited amount of memory on the NodeMcu board, you’re going to want to wrangle as much memory as you can out of it – hence the compilation of the webserver.lua script to webserver.lc, and the garbage collection that follows.

So – init.lua is being used here just to bootstrap our board to do something more useful, but all the action’s really taking place inside of webserver.lc. What’s happening in there? Well, in the webserver.lua file we can start to figure that out. After the initial comments, we see a few functions declared:

print("Start soft AP")

wifi.setmode(wifi.SOFTAP)
local cfg={}
cfg.ssid="DoitWiFi";
cfg.pwd="12345678"
wifi.ap.config(cfg)

cfg={}
cfg.ip="192.168.1.1"
cfg.netmask="255.255.255.0"
cfg.gateway="192.168.1.1"
wifi.ap.setip(cfg)

Straightforward enough – we print a string to the console to say we’re starting a software access point, then define the WiFi mode we’re going to use and the parameters we’ll apply to it. Remember when you connected your cellphone browser to http://192.168.1.1 earlier? This is where that IP address came from.

After that, we’ll set up the initialisation of the GPIO pins using start_init that the web server can controls in this demo:

start_init = function()
gpio.mode(0, gpio.OUTPUT);
gpio.mode(1, gpio.OUTPUT);
gpio.write(0,gpio.HIGH);
gpio.write(1,gpio.HIGH);
D1_state=0;
D0_state=0;
end

Followed by a pair of functions sendFileContents and responseHeader used by the actual http server to read and send contents, and set response headers appropriately.

sendFileContents = function(conn, filename)
if file.open(filename, "r") then
--conn:send(responseHeader("200 OK","text/html"));
repeat
local line=file.readline()
if line then
conn:send(line);
end
until not line
file.close();
else
conn:send(responseHeader("404 Not Found","text/html"));
conn:send("Page not found");
end
end

responseHeader = function(code, type)
return "HTTP/1.1 " .. code .. "\r\nConnection: close\r\nServer: nunu-Luaweb\r\nContent-Type: " ..
type .. "\r\n\r\n";
end

The penultimate block of code is where things get interesting – httpserver sets us up to listen for incoming requests on port 80 and acts in response to data that’s posted to it from the connected web browser. Rather that host all the HTML markup within this script, webserver.lua references the third file we find on our device (header.htm) and uses it’s contents to set all the appropriate markup attributes we need to make the web page it delivers somewhat respectable.

httpserver = function ()
start_init();
srv=net.createServer(net.TCP)
srv:listen(80,function(conn)
conn:on("receive",function(conn,request)
conn:send(responseHeader("200 OK","text/html"));
if string.find(request,"gpio=0") then
if D0_state==0 then
D0_state=1;gpio.write(0,gpio.LOW);
else
D0_state=0;gpio.write(0,gpio.HIGH);
end
elseif string.find(request,"gpio=1") then
if D1_state==0 then
D1_state=1;gpio.write(1,gpio.LOW);
else
D1_state=0;gpio.write(1,gpio.HIGH);
end
else
if D0_state==0 then
preset0_on="";
end
if D0_state==1 then
preset0_on="checked=\"checked\"";
end
if D1_state==0 then
preset1_on="";
end
if D1_state==1 then
preset1_on="checked=\"checked\"";
end

sendFileContents(conn,"header.htm");
conn:send("[div&gt;[input type=\"checkbox\" id=\"checkbox0\" name=\"checkbox0\" class=\"switch\" onclick=\"loadXMLDoc(0)\" "..preset0_on.." /&gt;");
conn:send("[label for=\"checkbox0\"&gt;D0[/label&gt;[/div&gt;");
conn:send("[div&gt;[input type=\"checkbox\" id=\"checkbox1\" name=\"checkbox1\" class=\"switch\" onclick=\"loadXMLDoc(1)\" "..preset1_on.." /&gt;");
conn:send("[label for=\"checkbox1\"&gt;D1[/label&gt;[/div&gt;");
conn:send("[/div&gt;");
end
print(request);
end)
conn:on("sent",function(conn)
conn:close();
conn = nil;
end)
end)
end

httpserver reads the desired states of the GPIO pins from the http request it receives from the client by looking for gpio=x values, and uses the gpio.write command to set the value of the pin as appropriate. You can imagine all sorts of uses for this, I’m sure – controlling lights, controlling devices, reading information from various sensors and displaying it in a web ui all spring to mind. We’ll look at some of those in future posts. But back to the code in hand – we finish up with our equivalent of main in this case is the final line of code;

httpserver()

that sets everything in motion.

My accompaniment for tonight’s exploration was Stone Brewing’s Your Father Smelt of Elderberries, an almost indescribably delicious medieval English style ale with a hint of elderberries. At 10.3% ABV you’re only going to be able to finish a couple of them before nothing makes much sense any more – but you’ll hopefully really enjoy it!


Leave a Reply

Your email address will not be published. Required fields are marked *

Read previous post:
2015-10-23 banggood delivery
Chips from China, beer from Washington

My first order from banggood.com came in 10 days after I placed it - a couple ESP8266 development boards, an...

Close