Full Code of UedaTakeyuki/mh-z19 for AI

master fa079c6957ec cached
28 files
67.6 KB
21.6k tokens
41 symbols
1 requests
Download .txt
Repository: UedaTakeyuki/mh-z19
Branch: master
Commit: fa079c6957ec
Files: 28
Total size: 67.6 KB

Directory structure:
gitextract_9gbtfjpy/

├── .github/
│   ├── FUNDING.yml
│   └── ISSUE_TEMPLATE/
│       ├── bug_report.md
│       ├── feature_request.md
│       ├── help-wanted.md
│       └── question.md
├── .gitignore
├── LICENSE
├── README.md
├── __init__.py
├── autostart.ini
├── autostart.sh
├── config.save.toml
├── config.toml
├── loop.sh
├── mh_z19.py
├── mh_z19.service
├── pypi/
│   ├── LICENSE
│   ├── README.md
│   ├── mh_z19/
│   │   ├── __init__.py
│   │   ├── __main__.py
│   │   └── pwm.py
│   ├── setup.cfg
│   └── setup.py
├── read.ini
├── read.py
├── send2monitor.ini
├── setid.sh
└── setup.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github:TakeyukiUEDA # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective:takeyuki-ueda # Replace with a single Open Collective username
ko_fi:atelierueda # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay:ueda # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie:TakeyukiUEDA # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve! Thank you!
title: ''
labels: ''
assignees: ''

---

PLEASE DON'T FORGET TO "STAR" THIS REPOSITORY :)

If you rather keep your project secret, feel free
to email the author at `<ueda@latelierdueda.com>`; any
private correspondence is treated as confidential.

When reporting a bug please include the following:

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Feel free to ask me anything!
title: ''
labels: ''
assignees: ''

---

PLEASE DON'T FORGET TO "STAR" THIS REPOSITORY :)

If you rather keep your project secret, feel free
to email the author at `<ueda@latelierdueda.com>`; any
private correspondence is treated as confidential.


================================================
FILE: .github/ISSUE_TEMPLATE/help-wanted.md
================================================
---
name: Help wanted
about: Feel free to ask me anything!
title: ''
labels: help wanted
assignees: ''

---

PLEASE DON'T FORGET TO "STAR" THIS REPOSITORY :)

If you rather keep your project secret, feel free
to email the author at `<ueda@latelierdueda.com>`; any
private correspondence is treated as confidential.


================================================
FILE: .github/ISSUE_TEMPLATE/question.md
================================================
---
name: Question
about: Feel free to ask me anything!
title: ''
labels: question
assignees: ''

---

PLEASE DON'T FORGET TO "STAR" THIS REPOSITORY :)

If you rather keep your project secret, feel free
to email the author at `<ueda@latelierdueda.com>`; any
private correspondence is treated as confidential.


================================================
FILE: .gitignore
================================================
*.pyc
*.bak
send2monitor.py
handlers
pypi/dist
pypi/mh_z19.egg-info


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Dr. Takeyuki Ueda

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# mh-z19
Read CO<sub>2</sub> concentration from mh-z19 sensor and handle it.

![MH-Z19](https://camo.qiitausercontent.com/a270df1162ed5c3bf9968b24064b91eed0dfcc11/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f34363534342f31353739663964622d306634302d373665382d303566332d3939336132346334376431382e706e67)

## cabling
Connect RPi & mh-z19 as:

- 5V on RPi and Vin on mh-z19
- GND(0v) on RPi and GND on mh-z19
- TxD and RxD are connected to cross between RPi and mh-z18 

Followings are example of cabling, but you can free to use other 5v and 0v Pin on the RPi. 

![Cabling](https://camo.qiitausercontent.com/112ad5fe41c82a16671d2882070384109c8860cc/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f34363534342f30383238333031342d363864322d633364652d313634342d3763386439623762363266642e6a706567)

```
pi@raspberrypi:~/mh-z19 $ gpio readall
 +-----+-----+---------+------+---+---Pi B+--+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 |   IN | 1 |  3 || 4  |   |      | 5v      |     |     |  <---- Vin
 |   3 |   9 |   SCL.1 |   IN | 1 |  5 || 6  |   |      | 0v      |     |     |  <---- Gnd
 |   4 |   7 | GPIO. 7 |   IN | 1 |  7 || 8  | 1 | ALT0 | TxD     | 15  | 14  |  <---- RxD
 |     |     |      0v |      |   |  9 || 10 | 1 | ALT0 | RxD     | 16  | 15  |  <---- TxD
 |  17 |   0 | GPIO. 0 |   IN | 0 | 11 || 12 | 0 | IN   | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |   IN | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |   IN | 0 | 15 || 16 | 0 | IN   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 0 | IN   | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI |   IN | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO |   IN | 0 | 21 || 22 | 0 | IN   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK |   IN | 0 | 23 || 24 | 1 | IN   | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | IN   | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 |   IN | 1 | 27 || 28 | 1 | IN   | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |   IN | 1 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |   IN | 1 | 31 || 32 | 0 | IN   | GPIO.26 | 26  | 12  |
 |  13 |  23 | GPIO.23 |   IN | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |   IN | 0 | 35 || 36 | 0 | IN   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |   IN | 0 | 37 || 38 | 0 | IN   | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 0 | IN   | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+---Pi B+--+---+------+---------+-----+-----+
```

## Install & Setup

### Full Set Install
Full Set include followings:

- Setup script: Install & Setup following useful stuffs as:
  - mh-z19:       A python module to read mh-z19 sensor.
  - PondSlider:   A multipurpose versatile sensor handler for python, which read mh-z19 value and handle it.
  - autostart.sh: Utility making mh-z19 as system service to act periodically.

Install Full Set, download from [release](https://github.com/UedaTakeyuki/mh-z19/releases)

```
git clone https://github.com/UedaTakeyuki/mh-z19.git
```

Then, got to the folder and issue ***setup.sh****

```
./setup.sh 
```
Necessary settings including serial port enabling are taken place in this script.

### Install only sensor module

For python 2.x
```bash:
sudo pip install mh_z19
```

For python 3.x
```bash:
sudo pip3 install mh_z19
```

In case you would use it [witout root permission](#how-to-use-without-root-permission), call pip without sudo as follows:

For python 2.x
```bash:
pip install mh_z19
```

For python 3.x
```bash:
pip3 install mh_z19
``` 

The differences of the interface between each Raspberry Pi model are resolved inside this module. For example, serial device name is difference between Raspberry Pi 3 and older model, but mh-z19 module automatically detect the model and read from appropriate serial device.

To use mh-z19, once you need to set up enabling serial port device on the Raspberry Pi.
Following [Wiki](https://github.com/UedaTakeyuki/mh-z19/wiki/How-to-Enable-Serial-Port-hardware-on-the-Raspberry-Pi) page might be informative.
Also, please refer [PyPi top page](https://pypi.org/project/mh-z19/) for detail.

## read CO2 Sensor value
```
pi@raspberrypi:~ $ sudo python3 -m mh_z19 
{'co2': 668}
```

As above, ***sudo*** might be necessary because mh-z19 sensor value is read through serial connection and it request root permission in general.

## Handle sensor value by [PondSlider](https://github.com/UedaTakeyuki/pondslider) multipurpose sensor handler.

The installed ***mh-z19*** module is correspond the [pondslider](https://github.com/UedaTakeyuki/pondslider) which is multiple & versatile sensor handler to save, send and to do other necessary ***something*** with the sensor value.

As an example, we introduce following use case:

- How to save sensor value to SD card as .CSV file.
- How to send sensor value to free Remote Monitoring Service.

### A brief explanation of pondslider
The pondslider read sensorvalue by ***sensor-handler*** specified, and pass the values to ***value-handlers**** which do something with it.

![PondSlider](https://warehouse-camo.cmh1.psfhosted.org/4a74a04ed15e93c05a7c126b59459d98738a62d9/68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f5565646154616b6579756b692f706f6e64736c696465722f6d61737465722f706963732f73732e323031382d31312d30332e31332e35362e31312e706e67)

Both sensor and value handlers are python module. For more detail of handlers, please refer [this](https://github.com/UedaTakeyuki/pondslider).

### How to use sensor value to send to server, save to strage, and so on.
This module correspond the [pondslider](https://github.com/UedaTakeyuki/pondslider) which is multiple & versatile sensor handler to save, send and to do other necessary ***something*** with the sensor value.

In case you choiced ***Full Install*** mentioned above, in other words, you've done ***setup.sh***,
the pondslider and there example handler's are already installed & setup.


### How to save CO2 value on a SD card as a .CSV file.
You can do it with ***save2strage*** value handler which is in ***handlers/value/saver/save2strage***, and configuration file to read from mh-z19 sensor and save by save2strage is prepared as ***config.save.toml*** on the mh-z19 installed folder.

On the mh-z19 folder By calling pondslider as follows;

```
sudo python -m pondslider --config config.save.toml
```

Then, ***/home/pi/DATA/co2.csv*** should be created and new line will be added for each call.

### How to send CO2 Value to the MONITOR™ Service

The MONITOR™ is a free Remote Monitoring Service to show latest data on the web.

<p>
<img src="pic/2018-11-20.11.23.19.png" width="24%">
<img src="pic/ss.2018-11-20.11.25.43.png" width="73%">
</p>

Please refer an [introduction](https://monitor.uedasoft.com/docs/UserGuide/) to grasp birds-eye understanding about MONIOR™.

A free account is available as [follow](https://monitor.uedasoft.com/docs/UserGuide/Signup.html).
Then, login and get a **view_id**, which is unique id to point a data area on the MONITOR™ to show latest value and chart, as [follow](https://monitor.uedasoft.com/docs/UserGuide/Value.html).

After get a value_id at your browser, return back to terminal of your Raspberry Pi, then you should set your this software to send measured value to the value_id. To do this, call **setid.sh** command in the mh_z19 installed directory. Let's say your value_id is ***vpgpargj***, issue ***setid.sh*** command as follows:

```
./setid.sh vpgpargj
```

Then, call "pondslider" python script as follows:

```
pi@raspberrypi:~/mh-z19 $ sudo python -m pondslider
{'co2': 742}
co2
{"ok":false,"reason":"ViewID not valid"}
```

Your time-series chart on the MONITOR™ display on the browser must be updated by the latest CO2 concentration value.

### How to set your Raspberry Pi to send CO2 data to MONITOR™ at 5 minute interval.
You can set it by autostart.sh command in the mh_z19 installed directory as follows:

```
./autostart.sh --on
```

You can turn off this as follows:

```
./autostart.sh --off
```

Also, You can check current status as follows:


```
./autostart.sh --status
```

For more detail, please refer this [blog](https://monitorserviceatelierueda.blogspot.com/2018/11/how-to-measure-room-co2-concentration.html). 

### How to send CO2 Value to the ATT M2X.

The Pondslider also support ATT M2X. For detail, please refer [this](https://github.com/UedaTakeyuki/handlers/blob/master/value/sender/send2m2x/README.md) document.

### Calibration, Detection range settings, and ABC(Automatic Baseline Correction) logic on/off.
Features about calibration (both MH-Z19 & MH-Z19B), detection range change (MH-Z19B) and ABC logic on/off(MH-Z19B) are implemented at version 0.2.1 or later.

I'm afraid I've just only implemented these without test due to lack necessary devices and apparatus for the test, fx: standard concentration CO2 GAS, also MH-Z19B module.
If you have these devices or apparatus and try to use these functions generously, I really appreciate your [issue report](https://github.com/UedaTakeyuki/mh-z19/issues) regardless result were positive or negative.

- positive report [with mh_z19b on a Raspberry Pi Zero W](https://github.com/UedaTakeyuki/mh-z19/issues/27), Thank you [richteas75](https://github.com/richteas75)!

For detail please refer this [wiki](https://github.com/UedaTakeyuki/mh-z19/wiki/CALIBRATION-&-detection-range).

## Undocumented response values of 0x86 command.
The [Revspace/MHZ19](https://revspace.nl/MHZ19#Command_0x86_.28read_concentration.29) shows values undocumented on the official datasheets ([MH-Z19](https://www.winsen-sensor.com/d/files/PDF/Infrared%20Gas%20Sensor/NDIR%20CO2%20SENSOR/MH-Z19%20CO2%20Ver1.0.pdf), [MH-Z19B](https://www.winsen-sensor.com/d/files/MH-Z19B.pdf)). In accordance with this, **--all** option add these values in the return json value as follows:

```bash:
sudo python -m mh_z19 --all
{"SS": 232, "UhUl": 10752, "TT": 61, "co2": 818, "temperature": 21}

sudo python3 -m mh_z19 --all
{"TT": 61, "co2": 807, "SS": 232, "temperature": 21, "UhUl": 10752}
```

or call **read_all()** function as follows:

```
>>> import mh_z19
>>> mh_z19.read_all()
{'SS': 232, 'UhUl': 10738, 'TT': 61, 'co2': 734, 'temperature': 21}
>>> 
```

## Use specific serial device.
In case you should use specific serial device instead of Raspberry Pi default serial device which this library automatically select, for example in case to need to use /dev/ttyUSB0 for **FT232 usb-serial converter** as [issue#12](https://github.com/UedaTakeyuki/mh-z19/issues/12), you can specify serial device by **--serial_device** option as follows:

```
sudo python -m mh_z19 --serial_device /dev/ttyUSB0
```

## How to use without root permission.
See this [wiki](https://github.com/UedaTakeyuki/mh-z19/wiki/How-to-use-without-root-permission.).

## How to use in your program.
See this [wiki](https://github.com/UedaTakeyuki/mh-z19/wiki/How-to-use-in-your-program.).

## PWM support.
See this [wiki](https://github.com/UedaTakeyuki/mh-z19/wiki/PWM-support.).

## In case you can't get the value.
Even if cabling seems no problem and uart seems to be prepateted well but you can't get sensor value. As [nincube8](https://github.com/nincube8) suggested that the [pull up](https://github.com/UedaTakeyuki/mh-z19/issues/22#issuecomment-683393350) by [1-5kΩ register](https://github.com/UedaTakeyuki/mh-z19/issues/26#issuecomment-744039360) can be working solution. Thank you [nincube8](https://github.com/nincube8)!

## Q&A
The forum is avai at [here](https://groups.google.com/g/mh_z19-users). Any questions, suggestions, reports are welcome!

## Blog
- [How to Measure ROOM CO2 concentration with 20$ sensor "MH-Z19" and Raspberry Pi.](https://monitorserviceatelierueda.blogspot.com/2018/11/how-to-measure-room-co2-concentration.html)
- [Monitoring all over the world with 3G Network for not more than 10$ monthly payment.](https://monitorserviceatelierueda.blogspot.com/2018/10/continuous-monitoring-all-over-world.html)
- [How to make shareable SD card by Raspberry Pi & PC.](https://monitorserviceatelierueda.blogspot.com/2018/09/how-to-make-shareable-sd-card-by.html)

## References

- [MH-H19B DataSheet version 1.5](https://www.winsen-sensor.com/d/files/MH-Z19B.pdf)
- [MH-H19 DataSheet version 1.0](https://www.winsen-sensor.com/d/files/PDF/Infrared%20Gas%20Sensor/NDIR%20CO2%20SENSOR/MH-Z19%20CO2%20Ver1.0.pdf)
- [MH-H19B DataSheet version 1.0](https://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf)
- [RevSpace](https://revspace.nl/MHZ19#Setting_the_measurement_range)


## history
- 0.1.0  2018.09.13  first version self-forked from [slider](https://github.com/UedaTakeyuki/slider).
- 0.2.2  2018.11.19  introduce [pondslider](https://pypi.org/project/pondslider/) and separate this [PyPi](https://pypi.org/project/mh-z19/) package.
- 2.0.0  2019.01.18  Add Calibration, ABC on/off requested by [this issue](https://github.com/UedaTakeyuki/mh-z19/issues/1). Please refer this [wiki](https://github.com/UedaTakeyuki/mh-z19/wiki/module).
- 0.3.5  2019.01.22  Both Python2 & Python3 support
- 2.3.6  2019.01.22  Merge [Pull Request #3](https://github.com/UedaTakeyuki/mh-z19/pull/3) & [Pull Request #4](https://github.com/UedaTakeyuki/mh-z19/pull/4). Thanks [David](https://github.com/kostaldavid8)!
- 2.3.7  2019.02.25  Add **--all** option which requested as [issue#5](https://github.com/UedaTakeyuki/mh-z19/issues/5), thanks [Rafał](https://github.com/rzarajczyk)!
- 2.3.8  2019.04.16  Merge [Pull Request #7](https://github.com/UedaTakeyuki/mh-z19/pull/7). Thanks [Alexander](https://github.com/belibak)!
- 2.3.8.1  2019.04.20  Merge [Pull Request #8](https://github.com/UedaTakeyuki/mh-z19/pull/8). Thanks [WO15](https://github.com/WO15)!
- 2.3.8.5  2019.04.21  Merge [Pull Request #9](https://github.com/UedaTakeyuki/mh-z19/pull/9). Thanks [WO15](https://github.com/WO15)!
- 2.3.8.6  2019.04.22  Merge [Pull Request #10](https://github.com/UedaTakeyuki/mh-z19/pull/10). Thanks [WO15](https://github.com/WO15)!
- 2.3.9  2019.05.06  Revise the serial port selection logic. Support using **PL011** uart on Raspberry Pi **Model 3 and Zero W** which is selected by setting dtoverlay=**pi3-miniuart-bt** or dtoverlay=**pi3-disable-bt**. Thanks **片岡さん** for your kindly [report](https://qiita.com/yukataoka/items/a3b4065e8210b8f372ff) including this issue!
- 2.4.1 2019.08.11 Add --serial_device option as solution of [issue#12](https://github.com/UedaTakeyuki/mh-z19/issues/12). Thanks [Actpohomoc](https://github.com/Actpohomoc) and [TBR-BRD](https://github.com/TBR-BRD)!
- 2.4.2 2019.12.12  Merge [Pull Request #15](https://github.com/UedaTakeyuki/mh-z19/pull/15). Thanks [WO15](https://github.com/WO15)!
- 2.5.1 2020.05.16 Add **--serial_console_untouched** option to support **execution without sudo** asked as [issue#17](https://github.com/UedaTakeyuki/mh-z19/issues/17). Thanks [ralphbe91](https://github.com/ralphbe91)!
- 2.5.2 2020.06.30  Update the link for datasheet of MH-Z19B from version 1.0 to version 1.5 based be pointed it out as [issue#18](https://github.com/UedaTakeyuki/mh-z19/issues/18). Thanks [WO15](https://github.com/WO15)!
- 2.6.1 2020.07.07 Add **--detection_range_10000** option to support **Set 0~10000ppm detection range** asked as [issue#19](https://github.com/UedaTakeyuki/mh-z19/issues/19). Thanks [WO15](https://github.com/WO15)!
- 2.6.3 2020.08.27 Fix [issue#21](https://github.com/UedaTakeyuki/mh-z19/issues/21). Thanks [idegre](https://github.com/idegre)!
- 3.0.0 2021.02.05 [PWM support](https://github.com/UedaTakeyuki/mh-z19/wiki/PWM-support.).
- 3.0.1 2021.02.17 Fix a degradation of not running with python3. Thank you **Masahiko OHKUBO** san for your report.
- 3.0.2 2021.03.25 Fix to support RPi4 correctly as [issue#29](https://github.com/UedaTakeyuki/mh-z19/issues/29). Thanks [iperniaf](https://github.com/iperniaf)!
- 3.0.3 2021.11.08 Fix [issue#35](https://github.com/UedaTakeyuki/mh-z19/issues/35). Thanks [false](https://github.com/false-git)!
- 3.0.4 2011.11.11 Fix [issue#36](https://github.com/UedaTakeyuki/mh-z19/issues/36). Thanks [David Bock](https://github.com/DavidBock)
- 3.0.5 2022.01.01 Fix [issue#38](https://github.com/UedaTakeyuki/mh-z19/issues/38). Thanks [kzehnter](https://github.com/kzehnter)!
- 3.1.0 2022.01.22 add ``--co2valueonly`` option by merging [issue#39](https://github.com/UedaTakeyuki/mh-z19/issues/39) and [issue#40](https://github.com/UedaTakeyuki/mh-z19/issues/40). Thanks [jonesthefox](https://github.com/jonesthefox)!
- 3.1.1 2022.01.23 remove code clone.
- 3.1.2 2022.01.31 Fix [issue#41](https://github.com/UedaTakeyuki/mh-z19/issues/41). Thanks [Christopher M. Pierce](https://github.com/electronsandstuff)!
- 3.1.3 2022.02.25 Fix [issue#43](https://github.com/UedaTakeyuki/mh-z19/issues/43). Thanks [Jannis Möller](https://github.com/jannismoeller)!
- 3.1.4 2023.11.14 Don't install python2-pip if OS version is BullsEye or later.
- 3.1.5 2024.05.06 FIx [issue#53](https://github.com/UedaTakeyuki/mh-z19/issues/53). Thanks [Tats Shibata](https://github.com/rewse)
- 3.1.6 2024.08.29 FIx [issue#54](https://github.com/UedaTakeyuki/mh-z19/issues/54). Thanks [80kpc](https://github.com/ziyucao)
- 3.1.7 2025.03.01 drop the RPi.GPIO dependenc. Thanks [mroelandts](https://github.com/mroelandts)


================================================
FILE: __init__.py
================================================
 


================================================
FILE: autostart.ini
================================================
cmd=mh_z19


================================================
FILE: autostart.sh
================================================
#!/bin/bash

################################################################
# 
# Autostart setting
# 
# usage: ./autostart.sh --on/--off
#
#
# @author Dr. Takeyuki UEDA
# @copyright Copyright© Atelier UEDA 2018 - All rights reserved.
#
. autostart.ini
CMD=$cmd
SCRIPT_DIR=$(cd $(dirname $0); pwd)
#echo $cwd

usage_exit(){
	echo "Usage: $0 [--on]/[--off]" 1>&2
  echo "  [--on]:               Set autostart as ON. " 			1>&2
  echo "  [--off]:              Set autostart as OFF. " 		1>&2
  echo "  [--status]:           Show current status. " 		  1>&2
  exit 1
}

on(){
	sed -i "s@^WorkingDirectory=.*@WorkingDirectory=${SCRIPT_DIR}@" ${CMD}.service
	sudo ln -s ${SCRIPT_DIR}\/${CMD}.service /etc/systemd/system/${CMD}.service
	sudo systemctl daemon-reload
	sudo systemctl enable ${CMD}.service
	sudo systemctl start ${CMD}.service
}

off(){
	sudo systemctl stop ${CMD}.service
	sudo systemctl disable ${CMD}.service
}

status(){
	sudo systemctl status ${CMD}.service
}
while getopts ":-:" OPT
do
  case $OPT in
    -)
				case "${OPTARG}" in
					on)
								on
								;;
					off)
								off
								;;
					status)
								status
								;;
				esac
				;;
    \?) usage_exit
        ;;
  esac
done


================================================
FILE: config.save.toml
================================================
[[sensors]]
  handler = "mh_z19"
  [[sensors.values]]
    name = "co2"
    handlers = [
#      "send2monitor", # with send2monitor.ini on the current directory.
#      "handlers.value.sender.send2m2x", # with send2m2x.ini on the handler/value/sender/send2m2x
      "handlers.value.saver.save2strage"
  ]


================================================
FILE: config.toml
================================================
[[sensors]]
  handler = "mh_z19"
  [[sensors.values]]
    name = "co2"
    handlers = [
      "send2monitor", # with send2monitor.ini on the current directory.
#      "handlers.value.sender.send2m2x", # with send2m2x.ini on the handler/value/sender/send2m2x
#      "handlers.value.saver.save2strage"
  ]


================================================
FILE: loop.sh
================================================
#!/bin/bash
SCRIPT_DIR=$(cd $(dirname $0); pwd)
while true; do sudo python ${SCRIPT_DIR}/read.py; sleep 5m; done


================================================
FILE: mh_z19.py
================================================
# -*- coding: utf-8 -*-
# original: https://raw.githubusercontent.com/UedaTakeyuki/slider/master/mh_z19.py
#
# © Takeyuki UEDA 2015 -

import serial
import time
import subprocess
import traceback
import getrpimodel
import struct
import platform
import argparse
import sys
import json
import os.path

#import RPi.GPIO as GPIO
from gpiozero import Button

# setting
version = "3.1.7"
pimodel        = getrpimodel.model()
pimodel_strict = getrpimodel.model_strict()
retry_count    = 3

# exception
class GPIO_Edge_Timeout(Exception):
  pass

if pimodel == "5":
  partial_serial_dev = 'ttyAMA0'
else:
  if os.path.exists('/dev/serial0'):
    partial_serial_dev = 'serial0'
  elif pimodel == "3 Model B" or pimodel == "4 Model B" or pimodel_strict == "Zero W":
    partial_serial_dev = 'ttyS0'
  else:
    partial_serial_dev = 'ttyAMA0'

serial_dev = '/dev/%s' % partial_serial_dev
#stop_getty = 'sudo systemctl stop serial-getty@%s.service' % partial_serial_dev
#start_getty = 'sudo systemctl start serial-getty@%s.service' % partial_serial_dev

# major version of running python
p_ver = platform.python_version_tuple()[0]

def start_getty():
  start_getty = ['sudo', 'systemctl', 'start', 'serial-getty@%s.service' % partial_serial_dev]
  p = subprocess.call(start_getty)

def stop_getty():
  stop_getty = ['sudo', 'systemctl', 'stop', 'serial-getty@%s.service' % partial_serial_dev]
  p = subprocess.call(stop_getty)

def set_serialdevice(serialdevicename):
  global serial_dev
  serial_dev = serialdevicename

def connect_serial():
  return serial.Serial(serial_dev,
                        baudrate=9600,
                        bytesize=serial.EIGHTBITS,
                        parity=serial.PARITY_NONE,
                        stopbits=serial.STOPBITS_ONE,
                        timeout=1.0)

def read_concentration():
  try:
    ser = connect_serial()
    for retry in range(retry_count):
      result=ser.write(b"\xff\x01\x86\x00\x00\x00\x00\x00\x79")
      s=ser.read(9)

      if p_ver == '2':
        if len(s) >= 4 and s[0] == "\xff" and s[1] == "\x86" and checksum(s[1:-1]) == s[-1]:
          return ord(s[2])*256 + ord(s[3])
      else:
        if len(s) >= 4 and s[0] == 0xff and s[1] == 0x86 and ord(checksum(s[1:-1])) == s[-1]:
          return s[2]*256 + s[3]
  except:
     traceback.print_exc()
  return ""

def mh_z19():
  co2 = read_concentration()
  if not co2:
    return {}
  else:
    return {'co2': co2}

def read(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()

  result = mh_z19()

  if not serial_console_untouched:
    start_getty()
  return result

def read_co2valueonly(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()

  result = read_concentration()

  if not serial_console_untouched:
    start_getty()
  return result

def read_all(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()
  try:
    ser = connect_serial()
    for retry in range(retry_count):
      result=ser.write(b"\xff\x01\x86\x00\x00\x00\x00\x00\x79")
      s=ser.read(9)

      if p_ver == '2':
        if len(s) >= 9 and s[0] == "\xff" and s[1] == "\x86" and checksum(s[1:-1]) == s[-1]:
          return {'co2': ord(s[2])*256 + ord(s[3]),
                  'temperature': ord(s[4]) - 40,
                  'TT': ord(s[4]),
                  'SS': ord(s[5]),
                  'UhUl': ord(s[6])*256 + ord(s[7])
                  }
        break
      else:
        if len(s) >= 9 and s[0] == 0xff and s[1] == 0x86 and ord(checksum(s[1:-1])) == s[-1]:
          return {'co2': s[2]*256 + s[3],
                  'temperature': s[4] - 40,
                  'TT': s[4],
                  'SS': s[5],
                  'UhUl': s[6]*256 + s[7]
                  }
  except:
     traceback.print_exc()

  if not serial_console_untouched:
    start_getty()
  return {}

def abc_on(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()
  ser = connect_serial()
  result=ser.write(b"\xff\x01\x79\xa0\x00\x00\x00\x00\xe6")
  ser.close()
  if not serial_console_untouched:
    start_getty()

def abc_off(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()
  ser = connect_serial()
  result=ser.write(b"\xff\x01\x79\x00\x00\x00\x00\x00\x86")
  ser.close()
  if not serial_console_untouched:
    start_getty()

def span_point_calibration(span, serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()
  ser = connect_serial()
  if p_ver == '2':
    b3 = span / 256;
  else:
    b3 = span // 256;   
  byte3 = struct.pack('B', b3)
  b4 = span % 256; byte4 = struct.pack('B', b4)
  c = checksum([0x01, 0x88, b3, b4])
  request = b"\xff\x01\x88" + byte3 + byte4 + b"\x00\x00\x00" + c
  result = ser.write(request)
  ser.close()
  if not serial_console_untouched:
    start_getty()

def zero_point_calibration(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()
  ser = connect_serial()
  request = b"\xff\x01\x87\x00\x00\x00\x00\x00\x78"
  result = ser.write(request)
  ser.close()
  if not serial_console_untouched:
    start_getty()

def detection_range_10000(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()
  ser = connect_serial()
  request = b"\xff\x01\x99\x00\x00\x00\x27\x10\x2F"
  result = ser.write(request)
  ser.close()
  if not serial_console_untouched:
    start_getty()

def detection_range_5000(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()
  ser = connect_serial()
  request = b"\xff\x01\x99\x00\x00\x00\x13\x88\xcb"
  result = ser.write(request)
  ser.close()
  if not serial_console_untouched:
    start_getty()

def detection_range_2000(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()
  ser = connect_serial()
  request = b"\xff\x01\x99\x00\x00\x00\x07\xd0\x8F"
  result = ser.write(request)
  ser.close()
  if not serial_console_untouched:
    start_getty()

def read_from_pwm(gpio=12, range=5000):
  CYCLE_START_HIGHT_TIME = 2
  TIMEOUT = 2000 # must be larger than PWM cycle time.

  '''
  GPIO.setmode(GPIO.BCM)
  GPIO.setup(gpio,GPIO.IN)
  '''
  btn = Button(gpio)

  # wait falling ¯¯|_ to see end of last cycle
  '''
  channel = GPIO.wait_for_edge(gpio, GPIO.FALLING, timeout=TIMEOUT)
  if channel is None:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))
  '''
  starting = time.time() * 1000
  btn.wait_for_press(2) # 2 sec for timeout because 1sec cycle of pwm
  last_falling = time.time() * 1000
  if last_falling - starting > 1000:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))

  # wait rising __|¯ to catch the start of this cycle
  '''
  channel = GPIO.wait_for_edge(gpio,GPIO.RISING, timeout=TIMEOUT)
  if channel is None:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))
  else:
    rising = time.time() * 1000
  '''
  starting = time.time() * 1000
  btn.wait_for_release(2) # 2 sec for timeout because 1sec cycle of pwm
  rising   = time.time() * 1000
  if rising - starting > 1000:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))

  # wait falling ¯¯|_ again to catch the end of TH duration
  '''
  channel = GPIO.wait_for_edge(gpio, GPIO.FALLING, timeout=TIMEOUT)
  if channel is None:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))
  else:
    falling = time.time() * 1000
  '''
  starting = time.time() * 1000
  btn.wait_for_press(2) # 2 sec for timeout because 1sec cycle of pwm
  falling  = time.time() * 1000
  if falling - starting > 1000:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))


  return {'co2': int(falling -rising - CYCLE_START_HIGHT_TIME) / 2 *(range/500)}

def checksum(array):
  if p_ver == '2' and isinstance(array, str):
    array = [ord(c) for c in array]
  csum = sum(array) % 0x100
  if csum == 0:
    return struct.pack('B', 0)
  else:
    return struct.pack('B', 0xff - csum + 1)

if __name__ == '__main__':
#  value = read()
#  print (value)
  parser = argparse.ArgumentParser(
    description='''return CO2 concentration as object as {'co2': 416}''',
  )
  parser.add_argument("--serial_device",
                      type=str,
                      help='''Use this serial device file''')

  parser.add_argument("--serial_console_untouched",
                      action='store_true',
                      help='''Don't close/reopen serial console before/after sensor reading''')


  group = parser.add_mutually_exclusive_group()

  group.add_argument("--version",
                      action='store_true',
                      help='''show version''')
  group.add_argument("--all",
                      action='store_true',
                      help='''return all (co2, temperature, TT, SS and UhUl) as json''')
  group.add_argument("--co2valueonly",
                      action='store_true',
                      help='''return co2 value alone, as unlabeled string''')
  group.add_argument("--abc_on",
                      action='store_true',
                      help='''Set ABC functionality on model B as ON.''')
  group.add_argument("--abc_off",
                      action='store_true',
                      help='''Set ABC functionality on model B as OFF.''')
  
  parser.add_argument("--span_point_calibration",
                      type=int,
                      metavar="span",
                      help='''Call calibration function with SPAN point''')
  parser.add_argument("--zero_point_calibration",
                      action='store_true',
                      help='''Call calibration function with ZERO point''')
  parser.add_argument("--detection_range_10000",
                      action='store_true',
                      help='''Set detection range as 10000''')
  parser.add_argument("--detection_range_5000",
                      action='store_true',
                      help='''Set detection range as 5000''')
  parser.add_argument("--detection_range_2000",
                      action='store_true',
                      help='''Set detection range as 2000''')

  parser.add_argument("--pwm",
                      action='store_true',
                      help='''Read CO2 concentration from PWM, see also `--pwm_range` and/or `--pwm_gpio`''')

  parser.add_argument("--pwm_range",
                      type=int,
                      choices=[2000,5000,10000],
                      default=5000,
                      metavar="range",
                      help='''with --pwm, use this to compute co2 concentration, default is 5000''')

  parser.add_argument("--pwm_gpio",
                      type=int,
                      default=12,
                      metavar="gpio(BCM)",
                      help='''with --pwm, read from this gpio pin on RPi, default is 12''')

  args = parser.parse_args()

  if args.serial_device is not None:
    set_serialdevice(args.serial_device)

  if args.abc_on:
    abc_on(args.serial_console_untouched)
    print ("Set ABC logic as on.")
  elif args.abc_off:
    abc_off(args.serial_console_untouched)
    print ("Set ABC logic as off.")
  elif args.span_point_calibration is not None:
    span_point_calibration(args.span_point_calibration, args.serial_console_untouched)
    print ("Call Calibration with SPAN point.")
  elif args.zero_point_calibration:
    print ("Call Calibration with ZERO point.")
    zero_point_calibration(args.serial_console_untouched)
  elif args.detection_range_10000:
    detection_range_10000(args.serial_console_untouched)
    print ("Set Detection range as 10000.")
  elif args.detection_range_5000:
    detection_range_5000(args.serial_console_untouched)
    print ("Set Detection range as 5000.")
  elif args.detection_range_2000:
    detection_range_2000(args.serial_console_untouched)
    print ("Set Detection range as 2000.")
  elif args.pwm:
    print (read_from_pwm(gpio=args.pwm_gpio, range=args.pwm_range))
  elif args.version:
    print (version)
  elif args.all:
    value = read_all(args.serial_console_untouched)
    print (json.dumps(value))
  elif args.co2valueonly:
    value = read_co2valueonly(args.serial_console_untouched)
    print (value)
  else:
    value = read(args.serial_console_untouched)
    print (json.dumps(value))

  sys.exit(0)


================================================
FILE: mh_z19.service
================================================

[Unit]
Description=Get temp, humid, and humiditydeficit data & Post to the monitor
After=rc-local.service
[Service]
WorkingDirectory=/home/pi/mh-z19
ExecStart=/usr/bin/sudo /usr/bin/python -m pondslider --interval 5
Restart=always
#RestartSec=90
RestartSec=30
Type=simple
PIDFile=/var/run/mh-z19.pid
[Install]
WantedBy=multi-user.target



================================================
FILE: pypi/LICENSE
================================================
MIT License

Copyright (c) 2018 Dr. Takeyuki Ueda

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: pypi/README.md
================================================
# mh-z19
Read CO2 concentration from mh-z19 sensor

![MH-Z19](https://camo.githubusercontent.com/8456a67ab97a13866d928d3a14dff59a57cdeccb/68747470733a2f2f63616d6f2e716969746175736572636f6e74656e742e636f6d2f613237306466313136326564356333626639393638623234303634623931656564306466636331312f3638373437343730373333613266326637313639363937343631326436393664363136373635326437333734366637323635326537333333326536313664363137613666366536313737373332653633366636643266333032663334333633353334333432663331333533373339363633393634363232643330363633343330326433373336363533383264333033353636333332643339333933333631333233343633333433373634333133383265373036653637)

## install
```
pip install mh_z19
```
## installs
[![Downloads](https://pepy.tech/badge/mh-z19)](https://pepy.tech/project/mh-z19)
[![Downloads](https://pepy.tech/badge/mh-z19/month)](https://pepy.tech/project/mh-z19)
[![Downloads](https://pepy.tech/badge/mh-z19/week)](https://pepy.tech/project/mh-z19)

## how to use
Use as python script.
```
pi@raspberrypi:~/mh-z19/pypi $ sudo python -m mh_z19
{'co2': 500}
```

Import module and call read()
```
pi@raspberrypi:~/mh-z19/pypi $ sudo python
Python 2.7.13 (default, Nov 24 2017, 17:33:09) 
[GCC 6.3.0 20170516] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import mh_z19
>>> mh_z19.read()
{'co2': 477}
>>> 
```

The ***sudo*** might be necessary because mh_z19 module use Serial.

The differences of the interface between each Raspberry Pi model are resolved inside this module. For example, serial device name is difference between Raspberry Pi 3 and older model, but mh-z19 module automatically detect the model and read from appropriate serial device.

To use mh-z19, once you need to set up enabling serial port device on the Raspberry Pi.
Following [Wiki](https://github.com/UedaTakeyuki/mh-z19/wiki/How-to-Enable-Serial-Port-hardware-on-the-Raspberry-Pi) page might be informative.

## cabling
Connect RPi & mh-z19 as:

- 5V on RPi and Vin on mh-z19
- GND(0v) on RPi and GND on mh-z19
- TxD and RxD are connected to cross between RPi and mh-z18 

Followings are example of cabling, but you can free to use other 5v and 0v Pin on the RPi. 

![Cabling](https://camo.githubusercontent.com/3cd4c1b482ea902b7e66dca13d4260193c831a63/68747470733a2f2f63616d6f2e716969746175736572636f6e74656e742e636f6d2f313132616435666534316338326131363637316432383832303730333834313039633838363063632f36383734373437303733336132663266373136393639373436313264363936643631363736353264373337343666373236353265373333333265363136643631376136663665363137373733326536333666366432663330326633343336333533343334326633303338333233383333333033313334326433363338363433323264363333333634363532643331333633343334326433373633333836343339363233373632333633323636363432653661373036353637)

```
pi@raspberrypi:~/mh-z19 $ gpio readall
 +-----+-----+---------+------+---+---Pi B+--+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 |   IN | 1 |  3 || 4  |   |      | 5v      |     |     |  <---- Vin
 |   3 |   9 |   SCL.1 |   IN | 1 |  5 || 6  |   |      | 0v      |     |     |  <---- Gnd
 |   4 |   7 | GPIO. 7 |   IN | 1 |  7 || 8  | 1 | ALT0 | TxD     | 15  | 14  |  <---- RxD
 |     |     |      0v |      |   |  9 || 10 | 1 | ALT0 | RxD     | 16  | 15  |  <---- TxD
 |  17 |   0 | GPIO. 0 |   IN | 0 | 11 || 12 | 0 | IN   | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |   IN | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |   IN | 0 | 15 || 16 | 0 | IN   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 0 | IN   | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI |   IN | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO |   IN | 0 | 21 || 22 | 0 | IN   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK |   IN | 0 | 23 || 24 | 1 | IN   | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | IN   | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 |   IN | 1 | 27 || 28 | 1 | IN   | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |   IN | 1 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |   IN | 1 | 31 || 32 | 0 | IN   | GPIO.26 | 26  | 12  |
 |  13 |  23 | GPIO.23 |   IN | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |   IN | 0 | 35 || 36 | 0 | IN   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |   IN | 0 | 37 || 38 | 0 | IN   | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 0 | IN   | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+---Pi B+--+---+------+---------+-----+-----+
```

## Watch CO2 concentration on your browser 
<img src="https://github.com/UedaTakeyuki/mh-z19/raw/master/pic/2018-11-20.11.23.19.png" width="24%">

[MONITOR™](https://monitor3.uedasoft.com) is a free Remote Monitoring Service to show latest data on the web. You can see the current CO2 concentration value measured by your MH-Z19 device on your smartphone. For detail, please refer this [blog](https://monitorserviceatelierueda.blogspot.com/2018/11/how-to-measure-room-co2-concentration.html). 


## Calibration, Detection range settings, and ABC(Automatic Baseline Correction) logic on/off.
Features about calibration (both MH-Z19 & MH-Z19B), detection range change (MH-Z19B) and ABC logic on/off(MH-Z19B) are implemented at version 0.2.1 or later.

I'm afraid I've just only implemented these without test due to lack necessary devices and apparatus for the test, fx: standard concentration CO2 gas, also MH-Z19B module.
If you have these devices or apparatus and try to use these functions generously, I'm really appreciate your [issue report](https://github.com/UedaTakeyuki/mh-z19/issues) regardless result were positive or negative.

For detail please refer this [wiki](https://github.com/UedaTakeyuki/mh-z19/wiki/CALIBRATION-&-detection-range).

## Undocumented response values of 0x86 command.
The [Revspace/MHZ19](https://revspace.nl/MHZ19#Command_0x86_.28read_concentration.29) shows values undocumented on the official datasheets([MH-Z19](https://www.winsen-sensor.com/d/files/PDF/Infrared%20Gas%20Sensor/NDIR%20CO2%20SENSOR/MH-Z19%20CO2%20Ver1.0.pdf), [MH-Z19B](https://www.winsen-sensor.com/d/files/MH-Z19B.pdf)). In accordance with this, **--all** option add these values in the return json value as follows:

```bash:
sudo python -m mh_z19 --all
{"SS": 232, "UhUl": 10752, "TT": 61, "co2": 818, "temperature": 21}

sudo python3 -m mh_z19 --all
{"TT": 61, "co2": 807, "SS": 232, "temperature": 21, "UhUl": 10752}
```

or call **read_all()** function as follows:

```
>>> import mh_z19
>>> mh_z19.read_all()
{'SS': 232, 'UhUl': 10738, 'TT': 61, 'co2': 734, 'temperature': 21}
>>> 
```

## Use specific serial device.
In case you should use specific serial device insted of Raspberry Pi default serial device which this library automatically select, for example in case to need to use /dev/ttyUSB0 for **FT232 usb-serial converter** as [issue#12](https://github.com/UedaTakeyuki/mh-z19/issues/12), you can specify serial device by **--serial_device** option as follows:

```
sudo python -m mh_z19 --serial_device /dev/ttyUSB0
```

## How to use without root permission.
See this [wiki](https://github.com/UedaTakeyuki/mh-z19/wiki/How-to-use-without-root-permission.).

## How to use in your program.
See this [wiki](https://github.com/UedaTakeyuki/mh-z19/wiki/How-to-use-in-your-program.).

## PWM support.
See this [wiki](https://github.com/UedaTakeyuki/mh-z19/wiki/PWM-support.).

## In case you can't get the value.
Even if cabling seems no problem and uart seems to be prepateted well but you can't get sensor value. As [nincube8](https://github.com/nincube8) suggested that the [pull up](https://github.com/UedaTakeyuki/mh-z19/issues/22#issuecomment-683393350) by [1-5kΩ register](https://github.com/UedaTakeyuki/mh-z19/issues/26#issuecomment-744039360) can be working solution. Thank you [nincube8](https://github.com/nincube8)!

## Q&A
The forum is avai at [here](https://groups.google.com/g/mh_z19-users). Any questions, suggestions, reports are welcome!

## Blog
- [How to Measure ROOM CO2 concentration with 20$ sensor "MH-Z19" and Raspberry Pi.](https://monitorserviceatelierueda.blogspot.com/2018/11/how-to-measure-room-co2-concentration.html)
- [Monitoring all over the world with 3G Network for not more than 10$ monthly payment.](https://monitorserviceatelierueda.blogspot.com/2018/10/continuous-monitoring-all-over-world.html)
- [How to make shareable SD card by Raspberry Pi & PC.](https://monitorserviceatelierueda.blogspot.com/2018/09/how-to-make-shareable-sd-card-by.html)

## References

- [MH-H19B DataSheet version 1.5](https://www.winsen-sensor.com/d/files/MH-Z19B.pdf)
- [MH-H19 DataSheet version 1.0](https://www.winsen-sensor.com/d/files/PDF/Infrared%20Gas%20Sensor/NDIR%20CO2%20SENSOR/MH-Z19%20CO2%20Ver1.0.pdf)
- [MH-H19B DataSheet version 1.0](https://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf)
- [RevSpace](https://revspace.nl/MHZ19#Setting_the_measurement_range)

## history
- 0.1.1  2018.11.05  first version self-forked from [slider](https://github.com/UedaTakeyuki/slider).
- 0.1.3  2018.11.06  fix Readme.
- 0.1.4  2018.11.15  revise Readme.
- 0.1.6  2018.11.29  revise Readme.
- 0.2.1  2019.01.18  add followings without test (sorry)
                       abc_on(), abc_off(), span_point_calibration(),
											 xero_point_calibration(), detection_range_5000(),
											 detection_range_2000(), checksum()
- 0.3.5  2019.01.22  Both Python2 & Python3 support
- 0.3.6  2019.01.22  Merge [Pull Request #3](https://github.com/UedaTakeyuki/mh-z19/pull/3) & [Pull Request #4](https://github.com/UedaTakeyuki/mh-z19/pull/4). Thanks [David](https://github.com/kostaldavid8)!
- 0.3.7  2019.02.25  Add --all option which requested as [issue#5](https://github.com/UedaTakeyuki/mh-z19/issues/5), thanks [Rafał](https://github.com/rzarajczyk)!
- 0.3.8  2019.04.16  Merge [Pull Request #7](https://github.com/UedaTakeyuki/mh-z19/pull/7). Thanks [Alexander](https://github.com/belibak)!
- 0.3.8.5  2019.04.21  Merge [Pull Request #9](https://github.com/UedaTakeyuki/mh-z19/pull/9). Thanks [WO15](https://github.com/WO15)!
- 0.3.9  2019.05.06  Revise the serial port selection logic. Support using **PL011** uart on Raspberry Pi **Model 3 and Zero W** which is selected by setting dtoverlay=**pi3-miniuart-bt** or dtoverlay=**pi3-disable-bt**. Thanks **片岡さん** for your kindly [report](https://qiita.com/yukataoka/items/a3b4065e8210b8f372ff) including this issue!
- 0.4.1 2019.08.11 Add --serial_device option as solution of [issue#12](https://github.com/UedaTakeyuki/mh-z19/issues/12). Thanks [Actpohomoc](https://github.com/Actpohomoc) and [TBR-BRD](https://github.com/TBR-BRD)!
- 0.5.1 2020.05.16 Add **--serial_console_untouched** option to support **execution without sudo** asked as [issue#17](https://github.com/UedaTakeyuki/mh-z19/issues/17). Thanks [ralphbe91](https://github.com/ralphbe91)!
- 0.5.2 2020.06.30 Update the link for datasheet of MH-Z19B from version 1.0 to version 1.5 based be pointed it out as [issue#18](https://github.com/UedaTakeyuki/mh-z19/issues/18). Thanks [WO15](https://github.com/WO15)!
- 0.6.1 2020.07.07 Add **--detection_range_10000** option to support **Set 0~10000ppm detection range** asked as [issue#19](https://github.com/UedaTakeyuki/mh-z19/issues/19). Thanks [WO15](https://github.com/WO15)!
- 0.6.3 2020.08.27 Fix [issue#21](https://github.com/UedaTakeyuki/mh-z19/issues/21). Thanks [idegre](https://github.com/idegre)!
- 3.0.0 2021.02.05 [PWM support](https://github.com/UedaTakeyuki/mh-z19/wiki/PWM-support.).
- 3.0.1 2021.02.17 Fix a degradation of not running with python3. Thank you **Masahiko OHKUBO** san for your report.
- 3.0.2 2021.03.25 Fix to support RPi4 correctly as [issue#29](https://github.com/UedaTakeyuki/mh-z19/issues/29). Thanks [iperniaf](https://github.com/iperniaf)!
- 3.0.3 2021.11.08 Fix [issue#35](https://github.com/UedaTakeyuki/mh-z19/issues/35). Thanks [false](https://github.com/false-git)!
- 3.0.4 2011.11.11 Fix [issue#36](https://github.com/UedaTakeyuki/mh-z19/issues/36). Thanks [David Bock](https://github.com/DavidBock)
- 3.0.5 2022.01.01 Fix [issue#38](https://github.com/UedaTakeyuki/mh-z19/issues/38). Thanks [kzehnter](https://github.com/kzehnter)!
- 3.1.0 2022.01.22 add ``--co2valueonly`` option by merging [issue#39](https://github.com/UedaTakeyuki/mh-z19/issues/39) and [issue#40](https://github.com/UedaTakeyuki/mh-z19/issues/40). Thanks [jonesthefox](https://github.com/jonesthefox)!
- 3.1.1 2022.01.23 remove code clone.
- 3.1.2 2022.01.31 Fix [issue#41](https://github.com/UedaTakeyuki/mh-z19/issues/41). Thanks [Christopher M. Pierce](https://github.com/electronsandstuff)!
- 3.1.3 2022.02.25 Fix [issue#43](https://github.com/UedaTakeyuki/mh-z19/issues/43). Thanks [Jannis Möller](https://github.com/jannismoeller)!
- 3.1.5 2024.05.06 FIx [issue#53](https://github.com/UedaTakeyuki/mh-z19/issues/53). Thanks [Tats Shibata](https://github.com/rewse)
- 3.1.6 2024.08.29 FIx [issue#54](https://github.com/UedaTakeyuki/mh-z19/issues/54). Thanks [80kpc](https://github.com/ziyucao)
- 3.1.7 2025.03.01 drop the RPi.GPIO dependenc. Thanks [mroelandts](https://github.com/mroelandts)


================================================
FILE: pypi/mh_z19/__init__.py
================================================
# -*- coding: utf-8 -*-
# original: https://raw.githubusercontent.com/UedaTakeyuki/slider/master/mh_z19.py
#
# © Takeyuki UEDA 2015 -

import serial
import subprocess
import traceback
import getrpimodel
import struct
import platform
import os.path

# import RPi.GPIO as GPIO
import mh_z19.pwm as pwm

# setting
version = "3.1.7"
pimodel        = getrpimodel.model()
pimodel_strict = getrpimodel.model_strict()
retry_count    = 3

if pimodel == "5":
  partial_serial_dev = 'ttyAMA0'
else:
  if os.path.exists('/dev/serial0'):
    partial_serial_dev = 'serial0'
  elif pimodel == "3 Model B" or pimodel == "4 Model B" or pimodel_strict == "Zero W":
    partial_serial_dev = 'ttyS0'
  else:
    partial_serial_dev = 'ttyAMA0'
  
serial_dev = '/dev/%s' % partial_serial_dev
#stop_getty = 'sudo systemctl stop serial-getty@%s.service' % partial_serial_dev
#start_getty = 'sudo systemctl start serial-getty@%s.service' % partial_serial_dev
#start_getty = ['sudo', 'systemctl', 'start', 'serial-getty@%s.service' % partial_serial_dev]
#stop_getty = ['sudo', 'systemctl', 'stop', 'serial-getty@%s.service' % partial_serial_dev]

# major version of running python
p_ver = platform.python_version_tuple()[0]

def start_getty():
#  p = subprocess.call(start_getty, stdout=subprocess.PIPE, shell=True)
  start_getty = ['sudo', 'systemctl', 'start', 'serial-getty@%s.service' % partial_serial_dev]
  p = subprocess.call(start_getty)

def stop_getty():
#  p = subprocess.call(stop_getty, stdout=subprocess.PIPE, shell=True)
  stop_getty = ['sudo', 'systemctl', 'stop', 'serial-getty@%s.service' % partial_serial_dev]
  p = subprocess.call(stop_getty)

def set_serialdevice(serialdevicename):
  global serial_dev
  serial_dev = serialdevicename

def connect_serial():
  return serial.Serial(serial_dev,
                        baudrate=9600,
                        bytesize=serial.EIGHTBITS,
                        parity=serial.PARITY_NONE,
                        stopbits=serial.STOPBITS_ONE,
                        timeout=1.0)

def read_concentration():
  try:
    ser = connect_serial()
    for retry in range(retry_count):
      result=ser.write(b"\xff\x01\x86\x00\x00\x00\x00\x00\x79")
      s=ser.read(9)

      if p_ver == '2':
        if len(s) >= 4 and s[0] == "\xff" and s[1] == "\x86" and checksum(s[1:-1]) == s[-1]:
          return ord(s[2])*256 + ord(s[3])
      else:
        if len(s) >= 4 and s[0] == 0xff and s[1] == 0x86 and ord(checksum(s[1:-1])) == s[-1]:
          return s[2]*256 + s[3]
  except:
     traceback.print_exc()
  return ""

def mh_z19():
  co2 = read_concentration()
  if not co2:
    return {}
  else:
    return {'co2': co2}

def read(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()

  result = mh_z19()

  if not serial_console_untouched:
    start_getty()
  return result

def read_co2valueonly(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()

  result = read_concentration()

  if not serial_console_untouched:
    start_getty()
  return result

def read_all(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()
  try:
    ser = connect_serial()
    for retry in range(retry_count):
      result=ser.write(b"\xff\x01\x86\x00\x00\x00\x00\x00\x79")
      s=ser.read(9)

      if p_ver == '2':
        if len(s) >= 9 and s[0] == "\xff" and s[1] == "\x86" and checksum(s[1:-1]) == s[-1]:
          return {'co2': ord(s[2])*256 + ord(s[3]),
                  'temperature': ord(s[4]) - 40,
                  'TT': ord(s[4]),
                  'SS': ord(s[5]),
                  'UhUl': ord(s[6])*256 + ord(s[7])
                  }
        break
      else:
        if len(s) >= 9 and s[0] == 0xff and s[1] == 0x86 and ord(checksum(s[1:-1])) == s[-1]:
          return {'co2': s[2]*256 + s[3],
                  'temperature': s[4] - 40,
                  'TT': s[4],
                  'SS': s[5],
                  'UhUl': s[6]*256 + s[7]
                  }
  except:
     traceback.print_exc()

  if not serial_console_untouched:
    start_getty()
  return {}

def abc_on(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()
  ser = connect_serial()
  result=ser.write(b"\xff\x01\x79\xa0\x00\x00\x00\x00\xe6")
  ser.close()
  if not serial_console_untouched:
    start_getty()

def abc_off(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()
  ser = connect_serial()
  result=ser.write(b"\xff\x01\x79\x00\x00\x00\x00\x00\x86")
  ser.close()
  if not serial_console_untouched:
    start_getty()

def span_point_calibration(span, serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()
  ser = connect_serial()
  if p_ver == '2':
    b3 = span / 256;
  else:
    b3 = span // 256;   
  byte3 = struct.pack('B', b3)
  b4 = span % 256; byte4 = struct.pack('B', b4)
  c = checksum([0x01, 0x88, b3, b4])
  request = b"\xff\x01\x88" + byte3 + byte4 + b"\x00\x00\x00" + c
  result = ser.write(request)
  ser.close()
  if not serial_console_untouched:
    start_getty()

def zero_point_calibration(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()
  ser = connect_serial()
  request = b"\xff\x01\x87\x00\x00\x00\x00\x00\x78"
  result = ser.write(request)
  ser.close()
  if not serial_console_untouched:
    start_getty()

def detection_range_10000(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()
  ser = connect_serial()
  request = b"\xff\x01\x99\x00\x00\x00\x27\x10\x2F"
  result = ser.write(request)
  ser.close()
  if not serial_console_untouched:
    start_getty()

def detection_range_5000(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()
  ser = connect_serial()
  request = b"\xff\x01\x99\x00\x00\x00\x13\x88\xcb"
  result = ser.write(request)
  ser.close()
  if not serial_console_untouched:
    start_getty()

def detection_range_2000(serial_console_untouched=False):
  if not serial_console_untouched:
    stop_getty()
  ser = connect_serial()
  request = b"\xff\x01\x99\x00\x00\x00\x07\xd0\x8F"
  result = ser.write(request)
  ser.close()
  if not serial_console_untouched:
    start_getty()

def read_from_pwm(gpio=12, range=5000):
#  if pimodel == "5":
    return pwm.read_from_pwm_with_gpiozero(gpio=12, range=5000)
#  else:
#    return pwm.read_from_pwm_with_gpio(gpio=12, range=5000)
'''
  CYCLE_START_HIGHT_TIME = 2
  TIMEOUT = 2000 # must be larger than PWM cycle time.

  GPIO.setmode(GPIO.BCM)
  GPIO.setup(gpio,GPIO.IN)

  # wait falling ¯¯|_ to see end of last cycle
  channel = GPIO.wait_for_edge(gpio, GPIO.FALLING, timeout=TIMEOUT)
  if channel is None:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))

  # wait rising __|¯ to catch the start of this cycle
  channel = GPIO.wait_for_edge(gpio,GPIO.RISING, timeout=TIMEOUT)
  if channel is None:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))
  else:
    rising = time.time() * 1000

  # wait falling ¯¯|_ again to catch the end of TH duration
  channel = GPIO.wait_for_edge(gpio, GPIO.FALLING, timeout=TIMEOUT)
  if channel is None:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))
  else:
    falling = time.time() * 1000

  return {'co2': int(falling -rising - CYCLE_START_HIGHT_TIME) / 2 *(range/500)}
'''

def checksum(array):
  if p_ver == '2' and isinstance(array, str):
    array = [ord(c) for c in array]
  csum = sum(array) % 0x100
  if csum == 0:
    return struct.pack('B', 0)
  else:
    return struct.pack('B', 0xff - csum + 1)


================================================
FILE: pypi/mh_z19/__main__.py
================================================
# -*- coding: utf-8 -*-
# original: https://raw.githubusercontent.com/UedaTakeyuki/slider/master/mh_z19.py
#
# © Takeyuki UEDA 2015 -
import sys
import argparse
import json
import mh_z19.__init__ as mh_z19
#import __init__ as mh_z19

parser = argparse.ArgumentParser(
  description='''return CO2 concentration as object as {'co2': 416}''',
)
parser.add_argument("--serial_device",
                    type=str,
                    help='''Use this serial device file''')

parser.add_argument("--serial_console_untouched",
                    action='store_true',
                    help='''Don't close/reopen serial console before/after sensor reading''')


group = parser.add_mutually_exclusive_group()

group.add_argument("--version",
                    action='store_true',
                    help='''show version''')
group.add_argument("--all",
                    action='store_true',
                    help='''return all (co2, temperature, TT, SS and UhUl) as json''')
group.add_argument("--co2valueonly",
                    action='store_true',
                    help='''return co2 value alone, as unlabeled string''')
group.add_argument("--abc_on",
                    action='store_true',
                    help='''Set ABC functionality on model B as ON.''')
group.add_argument("--abc_off",
                    action='store_true',
                    help='''Set ABC functionality on model B as OFF.''')

parser.add_argument("--span_point_calibration",
                    type=int,
                    metavar="span",
                    help='''Call calibration function with SPAN point''')
parser.add_argument("--zero_point_calibration",
                    action='store_true',
                    help='''Call calibration function with ZERO point''')
parser.add_argument("--detection_range_10000",
                    action='store_true',
                    help='''Set detection range as 10000''')
parser.add_argument("--detection_range_5000",
                    action='store_true',
                    help='''Set detection range as 5000''')
parser.add_argument("--detection_range_2000",
                    action='store_true',
                    help='''Set detection range as 2000''')

parser.add_argument("--pwm",
                    action='store_true',
                    help='''Read CO2 concentration from PWM, see also `--pwm_range` and/or `--pwm_gpio`''')

parser.add_argument("--pwm_range",
                    type=int,
                    choices=[2000,5000,10000],
                    default=5000,
                    metavar="range",
                    help='''with --pwm, use this to compute co2 concentration, default is 5000''')

parser.add_argument("--pwm_gpio",
                    type=int,
                    default=12,
                    metavar="gpio(BCM)",
                    help='''with --pwm, read from this gpio pin on RPi, default is 12''')

args = parser.parse_args()

if args.serial_device is not None:
  mh_z19.set_serialdevice(args.serial_device)

#print(args.serial_console_untouched)

if args.abc_on:
  mh_z19.abc_on(args.serial_console_untouched)
  print ("Set ABC logic as on.")
elif args.abc_off:
  mh_z19.abc_off(args.serial_console_untouched)
  print ("Set ABC logic as off.")
elif args.span_point_calibration is not None:
  mh_z19.span_point_calibration(args.span_point_calibration, args.serial_console_untouched)
  print ("Call Calibration with SPAN point.")
elif args.zero_point_calibration:
  print ("Call Calibration with ZERO point.")
  mh_z19.zero_point_calibration(args.serial_console_untouched)
elif args.detection_range_10000:
  mh_z19.detection_range_10000(args.serial_console_untouched)
  print ("Set Detection range as 10000.")
elif args.detection_range_5000:
  mh_z19.detection_range_5000(args.serial_console_untouched)
  print ("Set Detection range as 5000.")
elif args.detection_range_2000:
  mh_z19.detection_range_2000(args.serial_console_untouched)
  print ("Set Detection range as 2000.")
elif args.pwm:
  print (mh_z19.read_from_pwm(gpio=args.pwm_gpio, range=args.pwm_range))
elif args.version:
  print (mh_z19.version)
elif args.all:
  value = mh_z19.read_all(args.serial_console_untouched)
  print (json.dumps(value))
elif args.co2valueonly:
  value = mh_z19.read_co2valueonly(args.serial_console_untouched)
  print (value)
else:
  value = mh_z19.read(args.serial_console_untouched)
  print (json.dumps(value))

sys.exit(0)

================================================
FILE: pypi/mh_z19/pwm.py
================================================
# -*- coding: utf-8 -*-
#
# © Takeyuki UEDA 2024 -
import time

# import RPi.GPIO as GPIO
from gpiozero import Button

# exception
class GPIO_Edge_Timeout(Exception):
  pass

'''
def read_from_pwm_with_gpio(gpio=12, range=5000):
  CYCLE_START_HIGHT_TIME = 2
  TIMEOUT = 2000 # must be larger than PWM cycle time.

  GPIO.setmode(GPIO.BCM)
  GPIO.setup(gpio,GPIO.IN)

  # wait falling ¯¯|_ to see end of last cycle
  channel = GPIO.wait_for_edge(gpio, GPIO.FALLING, timeout=TIMEOUT)
  if channel is None:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))

  # wait rising __|¯ to catch the start of this cycle
  channel = GPIO.wait_for_edge(gpio,GPIO.RISING, timeout=TIMEOUT)
  if channel is None:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))
  else:
    rising = time.time() * 1000

  # wait falling ¯¯|_ again to catch the end of TH duration
  channel = GPIO.wait_for_edge(gpio, GPIO.FALLING, timeout=TIMEOUT)
  if channel is None:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))
  else:
    falling = time.time() * 1000

  return {'co2': int(falling -rising - CYCLE_START_HIGHT_TIME) / 2 *(range/500)}
'''

# refer https://gpiozero.readthedocs.io/en/latest/migrating_from_rpigpio.html#input-devices
def read_from_pwm_with_gpiozero(gpio=12, range=5000):
  CYCLE_START_HIGHT_TIME = 2
  TIMEOUT = 2000 # must be larger than PWM cycle time.

  '''
  GPIO.setmode(GPIO.BCM)
  GPIO.setup(gpio,GPIO.IN)
  '''
  btn = Button(gpio)

  # wait falling ¯¯|_ to see end of last cycle
  '''  
  channel = GPIO.wait_for_edge(gpio, GPIO.FALLING, timeout=TIMEOUT)
  if channel is None:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))
  '''
  starting = time.time() * 1000
  btn.wait_for_press(2) # 2 sec for timeout because 1sec cycle of pwm
  last_falling = time.time() * 1000
  if last_falling - starting > 1000:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))

  # wait rising __|¯ to catch the start of this cycle
  '''
  channel = GPIO.wait_for_edge(gpio,GPIO.RISING, timeout=TIMEOUT)
  if channel is None:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))
  else:
    rising = time.time() * 1000
  '''
  starting = time.time() * 1000
  btn.wait_for_release(2) # 2 sec for timeout because 1sec cycle of pwm
  rising   = time.time() * 1000
  if rising - starting > 1000:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))

  # wait falling ¯¯|_ again to catch the end of TH duration
  '''
  channel = GPIO.wait_for_edge(gpio, GPIO.FALLING, timeout=TIMEOUT)
  if channel is None:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))
  else:
    falling = time.time() * 1000
  '''
  starting = time.time() * 1000
  btn.wait_for_press(2) # 2 sec for timeout because 1sec cycle of pwm
  falling  = time.time() * 1000
  if falling - starting > 1000:
    raise GPIO_Edge_Timeout("gpio {} edge timeout".format(gpio))

  return {'co2': int(falling -rising - CYCLE_START_HIGHT_TIME) / 2 *(range/500)}


================================================
FILE: pypi/setup.cfg
================================================
[metadata]
description-file = README.md


================================================
FILE: pypi/setup.py
================================================
from setuptools import setup
import platform

if platform.python_version_tuple()[0] == '2':
  with open("README.md") as f:
    long_description = f.read()
else:
  with open("README.md", encoding='utf8') as f:
    long_description = f.read()

setup(
  name = 'mh_z19',
  packages = ['mh_z19'], # this must be the same as the name above
  version = '3.1.7',
  description = 'mh-z19 CO2 concentration sensor library for All models of Raspberry Pi',
  long_description=long_description,
  long_description_content_type="text/markdown",
  author = 'Takeyuki UEDA',
  author_email = 'gde00107@nifty.com',
  license='MIT',
  url = 'https://github.com/UedaTakeyuki/mh-z19', # use the URL to the github repo
  keywords = ['sensor', 'IoT', 'Raspberry Pi', 'mh-z19', 'CO2'], # arbitrary keywords
  classifiers = ['Development Status :: 5 - Production/Stable',
                 'Programming Language :: Python',
                 'Topic :: Terminals'
  ],
  install_requires=[
    'getrpimodel>=0.1.26',
    'pyserial',
    'requests',
    'argparse',
#    'RPi.GPIO',
    'gpiozero',
    'lgpio'
  ]
)


================================================
FILE: read.ini
================================================
[mode]
#run_mode=dummy
run_mode=real

[valueid]
co2=


================================================
FILE: read.py
================================================
# coding:utf-8
# Copy Right Atelier UEDA  © 2015 -
#
# require: 'lol_dht' https://github.com/technion/lol_dht22
#          
# return:  {"temp": , "humidity":}

import os
import sys
import ConfigParser
import mh_z19
import traceback
import urllib3
import json
import requests
from urllib3.exceptions import InsecureRequestWarning
# refer http://73spica.tech/blog/requests-insecurerequestwarning-disable/
urllib3.disable_warnings(InsecureRequestWarning)

# get configration
configfile = os.path.dirname(os.path.abspath(__file__))+'/'+os.path.splitext(os.path.basename(__file__))[0]+'.ini'
ini = ConfigParser.SafeConfigParser()
ini.read(configfile)

def read():
  global ini
  try:
    if ini.get("mode", "run_mode") == "dummy":
      result = {"co2":400}
    else:
      result = mh_z19.read()
    return result
  except:
    traceback.print_exc()

def send(valueid, value):
  r = requests.post('https://monitor3.uedasoft.com/postvalue.php', data={'valueid': valueid, 'value': value}, timeout=10, verify=False)
  print r.text

if __name__ == '__main__':
  values = read()
  print json.dumps(values)
  if values is not None:
    if ini.get("valueid", "co2"):
      print "co2 concentration sending..."
      send(ini.get("valueid", "co2"), values['co2'])




================================================
FILE: send2monitor.ini
================================================
[valueid]
co2=vpgqargj

[server]
url=https://monitor3.uedasoft.com/postvalue.php

[error_recovery]
recover_on=true
#recover_on=false
counterfile=/tmp/sendcounter.txt
threshold=3
recover_command=sudo reboot



================================================
FILE: setid.sh
================================================
#!/bin/bash

function usage() {
cat <<_EOT_
Usage:
  $0 [your_view_id]
Description:
  Set [your_view_id]
Options:
_EOT_
exit 1
}

if [ $# -ne 1 ]; then
  usage
fi

sed -i "s/^co2=.*/co2=$1/" send2monitor.ini


================================================
FILE: setup.sh
================================================
sudo apt-get install python3-pip git-core
sudo pip3 install mh_z19 pondslider incremental_counter error_counter --break-system-packages
if [ ${cat /etc/debian_version} -lt 11 ] ; then
  sudo apt-get install python-pip
  sudo pip install mh_z19 pondslider incremental_counter error_counter
fi
git clone https://github.com/UedaTakeyuki/handlers
ln -s handlers/value/sender/send2monitor/send2monitor.py
sudo sed -i "s/^enable_uart=.*/enable_uart=1/" /boot/config.txt
read -p "Would you like to reboot now?  (y/n) :" YN
if [ "${YN}" = "y" ]; then
  sudo reboot
else
  exit 1;
fi
Download .txt
gitextract_9gbtfjpy/

├── .github/
│   ├── FUNDING.yml
│   └── ISSUE_TEMPLATE/
│       ├── bug_report.md
│       ├── feature_request.md
│       ├── help-wanted.md
│       └── question.md
├── .gitignore
├── LICENSE
├── README.md
├── __init__.py
├── autostart.ini
├── autostart.sh
├── config.save.toml
├── config.toml
├── loop.sh
├── mh_z19.py
├── mh_z19.service
├── pypi/
│   ├── LICENSE
│   ├── README.md
│   ├── mh_z19/
│   │   ├── __init__.py
│   │   ├── __main__.py
│   │   └── pwm.py
│   ├── setup.cfg
│   └── setup.py
├── read.ini
├── read.py
├── send2monitor.ini
├── setid.sh
└── setup.sh
Download .txt
SYMBOL INDEX (41 symbols across 4 files)

FILE: mh_z19.py
  class GPIO_Edge_Timeout (line 28) | class GPIO_Edge_Timeout(Exception):
  function start_getty (line 48) | def start_getty():
  function stop_getty (line 52) | def stop_getty():
  function set_serialdevice (line 56) | def set_serialdevice(serialdevicename):
  function connect_serial (line 60) | def connect_serial():
  function read_concentration (line 68) | def read_concentration():
  function mh_z19 (line 85) | def mh_z19():
  function read (line 92) | def read(serial_console_untouched=False):
  function read_co2valueonly (line 102) | def read_co2valueonly(serial_console_untouched=False):
  function read_all (line 112) | def read_all(serial_console_untouched=False):
  function abc_on (line 145) | def abc_on(serial_console_untouched=False):
  function abc_off (line 154) | def abc_off(serial_console_untouched=False):
  function span_point_calibration (line 163) | def span_point_calibration(span, serial_console_untouched=False):
  function zero_point_calibration (line 180) | def zero_point_calibration(serial_console_untouched=False):
  function detection_range_10000 (line 190) | def detection_range_10000(serial_console_untouched=False):
  function detection_range_5000 (line 200) | def detection_range_5000(serial_console_untouched=False):
  function detection_range_2000 (line 210) | def detection_range_2000(serial_console_untouched=False):
  function read_from_pwm (line 220) | def read_from_pwm(gpio=12, range=5000):
  function checksum (line 273) | def checksum(array):

FILE: pypi/mh_z19/__init__.py
  function start_getty (line 42) | def start_getty():
  function stop_getty (line 47) | def stop_getty():
  function set_serialdevice (line 52) | def set_serialdevice(serialdevicename):
  function connect_serial (line 56) | def connect_serial():
  function read_concentration (line 64) | def read_concentration():
  function mh_z19 (line 81) | def mh_z19():
  function read (line 88) | def read(serial_console_untouched=False):
  function read_co2valueonly (line 98) | def read_co2valueonly(serial_console_untouched=False):
  function read_all (line 108) | def read_all(serial_console_untouched=False):
  function abc_on (line 141) | def abc_on(serial_console_untouched=False):
  function abc_off (line 150) | def abc_off(serial_console_untouched=False):
  function span_point_calibration (line 159) | def span_point_calibration(span, serial_console_untouched=False):
  function zero_point_calibration (line 176) | def zero_point_calibration(serial_console_untouched=False):
  function detection_range_10000 (line 186) | def detection_range_10000(serial_console_untouched=False):
  function detection_range_5000 (line 196) | def detection_range_5000(serial_console_untouched=False):
  function detection_range_2000 (line 206) | def detection_range_2000(serial_console_untouched=False):
  function read_from_pwm (line 216) | def read_from_pwm(gpio=12, range=5000):
  function checksum (line 250) | def checksum(array):

FILE: pypi/mh_z19/pwm.py
  class GPIO_Edge_Timeout (line 10) | class GPIO_Edge_Timeout(Exception):
  function read_from_pwm_with_gpiozero (line 44) | def read_from_pwm_with_gpiozero(gpio=12, range=5000):

FILE: read.py
  function read (line 25) | def read():
  function send (line 36) | def send(valueid, value):
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (73K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 855,
    "preview": "# These are supported funding model platforms\n\ngithub:TakeyukiUEDA # Replace with up to 4 GitHub Sponsors-enabled userna"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 736,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve! Thank you!\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\nPLEA"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 310,
    "preview": "---\nname: Feature request\nabout: Feel free to ask me anything!\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\nPLEASE DON'T FOR"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/help-wanted.md",
    "chars": 315,
    "preview": "---\nname: Help wanted\nabout: Feel free to ask me anything!\ntitle: ''\nlabels: help wanted\nassignees: ''\n\n---\n\nPLEASE DON'"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "chars": 309,
    "preview": "---\nname: Question\nabout: Feel free to ask me anything!\ntitle: ''\nlabels: question\nassignees: ''\n\n---\n\nPLEASE DON'T FORG"
  },
  {
    "path": ".gitignore",
    "chars": 68,
    "preview": "*.pyc\n*.bak\nsend2monitor.py\nhandlers\npypi/dist\npypi/mh_z19.egg-info\n"
  },
  {
    "path": "LICENSE",
    "chars": 1074,
    "preview": "MIT License\n\nCopyright (c) 2018 Dr. Takeyuki Ueda\n\nPermission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "README.md",
    "chars": 17842,
    "preview": "# mh-z19\nRead CO<sub>2</sub> concentration from mh-z19 sensor and handle it.\n\n![MH-Z19](https://camo.qiitausercontent.co"
  },
  {
    "path": "__init__.py",
    "chars": 2,
    "preview": " \n"
  },
  {
    "path": "autostart.ini",
    "chars": 11,
    "preview": "cmd=mh_z19\n"
  },
  {
    "path": "autostart.sh",
    "chars": 1205,
    "preview": "#!/bin/bash\n\n################################################################\n# \n# Autostart setting\n# \n# usage: ./autos"
  },
  {
    "path": "config.save.toml",
    "chars": 304,
    "preview": "[[sensors]]\n  handler = \"mh_z19\"\n  [[sensors.values]]\n    name = \"co2\"\n    handlers = [\n#      \"send2monitor\", # with se"
  },
  {
    "path": "config.toml",
    "chars": 304,
    "preview": "[[sensors]]\n  handler = \"mh_z19\"\n  [[sensors.values]]\n    name = \"co2\"\n    handlers = [\n      \"send2monitor\", # with sen"
  },
  {
    "path": "loop.sh",
    "chars": 113,
    "preview": "#!/bin/bash\nSCRIPT_DIR=$(cd $(dirname $0); pwd)\nwhile true; do sudo python ${SCRIPT_DIR}/read.py; sleep 5m; done\n"
  },
  {
    "path": "mh_z19.py",
    "chars": 12298,
    "preview": "# -*- coding: utf-8 -*-\n# original: https://raw.githubusercontent.com/UedaTakeyuki/slider/master/mh_z19.py\n#\n# © Takeyuk"
  },
  {
    "path": "mh_z19.service",
    "chars": 339,
    "preview": "\n[Unit]\nDescription=Get temp, humid, and humiditydeficit data & Post to the monitor\nAfter=rc-local.service\n[Service]\nWor"
  },
  {
    "path": "pypi/LICENSE",
    "chars": 1074,
    "preview": "MIT License\n\nCopyright (c) 2018 Dr. Takeyuki Ueda\n\nPermission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "pypi/README.md",
    "chars": 13613,
    "preview": "# mh-z19\nRead CO2 concentration from mh-z19 sensor\n\n![MH-Z19](https://camo.githubusercontent.com/8456a67ab97a13866d928d3"
  },
  {
    "path": "pypi/mh_z19/__init__.py",
    "chars": 7598,
    "preview": "# -*- coding: utf-8 -*-\n# original: https://raw.githubusercontent.com/UedaTakeyuki/slider/master/mh_z19.py\n#\n# © Takeyuk"
  },
  {
    "path": "pypi/mh_z19/__main__.py",
    "chars": 4425,
    "preview": "# -*- coding: utf-8 -*-\n# original: https://raw.githubusercontent.com/UedaTakeyuki/slider/master/mh_z19.py\n#\n# © Takeyuk"
  },
  {
    "path": "pypi/mh_z19/pwm.py",
    "chars": 3008,
    "preview": "# -*- coding: utf-8 -*-\n#\n# © Takeyuki UEDA 2024 -\nimport time\n\n# import RPi.GPIO as GPIO\nfrom gpiozero import Button\n\n#"
  },
  {
    "path": "pypi/setup.cfg",
    "chars": 40,
    "preview": "[metadata]\ndescription-file = README.md\n"
  },
  {
    "path": "pypi/setup.py",
    "chars": 1090,
    "preview": "from setuptools import setup\nimport platform\n\nif platform.python_version_tuple()[0] == '2':\n  with open(\"README.md\") as "
  },
  {
    "path": "read.ini",
    "chars": 53,
    "preview": "[mode]\n#run_mode=dummy\nrun_mode=real\n\n[valueid]\nco2=\n"
  },
  {
    "path": "read.py",
    "chars": 1254,
    "preview": "# coding:utf-8\n# Copy Right Atelier UEDA  © 2015 -\n#\n# require: 'lol_dht' https://github.com/technion/lol_dht22\n#       "
  },
  {
    "path": "send2monitor.ini",
    "chars": 207,
    "preview": "[valueid]\nco2=vpgqargj\n\n[server]\nurl=https://monitor3.uedasoft.com/postvalue.php\n\n[error_recovery]\nrecover_on=true\n#reco"
  },
  {
    "path": "setid.sh",
    "chars": 208,
    "preview": "#!/bin/bash\n\nfunction usage() {\ncat <<_EOT_\nUsage:\n  $0 [your_view_id]\nDescription:\n  Set [your_view_id]\nOptions:\n_EOT_\n"
  },
  {
    "path": "setup.sh",
    "chars": 575,
    "preview": "sudo apt-get install python3-pip git-core\nsudo pip3 install mh_z19 pondslider incremental_counter error_counter --break-"
  }
]

About this extraction

This page contains the full source code of the UedaTakeyuki/mh-z19 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (67.6 KB), approximately 21.6k tokens, and a symbol index with 41 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!