Skip to content

Commit

Permalink
First version
Browse files Browse the repository at this point in the history
  • Loading branch information
fdobrovolny committed Apr 22, 2023
1 parent c2efe32 commit 1190bc1
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 2 deletions.
129 changes: 127 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,127 @@
# mh-z16
Lightweight python library for interactions with MH-Z16 Intelligent Infrared Gas Module CO2
# mh-zxx
Lightweight python library for interactions with MH-Z16, MH-Z19 and similar Intelligent Infrared Gas Module CO2

# MH-ZXX CO2 Sensor API

* [CO2Sensor](#mh_zxx.base.CO2Sensor)
* [\_\_init\_\_](#mh_zxx.base.CO2Sensor.__init__)
* [read\_co2](#mh_zxx.base.CO2Sensor.read_co2)
* [read\_temperature](#mh_zxx.base.CO2Sensor.read_temperature)
* [read\_status](#mh_zxx.base.CO2Sensor.read_status)
* [read](#mh_zxx.base.CO2Sensor.read)
* [zero\_calibration](#mh_zxx.base.CO2Sensor.zero_calibration)
* [span\_calibration](#mh_zxx.base.CO2Sensor.span_calibration)
* [set\_auto\_calibration](#mh_zxx.base.CO2Sensor.set_auto_calibration)
* [close](#mh_zxx.base.CO2Sensor.close)

<a id="mh_zxx.base.CO2Sensor"></a>

## CO2Sensor

```python
class CO2Sensor()
```

MH-Z16 and MH-Z19 CO2 sensor.

<a id="mh_zxx.base.CO2Sensor.__init__"></a>

#### \_\_init\_\_

```python
def __init__(port: str, baudrate: int = 9600, timeout: float = 0.2)
```

Setup CO2 sensor

**Arguments**:

- `port`: The serial port name, for example `/dev/ttyUSB0` (Linux),
`/dev/tty.usbserial` (OS X) or `COM4` (Windows).
- `baudrate`: Baudrate to use.
- `timeout`: Read timeout after each command.

<a id="mh_zxx.base.CO2Sensor.read_co2"></a>

#### read\_co2

```python
def read_co2() -> int
```

Read CO2 concentration.

<a id="mh_zxx.base.CO2Sensor.read_temperature"></a>

#### read\_temperature

```python
def read_temperature() -> float
```

Read temperature.

<a id="mh_zxx.base.CO2Sensor.read_status"></a>

#### read\_status

```python
def read_status() -> int
```

Read status.

<a id="mh_zxx.base.CO2Sensor.read"></a>

#### read

```python
def read() -> Tuple[int, float, int, int]
```

Read all values.

**Returns**:

CO2 concentration, temperature, status, co2 auto calibration point

<a id="mh_zxx.base.CO2Sensor.zero_calibration"></a>

#### zero\_calibration

```python
def zero_calibration()
```

Zero calibration.

<a id="mh_zxx.base.CO2Sensor.span_calibration"></a>

#### span\_calibration

```python
def span_calibration(span: int)
```

Span calibration.

<a id="mh_zxx.base.CO2Sensor.set_auto_calibration"></a>

#### set\_auto\_calibration

```python
def set_auto_calibration(state: bool)
```

Set auto calibration point.

<a id="mh_zxx.base.CO2Sensor.close"></a>

#### close

```python
def close()
```

Close serial port.

32 changes: 32 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Install packages as defined in this file into the Python environment."""
from setuptools import setup, find_namespace_packages


# The version of this tool is based on the following steps:
# https://packaging.python.org/guides/single-sourcing-package-version/
VERSION = {}

with open("./src/mh_zxx/version.py") as fp:
# pylint: disable=W0122
exec(fp.read(), VERSION)

setup(
name="mh_zxx",
author="Filip Dobrovolny",
author_email="[email protected]",
url="https://github.com/fdobrovolny/mh-zxx",
description="Python library for interaction with MH-Z16, MH-Z19 and similar.",
version=VERSION.get("__version__", "0.0.0"),
package_dir={"": "src"},
packages=find_namespace_packages(where="src", exclude=["tests"]),
install_requires=[
"setuptools>=45.0",
"pyserial>=3.5",
],
classifiers=[
"Development Status :: 1 - Planning",
"Programming Language :: Python :: 3.0",
"Topic :: Utilities",
"Topic :: System :: Hardware",
],
)
5 changes: 5 additions & 0 deletions src/mh_zxx/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from mh_z16.base import CO2Sensor
from mh_z16.version import __version__


__all__ = ["CO2Sensor", "__version__"]
124 changes: 124 additions & 0 deletions src/mh_zxx/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import logging
from typing import List, Tuple

from serial import Serial


logger = logging.getLogger(__name__)


class CO2Sensor:
"""
MH-Z16 and MH-Z19 CO2 sensor.
"""

serial: Serial

def __init__(
self,
port: str,
baudrate: int = 9600,
timeout: float = 0.2,
):
"""
Setup CO2 sensor
:param port: The serial port name, for example `/dev/ttyUSB0` (Linux),
`/dev/tty.usbserial` (OS X) or `COM4` (Windows).
:param baudrate: Baudrate to use.
:param timeout: Read timeout after each command.
"""
self.serial = Serial(port=port, baudrate=baudrate, timeout=timeout)
self.serial.open()

READ_CO2 = [0xff, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00]
ZERO_CALIBRATION = [0xff, 0x01, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00]
SPAN_CALIBRATION = [0xff, 0x01, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00]
AUTO_CALIBRATION = [0xff, 0x01, 0x79, 0xa0, 0x00, 0x00, 0x00, 0x00]
AUTO_CALIBRATION_OFF = [0xff, 0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00]

@staticmethod
def _checksum(data: bytes | List[int]) -> int:
"""
Calculate _checksum of the command.
"""
return 0xff - (sum(data) % 256) + 1

def _write_command(self, command: List[int]):
"""
Write command to the sensor.
"""
command.append(self._checksum(command))
self.serial.write(command)

def _read_response(self) -> bytes:
"""
Read response from the sensor.
"""
return self.serial.read(9)

def read_co2(self) -> int:
"""
Read CO2 concentration.
"""
return self.read()[0]

def read_temperature(self) -> float:
"""
Read temperature.
"""
return self.read()[1]

def read_status(self) -> int:
"""
Read status.
"""
return self.read()[2]

def read(self) -> Tuple[int, float, int, int]:
"""
Read all values.
:note: last value is auto calibration point, see
:link: https://revspace.nl/MH-Z19#Command_set
:return: CO2 concentration, temperature, status, co2 auto calibration point
"""
self._write_command(self.READ_CO2)
response = self._read_response()
if response[0] != 0xff or response[1] != 0x86:
raise ValueError("Invalid response")
elif response[8] != self._checksum(response[:8]):
raise ValueError("Invalid checksum")

return response[2] * 256 + response[3], response[4] - 40, response[5], response[6]

def zero_calibration(self):
"""
Zero calibration.
"""
self._write_command(self.ZERO_CALIBRATION)

def span_calibration(self, span: int):
"""
Span calibration.
"""
command = self.SPAN_CALIBRATION.copy()
command[3] = span // 256
command[4] = span % 256
self._write_command(command)

def set_auto_calibration(self, state: bool):
"""
Set auto calibration point.
"""
if state:
self._write_command(self.AUTO_CALIBRATION)
else:
self._write_command(self.AUTO_CALIBRATION_OFF)

def close(self):
"""
Close serial port.
"""
self.serial.close()
1 change: 1 addition & 0 deletions src/mh_zxx/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "0.0.1"

0 comments on commit 1190bc1

Please sign in to comment.