home-assistant / core

:house_with_garden: Open source home automation that puts local control and privacy first.
https://www.home-assistant.io
Apache License 2.0
73.54k stars 30.72k forks source link

Particulate Matter Sensor Component - values altered by restarting Home Assistant #14164

Closed Bit-River closed 5 years ago

Bit-River commented 6 years ago

Home Assistant release with the issue: 0.68, noticed since 0.66 when sensor first connected

Last working Home Assistant release (if known): Not known.

Operating environment (Hass.io/Docker/Windows/etc.): Hass.io, and Hassbian

Component/platform: Particulate Matter Sensor - https://www.home-assistant.io/components/sensor.serial_pm/

Description of problem:

Values from this sensor jump into a different range after a restart, and also after a reboot. Two+ restarts are then required to bring the sensor back into expected ranges.

Effect of issue can be seen in the graph below when Home Assistant is restarted at 13:10, 14:15, and again 14:55 when the values eventually return to the range the sensor reports usually. The air is of good quality where the sensor is placed, the context is the Irish countryside.

particulate matter sensor values

Ridiculous values have been recorded (smog city levels), and a reboot has fixed them:

particulate matter sensor ridiculous values

Device, and configuration entry as below. Not tested with another device at this time.

- platform: serial_pm
    serial_device: /dev/ttyUSB0
    name: Nova
    brand: novafitness,sds021

Additional information:

I've mentioned this in the forum:

https://community.home-assistant.io/t/particulate-matter-sensor-nova-sds021-wildly-varying-values-anyone-else/49958

Bit-River commented 6 years ago

I have tried this component with the Nova SDS011 and the same issue occurs.

As a test / work-around I made a small api in python. This code only recreated the error if automatically restarted at reboot time, when I delayed the start-up of the api by 30 seconds the issue stopped.

I recommend a delay be introduced before this component is loaded, and this may alleviate the issue. In the interim, the api below both work well and can be used in HA with a simple rest sensor.

Api code used for SDS021 testing:


#!/usr/bin/python

# Forked from: https://github.com/MatejKovacic/SDS011-SDS018-and-SDS021-dust-sensors-reader
# Credits: Matjaz Rihtar, Matej Kovacic

from flask import Flask, jsonify
import serial, struct, time

# Set default USB port
USBPORT = "/dev/ttyUSB0"

class SDS021Reader:

    def __init__(self, inport):
        self.serial = serial.Serial(port=inport, baudrate=9600)

    def readValue( self ):
        step = 0
        while True:
            while self.serial.inWaiting() != 0:
                v = ord(self.serial.read())

                if step == 0:
                    if v == 170:
                        step = 1

                elif step == 1:
                    if v == 192:
                        values = [0,0,0,0,0,0,0]
                        step = 2
                    else:
                        step = 0

                elif step > 8:
                    step = 0
                    # Compute PM2.5 and PM10 values
                    pm25 = (values[1]*256 + values[0])/10.0
                    pm10 = (values[3]*256 + values[2])/10.0
                    return [pm25,pm10]

                elif step >= 2:
                    values[step - 2] = v
                    step = step + 1

app = Flask(__name__)

reader = SDS021Reader(USBPORT)

@app.route('/api/airquality', methods=['GET'])
def get_air_quality():
    pm_25_value , pm_10_value = reader.readValue()
    return jsonify({'PM25': str(pm_25_value), 'PM10': str(pm_10_value)})

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=False)

And for the SDS011:

#!/usr/bin/python
# coding=utf-8
# "DATASHEET": http://cl.ly/ekot
# https://gist.github.com/kadamski/92653913a53baf9dd1a8

from flask import Flask, jsonify
import serial, struct, time

CMD_MODE = 2
CMD_QUERY_DATA = 4
CMD_SLEEP = 6
MODE_ACTIVE = 0
MODE_QUERY = 1

ser = serial.Serial()
ser.port = "/dev/ttyUSB0"
ser.baudrate = 9600

ser.open()
ser.flushInput()

byte, data = 0, ""

def construct_command(cmd, data=[]):
    assert len(data) <= 12
    data += [0,]*(12-len(data))
    checksum = (sum(data)+cmd-2)%256
    ret = "\xaa\xb4" + chr(cmd)
    ret += ''.join(chr(x) for x in data)
    ret += "\xff\xff" + chr(checksum) + "\xab"
    return ret

def process_data(d):
    r = struct.unpack('<HHxxBB', d[2:])
    pm25 = r[0]/10.0
    pm10 = r[1]/10.0
    checksum = sum(ord(v) for v in d[2:8])%256
    return [pm25, pm10]

def read_response():
    byte = 0
    while byte != "\xaa":
        byte = ser.read(size=1)

    d = ser.read(size=9)
    return byte + d

def cmd_set_mode(mode=MODE_QUERY):
    ser.write(construct_command(CMD_MODE, [0x1, mode]))
    read_response()

def cmd_query_data():
    ser.write(construct_command(CMD_QUERY_DATA))
    d = read_response()
    values = []
    if d[1] == "\xc0":
        values = process_data(d)
    return values

def cmd_set_sleep(sleep=1):
    mode = 0 if sleep else 1
    ser.write(construct_command(CMD_SLEEP, [0x1, mode]))
    read_response()

app = Flask(__name__)

@app.route('/api/airquality', methods=['GET'])
def get_air_quality():
    cmd_set_sleep(0)
    cmd_set_mode(1);

    quick_average_pm_two_five=[]
    quick_average_pm_ten=[]

    for t in range(5):
        values = cmd_query_data();
        if values is not None:
            quick_average_pm_two_five.append(values[0])
            quick_average_pm_ten.append(values[1])
            time.sleep(1)

    pm_25_value=(sum(quick_average_pm_two_five) / float(len(quick_average_pm_two_five)))
    pm_10_value=(sum(quick_average_pm_ten) / float(len(quick_average_pm_ten)))

    cmd_set_mode(0);
    cmd_set_sleep()

    quick_average_pm_two_five=[]
    quick_average_pm_ten=[]
    return jsonify({'PM25': str(pm_25_value), 'PM10': str(pm_10_value)})

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=False)
sherazlodhi commented 6 years ago

How do we know that the value which is shown at startup is wrong and value after long time is correct. What I think is that if sds011 is running for long time may be it starts giving the lowest value? Whenever the sensor runs full night its values are dropped to around 5-6 and if i restart in morning they become 23- 30 and stay there until evening.

How do we know which values are actually correct?

balloobbot commented 6 years ago

There hasn't been any activity on this issue recently. Due to the high number of incoming GitHub notifications, we have to clean some of the old issues, as many of them have already been resolved with the latest updates.

Please make sure to update to the latest Home Assistant version and check if that solves the issue. Let us know if that works for you by adding a comment :+1:

balloobbot commented 5 years ago

This issue will be auto-closed because there hasn't been any activity for a few months. Feel free to open a new one if you still experience this problem 👍

calculin2001 commented 5 years ago

I have the same problem !! no solution ??

jfedor2 commented 3 years ago

I have encountered this problem as well (with the PMS5003 sensor) and I believe it is caused by an issue within the pmsensor library. It was actually fixed years ago here:

https://github.com/open-homeautomation/pmsensor/commit/d9725edaf2f018b509375a5533b92c0b712b63cd

Unfortunately there has been no release of the library after that commit so Home Assistant is still using old code without the fix.