├── .gitignore ├── README.md ├── ROM ├── README.md └── nodemcu-master-9-modules-2016-05-05-20-09-20-integer.bin ├── display ├── .gitignore ├── Makefile ├── init.lua └── mqtt_display_node.lua ├── power_test ├── Makefile ├── init.lua └── power_test.lua └── sensor ├── .gitignore ├── Makefile ├── init.lua └── mqtt_temp_node.lua /.gitignore: -------------------------------------------------------------------------------- 1 | init_local.lua 2 | init_clean.lua 3 | association_test 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## What? 2 | 3 | Simple sensor and display code for an [MQTT demo series on Hackaday](http://hackaday.com/2016/05/09/minimal-mqtt-building-a-broker/). 4 | 5 | ## Contents 6 | 7 | `display` is an RGB LED that tells you the temperature, based on data from `sensor`. The whole thing runs on NodeMCU, and the ROM version I used is here too. 8 | 9 | Read the articles, set up an MQTT server (or use http://test.mosquitto.org), and flash up two nodes. You're on your way to independent home automation. 10 | 11 | 12 | -------------------------------------------------------------------------------- /ROM/README.md: -------------------------------------------------------------------------------- 1 | ## MQTT & DHT sensor build: http://nodemcu-build.com/ 2 | 3 | This was built against the master branch and includes the following modules: 4 | dht, file, gpio, mqtt, node, tmr, uart, wifi, ws2812. 5 | 6 | ## RUNME 7 | 8 | If you've got `esptool.py` installed, you can simply: 9 | `esptool.py --port /dev/ttyUSB0 --baud 57600 write_flash 0x00000 nodemcu-master-9-modules-2016-05-05-20-09-20-integer.bin` 10 | 11 | -------------------------------------------------------------------------------- /ROM/nodemcu-master-9-modules-2016-05-05-20-09-20-integer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagon5un/hackaday_mqtt/83360bb5d8f396fab0f401f28118f151e5377786/ROM/nodemcu-master-9-modules-2016-05-05-20-09-20-integer.bin -------------------------------------------------------------------------------- /display/.gitignore: -------------------------------------------------------------------------------- 1 | init_local.lua 2 | -------------------------------------------------------------------------------- /display/Makefile: -------------------------------------------------------------------------------- 1 | LUAFILES:= init.lua mqtt_display_node.lua 2 | 3 | PORT=/dev/ttyUSB0 4 | BAUD=9600 5 | 6 | all: flash restart term 7 | 8 | flash: $(LUAFILES) 9 | nodemcu-uploader -p $(PORT) -b $(BAUD) upload $^ 10 | 11 | restart: 12 | echo "node.restart()" | cat > $(PORT) 13 | 14 | term: 15 | pyterm $(PORT) $(BAUD) 16 | 17 | -------------------------------------------------------------------------------- /display/init.lua: -------------------------------------------------------------------------------- 1 | WIFI_ESSID = "" 2 | WIFI_PASSWORD = "" 3 | EXEC_FILE = "mqtt_display_node.lua" 4 | timeout = 15 5 | 6 | wifi.setmode(wifi.STATION) 7 | wifi.sta.config(WIFI_ESSID, WIFI_PASSWORD) 8 | 9 | function print_wifi_status(status) 10 | if status == wifi.STA_WRONGPWD then 11 | print("wrong passsword") 12 | elseif status == wifi.STA_APNOTFOUND then 13 | print("can't find AP") 14 | elseif status == wifi.STA_FAIL then 15 | print("failed") 16 | elseif status == wifi.STA_GOTIP then 17 | print("got IP") 18 | end 19 | end 20 | 21 | -- Loop until WiFi configured, then branch 22 | -- If there's a configuration problem with AP name or PW, 23 | -- delete the init.lua file and reboot. 24 | counts = 1 25 | tmr.alarm(0, 1*1000, tmr.ALARM_AUTO, function() 26 | status = wifi.sta.status() 27 | print_wifi_status(status) 28 | if (status == wifi.STA_WRONGPWD or status == wifi.STA_APNOTFOUND) then 29 | print("wifi incorrectly configured: removing init.lua") 30 | file.remove("init.lua") 31 | tmr.unregister(0) 32 | node.restart() 33 | end 34 | if status ~= wifi.STA_GOTIP then 35 | print("waiting for wifi " .. counts) 36 | counts = counts + 1 37 | if counts > timeout then -- timeout 38 | tmr.unregister(0) 39 | print("failed to acquire IP address, restarting") 40 | node.restart() 41 | end 42 | 43 | else 44 | tmr.unregister(0) 45 | print(wifi.sta.getip()) 46 | dofile(EXEC_FILE) 47 | end 48 | end 49 | ) 50 | 51 | -- cancel init just in case it's porked 52 | -- file.remove("init.lua") 53 | 54 | 55 | -------------------------------------------------------------------------------- /display/mqtt_display_node.lua: -------------------------------------------------------------------------------- 1 | mqtt_address = "192.168.1.49" 2 | temp_topic = "home/outdoors/temperature" 3 | humid_topic = "home/outdoors/humidity" 4 | status_topic = "home/outdoors/status" 5 | ws2812b_pin = 4 -- Pin D4 on my setup 6 | status = "on" 7 | temp = 20 8 | 9 | -- Set up named client with 60 sec keepalive, 10 | -- no username/password, 11 | -- and a clean session each time 12 | m = mqtt.Client("monitor", 60, "", "", 1) 13 | m:on("offline", function() print("mqtt offline"); end) 14 | 15 | -- subscribe as soon as connected 16 | m:on("connect", function() print("mqtt connected") subscribe_topics(m) end ) 17 | 18 | function subscribe_topics(client) 19 | client:subscribe(temp_topic, 0, subscribed(temp_topic)) 20 | client:subscribe(humid_topic, 0, subscribed(humid_topic)) 21 | client:subscribe(status_topic, 0, subscribed(status_topic)) 22 | end 23 | 24 | function subscribed(topic) 25 | print("subscribed to " .. topic) 26 | end 27 | 28 | -- Deal with incoming messages 29 | m:on("message", function(client, topic, data) handle_message(client, topic, data) end) 30 | 31 | -- Print out all data & display temperature data 32 | function handle_message(client, topic, data) 33 | print(topic .. ": " .. data) 34 | if topic == status_topic then 35 | status = data 36 | elseif topic == temp_topic then 37 | temp = data 38 | end 39 | -- and display 40 | if status == "on" or status == "o" then 41 | display_temp(temp) 42 | else 43 | display_off() 44 | end 45 | end 46 | 47 | -- Fancy temperature display! Blue-green-yellow-red fade. 48 | function display_temp(data) 49 | data = tonumber(data) 50 | r = math.min(2*math.max(data-20, 0), 40) 51 | b = math.max(40-2*data, 0) 52 | g = 20 - math.min(math.abs(20 - data), 20) 53 | ws2812.writergb(ws2812b_pin, string.char(r, g, b)) 54 | end 55 | 56 | function display_off() 57 | ws2812.writergb(ws2812b_pin, string.char(0, 0, 0)) 58 | end 59 | 60 | m:connect(mqtt_address, 1883, 0, 1) 61 | -------------------------------------------------------------------------------- /power_test/Makefile: -------------------------------------------------------------------------------- 1 | LUAFILES:= init.lua power_test.lua 2 | 3 | PORT=/dev/ttyUSB0 4 | BAUD=9600 5 | 6 | all: flash restart term 7 | 8 | flash: $(LUAFILES) 9 | nodemcu-uploader -p $(PORT) -b $(BAUD) upload $^ 10 | 11 | restart: 12 | echo "node.restart()" | cat > $(PORT) 13 | 14 | term: 15 | pyterm $(PORT) $(BAUD) 16 | 17 | -------------------------------------------------------------------------------- /power_test/init.lua: -------------------------------------------------------------------------------- 1 | WIFI_ESSID = "" 2 | WIFI_PASSWORD = "" 3 | EXEC_FILE = "power_test.lua" 4 | timeout = 15 5 | 6 | wifi.setmode(wifi.STATION) 7 | wifi.sta.config(WIFI_ESSID, WIFI_PASSWORD) 8 | 9 | function print_wifi_status(status) 10 | if status == wifi.STA_WRONGPWD then 11 | print("wrong passsword") 12 | elseif status == wifi.STA_APNOTFOUND then 13 | print("can't find AP") 14 | elseif status == wifi.STA_FAIL then 15 | print("failed") 16 | elseif status == wifi.STA_GOTIP then 17 | print("got IP") 18 | end 19 | end 20 | 21 | -- Loop until WiFi configured, then branch 22 | -- If there's a configuration problem with AP name or PW, 23 | -- delete the init.lua file and reboot. 24 | counts = 1 25 | tmr.alarm(0, 1*1000, tmr.ALARM_AUTO, function() 26 | status = wifi.sta.status() 27 | print_wifi_status(status) 28 | if (status == wifi.STA_WRONGPWD or status == wifi.STA_APNOTFOUND) then 29 | print("wifi incorrectly configured: removing init.lua") 30 | file.remove("init.lua") 31 | tmr.unregister(0) 32 | node.restart() 33 | end 34 | if status ~= wifi.STA_GOTIP then 35 | print("waiting for wifi " .. counts) 36 | counts = counts + 1 37 | if counts > timeout then -- timeout 38 | tmr.unregister(0) 39 | print("failed to acquire IP address, restarting") 40 | node.restart() 41 | end 42 | 43 | else 44 | tmr.unregister(0) 45 | print(wifi.sta.getip()) 46 | dofile(EXEC_FILE) 47 | end 48 | end 49 | ) 50 | 51 | -- cancel init just in case it's porked 52 | -- file.remove("init.lua") 53 | 54 | 55 | -------------------------------------------------------------------------------- /power_test/power_test.lua: -------------------------------------------------------------------------------- 1 | mqtt_address = "192.168.1.49" 2 | client_id = "power_test" 3 | memento_topic = "home/powertest/memento" 4 | command_topic = "home/powertest/command" 5 | status_topic = "home/powertest/status" 6 | 7 | boot_state = nil 8 | sleep_seconds = 10 9 | 10 | m = mqtt.Client(client_id, 60, "", "", 1) 11 | 12 | -- subscribe as soon as connected 13 | m:on("connect", function() 14 | m:subscribe(memento_topic, 0) 15 | m:subscribe(command_topic, 0) 16 | send_status("back online") 17 | end ) 18 | 19 | -- Deal with incoming messages 20 | m:on("message", function(client, topic, data) 21 | handle_message(client, topic, data) 22 | end) 23 | 24 | -- Shortcuts 25 | function send_status(message) 26 | m:publish(status_topic, message, 0, 0) 27 | end 28 | 29 | function send_memento(message) 30 | -- retained 31 | m:publish(memento_topic, message, 0, 1) 32 | end 33 | 34 | -- Retrieve / set wakeup status from memento topic 35 | -- Respond to commands on command topic 36 | function handle_message(client, topic, data) 37 | 38 | if topic == memento_topic then 39 | if boot_state == nil then 40 | -- prevent from reading twice 41 | boot_state = data 42 | send_status("boot state: " .. boot_state) 43 | -- if reset by hardware or power, should say reset 44 | send_memento("hard reset or fault") 45 | end 46 | 47 | elseif topic == command_topic then 48 | if data == "sleep" then 49 | go_to_sleep() 50 | elseif data == "restart" then 51 | send_status("restarting...") 52 | send_memento("command restart") 53 | tmr.alarm(2, 1000, tmr.ALARM_SINGLE, node.restart) 54 | elseif data == "ping" then 55 | send_status("pong") 56 | end 57 | 58 | else 59 | -- default, for debugging over serial if desperate 60 | print(topic .. ": " .. data) 61 | end 62 | end 63 | 64 | -- Verbose sleeping 65 | function go_to_sleep() 66 | send_memento("sleep") 67 | send_status("going to sleep now...") 68 | -- this delay to make sure above messages get sent 69 | tmr.alarm(2, 1000, tmr.ALARM_SINGLE, function() 70 | node.dsleep(sleep_seconds*1000000) 71 | end) 72 | end 73 | 74 | -- Connect up and do something, so you know it's not sleeping 75 | m:connect(mqtt_address, 1883, 0, 1) 76 | tmr.alarm(1, 10*1000, tmr.ALARM_AUTO, function() 77 | send_status("still awake") 78 | end) 79 | 80 | -------------------------------------------------------------------------------- /sensor/.gitignore: -------------------------------------------------------------------------------- 1 | init_local.lua 2 | -------------------------------------------------------------------------------- /sensor/Makefile: -------------------------------------------------------------------------------- 1 | LUAFILES:= init.lua mqtt_temp_node.lua 2 | 3 | PORT=/dev/ttyUSB0 4 | BAUD=9600 5 | 6 | all: flash restart term 7 | 8 | flash: $(LUAFILES) 9 | nodemcu-uploader -p $(PORT) -b $(BAUD) upload $^ 10 | 11 | restart: 12 | echo "node.restart()" | cat > $(PORT) 13 | 14 | term: 15 | pyterm $(PORT) $(BAUD) 16 | 17 | -------------------------------------------------------------------------------- /sensor/init.lua: -------------------------------------------------------------------------------- 1 | WIFI_ESSID = "" 2 | WIFI_PASSWORD = "" 3 | EXEC_FILE = "mqtt_temp_node.lua" 4 | timeout = 15 5 | 6 | wifi.setmode(wifi.STATION) 7 | wifi.sta.config(WIFI_ESSID, WIFI_PASSWORD) 8 | 9 | function print_wifi_status(status) 10 | if status == wifi.STA_WRONGPWD then 11 | print("wrong passsword") 12 | elseif status == wifi.STA_APNOTFOUND then 13 | print("can't find AP") 14 | elseif status == wifi.STA_FAIL then 15 | print("failed") 16 | elseif status == wifi.STA_GOTIP then 17 | print("got IP") 18 | end 19 | end 20 | 21 | -- Loop until WiFi configured, then branch 22 | -- If there's a configuration problem with AP name or PW, 23 | -- delete the init.lua file and reboot. 24 | counts = 1 25 | tmr.alarm(0, 1*1000, tmr.ALARM_AUTO, function() 26 | status = wifi.sta.status() 27 | print_wifi_status(status) 28 | if (status == wifi.STA_WRONGPWD or status == wifi.STA_APNOTFOUND) then 29 | print("wifi incorrectly configured: removing init.lua") 30 | file.remove("init.lua") 31 | tmr.unregister(0) 32 | node.restart() 33 | end 34 | if status ~= wifi.STA_GOTIP then 35 | print("waiting for wifi " .. counts) 36 | counts = counts + 1 37 | if counts > timeout then -- timeout 38 | tmr.unregister(0) 39 | print("failed to acquire IP address, restarting") 40 | node.restart() 41 | end 42 | 43 | else 44 | tmr.unregister(0) 45 | print(wifi.sta.getip()) 46 | dofile(EXEC_FILE) 47 | end 48 | end 49 | ) 50 | 51 | -- cancel init just in case it's porked 52 | -- file.remove("init.lua") 53 | 54 | 55 | -------------------------------------------------------------------------------- /sensor/mqtt_temp_node.lua: -------------------------------------------------------------------------------- 1 | mqtt_address = "192.168.1.49" 2 | temp_topic = "home/outdoors/temperature" 3 | humid_topic = "home/outdoors/humidity" 4 | status_topic = "home/outdoors/status" 5 | update_frequency = 10 -- sec 6 | dht_pin = 4 -- Pin D4 on my setup 7 | 8 | -- Set up named client with 60 sec keepalive, 9 | -- no username/password, 10 | -- and a clean session each time 11 | m = mqtt.Client("outdoors", 60, "", "", 1) 12 | 13 | function subscribed(topic) 14 | print("subscribed to " .. topic) 15 | end 16 | 17 | function subscribe_topics(client) 18 | client:subscribe(temp_topic, 0, subscribed(temp_topic)) 19 | -- client:subscribe(humid_topic, 0, subscribed(humid_topic)) 20 | -- client:subscribe(status_topic, 0, subscribed(status_topic)) 21 | end 22 | 23 | function handle_message(client, topic, data) 24 | if topic == temp_topic then 25 | display_temp(data) 26 | end 27 | end 28 | function display_temp(data) 29 | print("temp: " .. data) 30 | end 31 | 32 | 33 | -- subscribe as soon as connected 34 | m:on("connect", function() print("mqtt connected") end ) 35 | m:on("offline", function() print("mqtt offline"); end) 36 | m:on("message", function(client, topic, data) handle_message(client, topic, data) end) 37 | 38 | m:connect(mqtt_address, 1883, 0, 1) 39 | 40 | function publish_temp_humidity(client) 41 | status, temp, humi, temp_dec, humi_dec = dht.read(dht_pin) 42 | if status == dht.OK then 43 | m:publish(temp_topic, temp, 0, 0) 44 | m:publish(humid_topic, humi, 0, 0) 45 | elseif status == dht.ERROR_CHECKSUM then 46 | print( "DHT Checksum error." ) 47 | elseif status == dht.ERROR_TIMEOUT then 48 | print( "DHT timed out." ) 49 | end 50 | end 51 | 52 | tmr.alarm(1, update_frequency*1000, tmr.ALARM_AUTO, function() publish_temp_humidity(m) end) 53 | 54 | 55 | 56 | -- Set up periodic connections / display updates 57 | -- tmr.alarm(1, 5*1000, tmr.ALARM_AUTO, function() m:publish(status_topic, "ping", 0, 0) end) 58 | --------------------------------------------------------------------------------