Skip to content

Commit 72d6d1f

Browse files
authored
feat: add MH-Z19 sensor module (#365)
* feat: add MH-Z19 sensor module This adds support for NDIR CO2 sensor MH-Z19. The sensor module supports reading CO2 ppm value and configuring the ppm range over the UART/serial interface.
1 parent 88cff85 commit 72d6d1f

File tree

2 files changed

+75
-0
lines changed

2 files changed

+75
-0
lines changed

config.example.yml

+12
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ sensor_modules:
3434
type: DS18S20
3535
address: 000803702e49
3636

37+
- name: mhz19
38+
module: mhz19
39+
device: "/dev/ttyS1"
40+
range: 5000
41+
3742
digital_inputs:
3843
- name: button
3944
module: raspberrypi
@@ -76,3 +81,10 @@ sensor_inputs:
7681
module: ds18b22
7782
interval: 10
7883
digits: 2
84+
85+
- name: co2_mhz19
86+
module: mhz19
87+
interval: 30
88+
ha_discovery:
89+
name: CO2 MH-Z19
90+
device_class: carbon_dioxide

mqtt_io/modules/sensor/mhz19.py

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""
2+
MH-Z19 NDIR CO2 sensor
3+
"""
4+
5+
from mqtt_io.modules.sensor import GenericSensor
6+
from mqtt_io.types import CerberusSchemaType, ConfigType, SensorValueType
7+
8+
REQUIREMENTS = ("pyserial",)
9+
CONFIG_SCHEMA: CerberusSchemaType = {
10+
"device": dict(type="string", required=True, empty=False),
11+
"range": dict(type="integer", required=False, empty=False, default=5000,
12+
allowed=[2000, 5000, 10000]),
13+
}
14+
15+
class Sensor(GenericSensor):
16+
"""
17+
Implementation of Sensor class for the MH-Z19 CO2 sensor using UART/serial.
18+
"""
19+
20+
@staticmethod
21+
def _calc_checksum(data: bytes) -> bytes:
22+
value = sum(data[1:]) % 0x100
23+
value = (0xff - value + 1) % 0x100
24+
return value.to_bytes(1, "big")
25+
26+
@staticmethod
27+
def _check_checksum(data: bytes) -> bool:
28+
return Sensor._calc_checksum(data[:-1]) == data[-1:]
29+
30+
@staticmethod
31+
def _add_checksum(data: bytes) -> bytes:
32+
return data + Sensor._calc_checksum(data)
33+
34+
def setup_module(self) -> None:
35+
# pylint: disable=import-error,import-outside-toplevel
36+
import serial # type: ignore
37+
38+
self.ser = serial.Serial(
39+
port=self.config["device"],
40+
baudrate=9600,
41+
bytesize=serial.EIGHTBITS,
42+
parity=serial.PARITY_NONE,
43+
stopbits=serial.STOPBITS_ONE,
44+
)
45+
46+
# setup detection range
47+
cmd = Sensor._add_checksum(b"\xff\x01\x99\x00\x00\x00" +
48+
self.config["range"].to_bytes(2, "big"))
49+
self.ser.write(cmd)
50+
# no response
51+
52+
def cleanup(self) -> None:
53+
self.ser.close()
54+
55+
def get_value(self, sens_conf: ConfigType) -> SensorValueType:
56+
self.ser.write(Sensor._add_checksum(b"\xff\x01\x86\x00\x00\x00\x00\x00"))
57+
resp = self.ser.read(9)
58+
59+
if len(resp) == 9:
60+
if resp[0:2] == b"\xff\x86" and Sensor._check_checksum(resp):
61+
return int.from_bytes(resp[2:4], "big")
62+
63+
return None

0 commit comments

Comments
 (0)