diff --git a/aqi_monitor_py/README.md b/aqi_monitor_py/README.md new file mode 100644 index 0000000..8fa2169 --- /dev/null +++ b/aqi_monitor_py/README.md @@ -0,0 +1,7 @@ +# Python Sensor Application +Updated script running on a RaspberryPi. Pure python implementation. + +## Install libraries +``` +pip install -r requirements.txt +``` diff --git a/aqi_monitor_py/requirements.txt b/aqi_monitor_py/requirements.txt new file mode 100644 index 0000000..c8a24d8 --- /dev/null +++ b/aqi_monitor_py/requirements.txt @@ -0,0 +1,7 @@ +ipython +simple-sds011 +requests +RPi.bme280 + +cffi +smbus-cffi diff --git a/aqi_monitor_py/sensor/config.sample.json b/aqi_monitor_py/sensor/config.sample.json new file mode 100644 index 0000000..377a334 --- /dev/null +++ b/aqi_monitor_py/sensor/config.sample.json @@ -0,0 +1,5 @@ +{ + "url": "https://www.example.com/api/aqi", + "username": "username", + "password": "password" +} \ No newline at end of file diff --git a/aqi_monitor_py/sensor/main.py b/aqi_monitor_py/sensor/main.py new file mode 100644 index 0000000..0a17e29 --- /dev/null +++ b/aqi_monitor_py/sensor/main.py @@ -0,0 +1,84 @@ +"""entry point to collect data from sensors""" + +import json +import sys + +from os import path + +import requests + +from sensor_sds011 import SDS +from sensor_bme280 import BmeSensor + + +class PiSensor: + """collect and send data""" + + SENSOR_ID = 1 + + def __init__(self): + self.data = False + + def get_data(self): + """get all data from sensors""" + self.data = {} + self.get_sds011() + self.get_bme() + self.add_static() + + def get_sds011(self): + """get data dict from sds011""" + sds_data = SDS().collect() + self.data.update(sds_data) + + def get_bme(self): + """get data dict from bme""" + bme_data = BmeSensor().collect() + self.data.update(bme_data) + + def add_static(self): + """add static values to data""" + self.data.update( + { + "sensor_id": self.SENSOR_ID, + "uptime": self.get_uptime(), + } + ) + + @staticmethod + def get_uptime(): + """read uptime""" + with open("/proc/uptime", "r", encoding="utf-8") as f: + uptime_seconds = float(f.readline().split()[0]) + + return uptime_seconds + + def send_data(self): + """post data to api endpoint""" + config = self.read_config() + auth = (config["username"], config["password"]) + response = requests.post(config["url"], json=self.data, auth=auth) + if not response.ok: + print(response.text) + + def read_config(self): + """read config file""" + # build path + root_folder = path.dirname(sys.argv[0]) + if root_folder == '/sbin': + # running interactive + config_path = 'config.json' + else: + config_path = path.join(root_folder, 'config.json') + + with open(config_path, "r", encoding="utf-8") as f: + config = json.loads(f.read()) + + return config + + +if __name__ == "__main__": + sensor = PiSensor() + sensor.get_data() + print(sensor.data) + sensor.send_data() diff --git a/aqi_monitor_py/sensor/sensor_bme280.py b/aqi_monitor_py/sensor/sensor_bme280.py new file mode 100644 index 0000000..059bbb4 --- /dev/null +++ b/aqi_monitor_py/sensor/sensor_bme280.py @@ -0,0 +1,40 @@ +"""interact with temperature sensor""" +# pylint: disable=import-error + +import smbus2 +import bme280 + + +class BmeSensor: + """interact with BME280 sensor on pi""" + + PORT = 1 + ADDRESS = 0x76 + + def __init__(self): + self.data = False + + def collect(self): + """collect""" + print("collect data from bme280") + self.get_data() + temperature_values = self.format_data() + print(f"bme280 data: {temperature_values}") + + return temperature_values + + def get_data(self): + """read data from sensor""" + bus = smbus2.SMBus(self.PORT) + calibration_params = bme280.load_calibration_params(bus, self.ADDRESS) + self.data = bme280.sample(bus, self.ADDRESS, calibration_params) + + def format_data(self): + """build dict to send""" + temperature_values = { + "temperature": round(self.data.temperature, 2), + "pressure": round(self.data.pressure), + "humidity": round(self.data.humidity, 2), + } + + return temperature_values diff --git a/aqi_monitor_py/sensor/sensor_sds011.py b/aqi_monitor_py/sensor/sensor_sds011.py new file mode 100644 index 0000000..5a9b630 --- /dev/null +++ b/aqi_monitor_py/sensor/sensor_sds011.py @@ -0,0 +1,80 @@ +"""interact with aqi and environment sensors on pi4""" +import os +from time import sleep +import simple_sds011 # pylint: disable=import-error + + +class SDS: + """collect data from sds011 sensor""" + + def __init__(self): + self.port = self.get_port() + self.pm = simple_sds011.SDS011(self.port) + + def collect(self): + """collect average values""" + print("start collect") + self.startup() + pm_values = self.query_sensor() + self.shutdown() + print(f"pm_values: {pm_values}") + print("finish collect") + + return pm_values + + def get_port(self): + """find tty port for sds sensor""" + print("find usb port") + usbs = [i for i in os.listdir("/dev/") if i.startswith("ttyUSB")] + if len(usbs) > 1: + raise ValueError(f"too many ttyUSBs found: {usbs}") + + port = f"/dev/{usbs[0]}" + + return port + + def startup(self): + """activate and set mode""" + print("startup sensor") + self.pm.active = 1 + sleep(0.5) + self.pm.mode = simple_sds011.MODE_PASSIVE + print("warm up") + sleep(20) + + def query_sensor(self): + """query 15 times""" + print("collect samples") + pm25_sample = [] + pm10_sample = [] + + for _ in range(15): + response = self.pm.query() + pm25, pm10 = response.get("value").values() + + pm25_sample.append(pm25) + pm10_sample.append(pm10) + + sleep(1) + + print(pm25_sample) + print(pm10_sample) + pm25_avg = self.avg(pm25_sample) + pm10_avg = self.avg(pm10_sample) + + pm_values = { + "pm25": pm25_avg, + "pm10": pm10_avg + } + + return pm_values + + def shutdown(self): + """deactivate""" + print("shutdown sensor") + self.pm.active = 0 + + @staticmethod + def avg(lst): + """calc average of list""" + return round(sum(lst) / len(lst), 1) diff --git a/aqi_monitor_py/service/sensor.service b/aqi_monitor_py/service/sensor.service new file mode 100644 index 0000000..0b602fe --- /dev/null +++ b/aqi_monitor_py/service/sensor.service @@ -0,0 +1,8 @@ +[Unit] +Description=sensor main.py + +[Service] +User=simon +Group=simon +Type=oneshot +ExecStart=python /home/simon/aqi_monitor/aqi_monitor_py/sensor/main.py \ No newline at end of file diff --git a/aqi_monitor_py/service/sensor.timer b/aqi_monitor_py/service/sensor.timer new file mode 100644 index 0000000..87fd0fb --- /dev/null +++ b/aqi_monitor_py/service/sensor.timer @@ -0,0 +1,9 @@ +[Unit] +Description=sensor main.py + +[Timer] +OnBootSec=1min +OnUnitActiveSec=3min + +[Install] +WantedBy=multi-user.target \ No newline at end of file