-
-
Save binh-bk/b4f2f7c9b418918bbf4b1b8bc48b6e5f to your computer and use it in GitHub Desktop.
SDS011 dust sensor reading
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python2 # only works with python2.7 | |
# coding=utf-8 | |
''' | |
forked from A. Adamski's gist: https://gist.github.com/kadamski/92653913a53baf9dd1a8 | |
"DATASHEET": http://cl.ly/ekot | |
Binh Nguyen, November 2018 | |
adapted from kadamski script with additional features: | |
1. logging | |
2. calculate AQI (US) | |
3. publish to MQTT broker. | |
4. Schedule interval run mode | |
- required pyserial, install by: sudo apt install python-serial | |
or pip install pyserial | |
''' | |
from __future__ import print_function | |
import serial, struct, sys, time, json | |
import paho.mqtt.publish as publish | |
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 = "/dev/ttyUSB0" | |
ser.baudrate = 9600 | |
ser.open() | |
ser.flushInput() | |
byte, data = 0, "" | |
global lastTime | |
lastTime = 0 | |
logFile = '/home/pi/Desktop/sambaShare/weather/sds01.csv' | |
AQIs = {'Good':{'aqi':[0,50], 'PM2.5':[0,12.0], 'PM10':[0,54]}, | |
'Moderate':{'aqi':[51,100], 'PM2.5':[12.1,35.4], 'PM10':[54,154]}, | |
'Unhealthy for Sensitive Groups':{'aqi':[101,150], 'PM2.5':[35.5,55.4], 'PM10':[155,254]}, | |
'Unhealthy':{'aqi':[151,200], 'PM2.5':[55.5,150.4], 'PM10':[255,354]}, | |
'Very Unhealthy':{'aqi':[201,300], 'PM2.5':[150.5,250.4], 'PM10':[355,425]}, | |
'Hazardous':{'aqi':[301,400], 'PM2.5':[250.4,350.4], 'PM10':[425,504]}, | |
'Very Harzadous':{'aqi':[401,500], 'PM2.5':[350.5,500.4], 'PM10':[504,604]} | |
} | |
def findRange(pollulant, conc): | |
'''return key of the category, PM2.5, 30 ug/m3''' | |
for key,value in AQIs.items(): | |
range_ = value[pollulant] | |
if conc >= range_[0] and conc <= range_[1]: | |
return key | |
def calAQI(pollulant,conc): | |
'''return AQI based US EPA for PM2.5 and PM10, p16, 2013 Technical Asst.''' | |
key = findRange(pollulant, conc) | |
I_Lo = AQIs[key]['aqi'][0] | |
I_Hi = AQIs[key]['aqi'][1] | |
BP_Lo = AQIs[key][pollulant][0] | |
BP_Hi = AQIs[key][pollulant][1] | |
AQI_X = (I_Hi-I_Lo)*(conc-BP_Lo)/(BP_Hi-BP_Lo) + I_Lo | |
return round(AQI_X,1), key | |
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() | |
def run(): | |
''' turn on, stablize for 25 seconds before reading and then sleep''' | |
cmd_set_sleep(0) | |
cmd_set_mode(1) | |
cmd_firmware_ver() | |
time.sleep(25) | |
cmd_query_data() | |
cmd_set_mode(0) | |
time.sleep(5) | |
cmd_set_sleep() | |
return None | |
def push_MQTT(mesg): | |
'''push message to a local broker''' | |
topic = 'sensors/nova_sds011' | |
host = '192.168.1.xx | |
auth = {'username':'janeoe', 'password':'password'} | |
try: | |
publish.single(topic, mesg, hostname=host, auth=auth) | |
except socket.error: | |
pass | |
return None | |
def schedule(snapTime=600): | |
global lastTime | |
if time.time()-lastTime >=snapTime: | |
ts = time.strftime('%x %X', time.localtime()) | |
print("Run script at: {}, lastRun was {}".format(ts, lastTime)) | |
lastTime=time.time() | |
run() | |
aqi_pm25, status_pm25 = calAQI('PM2.5', pm25) | |
aqi_pm10, status_pm10 = calAQI('PM10', pm10) | |
if aqi_pm25 > aqi_pm10: | |
air_quality = status_pm25 | |
air_aqi = aqi_pm25 | |
else: | |
air_quality = status_pm10 | |
air_aqi = aqi_pm10 | |
mesg = {'sensor':'Nova_SDS011','timestamp':ts, 'pm25':pm25, 'pm10':pm10,\ | |
'aqi25':aqi_pm25,'aqi10':aqi_pm10,'level':air_quality} | |
mesg = json.dumps(mesg,'UTF-8') | |
push_MQTT(mesg) | |
print('Pushed MSG {}'.format(mesg)) | |
payload = ','.join([ts, str(pm25), str(pm10), str(aqi_pm25), str(aqi_pm10), air_quality])+ '\n' | |
with open(logFile, 'a+') as f: | |
f.write(payload) | |
print('Save data: {}'.format(payload)) | |
print('Log file: {}'.format(logFile)) | |
return None | |
if __name__ == "__main__": | |
while True: | |
'''keep loop running and read sensor every 5 minutes | |
schedule(300) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment