Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import argparse
- import os
- import signal
- from datetime import datetime
- from time import time, sleep
- import serial
- import serial.serialutil
- from influxdb_client import InfluxDBClient
- _EXIT = False
- def handle_signal(signum, frame):
- global _EXIT
- print("\nreceived %d signal, prepare to exit" % signum)
- _EXIT = True
- def parse_args():
- parser = argparse.ArgumentParser()
- parser.add_argument(dest='file', nargs='?', type=str, default='/dev/ttyUSB0',
- help="tty device, select your serial port (default: /dev/ttyUSB0)")
- parser.add_argument('-b', '--baudrate', dest='baudrate', metavar='', type=int, default=9600,
- help="set serialport baudrate (default: 9600)")
- parser.add_argument('-d', '--delay', dest='delay', metavar='', type=float, default=0.0,
- help="delay between readings (default: fastest possible)")
- parser.add_argument('-c', '--count', dest='count', metavar='', type=int, default=0,
- help="how many readings to do (default: infinity)")
- return parser.parse_args()
- class PlantowerRecord:
- """
- object contains all data from sensor,
- """
- def __init__(self, data_dict: dict):
- self.timestamp = datetime.now()
- self.__dict__.update(data_dict)
- def __getitem__(self, item):
- return self.__dict__[item]
- def __str__(self):
- string = (
- ">>> {timestamp:s} \n"
- "(ug/m³) normal env %normal %env norm\n"
- "PM 1.0: {pm1_0:>6d} {pm1_0_env:>6d} ---.-% ---.-% None\n"
- "PM 2.5: {pm2_5:>6d} {pm2_5_env:>6d} {pm2_5_normn:6.1f}% {pm2_5_norme:6.1f}% 25\n"
- "PM 10.0: {pm10_0:>6d} {pm10_0_env:>6d} {pm10_0_normn:6.1f}% {pm10_0_norme:6.1f}% 50\n\n"
- "particles count (um / 0.1 L)\n"
- " 0.3 um: {um0_3:>8d}\n"
- " 0.5 um: {um0_5:>8d}\n"
- " 1.0 um: {um1_0:>8d}\n"
- " 2.5 um: {um2_5:>8d}\n"
- " 5.0 um: {um5_0:>8d}\n"
- "10.0 um: {um10_0:>8d}\n\n"
- "checksum: {check:s}\n"
- )
- return string.format(
- timestamp=str(self.timestamp),
- pm1_0=self.data1, pm2_5=self.data2, pm10_0=self.data3, pm1_0_env=self.data4, pm2_5_env=self.data5,
- pm10_0_env=self.data6, um0_3=self.data7, um0_5=self.data8, um1_0=self.data9, um2_5=self.data10,
- um5_0=self.data11, um10_0=self.data12,
- pm2_5_norme=(self.data5 / 25) * 100, pm10_0_norme=(self.data6 / 50) * 100,
- pm2_5_normn=(self.data2 / 25) * 100, pm10_0_normn=(self.data3 / 50) * 100,
- check='Match' if self.checksum_match else "NOT MATCH (data error occurred)"
- )
- def verify_offset(fd, data):
- """take care of data position"""
- if not (data[0], data[1]) == (0x42, 0x4d): # check if first two bytes matches to device protocol specification
- print('Data not in proper position, trying to set proper offset...')
- set_fixed_position(fd)
- data = fd.read(32)
- if not (data[0], data[1]) == (0x42, 0x4d):
- print(
- 'Synchronisation failed, check if proper baudrate are set (default: 9600, see --help)')
- exit(1)
- def set_fixed_position(fd):
- """Simple method to set file pointer on proper position (where 0x424d becoming first two values)"""
- data = fd.read(32)
- if (data[0], data[1]) == (0x42, 0x4d):
- return
- offset = 0
- for i in range(31):
- if (data[i], data[i + 1]) == (0x42, 0x4d):
- offset = i
- fd.read(offset)
- def read_data(fd) -> PlantowerRecord:
- """Simple method that reads data from sensor and return prepared PlantowerRecord object"""
- data = fd.read(32)
- verify_offset(fd, data)
- calculated_checksum = 0
- for i in range(29):
- calculated_checksum += data[i] # calculated checksum on given data
- checksum = (data[30] << 8) + data[31] # checksum provided by device
- data_dict = {
- # frame data size, not useful for user
- 'frame': (data[2] << 8) + data[3],
- # pm1.0 unit ug/m3 (CF=1, standard particle) (?)
- 'data1': (data[4] << 8) + data[5],
- # pm2.5 unit ug/m3 (CF=1, standard particle) (?)
- 'data2': (data[6] << 8) + data[7],
- # pm10 unit ug/m3 (CF=1, standard particle) (?)
- 'data3': (data[8] << 8) + data[9],
- # pm1.0 unit ug/m3 (under atmospheric environment) (?)
- 'data4': (data[10] << 8) + data[11],
- # pm2.5 unit ug/m3 (under atmospheric environment) (?)
- 'data5': (data[12] << 8) + data[13],
- # pm10 unit ug/m3 (under atmospheric environment) (?)
- 'data6': (data[14] << 8) + data[15],
- # number of particles with diameter beyond 0.3 um in 0.1L of air.
- 'data7': (data[16] << 8) + data[17],
- # number of particles with diameter beyond 0.5 um in 0.1L of air.
- 'data8': (data[18] << 8) + data[19],
- # number of particles with diameter beyond 1.0 um in 0.1L of air.
- 'data9': (data[20] << 8) + data[21],
- # number of particles with diameter beyond 2.5 um in 0.1L of air.
- 'data10': (data[22] << 8) + data[23],
- # number of particles with diameter beyond 5.0 um in 0.1L of air.
- 'data11': (data[24] << 8) + data[25],
- # the number of particles with diameter beyond 10.0 um in 0.1L of air.
- 'data12': (data[26] << 8) + data[27],
- 'data13': (data[28] << 8) + data[29], # reserved, not used
- 'checksum_match': True if calculated_checksum == checksum else False
- }
- return PlantowerRecord(data_dict)
- def save(client, record):
- if not record.checksum_match:
- print("checksum missmatch!")
- return
- now = datetime.now()
- t = int(now.timestamp() * 1000 * 1000 * 1000)
- #print(now.isoformat())
- data = [
- {
- "measurement": "plantower",
- "time": t,
- "tags": {
- "data_type": "CF1",
- "val_type": "mass",
- },
- "fields": {
- "1.0": record.data1,
- "2.5": record.data2,
- "10": record.data3,
- }
- },
- {
- "measurement": "plantower",
- "time": t,
- "tags": {
- "data_type": "atmo",
- "val_type": "mass",
- },
- "fields": {
- "1.0": record.data4,
- "2.5": record.data5,
- "10": record.data6,
- }
- },
- {
- "measurement": "plantower",
- "time": t,
- "tags": {
- "val_type": "count",
- },
- "fields": {
- "0.3": record.data7,
- "0.5": record.data8,
- "1.0": record.data9,
- "2.5": record.data10,
- "5.0": record.data11,
- "10": record.data12,
- }
- },
- ]
- client.write("grafana", "pi4black", data)
- def print_loop(fd, args):
- client = InfluxDBClient(
- url="http://pi4black:8086",
- token="8WpxAr74led6pgP1i_lu4PyhVYUfuxPEP-3Zcth3GL4AehXFTHqomrFaRUYWb7u5DUzhzT1JB0ep9HX9KGLxQw==",
- org="pi4black",
- )
- write_api = client.write_api()
- check_time = time() + args.delay
- reported_ok = False
- while not _EXIT:
- record = read_data(fd)
- if time() >= check_time:
- check_time = time() + args.delay
- #print(record)
- try:
- save(write_api, record)
- except Exception as e:
- print("save failed:", e)
- else:
- if not reported_ok:
- print("save success! from now only reporting on failures")
- reported_ok = True
- def connect(file, baudrate):
- print('connecting to %s... ' % file, end='')
- while not _EXIT:
- if not os.path.exists(file):
- sleep(0.1)
- continue
- try:
- s = serial.Serial(port=file, baudrate=baudrate)
- except serial.serialutil.SerialException:
- sleep(0.1)
- continue
- else:
- print('Connected.')
- return s
- def main():
- args = parse_args()
- signal.signal(signal.SIGINT, handle_signal)
- signal.signal(signal.SIGQUIT, handle_signal)
- fd = connect(args.file, args.baudrate)
- while not _EXIT:
- try:
- print_loop(fd, args)
- except serial.serialutil.SerialException:
- print('Error ocurred, device disconnected? Trying to restore')
- fd.close()
- fd = connect(args.file, args.baudrate)
- if fd:
- fd.close()
- exit(0)
- if __name__ == "__main__":
- main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement