Difference between revisions of "Raspberry Pi Environmental Monitoring"
Jump to navigation
Jump to search
Michael.mast (talk | contribs) |
Michael.mast (talk | contribs) |
||
Line 42: | Line 42: | ||
</pre> | </pre> | ||
− | Now we edit the script to work with mysql<ref>https://www.w3schools.com/python/python_mysql_insert.asp</ref> | + | Now we edit the script to work with mysql<ref>https://www.w3schools.com/python/python_mysql_insert.asp</ref><br> |
+ | Please note that of this writing I the script searches the entire database for 0 values and deletes them. I need to go back and update to simply not write 0 values in the first place. | ||
+ | <pre> | ||
+ | #!/usr/bin/python -u | ||
+ | # coding=utf-8 | ||
+ | # "DATASHEET": http://cl.ly/ekot | ||
+ | # https://gist.github.com/kadamski/92653913a53baf9dd1a8 | ||
+ | from __future__ import print_function | ||
+ | import serial, struct, sys, time, json, subprocess, mysql.connector | ||
+ | |||
+ | mydb = mysql.connector.connect( | ||
+ | host="localhost", | ||
+ | user="air", | ||
+ | database="air" | ||
+ | ) | ||
+ | |||
+ | mycursor = mydb.cursor() | ||
+ | |||
+ | DEBUG = 0 | ||
+ | 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 | ||
+ | PERIOD_CONTINUOUS = 0 | ||
+ | |||
+ | ser = serial.Serial() | ||
+ | ser.port = "/dev/ttyUSB0" | ||
+ | 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 | ||
+ | return [pm25, pm10] | ||
+ | #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() | ||
+ | values = [] | ||
+ | if d[1] == "\xc0": | ||
+ | values = process_data(d) | ||
+ | return values | ||
+ | |||
+ | def cmd_set_sleep(sleep): | ||
+ | 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_firmware_ver() | ||
+ | cmd_set_working_period(PERIOD_CONTINUOUS) | ||
+ | cmd_set_mode(MODE_QUERY); | ||
+ | while True: | ||
+ | cmd_set_sleep(0) | ||
+ | for t in range(15): | ||
+ | values = cmd_query_data(); | ||
+ | if values is not None and len(values) == 2: | ||
+ | print("PM2.5: ", values[0], ", PM10: ", values[1]) | ||
+ | pm25=values[0] | ||
+ | pm10=values[1] | ||
+ | mycursor.execute('INSERT INTO aqi (pm25, pm10, date) VALUES (%s, %s, %s)' % (pm25,pm10,'now()')) | ||
+ | mydb.commit() | ||
+ | mycursor.execute("delete from aqi where pm10 = 0 and pm25 = 0") | ||
+ | time.sleep(2) | ||
+ | |||
+ | |||
+ | print("Going to sleep for 1 min...") | ||
+ | cmd_set_sleep(1) | ||
+ | time.sleep(60) | ||
+ | mycursor.execute("delete from aqi where date < (now() - interval 1 year)") | ||
+ | |||
+ | |||
+ | </pre> |
Revision as of 09:47, 24 December 2019
Purpose
To monitor air temperature, humidity, and air quality.
Air Quality
A friend sent me a Nova PM Sensor. I do not know python yet, but looking over the script that is readily available[1][2] I should be able to modify this to work for my needs.
mkdir -p /home/pi/build/air && cd /home/pi/build/air sudo apt install git-core python-serial python-enum mariadb-server python-mysql.connector wget -O aqi.py https://raw.githubusercontent.com/zefanja/aqi/master/python/aqi.py echo [] > aqi.json sed -i 's|/var/www/html/aqi.json|/home/pi/build/air/aqi.json|' aqi.py
At this point we can run the script, see output on the screen, and see the history written to the json file.
./aqi.py Y: 18, M: 1, D: 18, ID: 0xbc2d, CRC=OK PM2.5: 0.0 , PM10: 0.0 PM2.5: 0.7 , PM10: 0.7 PM2.5: 0.6 , PM10: 0.6 PM2.5: 0.7 , PM10: 0.7
However, I want the history written to a database. Configure mariadb anyway you want, if you have credentials keep them on hand.
create database air; create user air; grant all on air.* to 'air'; use air; create table aqi (pm25 FLOAT, pm10 FLOAT, date DATETIME); select * from aqi; Empty set (0.00 sec) insert into aqi values (2.1, 2.3, now()); Query OK, 1 row affected (0.02 sec) select * from aqi; +------+------+---------------------+ | pm25 | pm10 | date | +------+------+---------------------+ | 2.1 | 2.3 | 2019-12-20 13:32:18 | +------+------+---------------------+ 1 row in set (0.00 sec)
Now we edit the script to work with mysql[3]
Please note that of this writing I the script searches the entire database for 0 values and deletes them. I need to go back and update to simply not write 0 values in the first place.
#!/usr/bin/python -u # coding=utf-8 # "DATASHEET": http://cl.ly/ekot # https://gist.github.com/kadamski/92653913a53baf9dd1a8 from __future__ import print_function import serial, struct, sys, time, json, subprocess, mysql.connector mydb = mysql.connector.connect( host="localhost", user="air", database="air" ) mycursor = mydb.cursor() DEBUG = 0 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 PERIOD_CONTINUOUS = 0 ser = serial.Serial() ser.port = "/dev/ttyUSB0" 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 return [pm25, pm10] #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() values = [] if d[1] == "\xc0": values = process_data(d) return values def cmd_set_sleep(sleep): 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_firmware_ver() cmd_set_working_period(PERIOD_CONTINUOUS) cmd_set_mode(MODE_QUERY); while True: cmd_set_sleep(0) for t in range(15): values = cmd_query_data(); if values is not None and len(values) == 2: print("PM2.5: ", values[0], ", PM10: ", values[1]) pm25=values[0] pm10=values[1] mycursor.execute('INSERT INTO aqi (pm25, pm10, date) VALUES (%s, %s, %s)' % (pm25,pm10,'now()')) mydb.commit() mycursor.execute("delete from aqi where pm10 = 0 and pm25 = 0") time.sleep(2) print("Going to sleep for 1 min...") cmd_set_sleep(1) time.sleep(60) mycursor.execute("delete from aqi where date < (now() - interval 1 year)")