ESP8266 IoT DHT22 temperature and humidity – evolution 2

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://bitbucket.org/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).