md
NodeMCU, MQTT and Domoticz - part 1
January 17th, 2017
Next: NodeMCU, MQTT and Domoticz - part 2

In a previous post, I set up a tool chain to program a Wemos D1 mini with the NodeMCU firmware and showed how to upload and execute Lua scripts on the ESP8266 chip. In this post I continue with setting up a WiFi connection and updating a device in the home automation software Domoticz, a temperature sensor in two ways: using MQTT and using an HTML request.

Connecting to a Wifi network

When I checked the wireless IP address of D1 mini, I expected a nil answer, but instead something valid came back!
> print(wifi.sta.getip()) 192.168.0.122 255.255.255.0 192.168.0.1 >
Now, these chips are really exciting, but I doubt they are so powerful as to guess the name and password of the LAN. In all likelyhood, the data was stored in the system ROM when I connected to the wireless LAN in the Arduino IDE. If you have not yet connected to your WiFi, you should enter the commands
> wifi.setmode(wifi.STATION) > wifi.sta.config("your_network_ssid","your_network_password")

Check that the connection is ok, ask to see the IP address of the device.

> print(wifi.sta.getip()) 192.168.0.136 255.255.255.0 192.168.0.1 >

You can log in on the router to look at connected devices. That way I found that the D1 mini identity was "ESP_B3B4B5" which is made up of the last 3 digits of its MAC address.

> print(wifi.sta.getmac()) 5c:cf:7f:b3:b4:b5 >

Publishing to an MQTT Broker

Lets replicate Elliot Williams's article MINIMAL MQTT: NETWORKED NODES. In what follows, I am assuming that there is an MQTT broker on a computer with IP address 192.168.0.49 on the network. Here is how I installed a broker on the Raspberry Pi hosting my home automation software.

Create a subscriber to all messages on the topic "home" from the MQTT broker on the Raspberry Pi.

pi@rpi2b:~ $ mosquitto_sub -h localhost -v -t home/#  
or on the desktop computer
michel@hp:~ $ mosquitto_sub -h 192.168.0.49 -v -t home/#  
or on both if wanted.

We will create an MQTT client on the D1 mini which can both subscribe and publish topics to an MQTT broker. We will look at publishing here. Once the client is created, it must connect to the broker before publishing to a topic. The following Lua code fragment does that publishing a message to the topic home/test.

> m = mqtt.Client("myD1mini", 120, "", "") -- blank user and password > m:connect("192.168.0.22") > m:publish("home/test", "hi from wemos", 0, 0)

And as expected, the subscriber will see the message:

pi@rpi2b:~ $ mosquitto_sub -h localhost -v -t home/# home/test hi from wemos  

There is no feedback at the D1 mini end. Let us add at the very least a message that confirms that the connection to the MQTT server was successful.

> m = mqtt.Client("myWemos", 120, "", "") -- blank user and password > m:on("connect", function() print("connected") end ) > m:connect("192.168.0.22") > connected > m:publish("home/test", "hi from wemos, again", 0, 0)
Basically, an unnamed callback function that prints "connected" has been defined in the second line and passed on to the MQTT object. It will invoke the callback function when it connects to the MQTT broker. That is why "connected" is seen echoed on the screen after the m:connect command.

Sending the Temperature to MQTT

Let's return to Elliot William's tutorial, to the section entitled Sensor Node. Following along with it, we will simulated a temperature sensor that is connected to the D1 mini which will publish the temperature to the MQTT broker. First we will create the sensor simulator which is essentially a random walk generator.

> function updateTemp() >> temp = temp + math.random(5) - 3 >> return temp >> end > temp=22
The last line creates and initializes the temperature variable.

Test the function by printing the value returned by updateTemp.

> for i=1,5,1 do >> print(i .. ': ' .. updateTemp() ..'°C') >> end 1: 20°C 2: 20°C 3: 18°C 4: 17°C 5: 18°C

Finally publish the temperature to the MQTT broker

> m = mqtt.Client("my_D1_mini", 120, "", "") > m:connect("192.168.0.22") > m:publish("home/outdoors/temperature", updateTemp(), 0, 1)
and verify that the message is received in a subscribing terminal. Note the trailing 1 in the publish statement. You may want to read about QoS in MQTT or the details in Elliot's tutorial.

Instead of doing this in a terminal, you could create a file, say temp2mqtt.lua (or download it).

-- Simulated temperature sensor publishing to MQTT mqtt_IP = '192.168.0.22' -- IP address of MQTT broker interval = 10*1000 -- interval between temperature updates (1000 = 1 second) temp = 22 -- initial temperature of simulated sensor function updateTemp() -- random walk simulating temperature sensor temp = temp + math.random(5) - 3 return temp end -- create and connect MQTT client m = mqtt.Client("WemosTest", 120, "", "") m:connect(mqtt_IP) -- function to publish MQTT temperature topic function post_temp() updateTemp() m:publish("home/outdoors/temperature", temp, 0, 1) end -- finally create a 10 second alarm which will post the temperature tmr.alarm(0, interval, tmr.ALARM_AUTO, post_temp)

Upload the file and execute it as explained above.

michel@hp:~/Documents/Site/michel/src/program/misc$ nodemcu-uploader upload temperature.lua michel@hp:~/Documents/Site/michel/src/program/misc$ nodemcu-uploader exec temperature.lua
There is no way to interrupt the loop except by pressing the RESET button on the D1 mini.

Temperature Device in Domoticz

Since Domoticz is set up to subscribe to MQTT messages with the topic domoticz/in, a simple modification of the published message in the previous script will update a temperature device in the home automation software.

The first step is to create a virtual temperature sensor. The steps are

Verify that all is working correctly by manually changing the displayed temperature using an HTML call in a web browser. This is the URL to display a 10°C temperature:
http://192.168.0.22:8080/json.htm?type=command&param=udevice&idx=32&nvalue=0&svalue=10

The IP address has to be adjusted and so does the device index to the right of idx. The temperature is sent as a string value (svalue) and the numeric value (nvalue) has no significance.

Once that is working, tweak the temperature script to create temp2domoticz_mqtt.lua (available for dowload):

-- Simulated temperature sensor connected to Domoticz via MQTT mqtt_IP = '192.168.0.22' -- IP address of MQTT broker interval = 10*1000 -- interval between temperature updates (1000 = 1 second) tempidx = 32 -- temperature device index number in Domoticz (idx) temp = 22 -- initial temperature of simulated sensor function updateTemp() -- random walk simulating temperature sensor temp = temp + math.random(5) - 3 return temp end -- create and connect MQTT client m = mqtt.Client("WemosTest", 120, "", "") m:connect(mqtt_IP) -- function to publish MQTT temperature topic function post_temp() updateTemp() m:publish('domoticz/in', '{ "idx" : '..tempidx..', "nvalue" : 0, "svalue" : "'..temp..'" }', 0, 0) end -- finally create an alarm which will post the temperature at regular intervals tmr.alarm(0, interval, tmr.ALARM_AUTO, post_temp)

Every 10 seconds, the temperature is published by the D1 mini to the MQTT broker which then pushes it to Domoticz MQTT client and consequently Domoticz updates its database and Web interface. This indirect communication takes a few seconds so the D1 mini polling interval cannot be reduced too much.

Doing without MQTT

Of course, it is possible to bypass the MQTT broker. The firmware (the one I obtained from NodeMCU custom builds, not Elliot Williams build) contains an HTTP client capable of performing requests. First we check how it works in the terminal by setting the temperature to 8°C and then we will change the Lua script. It is necessary to use a callback function in the HTTP request. The one below comes from the HTTP module documentation.

michel@hp:~$ miniterm.py --- Available ports: --- 1: /dev/ttyUSB0 USB2.0-Serial --- Enter port index or full name: 1 --- Miniterm on /dev/ttyUSB0 9600,8,N,1 --- --- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- > > function httpresult(code, data) >> if (code < 0) then >> print("HTTP request failed", "code: "..code) >> else >> print(data) >> end >> end > http.get("http://192.168.0.22:8080/json.htm?type=command&param=udevice&idx=32&nvalue=0&svalue=8",nil,httpresult) > 200 { "status" : "OK", "title" : "Update Device"
As can be seen, Domoticz responds with a JSON string confirming that it has updated the device. This is easily verified by checking the Web interface and looking at the displayed temperature and the date and time it was modified.

Here is a modified Lua script (temp2domoticz_http.lua).

-- Simulated temperature sensor connected to Domoticz via HTTP request domo_IP = '192.168.0.22:8080' -- IP and port of Domoticz server interval = 10*1000 -- interval between temperature updates (1000 = 1 second) tempidx = 32 -- temperature device index number in Domoticz (idx) temp = 22 -- initial temperature function updateTemp() -- random walk simulating temperature sensor temp = temp + math.random(5) - 3 return temp end -- function to display returned result from http GET function httpresult(code, data) if (code < 0) then print(code, "HTTP request failed") else print(code, data) end end -- function to publish temperature function post_temp() updateTemp() http.get('http://'..domo_IP..'/json.htm?type=command&param=udevice&idx='..tempidx..'&nvalue=0&svalue='..temp,nil,httpresult) end -- finally create an alarm which will post the temperature at regular intervals tmr.alarm(0, interval, tmr.ALARM_AUTO, post_temp)

Next

The next step is to subscribe to Domoticz' outgoing MQTT messages that have for topic domoticz/out. Those messages are JSON strings as one would expect with Domoticz. That is why I added the CJSON package to the version of NodeCMU installed on the D1 mini.

Next: NodeMCU, MQTT and Domoticz - part 2