Difference between revisions of "Raspberry Pi Environmental Monitoring"

From Michael's Information Zone
Jump to navigation Jump to search
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)")