-
-
Save kadamski/92653913a53baf9dd1a8 to your computer and use it in GitHub Desktop.
#!/usr/bin/python | |
# coding=utf-8 | |
# "DATASHEET": http://cl.ly/ekot | |
from __future__ import print_function | |
import serial, struct, sys, time | |
DEBUG = 1 | |
CMD_MODE = 2 | |
CMD_QUERY_DATA = 4 | |
CMD_DEVICE_ID = 5 | |
CMD_SLEEP = 6 | |
CMD_FIRMWARE = 7 | |
CMD_WORKING_PERIOD = 8 | |
MODE_ACTIVE = 0 | |
MODE_QUERY = 1 | |
ser = serial.Serial() | |
ser.port = sys.argv[1] | |
ser.baudrate = 9600 | |
ser.open() | |
ser.flushInput() | |
byte, data = 0, "" | |
def dump(d, prefix=''): | |
print(prefix + ' '.join(x.encode('hex') for x in d)) | |
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" | |
if DEBUG: | |
dump(ret, '> ') | |
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 | |
print("PM 2.5: {} μg/m^3 PM 10: {} μg/m^3 CRC={}".format(pm25, pm10, "OK" if (checksum==r[2] and r[3]==0xab) else "NOK")) | |
def process_version(d): | |
r = struct.unpack('<BBBHBB', d[3:]) | |
checksum = sum(ord(v) for v in d[2:8])%256 | |
print("Y: {}, M: {}, D: {}, ID: {}, CRC={}".format(r[0], r[1], r[2], hex(r[3]), "OK" if (checksum==r[4] and r[5]==0xab) else "NOK")) | |
def read_response(): | |
byte = 0 | |
while byte != "\xaa": | |
byte = ser.read(size=1) | |
d = ser.read(size=9) | |
if DEBUG: | |
dump(d, '< ') | |
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() | |
if d[1] == "\xc0": | |
process_data(d) | |
def cmd_set_sleep(sleep=1): | |
mode = 0 if sleep else 1 | |
ser.write(construct_command(CMD_SLEEP, [0x1, mode])) | |
read_response() | |
def cmd_set_working_period(period): | |
ser.write(construct_command(CMD_WORKING_PERIOD, [0x1, period])) | |
read_response() | |
def cmd_firmware_ver(): | |
ser.write(construct_command(CMD_FIRMWARE)) | |
d = read_response() | |
process_version(d) | |
def cmd_set_id(id): | |
id_h = (id>>8) % 256 | |
id_l = id % 256 | |
ser.write(construct_command(CMD_DEVICE_ID, [0]*10+[id_l, id_h])) | |
read_response() | |
if __name__ == "__main__": | |
cmd_set_sleep(0) | |
cmd_set_mode(1); | |
cmd_firmware_ver() | |
time.sleep(3) | |
cmd_query_data(); | |
cmd_set_mode(0); | |
cmd_set_sleep() |
@kadamski Thanks for this, it enabled me to make an air quality sensor for my own curiosity, and then again for a friend (which caused no end of hilarity as I realised I had to use python3).
I kind of failed to grasp the Python3 version of the same thing, ended up with this https://github.com/tyeth/john_air/blob/main/aqi_py3_win.py#L87 - not happy as was very close but just didn't quite grasp the encoding bit hence the 192 value for a byte 🤦
@kadamski, great stuff!
To keep a small form factor, I have connected an SDS011 directly to a Raspberry PI Zero W. This way you don't need a clunky USB UART things and ditto USB > MicroUSB converter. It all works like a charm, but once you wake it up after putting it to sleep, it reboots the RPI. Does anyone else have any experience with this? It's probably fixed with giving the sensor its own 5V feed I guess, but open to suggestion!
Remco
Thanks for the supports, after more hours of Googling and much trial&error I found out I am not the only one who ran into this issue. For who is interested, this topic summarizes it best: https://www.raspberrypi.org/forums/viewtopic.php?t=219026
Other posts on this topic:
- https://www.raspberrypi.org/forums/viewtopic.php?t=274549
- https://www.raspberrypi.org/forums/viewtopic.php?t=237119
The only option to make it somewhat more compact is to replace the USB > UART board with a angled micro USB so it doesn't stick out that much.
One last hope I have is to pick up power from the USB port to see if that doesn't reboot it, trying that later today. Setting the SDS011 to sleep and waking it up again does work via that USB dongle, so let's see what happens if you draw power from there instead of the GPIO pins directly ;-)
Cheers,
Remco
OK, not sure if it's the 'correct' way to go, but I did get it working withouth that stupid dongle. Wiring needs some TLC, but it's working now. I can put the sensor to sleep and wake it up again without my RPI rebooting. Complete lenghty lengthy write-up with more pics on https://tinker.rembrand.nl/2021/01/02/using-sds011-via-uart/
I combined it with a DHT22 sensor for now and it's happily recording every 5 minutes:
Cheers,
Remco
@remco have you tried.putting a capacitor between the 5v and gnd to smooth out the voltage drop?
Wouldn't even know which value to pick; my knowledge of electronics is limited to none ;-) I did read on several posts/boards that others did try that, including circuits with diodes and all, but to no avail. Best I can do is logic and this seems to work as expected. Until I find some other solution, I'll tidy this up and move it to an enclosure outside to do further testing with the scripts I am working on. I want to collect the data to a database, do some graphs, send it properly formated to luftdaten. All knowledge gaps so I have quite some challenges. Your scripts helped me to get started with the SDS011 though, so many thanks for that :-)
Remco
Wicked, also check the /var/log/syslog file or dmesg, i remember low voltage warnings in one of them when I was doing too much with my weak 5v supply
Wicked, also check the /var/log/syslog file or dmesg, i remember low voltage warnings in one of them when I was doing too much with my weak 5v supply
It's been running in the backyard since yesterday evening, 0 hits on 'voltage' issues it seems. Ever since then it has measured 10 times with 1 second intervals, put the SDS011 to sleep for about 5 minutes, wake up and measure again. So I am still optimistic this works ;-)
Pics of the enclosure you can find here. Currently using a standard weatherproof junction box which was always my entention for the outdoor enclosure.
Remco
What needs to change in the code if I would not use Sensor Hat?
@alokvermaiitr what you mean by Sensor Hat?
For those still having the issue where it maxes out, I wrote some new code which seems to work past the 20,000 mark https://github.com/FaisalAhmed123/Nova-PM2.5-Sensor-Reading-Interpreter/blob/main/main.py
@kadamski
Thank you.
I'll give it a try.
May I ask you again?