I usually never use libraries… but made an exception for these quick projects !
I’m pretty sure that most people reading this very article know about the (very) cheap ESP8266 Wifi module.
A bit more than a year ago, I actually made a small development board for it, which was recently used in the connected lamp that wakes me up. While what follows pales in comparison to what cnlohr has implemented on this chip over the last months, sometimes you just have small projects that you don’t want to spent days on.
Anyway, the ‘standard’ way of compiling programs for this neat little chip involves installing a cross-compiling toolchain on a Linux computer (or VM), and then using a dedicated tool to flash your program to the ESP8266.
As you can guess, this can quickly get tiring if all you want to do is blink an LED… but then I stumbled upon NodeMCU and Domoticz.
NodeMCU is an open-source firmware and development kit that helps electronics enthusiasts to prototype IoT products within a few Lua script lines. Concretely, it is a firmware you can flash to any ESP8266 board, which will then interpret a text file which contains your commands. And while you won’t find many websites detailing nodeMCU based projects, it is very convenient when the program you want to make only contains a few actions.
So let’s imagine you have one ESP8266 development board laying around to which you want to connect a sensor. Let’s also imagine you don’t want to use any Linux tool and want to have everything working as soon as possible. Getting NodeMCU on your board is as simple as:
– Generating your own NodeMCU build and selecting the libraries you want included in it
– Downloading NodeMCU Flasher and using it to flash the firmware you received in the previous step
– Downloading ESP8266 Lua Loader and using it to send your future Lua Scripts
As previously mentioned, the NodeMCU firmware running on your ESP8266 will simply run Lua scripts stored in the ESP8266 Flash. The NodeMCU libraries documentation may be found here.
When the platform first boots, it will try to start a file named init.lua which in our case will contain the commands to connect to our wifi network and start our main script:
--init.lua wifi.setmode(wifi.STATION) wifi.sta.config("mywifinetworkname","mywifinetworkpassword") wifi.sta.connect() tmr.alarm(1, 1000, 1, function() if wifi.sta.getip() == nil then print("IP unavaiable, Waiting...") else tmr.stop(1) print("ESP8266 mode is: " .. wifi.getmode()) print("The module MAC address is: " .. wifi.ap.getmac()) print("Config done, IP is "..wifi.sta.getip()) dofile ("domoticz.lua") end end)
Getting data isn’t particularly useful if it can’t correctly be stored and displayed to the user.
Domoticz is a Home Automation System that lets anyone monitor and configure various devices like lights, switches, various sensors/meters like temperature, rain, wind, UV and much more. It is open source, can be installed on Linux, Windows and embedded devices.
In my case I had it installed on my usbarmory and could access it in my browser in less than 10 minutes. You’ll find Domoticz main user manual here.
As a side note, I was very impressed to see how many devices Domoticz supports, while not neglecting the security aspects that comes with connecting sensors and lights to your local network.
In the Domoticz interface, simply add a virtual device of “light/switch” type and use this domoticz.lua script:
--domoticz.lua pin = PIRSENSOR_PIN last_state = 1 gpio.mode(pin, gpio.INPUT) tmr.alarm(0,100, 1, function() if gpio.read(pin) ~= last_state then last_state = gpio.read(pin) conn = net.createConnection(net.TCP, 0) conn:connect(8080,"YOURSERVERIP") conn:on("receive", function(conn, payload) end) conn:on("connection", function(conn, payload) if last_state == 1 then conn:send("GET /json.htm?type=command¶m=switchlight&idx=YOURSENSORIDX&switchcmd=Set%20Level&level=10" .." HTTP/1.1rn" .."Host: 127.0.0.1:8080rn" .."Connection: closern" .."Accept: */*rn" .."User-Agent: Mozilla/4.0 (compatible; esp8266 Lua; Windows NT 5.1)rn" .."rn") print("ON") else conn:send("GET /json.htm?type=command¶m=switchlight&idx=YOURSENSORIDX&switchcmd=Set%20Level&level=0" .." HTTP/1.1rn" .."Host: 127.0.0.1:8080rn" .."Connection: closern" .."Accept: */*rn" .."User-Agent: Mozilla/4.0 (compatible; esp8266 Lua; Windows NT 5.1)rn" .."rn") print("OFF") end end) conn:on("disconnection", function(conn, payload) end) end end)
I won’t explain the electrical wiring part of things as I’m fairly sure you can figure that out 😉 .
You’ll however need and find the ESP8266 pin number to nodemcu pin number look up table here.
The days are getting warmer and you can notice it indoors!
By connecting a DHT22 to your ESP8266 and strategically placing your platform (not like me) you can get the kind of graph above. Here’s the Lua script:
--domoticz.lua pin = 5 temp_to_send = 0 hum_to_send = 0 aggregate_hum = 0 aggregate_temp = 0 aggregate_counter = 0 status, temp, humi, temp_dec, humi_dec = dht.read(pin) if status == dht.OK then tmr.alarm(0,500, 1, function() status, temp, humi, temp_dec, humi_dec = dht.read(5) if status == dht.OK then aggregate_counter = aggregate_counter + 1 aggregate_hum = aggregate_hum + ((math.floor(humi)*1000) + humi_dec) aggregate_temp = aggregate_temp + ((math.floor(temp)*1000) + temp_dec) if aggregate_counter == 20 then temp_to_send = aggregate_temp/20 hum_to_send = aggregate_hum/20 aggregate_hum = 0 aggregate_temp = 0 aggregate_counter = 0 conn=net.createConnection(net.TCP, 0) conn:connect(8080,"YOURSERVERIP") conn:on("receive", function(conn, payload) end) conn:on("connection", function(conn, payload) conn:send("GET /json.htm?type=command¶m=udevice&idx=YOURSENSORIDX&nvalue=0&svalue=" .. string.format("%d.%d;%d.%d;0", temp_to_send/1000, temp_to_send%1000, hum_to_send/1000, hum_to_send%1000) .." HTTP/1.1rn" .."Host: 127.0.0.1:8080rn" .."Connection: closern" .."Accept: */*rn" .."User-Agent: Mozilla/4.0 (compatible; esp8266 Lua; Windows NT 5.1)rn" .."rn") --print("http://YOURSERVERIP:8080/json.htm?type=command¶m=udevice&idx=YOURSENSORIDX&nvalue=0&svalue=" .. string.format("%d.%03d;%d.%03d;0", math.floor(temp), temp_dec, math.floor(humi), humi_dec)) print(string.format("DHT Temperature:%d.%d Humidity:%d.%drn", temp_to_send/1000, temp_to_send%1000, hum_to_send/1000, hum_to_send%1000)) end) conn:on("disconnection", function(conn, payload) end) end end end) elseif status == dht.ERROR_CHECKSUM then print( "DHT Checksum error." ) elseif status == dht.ERROR_TIMEOUT then print( "DHT timed out." ) end
A few things to note here:
– some averaging was required to get consistent readings with the DHT22
– this particular LUA script uses an integer NodeMCU build
Unfortunately Domoticz doesn’t have a vibration sensor type, but can you still notice the REM cycles?
This graph can simply be generated by connecting an MPU6050 3 axis gyroscope + accelerometer to the ESP8266 and using that (big) script:
--domoticz.lua sda, scl = 5, 7 avg_x = 0 avg_y = 0 avg_z = 0 report_data = 0 aggregate_counter = 0 bigger_aggregate_counter = 0 aggregate_ax_val = 0 aggregate_ay_val = 0 aggregate_az_val = 0 bigger_aggregate_ax_val = 0 bigger_aggregate_ay_val = 0 bigger_aggregate_az_val = 0 aggregate_abs_ax_val = 0 aggregate_abs_ay_val = 0 aggregate_abs_az_val = 0 function write_reg_MPU(reg,val) i2c.start(0) i2c.address(0, 0x68, i2c.TRANSMITTER) i2c.write(0, reg) i2c.write(0, val) i2c.stop(0) end function read_reg_MPU(reg) i2c.start(0) i2c.address(0, 0x68, i2c.TRANSMITTER) i2c.write(0, reg) i2c.stop(0) i2c.start(0) i2c.address(0, 0x68, i2c.RECEIVER) c=i2c.read(0, 1) i2c.stop(0) --print(string.byte(c, 1)) return c end function new_data_interrupt() -- clear interrupt read_reg_MPU(58) -- read acceleration data i2c.start(0) i2c.address(0, 0x68, i2c.TRANSMITTER) i2c.write(0, 59) i2c.stop(0) i2c.start(0) i2c.address(0, 0x68, i2c.RECEIVER) c=i2c.read(0, 8) i2c.stop(0) Ax=bit.lshift(string.byte(c, 1), 8) + string.byte(c, 2) Ay=bit.lshift(string.byte(c, 3), 8) + string.byte(c, 4) Az=bit.lshift(string.byte(c, 5), 8) + string.byte(c, 6) temperature=bit.lshift(string.byte(c, 7), 8) + string.byte(c, 8) if (Ax > 0x7FFF) then Ax = Ax - 0x10000; end if (Ay > 0x7FFF) then Ay = Ay - 0x10000; end if (Az > 0x7FFF) then Az = Az - 0x10000; end if (temperature > 0x7FFF) then temperature = temperature - 0x10000; end temperature = (temperature*100 / 340) + 3653 -- /100 -- data aggregation aggregate_ax_val = aggregate_ax_val + Ax aggregate_ay_val = aggregate_ay_val + Ay aggregate_az_val = aggregate_az_val + Az aggregate_counter = aggregate_counter + 1 if (Az - avg_z) < 0 then aggregate_abs_az_val = aggregate_abs_az_val - Az + avg_z else aggregate_abs_az_val = aggregate_abs_az_val + Az - avg_z end if (Ay - avg_y) < 0 then aggregate_abs_ay_val = aggregate_abs_ay_val - Ay + avg_y else aggregate_abs_ay_val = aggregate_abs_ay_val + Ay - avg_y end if (Ax - avg_x) < 0 then aggregate_abs_ax_val = aggregate_abs_ax_val - Ax + avg_x else aggregate_abs_ax_val = aggregate_abs_ax_val + Ax - avg_x end end function second_timer() if aggregate_counter > 20 then report_data = (aggregate_abs_ax_val+aggregate_abs_ay_val+aggregate_abs_az_val)/(aggregate_counter) bigger_aggregate_az_val = bigger_aggregate_az_val + (aggregate_az_val/aggregate_counter) bigger_aggregate_ay_val = bigger_aggregate_ay_val + (aggregate_ay_val/aggregate_counter) bigger_aggregate_ax_val = bigger_aggregate_ax_val + (aggregate_ax_val/aggregate_counter) bigger_aggregate_counter = bigger_aggregate_counter + 1 if avg_z ~= 0 then conn=net.createConnection(net.TCP, 0) conn:connect(8080,"YOURSERVERIP") conn:on("receive", function(conn, payload) end) conn:on("connection", function(conn, payload) conn:send("GET /json.htm?type=command¶m=udevice&idx=YOURSENSORIDX&nvalue=0&svalue=" .. string.format("%d",report_data) .." HTTP/1.1rn" .."Host: 127.0.0.1:8080rn" .."Connection: closern" .."Accept: */*rn" .."User-Agent: Mozilla/4.0 (compatible; esp8266 Lua; Windows NT 5.1)rn" .."rn") end) print(string.format("%d",report_data)) end if bigger_aggregate_counter == 4 then avg_z = bigger_aggregate_az_val/4 avg_y = bigger_aggregate_ay_val/4 avg_x = bigger_aggregate_ax_val/4 bigger_aggregate_ax_val = 0 bigger_aggregate_ay_val = 0 bigger_aggregate_az_val = 0 bigger_aggregate_counter = 0 end aggregate_ax_val = 0 aggregate_ay_val = 0 aggregate_az_val = 0 aggregate_abs_ax_val = 0 aggregate_abs_ay_val = 0 aggregate_abs_az_val = 0 aggregate_counter = 0 end print("d") end ---test program i2c.setup(0, sda, scl, i2c.SLOW) -- init i2c i2c.start(0) -- start i2c c = i2c.address(0, 0x68, i2c.TRANSMITTER) -- see if something answers i2c.stop(0) -- stop i2c if c == true then print("Device found at address : "..string.format("0x%X",0x68)) else print("Device not found !!") end c = read_reg_MPU(117) -- Register 117 – Who Am I - 0x75 if string.byte(c, 1) == 104 then print("MPU6050 Device answered OK!") else print("Check Device - MPU6050 NOT available!") end read_reg_MPU(107) -- Register 107 – Power Management 1-0x6b if string.byte(c, 1)==64 then print("MPU6050 in SLEEP Mode !") else print("MPU6050 in ACTIVE Mode !") end write_reg_MPU(0x6B, 0) -- Initialize MPU, use 8MHz clock write_reg_MPU(25, 199) -- Sample Rate = Gyroscope Output Rate / (1 + SMPLRT_DIV) >> 40Hz write_reg_MPU(56, 0x01) -- Enables the Data Ready interrupt, which occurs each time a write operation to all of the sensor registers has been completed. gpio.mode(8, gpio.INT, gpio.PULLUP) -- set gpio6 as input interrupt gpio.trig(8, "up", new_data_interrupt) -- new data interupt handler --read_MPU_raw() tmr.alarm(0, 10000, 1, second_timer) --tmr.stop(0)
In the configuration shown above the accelerometer outputs data at a 40Hz frequency and triggers an interrupt every time a sample is ready. We then simply aggregate the absolute value of each axis acceleration output (after removing its average value) and send it to Domoticz.
RGBW lamps like the one shown above are starting to be quite popular on websites like eBay, alibaba etc… They use proprietary 2.4GHz signals which were recently reverse engineered, allowing anyone to control them with an NRF24L01.
But if you’re lazy like I was, you can also send UDP packets to the Wifi adapter that comes with them using a modified version of the script above and these 3 lines to switch on the lights when vibration is detected:
conn = net.createConnection(net.UDP, 0) conn:connect(8899,"WIFIBRIDGEIP") conn:send(string.char(0x47,0x00)) conn:close()
Just in case, I added a MAC filter on my router for that Wifi adapter. And if I’m not mistaken, the adapter doesn’t receive signals on the proprietary 2.4GHz. You may find different UDP packets examples here.
As you can guess I’ve shown here basic examples of the capabilities of the NodeMCU + Domoticz combo. Domoticz allows much more complex actions using scripts which can be triggered by the output of your installed sensors.
In my case I’m only using the monitoring capabilities of Domoticz, even though its main purpose it to automatize your complete home!