Unverified Commit 87d61960 authored by Daniel Friesel's avatar Daniel Friesel
Browse files

initial commit

parents
Loading
Loading
Loading
Loading

.gitignore

0 → 100644
+1 −0
Original line number Diff line number Diff line
config.lua

README.md

0 → 100644
+50 −0
Original line number Diff line number Diff line
# ESP8266 Lua/NodeMCU module for Sensirion SCD40/SCD41 CO₂ sensor

This repository contains a Lua module (`scd4x.lua`) as well as
MQTT-based HomeAssistant integration (`init.lua`) for **Sensirion SCD40/SCD41**
CO₂ sensors.

## Dependencies

scd4x.lua has been tested with Lua 5.1 on NodeMCU firmware 3.0.1 (Release
202112300746, integer build). It requires the following modules.

* i2c

The MQTT HomeAssistant integration in init.lua additionally needs the following
modules.

* gpio
* mqtt
* node
* tmr
* uart
* wifi

## Usage

Copy **scd4x.lua** to your NodeMCU board and set it up as follows.

```lua
scd4x = require("scd4x")
i2c.setup(0, sda_pin, scl_pin, i2c.SLOW)
scd4x.start()

-- can be called with up to 1 Hz
function some_timer_callback()
	local co2, raw_temp, raw_humi = scd4x.read()
	if co2 == nil then
		print("SCD4x error")
	else
		-- CO₂[ppm] == co2, Temperature[°c] == raw_temp/2¹⁶ - 45, Humidity[%] == raw_humi/2¹⁶
	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
+102 −0
Original line number Diff line number Diff line
station_cfg = {}
dofile("config.lua")

delayed_restart = tmr.create()
push_timer = tmr.create()
chip_id = node.chipid()
device_id = "esp8266_" .. chip_id
mqtt_prefix = "sensor/" .. device_id
mqttclient = mqtt.Client(device_id, 120)


print("ESP8266 " .. chip_id)

ledpin = 4
gpio.mode(ledpin, gpio.OUTPUT)
gpio.write(ledpin, 0)

scd4x = require("scd4x")
i2c.setup(0, 2, 1, i2c.SLOW)

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)
	if not scd4x.start() then
		print("SCD4x initialization error")
	end
	publishing = true
	mqttclient:publish(mqtt_prefix .. "/state", "online", 0, 1, function(client)
		publishing = false
		push_data()
	end)
end

function connect_mqtt()
	print("IP address: " .. wifi.sta.getip())
	print("Connecting to MQTT " .. mqtt_host)
	delayed_restart:stop()
	mqttclient:on("connect", hass_register)
	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 push_data()
	local co2, raw_temp, raw_humi = scd4x.read()
	if co2 == nil then
		print("SCD4x error")
	else
		local json_str = string.format('{"co2_ppm": %d, "temperature_degc": %d.%d, "humidity_relpercent": %d.%d, "rssi_dbm": %d}', co2, raw_temp/65536 - 45, (raw_temp%65536)/6554, raw_humi/65536, (raw_humi%65536)/6554, 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
	push_timer:start()
end

function hass_register()
	local hass_device = string.format('{"connections":[["mac","%s"]],"identifiers":["%s"],"model":"ESP8266","name":"ESP8266 with SCD4x","manufacturer":"DIY"}', wifi.sta.getmac(), device_id)
	local hass_entity_base = string.format('"device":%s,"state_topic":"%s/data","expire_after":600', hass_device, mqtt_prefix)
	local hass_co2 = string.format('{%s,"name":"CO₂","object_id":"%s_co2","unique_id":"%s_co2","device_class":"carbon_dioxide","unit_of_measurement":"ppm","value_template":"{{value_json.co2_ppm}}"}', hass_entity_base, device_id, device_id)
	local hass_temp = string.format('{%s,"name":"Temperature","object_id":"%s_temperature","unique_id":"%s_temperature","device_class":"temperature","unit_of_measurement":"°c","value_template":"{{value_json.temperature_degc}}"}', hass_entity_base, device_id, device_id)
	local hass_humi = string.format('{%s,"name":"Humidity","object_id":"%s_humidity","unique_id":"%s_humidity","device_class":"humidity","unit_of_measurement":"%%","value_template":"{{value_json.humidity_relpercent}}"}', hass_entity_base, device_id, device_id)
	local hass_rssi = string.format('{%s,"name":"RSSI","object_id":"%s_rssi","unique_id":"%s_rssi","device_class":"signal_strength","unit_of_measurement":"dBm","value_template":"{{value_json.rssi_dbm}}","entity_category":"diagnostic"}', hass_entity_base, device_id, device_id)

	mqttclient:publish("homeassistant/sensor/" .. device_id .. "/co2/config", hass_co2, 0, 1, function(client)
		mqttclient:publish("homeassistant/sensor/" .. device_id .. "/temperature/config", hass_temp, 0, 1, function(client)
			mqttclient:publish("homeassistant/sensor/" .. device_id .. "/humidity/config", hass_humi, 0, 1, function(client)
				mqttclient:publish("homeassistant/sensor/" .. device_id .. "/rssi/config", hass_rssi, 0, 1, function(client)
					collectgarbage()
					setup_client()
				end)
			end)
		end)
	end)
end

delayed_restart:register(20 * 1000, tmr.ALARM_SINGLE, node.restart)
push_timer:register(20 * 1000, tmr.ALARM_SEMI, push_data)

connect_wifi()

scd4x.lua

0 → 100644
+45 −0
Original line number Diff line number Diff line
local scd4x = {}
local device_address = 0x62

scd4x.bus_id = 0

function scd4x.start()
	i2c.start(scd4x.bus_id)
	if not i2c.address(scd4x.bus_id, device_address, i2c.TRANSMITTER) then
		return false
	end
	i2c.write(scd4x.bus_id, {0x21, 0xb1})
	i2c.stop(scd4x.bus_id)
	return true
end

function scd4x.stop()
	i2c.start(scd4x.bus_id)
	if not i2c.address(scd4x.bus_id, device_address, i2c.TRANSMITTER) then
		return false
	end
	i2c.write(scd4x.bus_id, {0x3f, 0x86})
	i2c.stop(scd4x.bus_id)
	return true
end

function scd4x.read()
	i2c.start(scd4x.bus_id)
	if not i2c.address(scd4x.bus_id, device_address, i2c.TRANSMITTER) then
		return nil
	end
	i2c.write(scd4x.bus_id, {0xec, 0x05})
	i2c.stop(scd4x.bus_id)
	i2c.start(scd4x.bus_id)
	if not i2c.address(scd4x.bus_id, device_address, i2c.RECEIVER) then
		return nil
	end
	local data = i2c.read(scd4x.bus_id, 9)
	i2c.stop(scd4x.bus_id)
	local co2 = string.byte(data, 1) * 256 + string.byte(data, 2)
	local temp_raw = 175 * (string.byte(data, 4) * 256 + string.byte(data, 5))
	local humi_raw = 100 * (string.byte(data, 7) * 256 + string.byte(data, 8))
	return co2, temp_raw, humi_raw
end

return scd4x