Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot Connect to MH_Z19B Sensor - code fix attached #1315

Open
tledwar opened this issue Jun 16, 2023 · 5 comments
Open

Cannot Connect to MH_Z19B Sensor - code fix attached #1315

tledwar opened this issue Jun 16, 2023 · 5 comments

Comments

@tledwar
Copy link

tledwar commented Jun 16, 2023

This is related to https://forum.radicaldiy.com/t/mh-z19b-not-work-with-mycodo-python-but-works-with/1563 which was posted on the forum.

Mycodo Version: 8.15.9
OS: Linux raspberrypi 6.1.21+ #1642 armv6l GNU/Linux
Platform: Pi Zero W

Issue:

Unable to get data from the sensor.

Reproduce Issue:

These items are mentioned in the above forum post and was part of the troubleshooting. I have not tried to not do these changes after I found a code fix.
Reviewed https://forum.radicaldiy.com/t/mh-z19b-wiring-sanity-check/677 to properly wire up the sensor
Disable Bluetooth and uninstal Py libraries
/boot/config.txt:

Disable Bluetooth

dtoverlay=disable-bt
[all]
enable_uart=1

Create an Input for MH_Z19B
image

Testing was done with the following configured serial ports:

  • ttyAMA0
  • ttyS0
  • serial0

Although it is worth noting the following linked serial ports at the OS level:
ls -la /dev/ser*
lrwxrwxrwx 1 root tty 7 Jun 9 10:22 /dev/serial0 → ttyAMA0
lrwxrwxrwx 1 root root 5 Jun 9 10:22 /dev/serial1 → ttyS0

After testing, the proper port to use at least on this platform is /dev/serial0.
image

Once the proper port is configured and the system runs, the following error is generated:

2023-06-16 12:05:05,067 - ERROR - mycodo.inputs.mh_z19b_6a3d18a1 - UART Location: "/dev/serial0".
2023-06-16 12:05:05,177 - DEBUG - mycodo.controllers.controller_input_6a3d18a1 - get_measurement() found
2023-06-16 12:05:05,179 - DEBUG - mycodo - Input controller with ID 6a3d18a1-c77a-421b-8a71-8be78a91bf88 activated.
2023-06-16 12:05:05,185 - DEBUG - mycodo.controllers.controller_input_6a3d18a1 - listener() not found
2023-06-16 12:05:05,209 - INFO - mycodo.controllers.controller_input_6a3d18a1 - Activated in 5605.7 ms
2023-06-16 12:05:06,238 - DEBUG - mycodo.inputs.mh_z19b_6a3d18a1 - No response
2023-06-16 12:05:06,425 - DEBUG - mycodo.controllers.controller_input_6a3d18a1 - Adding measurements to InfluxDB with ID 6a3d18a1-c77a-421b-8a71-8be78a91bf88: {}
2023-06-16 12:05:07,993 - INFO - mycodo.controllers.controller_input_69d8bf11 - Activated in 1929.7 ms
2023-06-16 12:05:07,994 - DEBUG - mycodo - Input controller with ID 69d8bf11-ef3d-405e-b7be-5696fae74a07 activated.

No response error is coming from this section of the code from inputs/mh_z19b.py:

try:
            self.ser.flushInput()
            self.ser.write(bytearray([0xff, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79]))
            time.sleep(.01)
            resp = self.ser.read(9)

            if not resp:
                self.logger.debug("No response")
            elif len(resp) < 4:
                self.logger.debug("Too few values in response '{}'".format(resp))
            elif resp[0] != 0xff or resp[1] != 0x86:
                self.logger.error("Bad checksum")
            elif len(resp) >= 4:
                high = resp[2]
                low = resp[3]
                co2 = (high * 256) + low
                self.value_set(0, co2)
        except:
            self.logger.exception("get_measurement()")
        finally:
            self.measuring = False

Expected behavior

The system shall be able to read values from the sensor.

Potential Code Fix

After some comparison to https://github.com/UedaTakeyuki/mh-z19 (please note this library is mentioned several times in the Mycodo mh_z19b code), I discovered one key difference and it was the stop and start of a getter service. Whenever a connection was made to the serial port for this sensor, the third party library had a control feature to start/stop the service. The service was related to below:

$ systemctl --type=service --state=running | grep getty
getty@tty1.service loaded active running Getty on tty1
serial-getty@serial0.service loaded active running Serial Getty on serial0
serial-getty@ttyAMA0.service loaded active running Serial Getty on ttyAMA0

I added these two methods to mh_z19b.py:

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

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

Then I added a call to these methods before and after the connection to the serial port in def initialize(self).

    def initialize(self):
        import serial

        self.logger.error('UART Location: "{dev}".'.format(
            dev=self.input_dev.uart_location))

        stop_getty()

        if is_device(self.input_dev.uart_location):
            try:
                self.ser = serial.Serial(
                    port=self.input_dev.uart_location,
                    baudrate=self.input_dev.baud_rate,
                    timeout=1,
                    writeTimeout=5)
            except serial.SerialException:
                self.logger.exception('Opening serial')
        else:
            self.logger.error('Could not open "{dev}". Check the device location is correct.'.format(
                dev=self.input_dev.uart_location))

        if self.abc_enable:
            self.abcon()
        else:
            self.abcoff()

        if self.measure_range:
            self.set_measure_range(self.measure_range)

        start_getty();

        time.sleep(0.1)

For the get_measurement(self) method, I had to do a little more work.
First it was necessary to add a new connection to the device. This method was reusing the old connection and it did not seem to work.
Second, I added a retry around the write command. This was used in the third party code.
And finally, the stop/start Getty commands.

    def get_measurement(self):
        """Gets the MH-Z19's CO2 concentration in ppmv."""
        import serial

        if not self.ser:
            self.logger.error("Error 101: Device not set up. See https://kizniche.github.io/Mycodo/Error-Codes#error-101 for more info.")
            return

        self.return_dict = copy.deepcopy(measurements_dict)

        while self.calibrating:
            time.sleep(0.1)
        self.measuring = True

        stop_getty()

        if is_device(self.input_dev.uart_location):
            try:
                self.ser = serial.Serial(
                    port=self.input_dev.uart_location,
                    baudrate=self.input_dev.baud_rate,
                    timeout=1,
                    writeTimeout=5)
            except serial.SerialException:
                self.logger.exception('Error opening serial port')
        else:
            self.logger.error('Could not open "{dev}". Check the device location is correct.'.format(
                dev=self.input_dev.uart_location))

        try:
            self.logger.debug(self.ser.name)

            for retry in range(5):
                result = self.ser.write(bytearray([0xff, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79]))
                resp = self.ser.read(9)

            self.logger.debug(len(resp))

            if not resp:
                self.logger.debug("No response")
            elif len(resp) < 4:
                self.logger.debug("Too few values in response '{}'".format(resp))
            elif resp[0] != 0xff or resp[1] != 0x86:
                self.logger.error("Bad checksum")
            elif len(resp) >= 4:
                high = resp[2]
                low = resp[3]
                co2 = (high * 256) + low
                self.value_set(0, co2)
        except:
            self.logger.exception(" Error getting data in get_measurement()")
        finally:
            self.measuring = False

        start_getty()

        return self.return_dict

Based on these code changes, I am actively receiving CO2 values using the input/output/functions defined at https://kylegabriel.com/projects/2021/09/mushroom-cultivation-automation.html

Complete file used during testing:
mh_z19b.py.txt

@kizniche
Copy link
Owner

This issue has been mentioned on Radical DIY Forum. There might be relevant details there:

https://forum.radicaldiy.com/t/mh-z19b-not-work-with-mycodo-python-but-works-with/1563/3

@kizniche
Copy link
Owner

I don't think getty is necessary. How are you wiring your sensor?

@tledwar
Copy link
Author

tledwar commented Jun 18, 2023 via email

@tledwar
Copy link
Author

tledwar commented Jun 18, 2023 via email

@tledwar
Copy link
Author

tledwar commented Jun 19, 2023

Connection is like this:

765d35a77294e0bfb98bd98d5a618c4f29b6e377

Sensor is correctly read with https://github.com/UedaTakeyuki/mh-z19 so it should be wired up correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants