From 2f1c00bed429abb24d2b9f45d3581461b17aa277 Mon Sep 17 00:00:00 2001 From: Evgeny Zinoviev Date: Mon, 22 May 2023 07:11:21 +0300 Subject: wip --- src/home/database/sensors.py | 42 +++++++++++++++++++++++ src/home/mqtt/temphum.py | 17 ---------- src/home/temphum/__init__.py | 38 ++++++++++++++++----- src/home/temphum/__init__.pyi | 9 +++++ src/home/temphum/nodes.py | 23 +++++++++++++ src/temphum_native_node.py | 77 +++++++++++++++++++++++++++++++++++++++++++ src/temphum_nodes_util.py | 6 ++-- src/temphumd.py | 77 ------------------------------------------- systemd/temphum_node.service | 10 ++++++ systemd/temphum_node@.service | 10 ++++++ systemd/temphumd.service | 10 ------ systemd/temphumd@.service | 10 ------ 12 files changed, 203 insertions(+), 126 deletions(-) create mode 100644 src/home/temphum/__init__.pyi create mode 100644 src/home/temphum/nodes.py create mode 100755 src/temphum_native_node.py delete mode 100755 src/temphumd.py create mode 100644 systemd/temphum_node.service create mode 100644 systemd/temphum_node@.service delete mode 100644 systemd/temphumd.service delete mode 100644 systemd/temphumd@.service diff --git a/src/home/database/sensors.py b/src/home/database/sensors.py index 8155108..34b44bf 100644 --- a/src/home/database/sensors.py +++ b/src/home/database/sensors.py @@ -3,6 +3,17 @@ from datetime import datetime from typing import Tuple, List from .clickhouse import ClickhouseDatabase from ..api.types import TemperatureSensorLocation +from ..temphum import TempHumNodes + + +migration_tables_map = { + 'temp_diana': TempHumNodes.KBN_SH_HALL.hash(), + 'temp_street': TempHumNodes.KBN_BH_2FL_STREET.hash(), + 'temp_roof': TempHumNodes.KBN_BH_2FL.hash(), + 'temp_room': TempHumNodes.KBN_BH_1FL_BEDROOM.hash(), + 'temp_spb1': TempHumNodes.SPB_FLAT120_CABINET.hash(), + 'temp': TempHumNodes.KBN_BH_1FL_LIVINGROOM.hash() +} def get_temperature_table(sensor: TemperatureSensorLocation) -> str: @@ -28,6 +39,37 @@ def get_temperature_table(sensor: TemperatureSensorLocation) -> str: class SensorsDatabase(ClickhouseDatabase): def __init__(self): super().__init__('home') + self.db.execute(""" + CREATE TABLE IF NOT EXISTS temphum ( + NodeID Uint32, + ClientTime DateTime, + ReceivedTime DateTime, + Temperature Int16, + RelativeHumidity UInt16, + SensorType Enum8('Si7021' = 0, 'DHT12' = 1, 'BME280' = 2), + ) ENGINE = MergeTree() + PARTITION BY toYYYYMMDD(ReceivedTime) + ORDER BY (NodeID, ReceivedTime); + """) + + def migrate(self): + for table_name, node_id in migration_tables_map.items(): + if table_name in ('temp_room',): + sensor_type = 'DHT12' + else: + sensor_type = 'Si7021' + + self.logger.info(f'starting copying table {table_name}') + self.db.execute(f""" + INSERT INTO temphum (NodeID, ClientTime, ReceivedTime, Temperature, RelativeHumidify, SensorType) + SELECT {node_id}, ClientTime, ReceivedTime, Temperature, RelativeHumidify, '{sensor_type}' FROM {table_name} + """) + self.logger.info(f'finished copying table {table_name}') + + def migrate_del_old_tables(self): + for table_name in migration_tables_map.keys(): + self.db.execute(f"DROP TABLE {table_name}") + self.logger.info(f'dropped table {table_name}') def add_temperature(self, home_id: int, diff --git a/src/home/mqtt/temphum.py b/src/home/mqtt/temphum.py index 83886ac..69bafb2 100644 --- a/src/home/mqtt/temphum.py +++ b/src/home/mqtt/temphum.py @@ -1,25 +1,8 @@ import paho.mqtt.client as mqtt import re -from enum import auto from .payload.temphum import TempHumDataPayload from .esp import MqttEspBase -from ..util import HashableEnum - - -class MqttTempHumNodes(HashableEnum): - KBN_SH_HALL = auto() - KBN_SH_BATHROOM = auto() - KBN_SH_LIVINGROOM = auto() - KBN_SH_BEDROOM = auto() - - KBN_BH_2FL = auto() - KBN_BH_2FL_STREET = auto() - KBN_BH_1FL_LIVINGROOM = auto() - KBN_BH_1FL_BEDROOM = auto() - KBN_BH_1FL_BATHROOM = auto() - - SPB_FLAT120_CABINET = auto() class MqttTempHum(MqttEspBase): diff --git a/src/home/temphum/__init__.py b/src/home/temphum/__init__.py index 55a7e1f..30a459e 100644 --- a/src/home/temphum/__init__.py +++ b/src/home/temphum/__init__.py @@ -1,18 +1,38 @@ -from .base import SensorType, TempHumSensor -from .si7021 import Si7021 -from .dht12 import DHT12 +from importlib import import_module __all__ = [ 'SensorType', 'TempHumSensor', + 'TempHumNodes', 'create_sensor' ] -def create_sensor(type: SensorType, bus: int) -> TempHumSensor: - if type == SensorType.Si7021: - return Si7021(bus) - elif type == SensorType.DHT12: - return DHT12(bus) +def __import_sensor(name): + if name == 'si7021': + return import_module('.si7021', __name__).Si7021 + elif name == 'dht12': + return import_module('.dht12', __name__).DHT12 else: - raise ValueError('unexpected sensor type') + raise ValueError(f'unexpected sensor type: {name}') + + +def __getattr__(name): + if name == 'create_sensor': + base = import_module('.base', __name__) + + def create_sensor(type: base.SensorType, bus: int) -> base.TempHumSensor: + if type == base.SensorType.Si7021: + return __import_sensor('si7021')(bus) + elif type == base.SensorType.DHT12: + return __import_sensor('dht12')(bus) + else: + raise ValueError(f'unexpected sensor type: {type}') + + return create_sensor + + elif name in ('SensorType', 'TempHumSensor'): + return getattr(import_module('.base', __name__), name) + + elif name == 'TempHumNodes': + return import_module('.nodes', __name__).TempHumNodes diff --git a/src/home/temphum/__init__.pyi b/src/home/temphum/__init__.pyi new file mode 100644 index 0000000..c0370da --- /dev/null +++ b/src/home/temphum/__init__.pyi @@ -0,0 +1,9 @@ +from .base import ( + SensorType as SensorType, + TempHumSensor as TempHumSensor +) +from .nodes import TempHumNodes as TempHumNodes + + +def create_sensor(type: SensorType, bus: int) -> TempHumSensor: + pass \ No newline at end of file diff --git a/src/home/temphum/nodes.py b/src/home/temphum/nodes.py new file mode 100644 index 0000000..b86a22a --- /dev/null +++ b/src/home/temphum/nodes.py @@ -0,0 +1,23 @@ +from ..util import HashableEnum +from enum import auto + + +class TempHumNodes(HashableEnum): + KBN_SH_HALL = auto() + KBN_SH_BATHROOM = auto() + KBN_SH_LIVINGROOM = auto() + KBN_SH_BEDROOM = auto() + + KBN_BH_2FL = auto() + KBN_BH_2FL_STREET = auto() + KBN_BH_1FL_LIVINGROOM = auto() + KBN_BH_1FL_BEDROOM = auto() + KBN_BH_1FL_BATHROOM = auto() + KBN_STR_EL_BOX = auto() + + SPB_FLAT120_CABINET = auto() + SPB_FLAT120_LIVINGROOM = auto() + SPB_FLAT120_BEDROOM = auto() + SPB_FLAT120_LVR_BALCONY = auto() + SPB_FLAT120_BDR_BALCONY = auto() + SPB_FLAT120_BATHROOM = auto() diff --git a/src/temphum_native_node.py b/src/temphum_native_node.py new file mode 100755 index 0000000..f4d1fca --- /dev/null +++ b/src/temphum_native_node.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +import asyncio +import json +import logging + +from typing import Optional + +from home.config import config +from home.temphum import SensorType, create_sensor, TempHumSensor + +logger = logging.getLogger(__name__) +sensor: Optional[TempHumSensor] = None +lock = asyncio.Lock() +delay = 0.01 + + +async def get_measurements(): + async with lock: + await asyncio.sleep(delay) + + temp = sensor.temperature() + rh = sensor.humidity() + + return rh, temp + + +async def handle_client(reader, writer): + request = None + while request != 'quit': + try: + request = await reader.read(255) + if request == b'\x04': + break + request = request.decode('utf-8').strip() + except Exception: + break + + if request == 'read': + try: + rh, temp = await asyncio.wait_for(get_measurements(), timeout=3) + data = dict(humidity=rh, temp=temp) + except asyncio.TimeoutError as e: + logger.exception(e) + data = dict(error='i2c call timed out') + else: + data = dict(error='invalid request') + + writer.write((json.dumps(data) + '\r\n').encode('utf-8')) + try: + await writer.drain() + except ConnectionResetError: + pass + + writer.close() + + +async def run_server(host, port): + server = await asyncio.start_server(handle_client, host, port) + async with server: + logger.info('Server started.') + await server.serve_forever() + + +if __name__ == '__main__': + config.load() + + if 'measure_delay' in config['sensor']: + delay = float(config['sensor']['measure_delay']) + + sensor = create_sensor(SensorType(config['sensor']['type']), + int(config['sensor']['bus'])) + + try: + host, port = config.get_addr('server.listen') + asyncio.run(run_server(host, port)) + except KeyboardInterrupt: + logging.info('Exiting...') diff --git a/src/temphum_nodes_util.py b/src/temphum_nodes_util.py index c700ca8..ebeb349 100755 --- a/src/temphum_nodes_util.py +++ b/src/temphum_nodes_util.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 -from home.mqtt.temphum import MqttTempHumNodes +from home.temphum import TempHumNodes if __name__ == '__main__': max_name_len = 0 - for node in MqttTempHumNodes: + for node in TempHumNodes: if len(node.name) > max_name_len: max_name_len = len(node.name) values = [] - for node in MqttTempHumNodes: + for node in TempHumNodes: hash = node.hash() if hash in values: raise ValueError(f'collision detected: {hash}') diff --git a/src/temphumd.py b/src/temphumd.py deleted file mode 100755 index f4d1fca..0000000 --- a/src/temphumd.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python3 -import asyncio -import json -import logging - -from typing import Optional - -from home.config import config -from home.temphum import SensorType, create_sensor, TempHumSensor - -logger = logging.getLogger(__name__) -sensor: Optional[TempHumSensor] = None -lock = asyncio.Lock() -delay = 0.01 - - -async def get_measurements(): - async with lock: - await asyncio.sleep(delay) - - temp = sensor.temperature() - rh = sensor.humidity() - - return rh, temp - - -async def handle_client(reader, writer): - request = None - while request != 'quit': - try: - request = await reader.read(255) - if request == b'\x04': - break - request = request.decode('utf-8').strip() - except Exception: - break - - if request == 'read': - try: - rh, temp = await asyncio.wait_for(get_measurements(), timeout=3) - data = dict(humidity=rh, temp=temp) - except asyncio.TimeoutError as e: - logger.exception(e) - data = dict(error='i2c call timed out') - else: - data = dict(error='invalid request') - - writer.write((json.dumps(data) + '\r\n').encode('utf-8')) - try: - await writer.drain() - except ConnectionResetError: - pass - - writer.close() - - -async def run_server(host, port): - server = await asyncio.start_server(handle_client, host, port) - async with server: - logger.info('Server started.') - await server.serve_forever() - - -if __name__ == '__main__': - config.load() - - if 'measure_delay' in config['sensor']: - delay = float(config['sensor']['measure_delay']) - - sensor = create_sensor(SensorType(config['sensor']['type']), - int(config['sensor']['bus'])) - - try: - host, port = config.get_addr('server.listen') - asyncio.run(run_server(host, port)) - except KeyboardInterrupt: - logging.info('Exiting...') diff --git a/systemd/temphum_node.service b/systemd/temphum_node.service new file mode 100644 index 0000000..2d28a36 --- /dev/null +++ b/systemd/temphum_node.service @@ -0,0 +1,10 @@ +[Unit] +Description=temperature and humidity sensor node +After=network-online.target + +[Service] +Restart=on-failure +ExecStart=/home/user/homekit/src/temphum_native_node.py --config /etc/temphum_node.toml + +[Install] +WantedBy=multi-user.target diff --git a/systemd/temphum_node@.service b/systemd/temphum_node@.service new file mode 100644 index 0000000..38bf7d0 --- /dev/null +++ b/systemd/temphum_node@.service @@ -0,0 +1,10 @@ +[Unit] +Description=temperature and humidity sensor node +After=network-online.target + +[Service] +Restart=on-failure +ExecStart=/home/user/homekit/src/temphum_native_node.py --config /etc/temphum-node-%i.toml + +[Install] +WantedBy=multi-user.target diff --git a/systemd/temphumd.service b/systemd/temphumd.service deleted file mode 100644 index 1da9617..0000000 --- a/systemd/temphumd.service +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=temperature and humidity daemon -After=network-online.target - -[Service] -Restart=on-failure -ExecStart=/home/user/homekit/src/temphumd.py --config /etc/temphumd.toml - -[Install] -WantedBy=multi-user.target diff --git a/systemd/temphumd@.service b/systemd/temphumd@.service deleted file mode 100644 index d1c840d..0000000 --- a/systemd/temphumd@.service +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=temperature and humidity daemon -After=network-online.target - -[Service] -Restart=on-failure -ExecStart=/home/user/homekit/src/temphumd.py --config /etc/temphumd-%i.toml - -[Install] -WantedBy=multi-user.target -- cgit v1.2.3