Skip to content

Instantly share code, notes, and snippets.

@kadamski
Last active March 31, 2024 02:03
Show Gist options
  • Save kadamski/92653913a53baf9dd1a8 to your computer and use it in GitHub Desktop.
Save kadamski/92653913a53baf9dd1a8 to your computer and use it in GitHub Desktop.
SDS011 dust sensor reading
#!/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
Copy link
Author

@all-days: I don't know how this is related to this gist.

@all-days
Copy link

@kadamski:
First of all, I appreciate your reply.

If I use this code, data of json form are stored.
But I would like to get data of csv form.
How should I modify it?
Please let me know.

@kadamski
Copy link
Author

@all-days: I haven't run this script for some years but it doesn't produce JSON for sure. It does not produce structured output at all, just a free form text. The output is produced by "print" statements in proces_version() and proces_data() functions. You can ignore (or remove from the script) output from proces_version() and just focus on the process_data() output. The line is:

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"))

You are interested only in the first argument to the print function, namely:

"PM 2.5: {} μg/m^3 PM 10: {} μg/m^3 CRC={}"

This specifies what will be printed. All the {} are placeholders which will be filled with proper values. You have to keep the number of those placeholders while changing this string (in other words, you have to keep 3 of them). If you want to have CSV, you could simply change this to:
"{},{},{}" so this line would end up beeing:
print("{},{},{}".format(pm25, pm10, "OK" if (checksum==r[2] and r[3]==0xab) else "NOK"))

Is this what you need?

@all-days
Copy link

@kadamski
Thank you.
I'll give it a try.
May I ask you again?

@tyeth
Copy link

tyeth commented Sep 8, 2020

@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 🤦

@RemBrandNL
Copy link

@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
image

@tyeth
Copy link

tyeth commented Jan 2, 2021 via email

@RemBrandNL
Copy link

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:

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

@RemBrandNL
Copy link

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:
image

Cheers,
Remco

image

@kadamski
Copy link
Author

kadamski commented Jan 2, 2021

@remco have you tried.putting a capacitor between the 5v and gnd to smooth out the voltage drop?

@RemBrandNL
Copy link

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

@tyeth
Copy link

tyeth commented Jan 2, 2021

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

@RemBrandNL
Copy link

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

image

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

@alokvermaiitr
Copy link

What needs to change in the code if I would not use Sensor Hat?

@kadamski
Copy link
Author

@alokvermaiitr what you mean by Sensor Hat?

@FaisalAhmed123
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment