summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2023-06-07 02:34:50 +0300
committerEvgeny Zinoviev <me@ch1p.io>2023-06-07 02:34:50 +0300
commitc44a3669100f72d108404a8fdccd18f55018c18b (patch)
tree2fe704fd702d503991b9213cdbc87d7d9fd4dcd5
parent5de1896f5be183d600361d70218c6d579f3a5899 (diff)
wip
-rw-r--r--src/home/mqtt/__init__.py10
-rw-r--r--src/home/mqtt/_module.py43
-rw-r--r--src/home/mqtt/_node.py111
-rw-r--r--src/home/mqtt/_wrapper.py55
-rw-r--r--src/home/mqtt/module/diagnostics.py7
-rw-r--r--src/home/mqtt/module/ota.py15
-rw-r--r--src/home/mqtt/module/relay.py8
-rw-r--r--src/home/mqtt/module/temphum.py13
-rw-r--r--src/home/mqtt/mqtt.py20
-rw-r--r--src/home/mqtt/relay.py59
-rw-r--r--src/home/mqtt/util.py31
-rw-r--r--src/home/temphum/__init__.py19
-rw-r--r--src/home/temphum/base.py20
-rw-r--r--src/home/temphum/dht12.py22
-rw-r--r--src/home/temphum/i2c.py52
-rw-r--r--src/home/temphum/si7021.py13
-rw-r--r--src/home/util.py9
-rwxr-xr-xsrc/inverter_mqtt_receiver.py4
-rwxr-xr-xsrc/inverter_mqtt_sender.py4
-rwxr-xr-xsrc/mqtt_node_util.py34
-rwxr-xr-xsrc/polaris_kettle_bot.py6
-rwxr-xr-xsrc/polaris_kettle_util.py4
-rwxr-xr-xsrc/pump_bot.py25
-rwxr-xr-xsrc/sensors_mqtt_receiver.py4
-rwxr-xr-xsrc/sensors_mqtt_sender.py4
-rwxr-xr-xsrc/temphum_mqtt_node.py5
-rwxr-xr-xsrc/temphum_smbus_util.py3
-rwxr-xr-xsrc/temphumd.py5
-rwxr-xr-xtools/mcuota.py98
-rwxr-xr-xtools/mcuota.sh14
30 files changed, 292 insertions, 425 deletions
diff --git a/src/home/mqtt/__init__.py b/src/home/mqtt/__init__.py
index c95061f..8633437 100644
--- a/src/home/mqtt/__init__.py
+++ b/src/home/mqtt/__init__.py
@@ -1,9 +1,5 @@
-from .mqtt import MqttBase, MqttPayload, MqttPayloadCustomField
+from .mqtt import Mqtt, MqttPayload, MqttPayloadCustomField
from ._node import MqttNode
from ._module import MqttModule
-from .util import (
- poll_tick,
- get_modules as get_mqtt_modules,
- import_module as import_mqtt_module,
- add_module as add_mqtt_module
-) \ No newline at end of file
+from ._wrapper import MqttWrapper
+from .util import get_modules as get_mqtt_modules \ No newline at end of file
diff --git a/src/home/mqtt/_module.py b/src/home/mqtt/_module.py
index 840534e..80f27bb 100644
--- a/src/home/mqtt/_module.py
+++ b/src/home/mqtt/_module.py
@@ -2,6 +2,10 @@ from __future__ import annotations
import abc
import logging
+import threading
+
+from time import sleep
+from ..util import next_tick_gen
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
@@ -10,16 +14,29 @@ if TYPE_CHECKING:
class MqttModule(abc.ABC):
- tick_interval: int
+ _tick_interval: int
_initialized: bool
+ _connected: bool
+ _ticker: Optional[threading.Thread]
+ _mqtt_node_ref: Optional[MqttNode]
def __init__(self, tick_interval=0):
- self.tick_interval = tick_interval
+ self._tick_interval = tick_interval
self._initialized = False
+ self._ticker = None
self._logger = logging.getLogger(self.__class__.__name__)
+ self._connected = False
+ self._mqtt_node_ref = None
- def init(self, mqtt: MqttNode):
- pass
+ def on_connect(self, mqtt: MqttNode):
+ self._connected = True
+ self._mqtt_node_ref = mqtt
+ if self._tick_interval:
+ self._start_ticker()
+
+ def on_disconnect(self, mqtt: MqttNode):
+ self._connected = False
+ self._mqtt_node_ref = None
def is_initialized(self):
return self._initialized
@@ -30,8 +47,24 @@ class MqttModule(abc.ABC):
def unset_initialized(self):
self._initialized = False
- def tick(self, mqtt: MqttNode):
+ def tick(self):
pass
+ def _tick(self):
+ g = next_tick_gen(self._tick_interval)
+ while self._connected:
+ sleep(next(g))
+ if not self._connected:
+ break
+ self.tick()
+
+ def _start_ticker(self):
+ if not self._ticker or not self._ticker.is_alive():
+ name_part = f'{self._mqtt_node_ref.id}/' if self._mqtt_node_ref else ''
+ self._ticker = None
+ self._ticker = threading.Thread(target=self._tick,
+ name=f'mqtt:{self.__class__.__name__}/{name_part}ticker')
+ self._ticker.start()
+
def handle_payload(self, mqtt: MqttNode, topic: str, payload: bytes) -> Optional[MqttPayload]:
pass
diff --git a/src/home/mqtt/_node.py b/src/home/mqtt/_node.py
index f34da0c..ddf5ba2 100644
--- a/src/home/mqtt/_node.py
+++ b/src/home/mqtt/_node.py
@@ -1,103 +1,80 @@
-import paho.mqtt.client as mqtt
+import logging
+import importlib
-from .mqtt import MqttBase
-from typing import List, Optional
-from ._module import MqttModule
+from typing import List, TYPE_CHECKING, Optional
from ._payload import MqttPayload
+from ._module import MqttModule
+if TYPE_CHECKING:
+ from ._wrapper import MqttWrapper
+else:
+ MqttWrapper = None
-class MqttNode(MqttBase):
+class MqttNode:
_modules: List[MqttModule]
_module_subscriptions: dict[str, MqttModule]
_node_id: str
_payload_callbacks: list[callable]
- # _devices: list[MqttEspDevice]
- # _message_callback: Optional[callable]
- # _ota_publish_callback: Optional[callable]
+ _wrapper: Optional[MqttWrapper]
- def __init__(self,
- node_id: str,
- # devices: Union[MqttEspDevice, list[MqttEspDevice]]
- ):
- super().__init__(clean_session=True)
+ def __init__(self, node_id: str):
self._modules = []
self._module_subscriptions = {}
self._node_id = node_id
self._payload_callbacks = []
- # if not isinstance(devices, list):
- # devices = [devices]
- # self._devices = devices
- # self._message_callback = None
- # self._ota_publish_callback = None
- # self._ota_mid = None
+ self._logger = logging.getLogger(self.__class__.__name__)
+ self._wrapper = None
- def on_connect(self, client: mqtt.Client, userdata, flags, rc):
- super().on_connect(client, userdata, flags, rc)
+ def on_connect(self, wrapper: MqttWrapper):
+ self._wrapper = wrapper
for module in self._modules:
if not module.is_initialized():
- module.init(self)
+ module.on_connect(self)
module.set_initialized()
- def on_disconnect(self, client: mqtt.Client, userdata, rc):
- super().on_disconnect(client, userdata, rc)
+ def on_disconnect(self):
+ self._wrapper = None
for module in self._modules:
module.unset_initialized()
- def on_publish(self, client: mqtt.Client, userdata, mid):
- pass # FIXME
- # if self._ota_mid is not None and mid == self._ota_mid and self._ota_publish_callback:
- # self._ota_publish_callback()
-
- def on_message(self, client: mqtt.Client, userdata, msg):
- try:
- topic = msg.topic
- actual_topic = topic[len(f'hk/{self._node_id}/'):]
-
- if actual_topic in self._module_subscriptions:
- payload = self._module_subscriptions[actual_topic].handle_payload(self, actual_topic, msg.payload)
- if isinstance(payload, MqttPayload):
- for f in self._payload_callbacks:
- f(payload)
+ def on_message(self, topic, payload):
+ if topic in self._module_subscriptions:
+ payload = self._module_subscriptions[topic].handle_payload(self, topic, payload)
+ if isinstance(payload, MqttPayload):
+ for f in self._payload_callbacks:
+ f(payload)
- except Exception as e:
- self._logger.exception(str(e))
-
- # def push_ota(self,
- # device_id,
- # filename: str,
- # publish_callback: callable,
- # qos: int):
- # device = next(d for d in self._devices if d.id == device_id)
- # assert device.secret is not None, 'device secret not specified'
- #
- # self._ota_publish_callback = publish_callback
- # payload = OtaPayload(secret=device.secret, filename=filename)
- # publish_result = self._client.publish(f'hk/{device.id}/{self.TOPIC_LEAF}/admin/ota',
- # payload=payload.pack(),
- # qos=qos)
- # self._ota_mid = publish_result.mid
- # self._client.loop_write()
- #
- # @classmethod
- # def get_mqtt_topics(cls, additional_topics: Optional[list[str]] = None):
- # return rf'^hk/(.*?)/{cls.TOPIC_LEAF}/(stat|stat1|otares'+('|'+('|'.join(additional_topics)) if additional_topics else '')+')$'
+ def load_module(self, module_name: str, *args, **kwargs) -> MqttModule:
+ module = importlib.import_module(f'..module.{module_name}', __name__)
+ if not hasattr(module, 'MODULE_NAME'):
+ raise RuntimeError(f'MODULE_NAME not found in module {module}')
+ cl = getattr(module, getattr(module, 'MODULE_NAME'))
+ instance = cl(*args, **kwargs)
+ self.add_module(instance)
+ return instance
def add_module(self, module: MqttModule):
self._modules.append(module)
- if self._connected:
- module.init(self)
+ if self._wrapper and self._wrapper._connected:
+ module.on_connect(self)
module.set_initialized()
def subscribe_module(self, topic: str, module: MqttModule, qos: int = 1):
+ if not self._wrapper or not self._wrapper._connected:
+ raise RuntimeError('not connected')
+
self._module_subscriptions[topic] = module
- self._client.subscribe(f'hk/{self._node_id}/{topic}', qos)
+ self._wrapper.subscribe(self.id, topic, qos)
def publish(self,
topic: str,
payload: bytes,
qos: int = 1):
- self._client.publish(f'hk/{self._node_id}/{topic}', payload, qos)
- self._client.loop_write()
+ self._wrapper.publish(self.id, topic, payload, qos)
def add_payload_callback(self, callback: callable):
- self._payload_callbacks.append(callback) \ No newline at end of file
+ self._payload_callbacks.append(callback)
+
+ @property
+ def id(self) -> str:
+ return self._node_id
diff --git a/src/home/mqtt/_wrapper.py b/src/home/mqtt/_wrapper.py
new file mode 100644
index 0000000..41f9d89
--- /dev/null
+++ b/src/home/mqtt/_wrapper.py
@@ -0,0 +1,55 @@
+import paho.mqtt.client as mqtt
+
+from .mqtt import Mqtt
+from ._node import MqttNode
+from ..config import config
+from ..util import strgen
+
+
+class MqttWrapper(Mqtt):
+ _nodes: list[MqttNode]
+
+ def __init__(self, topic_prefix='hk', randomize_client_id=False):
+ client_id = config['mqtt']['client_id']
+ if randomize_client_id:
+ client_id += '_'+strgen(6)
+ super().__init__(clean_session=True, client_id=client_id)
+ self._nodes = []
+ self._topic_prefix = topic_prefix
+
+ def on_connect(self, client: mqtt.Client, userdata, flags, rc):
+ super().on_connect(client, userdata, flags, rc)
+ for node in self._nodes:
+ node.on_connect(self)
+
+ def on_disconnect(self, client: mqtt.Client, userdata, rc):
+ super().on_disconnect(client, userdata, rc)
+ for node in self._nodes:
+ node.on_disconnect()
+
+ def on_message(self, client: mqtt.Client, userdata, msg):
+ try:
+ topic = msg.topic
+ for node in self._nodes:
+ node.on_message(topic[len(f'{self._topic_prefix}/{node.id}/'):], msg.payload)
+ except Exception as e:
+ self._logger.exception(str(e))
+
+ def add_node(self, node: MqttNode):
+ self._nodes.append(node)
+ if self._connected:
+ node.on_connect(self)
+
+ def subscribe(self,
+ node_id: str,
+ topic: str,
+ qos: int):
+ self._client.subscribe(f'{self._topic_prefix}/{node_id}/{topic}', qos)
+
+ def publish(self,
+ node_id: str,
+ topic: str,
+ payload: bytes,
+ qos: int):
+ self._client.publish(f'{self._topic_prefix}/{node_id}/{topic}', payload, qos)
+ self._client.loop_write()
diff --git a/src/home/mqtt/module/diagnostics.py b/src/home/mqtt/module/diagnostics.py
index c31cce2..fa6cc8e 100644
--- a/src/home/mqtt/module/diagnostics.py
+++ b/src/home/mqtt/module/diagnostics.py
@@ -48,14 +48,17 @@ class DiagnosticsPayload(MqttPayload):
class MqttDiagnosticsModule(MqttModule):
- def init(self, mqtt: MqttNode):
+ def on_connect(self, mqtt: MqttNode):
+ super().on_connect(mqtt)
for topic in ('diag', 'd1ag', 'stat', 'stat1'):
mqtt.subscribe_module(topic, self)
def handle_payload(self, mqtt: MqttNode, topic: str, payload: bytes) -> Optional[MqttPayload]:
+ message = None
if topic in ('stat', 'diag'):
message = DiagnosticsPayload.unpack(payload)
elif topic in ('stat1', 'd1ag'):
message = InitialDiagnosticsPayload.unpack(payload)
- self._logger.debug(message)
+ if message:
+ self._logger.debug(message)
return message
diff --git a/src/home/mqtt/module/ota.py b/src/home/mqtt/module/ota.py
index 5a1a309..e71cccc 100644
--- a/src/home/mqtt/module/ota.py
+++ b/src/home/mqtt/module/ota.py
@@ -42,18 +42,15 @@ class OtaPayload(MqttPayload):
class MqttOtaModule(MqttModule):
_ota_request: Optional[tuple[str, str, int]]
- _mqtt_ref: Optional[MqttNode]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._ota_request = None
- self._mqtt_ref = None
- def init(self, mqtt: MqttNode):
+ def on_connect(self, mqtt: MqttNode):
+ super().on_connect(mqtt)
mqtt.subscribe_module("otares", self)
- self._mqtt_ref = mqtt
-
if self._ota_request is not None:
secret, filename, qos = self._ota_request
self._ota_request = None
@@ -67,9 +64,9 @@ class MqttOtaModule(MqttModule):
def do_push_ota(self, secret: str, filename: str, qos: int):
payload = OtaPayload(secret=secret, filename=filename)
- self._mqtt_ref.publish('ota',
- payload=payload.pack(),
- qos=qos)
+ self._mqtt_node_ref.publish('ota',
+ payload=payload.pack(),
+ qos=qos)
def push_ota(self,
secret: str,
@@ -78,4 +75,4 @@ class MqttOtaModule(MqttModule):
if not self._initialized:
self._ota_request = (secret, filename, qos)
else:
- self.do_push_ota(secret, filename, qos) \ No newline at end of file
+ self.do_push_ota(secret, filename, qos)
diff --git a/src/home/mqtt/module/relay.py b/src/home/mqtt/module/relay.py
index bf22bfe..ae88ddb 100644
--- a/src/home/mqtt/module/relay.py
+++ b/src/home/mqtt/module/relay.py
@@ -58,16 +58,16 @@ class MqttRelayState:
class MqttRelayModule(MqttModule):
- def init(self, mqtt: MqttNode):
+ def on_connect(self, mqtt: MqttNode):
+ super().on_connect(mqtt)
mqtt.subscribe_module('relay/switch', self)
mqtt.subscribe_module('relay/status', self)
- @staticmethod
- def switchpower(mqtt: MqttNode,
+ def switchpower(self,
enable: bool,
secret: str):
payload = MqttPowerSwitchPayload(secret=secret, state=enable)
- mqtt.publish('relay/switch', payload=payload.pack())
+ self._mqtt_node_ref.publish('relay/switch', payload=payload.pack())
def handle_payload(self, mqtt: MqttNode, topic: str, payload: bytes) -> Optional[MqttPayload]:
message = None
diff --git a/src/home/mqtt/module/temphum.py b/src/home/mqtt/module/temphum.py
index 0e43f1b..9cdfedb 100644
--- a/src/home/mqtt/module/temphum.py
+++ b/src/home/mqtt/module/temphum.py
@@ -4,6 +4,7 @@ from .._module import MqttModule
from .._payload import MqttPayload
from ...util import HashableEnum
from typing import Optional
+from ...temphum import BaseSensor
two_digits_precision = lambda x: round(x, 2)
@@ -44,9 +45,17 @@ class MqttTempHumNodes(HashableEnum):
class MqttTempHumModule(MqttModule):
- def init(self, mqtt: MqttNode):
+ def __init__(self, sensor: Optional[BaseSensor] = None, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._sensor = sensor
+
+ def on_connect(self, mqtt: MqttNode):
+ super().on_connect(mqtt)
mqtt.subscribe_module('temphum/data', self)
+ def tick(self):
+ pass
+
def handle_payload(self,
mqtt: MqttNode,
topic: str,
@@ -54,4 +63,4 @@ class MqttTempHumModule(MqttModule):
if topic == 'temphum/data':
message = MqttTemphumDataPayload.unpack(payload)
self._logger.debug(message)
- return message \ No newline at end of file
+ return message
diff --git a/src/home/mqtt/mqtt.py b/src/home/mqtt/mqtt.py
index fad5d26..ba32889 100644
--- a/src/home/mqtt/mqtt.py
+++ b/src/home/mqtt/mqtt.py
@@ -5,6 +5,7 @@ import logging
from ..config import config
from ._payload import *
+from typing import Optional
def username_and_password() -> Tuple[str, str]:
@@ -13,11 +14,13 @@ def username_and_password() -> Tuple[str, str]:
return username, password
-class MqttBase:
+class Mqtt:
_connected: bool
- def __init__(self, clean_session=True):
- self._client = mqtt.Client(client_id=config['mqtt']['client_id'],
+ def __init__(self,
+ clean_session=True,
+ client_id: Optional[str] = None):
+ self._client = mqtt.Client(client_id=config['mqtt']['client_id'] if not client_id else client_id,
protocol=mqtt.MQTTv311,
clean_session=clean_session)
self._client.on_connect = self.on_connect
@@ -81,14 +84,3 @@ class MqttBase:
def on_publish(self, client: mqtt.Client, userdata, mid):
self._logger.debug(f'publish done, mid={mid}')
-
-
-class MqttEspDevice:
- id: str
- secret: Optional[str]
-
- def __init__(self,
- node_id: str,
- secret: Optional[str] = None):
- self.id = node_id
- self.secret = secret
diff --git a/src/home/mqtt/relay.py b/src/home/mqtt/relay.py
deleted file mode 100644
index cf657f7..0000000
--- a/src/home/mqtt/relay.py
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env python3
-import paho.mqtt.client as mqtt
-import re
-import logging
-
-from .mqtt import MQTTBase
-
-
-class MQTTRelayClient(MQTTBase):
- _home_id: str
-
- def __init__(self, home_id: str):
- super().__init__(clean_session=True)
- self._home_id = home_id
-
- def on_connect(self, client: mqtt.Client, userdata, flags, rc):
- super().on_connect(client, userdata, flags, rc)
-
- topic = f'home/{self._home_id}/#'
- self._logger.info(f"subscribing to {topic}")
-
- client.subscribe(topic, qos=1)
-
- def on_message(self, client: mqtt.Client, userdata, msg):
- try:
- match = re.match(r'^home/(.*?)/relay/(stat|power)(?:/(.+))?$', msg.topic)
- self._logger.info(f'topic: {msg.topic}')
- if not match:
- return
-
- name = match.group(1)
- subtopic = match.group(2)
-
- if name != self._home_id:
- return
-
- if subtopic == 'stat':
- stat_name, stat_value = match.group(3).split('/')
- self._logger.info(f'stat: {stat_name} = {stat_value}')
-
- except Exception as e:
- self._logger.exception(str(e))
-
-
-class MQTTRelayController(MQTTBase):
- _home_id: str
-
- def __init__(self, home_id: str):
- super().__init__(clean_session=True)
- self._home_id = home_id
-
- def set_power(self, enable: bool):
- self._client.publish(f'home/{self._home_id}/relay/power',
- payload=int(enable),
- qos=1)
- self._client.loop_write()
-
- def send_stat(self, stat: dict):
- pass
diff --git a/src/home/mqtt/util.py b/src/home/mqtt/util.py
index 91b6baf..390d463 100644
--- a/src/home/mqtt/util.py
+++ b/src/home/mqtt/util.py
@@ -1,38 +1,15 @@
-import time
import os
import re
-import importlib
-from ._node import MqttNode
-from . import MqttModule
from typing import List
-def poll_tick(freq):
- t = time.time()
- while True:
- t += freq
- yield max(t - time.time(), 0)
-
-
def get_modules() -> List[str]:
modules = []
- for name in os.listdir(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'module')):
+ modules_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'module')
+ for name in os.listdir(modules_dir):
+ if os.path.isdir(os.path.join(modules_dir, name)):
+ continue
name = re.sub(r'\.py$', '', name)
modules.append(name)
return modules
-
-
-def import_module(module: str):
- return importlib.import_module(
- f'..module.{module}', __name__)
-
-
-def add_module(mqtt_node: MqttNode, module: str) -> MqttModule:
- module = import_module(module)
- if not hasattr(module, 'MODULE_NAME'):
- raise RuntimeError(f'MODULE_NAME not found in module {module}')
- cl = getattr(module, getattr(module, 'MODULE_NAME'))
- instance = cl()
- mqtt_node.add_module(instance)
- return instance \ No newline at end of file
diff --git a/src/home/temphum/__init__.py b/src/home/temphum/__init__.py
index 55a7e1f..46d14e6 100644
--- a/src/home/temphum/__init__.py
+++ b/src/home/temphum/__init__.py
@@ -1,18 +1 @@
-from .base import SensorType, TempHumSensor
-from .si7021 import Si7021
-from .dht12 import DHT12
-
-__all__ = [
- 'SensorType',
- 'TempHumSensor',
- 'create_sensor'
-]
-
-
-def create_sensor(type: SensorType, bus: int) -> TempHumSensor:
- if type == SensorType.Si7021:
- return Si7021(bus)
- elif type == SensorType.DHT12:
- return DHT12(bus)
- else:
- raise ValueError('unexpected sensor type')
+from .base import SensorType, BaseSensor
diff --git a/src/home/temphum/base.py b/src/home/temphum/base.py
index e774433..602cab7 100644
--- a/src/home/temphum/base.py
+++ b/src/home/temphum/base.py
@@ -1,25 +1,19 @@
-import smbus
-
-from abc import abstractmethod, ABC
+from abc import ABC
from enum import Enum
-class TempHumSensor:
- @abstractmethod
+class BaseSensor(ABC):
+ def __init__(self, bus: int):
+ super().__init__()
+ self.bus = smbus.SMBus(bus)
+
def humidity(self) -> float:
pass
- @abstractmethod
def temperature(self) -> float:
pass
-class I2CTempHumSensor(TempHumSensor, ABC):
- def __init__(self, bus: int):
- super().__init__()
- self.bus = smbus.SMBus(bus)
-
-
class SensorType(Enum):
Si7021 = 'si7021'
- DHT12 = 'dht12'
+ DHT12 = 'dht12' \ No newline at end of file
diff --git a/src/home/temphum/dht12.py b/src/home/temphum/dht12.py
deleted file mode 100644
index d495766..0000000
--- a/src/home/temphum/dht12.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from .base import I2CTempHumSensor
-
-
-class DHT12(I2CTempHumSensor):
- i2c_addr = 0x5C
-
- def _measure(self):
- raw = self.bus.read_i2c_block_data(self.i2c_addr, 0, 5)
- if (raw[0] + raw[1] + raw[2] + raw[3]) & 0xff != raw[4]:
- raise ValueError("checksum error")
- return raw
-
- def temperature(self) -> float:
- raw = self._measure()
- temp = raw[2] + (raw[3] & 0x7f) * 0.1
- if raw[3] & 0x80:
- temp *= -1
- return temp
-
- def humidity(self) -> float:
- raw = self._measure()
- return raw[0] + raw[1] * 0.1
diff --git a/src/home/temphum/i2c.py b/src/home/temphum/i2c.py
new file mode 100644
index 0000000..7d8e2e3
--- /dev/null
+++ b/src/home/temphum/i2c.py
@@ -0,0 +1,52 @@
+import abc
+import smbus
+
+from .base import BaseSensor, SensorType
+
+
+class I2CSensor(BaseSensor, abc.ABC):
+ def __init__(self, bus: int):
+ super().__init__()
+ self.bus = smbus.SMBus(bus)
+
+
+class DHT12(I2CSensor):
+ i2c_addr = 0x5C
+
+ def _measure(self):
+ raw = self.bus.read_i2c_block_data(self.i2c_addr, 0, 5)
+ if (raw[0] + raw[1] + raw[2] + raw[3]) & 0xff != raw[4]:
+ raise ValueError("checksum error")
+ return raw
+
+ def temperature(self) -> float:
+ raw = self._measure()
+ temp = raw[2] + (raw[3] & 0x7f) * 0.1
+ if raw[3] & 0x80:
+ temp *= -1
+ return temp
+
+ def humidity(self) -> float:
+ raw = self._measure()
+ return raw[0] + raw[1] * 0.1
+
+
+class Si7021(I2CSensor):
+ i2c_addr = 0x40
+
+ def temperature(self) -> float:
+ raw = self.bus.read_i2c_block_data(self.i2c_addr, 0xE3, 2)
+ return 175.72 * (raw[0] << 8 | raw[1]) / 65536.0 - 46.85
+
+ def humidity(self) -> float:
+ raw = self.bus.read_i2c_block_data(self.i2c_addr, 0xE5, 2)
+ return 125.0 * (raw[0] << 8 | raw[1]) / 65536.0 - 6.0
+
+
+def create_sensor(type: SensorType, bus: int) -> BaseSensor:
+ if type == SensorType.Si7021:
+ return Si7021(bus)
+ elif type == SensorType.DHT12:
+ return DHT12(bus)
+ else:
+ raise ValueError('unexpected sensor type')
diff --git a/src/home/temphum/si7021.py b/src/home/temphum/si7021.py
deleted file mode 100644
index 6289e15..0000000
--- a/src/home/temphum/si7021.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from .base import I2CTempHumSensor
-
-
-class Si7021(I2CTempHumSensor):
- i2c_addr = 0x40
-
- def temperature(self) -> float:
- raw = self.bus.read_i2c_block_data(self.i2c_addr, 0xE3, 2)
- return 175.72 * (raw[0] << 8 | raw[1]) / 65536.0 - 46.85
-
- def humidity(self) -> float:
- raw = self.bus.read_i2c_block_data(self.i2c_addr, 0xE5, 2)
- return 125.0 * (raw[0] << 8 | raw[1]) / 65536.0 - 6.0
diff --git a/src/home/util.py b/src/home/util.py
index 93a9d8f..a721682 100644
--- a/src/home/util.py
+++ b/src/home/util.py
@@ -193,4 +193,11 @@ def filesize_fmt(num, suffix="B") -> str:
class HashableEnum(Enum):
def hash(self) -> int:
- return adler32(self.name.encode()) \ No newline at end of file
+ return adler32(self.name.encode())
+
+
+def next_tick_gen(freq):
+ t = time.time()
+ while True:
+ t += freq
+ yield max(t - time.time(), 0) \ No newline at end of file
diff --git a/src/inverter_mqtt_receiver.py b/src/inverter_mqtt_receiver.py
index d40647e..cc0f1ff 100755
--- a/src/inverter_mqtt_receiver.py
+++ b/src/inverter_mqtt_receiver.py
@@ -2,13 +2,13 @@
import paho.mqtt.client as mqtt
import re
-from home.mqtt import MqttBase
+from home.mqtt import Mqtt
from home.mqtt.payload.inverter import Status, Generation
from home.database import InverterDatabase
from home.config import config
-class MqttReceiver(MqttBase):
+class MqttReceiver(Mqtt):
def __init__(self):
super().__init__(clean_session=False)
self.database = InverterDatabase()
diff --git a/src/inverter_mqtt_sender.py b/src/inverter_mqtt_sender.py
index fb2a2d8..3215d18 100755
--- a/src/inverter_mqtt_sender.py
+++ b/src/inverter_mqtt_sender.py
@@ -5,11 +5,11 @@ import json
import inverterd
from home.config import config
-from home.mqtt import MqttBase, poll_tick
+from home.mqtt import Mqtt, poll_tick
from home.mqtt.payload.inverter import Status, Generation
-class MqttClient(MqttBase):
+class MqttClient(Mqtt):
def __init__(self):
super().__init__()
diff --git a/src/mqtt_node_util.py b/src/mqtt_node_util.py
index 0352c8f..d7ee127 100755
--- a/src/mqtt_node_util.py
+++ b/src/mqtt_node_util.py
@@ -5,19 +5,10 @@ from typing import Optional
from argparse import ArgumentParser, ArgumentError
from home.config import config
-from home.mqtt import MqttNode, get_mqtt_modules, import_mqtt_module, MqttModule
+from home.mqtt import MqttNode, MqttModule, MqttWrapper, get_mqtt_modules
-mqtt: Optional[MqttNode] = None
-
-
-def add_module(module: str) -> MqttModule:
- module = import_mqtt_module(module)
- if not hasattr(module, 'MODULE_NAME'):
- raise RuntimeError(f'MODULE_NAME not found in module {m}')
- cl = getattr(module, getattr(module, 'MODULE_NAME'))
- instance = cl()
- mqtt.add_module(instance)
- return instance
+mqtt_node: Optional[MqttNode] = None
+mqtt: Optional[MqttWrapper] = None
if __name__ == '__main__':
@@ -38,19 +29,22 @@ if __name__ == '__main__':
if (arg.switch_relay is not None or arg.node_secret is not None) and 'relay' not in arg.modules:
raise ArgumentError(None, '--relay is only allowed when \'relay\' module included in --modules')
- mqtt = MqttNode(node_id=arg.node_id)
+ mqtt = MqttWrapper(randomize_client_id=True)
+ mqtt_node = MqttNode(node_id=arg.node_id)
+
+ mqtt.add_node(mqtt_node)
# must-have modules
- ota_module = add_module('ota')
- add_module('diagnostics')
+ ota_module = mqtt_node.load_module('ota')
+ mqtt_node.load_module('diagnostics')
if arg.modules:
for m in arg.modules:
- module_instance = add_module(m)
+ module_instance = mqtt_node.load_module(m)
if m == 'relay' and arg.switch_relay is not None:
if not arg.node_secret:
raise ArgumentError(None, '--switch-relay requires --node-secret')
- module_instance.switchpower(mqtt,
+ module_instance.switchpower(mqtt_node,
arg.switch_relay == 1,
arg.node_secret)
@@ -61,10 +55,10 @@ if __name__ == '__main__':
if arg.push_ota:
if not os.path.exists(arg.push_ota):
raise OSError(f'--push-ota: file \"{arg.push_ota}\" does not exists')
- if not arg.node_secret:
- raise ArgumentError(None, 'pushing OTA requires --node-secret')
+ if not arg.node_secret:
+ raise ArgumentError(None, 'pushing OTA requires --node-secret')
- ota_module.push_ota(arg.node_secret, arg.push_ota, 1)
+ ota_module.push_ota(arg.node_secret, arg.push_ota, 1)
while True:
sleep(0.1)
diff --git a/src/polaris_kettle_bot.py b/src/polaris_kettle_bot.py
index 088707d..8438ab3 100755
--- a/src/polaris_kettle_bot.py
+++ b/src/polaris_kettle_bot.py
@@ -10,7 +10,7 @@ import paho.mqtt.client as mqtt
from home.telegram import bot
from home.api.types import BotType
-from home.mqtt import MqttBase
+from home.mqtt import Mqtt
from home.config import config
from home.util import chunks
from syncleo import (
@@ -204,7 +204,7 @@ class KettleInfo:
class KettleController(threading.Thread,
- MqttBase,
+ Mqtt,
DeviceListener,
IncomingMessageListener,
KettleInfoListener,
@@ -224,7 +224,7 @@ class KettleController(threading.Thread,
def __init__(self):
# basic setup
- MqttBase.__init__(self, clean_session=False)
+ Mqtt.__init__(self, clean_session=False)
threading.Thread.__init__(self)
self._logger = logging.getLogger(self.__class__.__name__)
diff --git a/src/polaris_kettle_util.py b/src/polaris_kettle_util.py
index 81326dd..816cff7 100755
--- a/src/polaris_kettle_util.py
+++ b/src/polaris_kettle_util.py
@@ -8,7 +8,7 @@ import paho.mqtt.client as mqtt
from typing import Optional
from argparse import ArgumentParser
from queue import SimpleQueue
-from home.mqtt import MqttBase
+from home.mqtt import Mqtt
from home.config import config
from syncleo import (
Kettle,
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
control_tasks = SimpleQueue()
-class MqttServer(MqttBase):
+class MqttServer(Mqtt):
def __init__(self):
super().__init__(clean_session=False)
diff --git a/src/pump_bot.py b/src/pump_bot.py
index 48efec4..ab73097 100755
--- a/src/pump_bot.py
+++ b/src/pump_bot.py
@@ -10,16 +10,17 @@ from home.telegram import bot
from home.telegram._botutil import user_any_name
from home.relay.sunxi_h3_client import RelayClient
from home.api.types import BotType
-from home.mqtt import MqttNode, MqttModule, MqttPayload, add_mqtt_module
-from home.mqtt.module.relay import MqttPowerStatusPayload
+from home.mqtt import MqttNode, MqttWrapper, MqttPayload
+from home.mqtt.module.relay import MqttPowerStatusPayload, MqttRelayModule
from home.mqtt.module.temphum import MqttTemphumDataPayload
from home.mqtt.module.diagnostics import InitialDiagnosticsPayload, DiagnosticsPayload
config.load('pump_bot')
-mqtt: Optional[MqttNode] = None
-mqtt_relay_module: Optional[MqttModule] = None
+mqtt: Optional[MqttWrapper] = None
+mqtt_node: Optional[MqttNode] = None
+mqtt_relay_module: Optional[MqttRelayModule] = None
time_format = '%d.%m.%Y, %H:%M:%S'
watering_mcu_status = {
@@ -117,13 +118,13 @@ def off(ctx: bot.Context, silent=False) -> None:
def watering_on(ctx: bot.Context) -> None:
- mqtt_relay_module.switchpower(mqtt, True, config.get('mqtt_water_relay.secret'))
+ mqtt_relay_module.switchpower(True, config.get('mqtt_water_relay.secret'))
ctx.reply(ctx.lang('sent'))
notify(ctx.user, UserAction.WATERING_ON)
def watering_off(ctx: bot.Context) -> None:
- mqtt_relay_module.switchpower(mqtt, False, config.get('mqtt_water_relay.secret'))
+ mqtt_relay_module.switchpower(False, config.get('mqtt_water_relay.secret'))
ctx.reply(ctx.lang('sent'))
notify(ctx.user, UserAction.WATERING_OFF)
@@ -233,14 +234,15 @@ def mqtt_payload_callback(payload: MqttPayload):
if __name__ == '__main__':
- mqtt = MqttNode(node_id=config.get('mqtt_water_relay.node_id'))
+ mqtt = MqttWrapper()
+ mqtt_node = MqttNode(node_id=config.get('mqtt_water_relay.node_id'))
if is_development_mode():
- add_mqtt_module(mqtt, 'diagnostics')
+ mqtt_node.load_module('diagnostics')
- mqtt_relay_module = add_mqtt_module(mqtt, 'temphum')
- mqtt_relay_module = add_mqtt_module(mqtt, 'relay')
+ mqtt_node.load_module('temphum')
+ mqtt_relay_module = mqtt_node.load_module('relay')
- mqtt.add_payload_callback(mqtt_payload_callback)
+ mqtt_node.add_payload_callback(mqtt_payload_callback)
mqtt.configure_tls()
mqtt.connect_and_loop(loop_forever=False)
@@ -252,4 +254,3 @@ if __name__ == '__main__':
mqtt.disconnect()
except:
pass
-
diff --git a/src/sensors_mqtt_receiver.py b/src/sensors_mqtt_receiver.py
index a377ddd..f7cb467 100755
--- a/src/sensors_mqtt_receiver.py
+++ b/src/sensors_mqtt_receiver.py
@@ -2,7 +2,7 @@
import paho.mqtt.client as mqtt
import re
-from home.mqtt import MqttBase
+from home.mqtt import Mqtt
from home.config import config
from home.mqtt.payload.sensors import Temperature
from home.api.types import TemperatureSensorLocation
@@ -16,7 +16,7 @@ def get_sensor_type(sensor: str) -> TemperatureSensorLocation:
raise ValueError(f'unexpected sensor value: {sensor}')
-class MqttServer(MqttBase):
+class MqttServer(Mqtt):
def __init__(self):
super().__init__(clean_session=False)
self.database = SensorsDatabase()
diff --git a/src/sensors_mqtt_sender.py b/src/sensors_mqtt_sender.py
index 87a28ca..393962a 100755
--- a/src/sensors_mqtt_sender.py
+++ b/src/sensors_mqtt_sender.py
@@ -3,12 +3,12 @@ import time
import json
from home.util import parse_addr, MySimpleSocketClient
-from home.mqtt import MqttBase, poll_tick
+from home.mqtt import Mqtt, poll_tick
from home.mqtt.payload.sensors import Temperature
from home.config import config
-class MqttClient(MqttBase):
+class MqttClient(Mqtt):
def __init__(self):
super().__init__(self)
self._home_id = config['mqtt']['home_id']
diff --git a/src/temphum_mqtt_node.py b/src/temphum_mqtt_node.py
index f4d1fca..bcd72da 100755
--- a/src/temphum_mqtt_node.py
+++ b/src/temphum_mqtt_node.py
@@ -6,10 +6,11 @@ import logging
from typing import Optional
from home.config import config
-from home.temphum import SensorType, create_sensor, TempHumSensor
+from home.temphum import SensorType, BaseSensor
+from home.temphum.i2c import create_sensor
logger = logging.getLogger(__name__)
-sensor: Optional[TempHumSensor] = None
+sensor: Optional[BaseSensor] = None
lock = asyncio.Lock()
delay = 0.01
diff --git a/src/temphum_smbus_util.py b/src/temphum_smbus_util.py
index 0f90835..c06bacd 100755
--- a/src/temphum_smbus_util.py
+++ b/src/temphum_smbus_util.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
from argparse import ArgumentParser
-from home.temphum import SensorType, create_sensor
+from home.temphum import SensorType
+from home.temphum.i2c import create_sensor
if __name__ == '__main__':
diff --git a/src/temphumd.py b/src/temphumd.py
index f4d1fca..bcd72da 100755
--- a/src/temphumd.py
+++ b/src/temphumd.py
@@ -6,10 +6,11 @@ import logging
from typing import Optional
from home.config import config
-from home.temphum import SensorType, create_sensor, TempHumSensor
+from home.temphum import SensorType, BaseSensor
+from home.temphum.i2c import create_sensor
logger = logging.getLogger(__name__)
-sensor: Optional[TempHumSensor] = None
+sensor: Optional[BaseSensor] = None
lock = asyncio.Lock()
delay = 0.01
diff --git a/tools/mcuota.py b/tools/mcuota.py
deleted file mode 100755
index 46968a8..0000000
--- a/tools/mcuota.py
+++ /dev/null
@@ -1,98 +0,0 @@
-#!/usr/bin/env python3
-import sys
-import os.path
-sys.path.extend([
- os.path.realpath(
- os.path.join(os.path.dirname(os.path.join(__file__)), '..')
- )
-])
-
-from time import sleep
-from argparse import ArgumentParser
-from src.home.config import config
-from src.home.mqtt import MqttRelay
-from src.home.mqtt.esp import MqttEspDevice
-
-
-def guess_filename(product: str, build_target: str):
- return os.path.join(
- products_dir,
- product,
- '.pio',
- 'build',
- build_target,
- 'firmware.bin'
- )
-
-
-def relayctl_publish_ota(filename: str,
- device_id: str,
- home_secret: str,
- qos: int):
- global stop
-
- def published():
- global stop
- stop = True
-
- mqtt_relay = MqttRelay(devices=MqttEspDevice(id=device_id, secret=home_secret))
- mqtt_relay.configure_tls()
- mqtt_relay.connect_and_loop(loop_forever=False)
- mqtt_relay.push_ota(device_id, filename, published, qos)
- while not stop:
- sleep(0.1)
- mqtt_relay.disconnect()
-
-
-stop = False
-products = {
- 'relayctl': {
- 'build_target': 'esp12e',
- 'callback': relayctl_publish_ota
- }
-}
-
-products_dir = os.path.join(
- os.path.dirname(__file__),
- '..',
- 'platformio'
-)
-
-
-def main():
- parser = ArgumentParser()
- parser.add_argument('--filename', type=str)
- parser.add_argument('--device-id', type=str, required=True)
- parser.add_argument('--product', type=str, required=True)
- parser.add_argument('--qos', type=int, default=1)
-
- config.load('mcuota_push', parser=parser)
- arg = parser.parse_args()
-
- if arg.product not in products:
- raise ValueError(f'invalid product: \'{arg.product}\' not found')
-
- if arg.device_id not in config['mqtt']['home_secrets']:
- raise ValueError(f'home_secret for home {arg.device_id} not found in config!')
-
- filename = arg.filename if arg.filename else guess_filename(arg.product, products[arg.product]['build_target'])
- if not os.path.exists(filename):
- raise OSError(f'file \'{filename}\' does not exists')
-
- print('Please confirm following OTA params.')
- print('')
- print(f' Device ID: {arg.device_id}')
- print(f' Product: {arg.product}')
- print(f'Firmware file: {filename}')
- print('')
- input('Press any key to continue or Ctrl+C to abort.')
-
- products[arg.product]['callback'](filename, arg.device_id, config['mqtt']['home_secrets'][arg.device_id], qos=arg.qos)
-
-
-if __name__ == '__main__':
- try:
- main()
- except Exception as e:
- print(str(e), file=sys.stderr)
- sys.exit(1)
diff --git a/tools/mcuota.sh b/tools/mcuota.sh
deleted file mode 100755
index b2e7910..0000000
--- a/tools/mcuota.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/bash
-
-DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
-
-. "$DIR/lib.bash"
-
-if [ -d "$DIR/../venv" ]; then
- echoinfo "activating python venv"
- . "$DIR/../venv/bin/activate"
-else
- echowarn "python venv not found"
-fi
-
-"$DIR/mcuota.py" "$@" \ No newline at end of file