This article documents a first project with the Espressif ESP8266 in its second evolution.
The objective is a module that will take periodic temperature and humidity measurements and publish them to an MQTT message broker.
This inital implementation is very basic, it is largely configured in code, though it does use DHCP. Later extensions might include a web interface for configuration of WLAN parameters etc, but for the moment the emphasis is assessment of reliability given some reports on the ‘net.
Evolution 2
The original design embedded key configuration variables in the main source code for simplicity in getting the code working.
Evolution 2 separates configuration variables from code, and provides a web interface for configuring the most common variables. The screenshot above shows the configuration screen including the use of a datalist on the SSID input field.
Hardware
A module was purchased with on board CP210x USB to serial chip. The only other component needed was the DHT22 digital temperature and humidity sensor.
NodeMCU was chosen for the ESP2866 firmware because of the inbuilt support for ‘interesting things’, including the DHT22.
Above is a breadboard of the system for development. The board had a 4MB (32Mb) flash chip on it.
Lots of online schematics show a pullup resistor on the DHT22 data wire.
Above is a scope capture of the DHT22 data wire, pulses are quite square and a pullup resistor is not needed, at least for short connections.
See also NodeMCU devkit V1 deep sleep.
Firmware
The firmware was downloaded from https://nodemcu-build.com/ , modules included were adc, dht, file, gpio, mqtt, net, node, ow, tmr, uart, wifi. (Some were not required for this project.)
The firmware version is:
NodeMCU custom build by frightanic.com
branch: master
commit: 22e1adc4b06c931797539b986c85e229e5942a5f
SSL: false
modules: adc,dht,encoder,file,gpio,mqtt,net,node,ow,tmr,uart,wifi
build built on: 2017-04-30 07:21
powered by Lua 5.1.4 on SDK 2.0.0(656edbf)
Code
The lua code was derived from several published articles, and none worked straight off, probably a result of changes to nodeMCU over time.
nodeMCU depends heavily on the use of callbacks. The preferred scheme is to schedule elements asynchronously… which seems a good idea but using the event for “GOT_IP” to initiate MQTT connection resulted in no connection (DHCP negotation has not been completed at this point).
An alternate method of trying to monitor the GOT_IP event did work successfully, reducing operating time and saving some battery.
There are now several files in the application.
The main application file is dhtiot.lua…
-- Remember to connect GPIO16 (D0) and RST for deep sleep function --# Settings # dofile("nodevars.lua") --# END settings # temperature = 0 humidity = 0 -- DHT22 sensor function get_sensor_Data() dht=require("dht") status,temp,humi,temp_decimial,humi_decimial = dht.read(dhtdata) if( status == dht.OK ) then -- Prevent "0.-2 deg C" or "-2.-6" temperature = temp.."."..(math.abs(temp_decimial)/100) humidity = humi.."."..(math.abs(humi_decimial)/100) -- If temp is zero and temp_decimal is negative, then add "-" to the temperature string if(temp == 0 and temp_decimial<0) then temperature = "-"..temperature end print("Temperature: "..temperature.." deg C") print("Humidity: "..humidity.."%") elseif( status == dht.ERROR_CHECKSUM ) then print( "DHT Checksum error" ) temperature=-1 --TEST elseif( status == dht.ERROR_TIMEOUT ) then print( "DHT Time out" ) temperature=-2 --TEST end dht=nil package.loaded["dht"]=nil end function swf() wifi.setmode(wifi.STATION) wifi.setphymode(wifi_signal_mode) wifi.sta.config({ssid=wifi_SSID,pwd=wifi_password}) wifi.sta.eventMonReg(wifi.STA_GOTIP,smq) wifi.sta.eventMonStart() wifi.sta.connect() if client_ip ~= "" then wifi.sta.setip({ip=client_ip,netmask=client_netmask,gateway=client_gateway}) end print("swf done...") end function smq() print(tmr.now()) print("wifi.sta.status()",wifi.sta.status()) if wifi.sta.status() ~= 5 then print("No Wifi connection...") else get_sensor_Data() m=mqtt.Client(client_id,120,username,password) print(" IP: ".. mqtt_broker_ip) print(" Port: ".. mqtt_broker_port) print("WiFi connected...") m:on("offline",slp) --m:on("connect", function(client) print ("connected") end) --m:on("offline", function(client) print ("offline") end) m:connect(mqtt_broker_ip,mqtt_broker_port,0,0, function(conn) print("Connected to MQTT") print(" IP: ".. mqtt_broker_ip) print(" Port: ".. mqtt_broker_port) print(" Client ID: ".. mqtt_client_id) print(" Username: ".. mqtt_username) print("emonhub/tx/"..nodeid.."/values"..":".. temperature .. "," .. humidity) m:publish("emonhub/tx/"..nodeid.."/values",temperature .. "," .. humidity, 0, 0, function(conn) print("Published.") m:close() tmr.alarm(0,1000,tmr.ALARM_SINGLE,slp) -- tmr.alarm(0,2000,tmr.ALARM_SINGLE,function() slp() end) end) end, function(conn,reason) print("MQTT connect failed",reason) end) end print("smq done...") end function slp() print(tmr.now()) node.dsleep(time_between_sensor_readings*1000-tmr.now()+8100,2) end print("Starting...") swf() -- Watchdog loop, will force deep sleep the operation somehow takes to long tmr.alarm(1,30000,1,function() node.dsleep(time_between_sensor_readings*1000) end)
The file nodevars.lua contains some node specific configuration variables…
-- wifi.PHYMODE_B 802.11b, More range, Low Transfer rate, More current draw -- wifi.PHYMODE_G 802.11g, Medium range, Medium transfer rate, Medium current draw -- wifi.PHYMODE_N 802.11n, Least range, Fast transfer rate, Least current draw wifi_signal_mode = wifi.PHYMODE_G -- If the settings below are filled out then the module connects -- using a static ip address which is faster than DHCP and -- better for battery life. Blank "" will use DHCP. -- My own tests show around 1-2 seconds with static ip -- and 4+ seconds for DHCP --client_ip="192.168.0.17" --client_netmask="255.255.255.0" --client_gateway="192.168.0.20" client_ip="" client_netmask="" client_gateway="" --- INTERVAL --- -- In milliseconds. Remember that the sensor reading, -- reboot and wifi reconnect takes a few seconds --time_between_sensor_readings = 60000-6000 time_between_sensor_readings = 60000
The web configuration interface is provided by wifi_setup.lua.
--declare cfgvars in init.lua print("Get available APs") available_aps = "" apdatalist="" wifi.setmode(wifi.STATION) wifi.sta.getap(function(t) if t then for k,v in pairs(t) do ap = string.format("%-10s",k) ap = trim(ap) print(ap) available_aps = available_aps .. ap .." " apdatalist=apdatalist .. "<option value='" .. ap .. "'>" end print(available_aps) print("Starting Alarm!") tmr.alarm(0,5000,1, function() setup_server(available_aps) end ) end end) local unescape = function (s) s = string.gsub(s, "+", " ") s = string.gsub(s, "%%(%x%x)", function (h) return string.char(tonumber(h, 16)) end) return s end function setup_server(aps) print("Setting up Wifi AP") wifi.setmode(wifi.SOFTAP) wifi.ap.config({ssid="ESP8266"}) wifi.ap.setip({ip="192.168.1.1",netmask="255.255.255.0",gateway="192.168.1.1"}) print("Setting up webserver") --dhcp server dhcp_config={} dhcp_config.start = "192.168.1.100" wifi.ap.dhcp.config(dhcp_config) wifi.ap.dhcp.start() --web server srv = nil srv=net.createServer(net.TCP) srv:listen(80,function(conn) conn:on("receive", function(socket,request) local buf = "" local _, _, method, path, vars = string.find(request, "([A-Z]+) (.+)?(.+) HTTP"); if(method == nil)then _, _, method, path = string.find(request, "([A-Z]+) (.+) HTTP") end if (vars ~= nil)then socket:send("Saving data...") file.open("config.lua", "w") for key,val in string.gmatch(vars,"([%w_]+)=([^%&]*)&*") do file.writeline(key .. '="' .. unescape(val) .. '"') end file.close() node.compile("config.lua") file.remove("config.lua") socket:send("restarting...") socket:on("sent",function(skt) skt:close();collectgarbage();node.restart() end) else buf = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<html><body>" buf = buf .. "<h3>Configure WiFi</h3><br>" buf = buf .. "<form method='get' action='http://" .. wifi.ap.getip() .."'>" buf = buf .. "Available APs: "..aps.."<br><br>" buf = buf .. cfgvars[1]..": <input list='apdatalist' name='"..cfgvars[1].."'><datalist id='apdatalist'>" .. apdatalist .. "</datalist><br><br>" i=2 while cfgvars[i] do buf=buf .. cfgvars[i] .. ": <input type='text' name='" .. cfgvars[i] .. "' value='" .. cfgdefs[i] .. "'></input><br><br>" i=i+1 end buf = buf .. "<br><button type='submit'>Save</button>" buf = buf .. "</form></body></html>" socket:send(buf) socket:on("sent",function(skt) skt:close();collectgarbage() end) end end) end) print("Please connect to: " .. wifi.ap.getip()) tmr.stop(0) end function trim(s) return (s:gsub("^%s*(.-)%s*$", "%1")) end
The startup manager is init.lua…
cfgvars={ "wifi_SSID", "wifi_password", "nodeid", "dhtdata", "mqtt_broker_ip", "mqtt_broker_port", "mqtt_username", "mqtt_password", "mqtt_client_id" } cfgdefs={ "", "", "", 4, "192.168.0.10", 1883, "", "", "", } print("\n\nHold Pin00 low for 5s t0 stop boot.") print("\n\nHold Pin00 low for 5s for config mode.") tmr.delay(1000000) if gpio.read(3) == 0 then print("Release to stop boot...") tmr.delay(4000000) if gpio.read(3) == 0 then print("Release now (wifit cfg)...") print("Starting wifi config mode...") dofile("wifi_setup.lua") return else print("...boot stopped") return end end print("Starting...") if pcall(function () print("Open config") dofile("config.lc") end) then dofile("dhtiot.lua") else print("Starting wifi config mode...") dofile("wifi_setup.lua") end
The code as it evolves is available online https://github.com/owenduffy/dht22iotm .
Problems
For whatever reason, I could not write new firmware on Windows 10 (32), It either didn’t work, or resulted in BSOD. I eventually wrote the firmware on Linux using esptool.py which has been reliable (though it did not work in Windows 10.
Similarly, Esplorer cause BSOD on the same machine communicating with the module. It did work reliably on my laptop with Windows 10 (64).
The WEMOS MiniPro D1 does not work with security enabled on a DLINK router where the other module pictured earlier works fine.
Test results
The system is running under test and small changes being explored to improve operation. With recent changes to get some event handling working, average current drawn from 5V is 5mA.
Follow on implementations
WEMOS D1 Mini Pro
Above, the WEMOS D1 Mini Pro with DHT shield with AM2302 fitted. The board had a 16MB (128Mb) flash chip on it though the specifications suggest that 2MB (16Mb) is the maximum supported flash. Interestingly the esptool.py programming utility v1.3 from Espressif supports 32Mb, though that may changes with v2.0.
Above, the WEMOS module with a Ge diode added for the deep sleep reset circuit (NodeMCU devkit V1 deep sleep).