Loading .gitignore 0 → 100644 +1 −0 Original line number Diff line number Diff line config.lua README.md 0 → 100644 +55 −0 Original line number Diff line number Diff line # ESP8266 Lua/NodeMCU module for Vindriktning PM1006 particle monitor This repository contains a Lua module (`pm1006.lua`) as well as ESP8266/NodeMCU MQTT gateway application example (`init.lua`) for the **PM1006** particulate matter (PM2.5) sensor found in IKEA Vindriktning. ## Dependencies pm1006.lua has been tested with Lua 5.1 on NodeMCU firmware 3.0.1 (Release 202112300746, integer build). Most practical applications (such as the example in init.lua) require the following modules. * gpio * mqtt * node * softuart * tmr * uart * wifi ## Setup Connect the PM1006 sensor to your ESP8266/NodeMCU board as [documented by Hypfer](https://github.com/Hypfer/esp8266-vindriktning-particle-sensor). If you use a different UART pin, you need to adjust the softuart.setup call in the examples provided in this repository to reflect that change. Keep in mind that some ESP8266 pins must have well-defined logic levels at boot time and may therefore be unsuitable for PM1006 connection. ## Usage Copy **pm1006.lua** to your NodeMCU board and set it up as follows. ```lua pm1006 = require("pm1006") port = softuart.setup(9600, nil, 2) port:on("data", 20, uart_callback) function uart_callback(data) local pm2_5 = pm1006.parse_frame(data) if pm25i ~= nil then -- pm2_5 contains PM2.5 value in µg/m³ else -- invalid frame header or checksum end end ``` See **init.lua** for an example. To use it, you need to create a **config.lua** file with WiFI and MQTT settings: ```lua station_cfg.ssid = "..." station_cfg.pwd = "..." mqtt_host = "..." ``` init.lua 0 → 100644 +75 −0 Original line number Diff line number Diff line station_cfg = {} dofile("config.lua") delayed_restart = tmr.create() chipid = node.chipid() mqtt_prefix = "sensor/esp8266_" .. chipid mqttclient = mqtt.Client("esp8266_" .. chipid, 120) print("ESP8266 " .. chipid) ledpin = 4 gpio.mode(ledpin, gpio.OUTPUT) gpio.write(ledpin, 0) pm1006 = require("pm1006") function log_restart() print("Network error " .. wifi.sta.status() .. ". Restarting in 20 seconds.") delayed_restart:start() end function setup_client() print("Connected") gpio.write(ledpin, 1) publishing = true mqttclient:publish(mqtt_prefix .. "/state", "online", 0, 1, function(client) publishing = false end) port = softuart.setup(9600, nil, 2) port:on("data", 20, uart_callback) end function connect_mqtt() print("IP address: " .. wifi.sta.getip()) print("Connecting to MQTT " .. mqtt_host) delayed_restart:stop() mqttclient:on("connect", setup_client) mqttclient:on("offline", log_restart) mqttclient:lwt(mqtt_prefix .. "/state", "offline", 0, 1) mqttclient:connect(mqtt_host) end function connect_wifi() print("WiFi MAC: " .. wifi.sta.getmac()) print("Connecting to ESSID " .. station_cfg.ssid) wifi.eventmon.register(wifi.eventmon.STA_GOT_IP, connect_mqtt) wifi.eventmon.register(wifi.eventmon.STA_DHCP_TIMEOUT, log_restart) wifi.eventmon.register(wifi.eventmon.STA_DISCONNECTED, log_restart) wifi.setmode(wifi.STATION) wifi.sta.config(station_cfg) wifi.sta.connect() end function uart_callback(data) local pm2_5 = pm1006.parse_frame(data) if pm2_5 == nil then print("Invalid PM1006 frame") return else local json_str = string.format('{"pm25_ugm3": %d, "rssi_dbm": %d}', pm2_5, wifi.sta.getrssi()) if not publishing then publishing = true gpio.write(ledpin, 0) mqttclient:publish(mqtt_prefix .. "/data", json_str, 0, 0, function(client) publishing = false gpio.write(ledpin, 1) collectgarbage() end) end end end delayed_restart:register(20 * 1000, tmr.ALARM_SINGLE, node.restart) connect_wifi() pm1006.lua 0 → 100644 +20 −0 Original line number Diff line number Diff line local pm1006 = {} function pm1006.parse_frame(data) if string.byte(data, 1) ~= 0x16 or string.byte(data, 2) ~= 0x11 or string.byte(data, 3) ~= 0x0b then -- invalid header return nil end local checksum = 0 for i = 1, 20 do checksum = (checksum + string.byte(data, i)) % 256 end if checksum ~= 0 then -- invalid checksum return nil end local pm2_5 = string.byte(data, 6) * 256 + string.byte(data, 7) return pm2_5 end return pm1006 Loading
README.md 0 → 100644 +55 −0 Original line number Diff line number Diff line # ESP8266 Lua/NodeMCU module for Vindriktning PM1006 particle monitor This repository contains a Lua module (`pm1006.lua`) as well as ESP8266/NodeMCU MQTT gateway application example (`init.lua`) for the **PM1006** particulate matter (PM2.5) sensor found in IKEA Vindriktning. ## Dependencies pm1006.lua has been tested with Lua 5.1 on NodeMCU firmware 3.0.1 (Release 202112300746, integer build). Most practical applications (such as the example in init.lua) require the following modules. * gpio * mqtt * node * softuart * tmr * uart * wifi ## Setup Connect the PM1006 sensor to your ESP8266/NodeMCU board as [documented by Hypfer](https://github.com/Hypfer/esp8266-vindriktning-particle-sensor). If you use a different UART pin, you need to adjust the softuart.setup call in the examples provided in this repository to reflect that change. Keep in mind that some ESP8266 pins must have well-defined logic levels at boot time and may therefore be unsuitable for PM1006 connection. ## Usage Copy **pm1006.lua** to your NodeMCU board and set it up as follows. ```lua pm1006 = require("pm1006") port = softuart.setup(9600, nil, 2) port:on("data", 20, uart_callback) function uart_callback(data) local pm2_5 = pm1006.parse_frame(data) if pm25i ~= nil then -- pm2_5 contains PM2.5 value in µg/m³ else -- invalid frame header or checksum end end ``` See **init.lua** for an example. To use it, you need to create a **config.lua** file with WiFI and MQTT settings: ```lua station_cfg.ssid = "..." station_cfg.pwd = "..." mqtt_host = "..." ```
init.lua 0 → 100644 +75 −0 Original line number Diff line number Diff line station_cfg = {} dofile("config.lua") delayed_restart = tmr.create() chipid = node.chipid() mqtt_prefix = "sensor/esp8266_" .. chipid mqttclient = mqtt.Client("esp8266_" .. chipid, 120) print("ESP8266 " .. chipid) ledpin = 4 gpio.mode(ledpin, gpio.OUTPUT) gpio.write(ledpin, 0) pm1006 = require("pm1006") function log_restart() print("Network error " .. wifi.sta.status() .. ". Restarting in 20 seconds.") delayed_restart:start() end function setup_client() print("Connected") gpio.write(ledpin, 1) publishing = true mqttclient:publish(mqtt_prefix .. "/state", "online", 0, 1, function(client) publishing = false end) port = softuart.setup(9600, nil, 2) port:on("data", 20, uart_callback) end function connect_mqtt() print("IP address: " .. wifi.sta.getip()) print("Connecting to MQTT " .. mqtt_host) delayed_restart:stop() mqttclient:on("connect", setup_client) mqttclient:on("offline", log_restart) mqttclient:lwt(mqtt_prefix .. "/state", "offline", 0, 1) mqttclient:connect(mqtt_host) end function connect_wifi() print("WiFi MAC: " .. wifi.sta.getmac()) print("Connecting to ESSID " .. station_cfg.ssid) wifi.eventmon.register(wifi.eventmon.STA_GOT_IP, connect_mqtt) wifi.eventmon.register(wifi.eventmon.STA_DHCP_TIMEOUT, log_restart) wifi.eventmon.register(wifi.eventmon.STA_DISCONNECTED, log_restart) wifi.setmode(wifi.STATION) wifi.sta.config(station_cfg) wifi.sta.connect() end function uart_callback(data) local pm2_5 = pm1006.parse_frame(data) if pm2_5 == nil then print("Invalid PM1006 frame") return else local json_str = string.format('{"pm25_ugm3": %d, "rssi_dbm": %d}', pm2_5, wifi.sta.getrssi()) if not publishing then publishing = true gpio.write(ledpin, 0) mqttclient:publish(mqtt_prefix .. "/data", json_str, 0, 0, function(client) publishing = false gpio.write(ledpin, 1) collectgarbage() end) end end end delayed_restart:register(20 * 1000, tmr.ALARM_SINGLE, node.restart) connect_wifi()
pm1006.lua 0 → 100644 +20 −0 Original line number Diff line number Diff line local pm1006 = {} function pm1006.parse_frame(data) if string.byte(data, 1) ~= 0x16 or string.byte(data, 2) ~= 0x11 or string.byte(data, 3) ~= 0x0b then -- invalid header return nil end local checksum = 0 for i = 1, 20 do checksum = (checksum + string.byte(data, i)) % 256 end if checksum ~= 0 then -- invalid checksum return nil end local pm2_5 = string.byte(data, 6) * 256 + string.byte(data, 7) return pm2_5 end return pm1006