diff --git a/app/config.py b/app/config.py index 05e4311..acd330e 100644 --- a/app/config.py +++ b/app/config.py @@ -24,6 +24,14 @@ class Bme680Settings(BaseModel): name: str = Field("Climate Sensor", description="The name of the sensor.") +class Pms5003ModuleSettings(BaseModel): + enabled: bool = Field(True, description="If sensor should be enabled or not.") + device: str = Field("/dev/ttyUSB0", description="The TTY path of the sensor.") + baudrate: int = Field(9600, description="The baudrate of the serial port.") + pin_enable: str = Field("GPIO22", description="The pin enable.") + pin_reset: str = Field("GPIO27", description="The pin reset.") + + class BridgeSettings(BaseModel): display_name: str = Field( "Bridge", description="The display name of the HAP bridge." @@ -31,6 +39,7 @@ class BridgeSettings(BaseModel): bme680: Bme680Settings = Field( Bme680Settings(), description="Settings for the BME680 module." ) + pms5003: Pms5003ModuleSettings = Field(Pms5003ModuleSettings(), description="Settings for the PMS5003 sensor.") class HomekitAccessoryProtocolSettings(BaseModel): diff --git a/app/main.py b/app/main.py index 8baef8f..fb8b971 100644 --- a/app/main.py +++ b/app/main.py @@ -8,6 +8,7 @@ from pyhap.accessory_driver import AccessoryDriver from app.config import Settings from app.sensors.bme import Bme680Sensor +from app.sensors.pms import Pms5003Sensor def get_bridge(accessory_driver: AccessoryDriver, settings: Settings): @@ -23,9 +24,13 @@ def get_bridge(accessory_driver: AccessoryDriver, settings: Settings): if settings.hap.bridge.bme680.enabled: bridge.add_accessory( Bme680Sensor( - accessory_driver, settings.hap.bridge.bme680.name, settings=settings + accessory_driver, settings=settings ) ) + if settings.hap.bridge.pms5003.enabled: + bridge.add_accessory( + Pms5003Sensor(accessory_driver, settings=settings) + ) return bridge @@ -43,5 +48,4 @@ if __name__ == "__main__": ) driver.add_accessory(accessory=get_bridge(driver, settings)) signal.signal(signal.SIGTERM, driver.signal_handler) - signal.signal(signal.SIGINT, driver.signal_handler) driver.start() diff --git a/app/sensors/bme.py b/app/sensors/bme.py index f21a73b..6db49a7 100644 --- a/app/sensors/bme.py +++ b/app/sensors/bme.py @@ -15,12 +15,12 @@ class Bme680Sensor(Accessory): category = CATEGORY_SENSOR # This is for the icon in the iOS Home app. - def __init__(self, driver, display_name, *, aid=None, settings: Settings): + def __init__(self, driver, *, aid=None, settings: Settings): """Here, we just store a reference to the current temperature characteristic and add a method that will be executed every time its value changes. """ # If overriding this method, be sure to call the super's implementation first. - super().__init__(driver, display_name, aid=aid) + super().__init__(driver, settings.hap.bridge.bme680.name, aid=aid) self.settings = settings self.sensor = bme680.BME680( diff --git a/app/sensors/pms.py b/app/sensors/pms.py new file mode 100644 index 0000000..e87b3a8 --- /dev/null +++ b/app/sensors/pms.py @@ -0,0 +1,56 @@ +import logging +import sys + +from prometheus_client import Gauge +from pyhap.accessory import Accessory +from pyhap.const import CATEGORY_SENSOR + +from app.config import Settings +from pms5003 import PMS5003 + + +class Pms5003Sensor(Accessory): + category = CATEGORY_SENSOR + + def __init__(self, driver, *, aid=None, settings: Settings): + super().__init__(driver, settings.hap.bridge.bme680.name, aid=aid) + + self.settings = settings + self.sensor = PMS5003( + device=settings.hap.bridge.pms5003.device, + baudrate=settings.hap.bridge.pms5003.baudrate, + pin_enable=settings.hap.bridge.pms5003.pin_enable, + pin_reset=settings.hap.bridge.pms5003.pin_reset + ) + + self._pms5003_pm_ug_per_m3_1 = Gauge( + "pms5003_pm_ug_per_m3_1", "The PM1.0 ug/m3 (ultrafine particles)." + ) + self._pms5003_pm_ug_per_m3_2 = Gauge( + "pms5003_pm_ug_per_m3_1", "PM2.5 ug/m3 (combustion particles, organic compounds, metals)." + ) + self._pms5003_pm_ug_per_m3_10 = Gauge( + "pms5003_pm_ug_per_m3_1", "PM10 ug/m3 (dust, pollen, mould spores)" + ) + + @Accessory.run_at_interval(120) + def run(self): + """ + This function runs the accessory. It polls for data and updates prometheus metrics. + """ + try: + data = self.sensor.read() + + self._pms5003_pm_ug_per_m3_1.set(data.pm_ug_per_m3(1.0)) + self._pms5003_pm_ug_per_m3_2.set(data.pm_ug_per_m3(2.5)) + self._pms5003_pm_ug_per_m3_10.set(data.pm_ug_per_m3(10)) + except IOError as e: + # This happens from time to time, best we stop and let systemd restart us. + logging.critical("Failed to read data from serial.") + sys.exit(1) + + def stop(self): + """We override this method to clean up any resources or perform final actions, as + this is called by the AccessoryDriver when the Accessory is being stopped. + """ + print("Stopping accessory.") diff --git a/config.yaml b/config.yaml index 489f95f..f7ad753 100644 --- a/config.yaml +++ b/config.yaml @@ -3,10 +3,16 @@ prometheus: port: 8000 hap: port: 51826 - persist_file: /home/denis/bme680-homekit/accessory.state + persist_file: accessory.state bridge: display_name: Bridge bme680: enabled: yes address: 118 # Primary I2C Address - name: "Climate Sensor" \ No newline at end of file + name: "Climate Sensor" + pms5003: + enabled: yes + device: "/dev/ttyUSB0" + baudrate: 9600 + pin_enable: "GPIO22" + pin_reset: "GPIO27" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index ba3f864..6c0f679 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ HAP-python==4.4.0 prometheus-client== 0.19.0 PyYAML==6.0.1 pydantic~=2.6.1 -pydantic-settings==2.1.0 \ No newline at end of file +pydantic-settings==2.1.0 +pms5003==1.0.0 \ No newline at end of file