summaryrefslogtreecommitdiff
path: root/include
diff options
context:
space:
mode:
Diffstat (limited to 'include')
-rw-r--r--include/py/homekit/config/config.py21
-rw-r--r--include/py/homekit/modem/__init__.py1
-rw-r--r--include/py/homekit/modem/config.py29
-rw-r--r--include/py/homekit/mqtt/_config.py22
-rw-r--r--include/py/homekit/mqtt/_wrapper.py21
-rw-r--r--include/py/homekit/mqtt/module/relay.py3
-rw-r--r--include/py/homekit/mqtt/module/temphum.py39
-rw-r--r--include/py/homekit/pio/products.py3
-rw-r--r--include/py/homekit/util.py40
9 files changed, 134 insertions, 45 deletions
diff --git a/include/py/homekit/config/config.py b/include/py/homekit/config/config.py
index 5fe1ae8..abdedad 100644
--- a/include/py/homekit/config/config.py
+++ b/include/py/homekit/config/config.py
@@ -41,6 +41,9 @@ class BaseConfigUnit(ABC):
self._data = {}
self._logger = logging.getLogger(self.__class__.__name__)
+ def __iter__(self):
+ return iter(self._data)
+
def __getitem__(self, key):
return self._data[key]
@@ -123,10 +126,10 @@ class ConfigUnit(BaseConfigUnit):
return None
@classmethod
- def _addr_schema(cls, required=False, **kwargs):
+ def _addr_schema(cls, required=False, only_ip=False, **kwargs):
return {
'type': 'addr',
- 'coerce': Addr.fromstring,
+ 'coerce': Addr.fromstring if not only_ip else Addr.fromipstring,
'required': required,
**kwargs
}
@@ -158,6 +161,7 @@ class ConfigUnit(BaseConfigUnit):
pass
v = MyValidator()
+ need_document = False
if rst == RootSchemaType.DICT:
normalized = v.validated({'document': self._data},
@@ -165,16 +169,21 @@ class ConfigUnit(BaseConfigUnit):
'type': 'dict',
'keysrules': {'type': 'string'},
'valuesrules': schema
- }})['document']
+ }})
+ need_document = True
elif rst == RootSchemaType.LIST:
v = MyValidator()
- normalized = v.validated({'document': self._data}, {'document': schema})['document']
+ normalized = v.validated({'document': self._data}, {'document': schema})
+ need_document = True
else:
normalized = v.validated(self._data, schema)
if not normalized:
raise cerberus.DocumentError(f'validation failed: {v.errors}')
+ if need_document:
+ normalized = normalized['document']
+
self._data = normalized
try:
@@ -235,6 +244,8 @@ class TranslationUnit(BaseConfigUnit):
class Translation:
LANGUAGES = ('en', 'ru')
+ DEFAULT_LANGUAGE = 'ru'
+
_langs: dict[str, TranslationUnit]
def __init__(self, name: str):
@@ -278,9 +289,7 @@ class Config:
and not isinstance(name, bool) \
and issubclass(name, AppConfigUnit) or name == AppConfigUnit:
self.app_name = name.NAME
- print(self.app_config)
self.app_config = name()
- print(self.app_config)
app_config = self.app_config
else:
self.app_name = name if isinstance(name, str) else None
diff --git a/include/py/homekit/modem/__init__.py b/include/py/homekit/modem/__init__.py
new file mode 100644
index 0000000..20e75b7
--- /dev/null
+++ b/include/py/homekit/modem/__init__.py
@@ -0,0 +1 @@
+from .config import ModemsConfig \ No newline at end of file
diff --git a/include/py/homekit/modem/config.py b/include/py/homekit/modem/config.py
new file mode 100644
index 0000000..16d1ba0
--- /dev/null
+++ b/include/py/homekit/modem/config.py
@@ -0,0 +1,29 @@
+from ..config import ConfigUnit, Translation
+from typing import Optional
+
+
+class ModemsConfig(ConfigUnit):
+ NAME = 'modems'
+
+ _strings: Translation
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._strings = Translation('modems')
+
+ @classmethod
+ def schema(cls) -> Optional[dict]:
+ return {
+ 'type': 'dict',
+ 'schema': {
+ 'ip': cls._addr_schema(required=True, only_ip=True),
+ 'gateway_ip': cls._addr_schema(required=False, only_ip=True),
+ 'legacy_auth': {'type': 'boolean', 'required': True}
+ }
+ }
+
+ def getshortname(self, modem: str, lang=Translation.DEFAULT_LANGUAGE):
+ return self._strings.get(lang)[modem]['short']
+
+ def getfullname(self, modem: str, lang=Translation.DEFAULT_LANGUAGE):
+ return self._strings.get(lang)[modem]['full'] \ No newline at end of file
diff --git a/include/py/homekit/mqtt/_config.py b/include/py/homekit/mqtt/_config.py
index 9ba9443..8aa3bfe 100644
--- a/include/py/homekit/mqtt/_config.py
+++ b/include/py/homekit/mqtt/_config.py
@@ -92,6 +92,7 @@ class MqttNodesConfig(ConfigUnit):
'type': 'dict',
'schema': {
'module': {'type': 'string', 'required': True, 'allowed': ['si7021', 'dht12']},
+ 'legacy_payload': {'type': 'boolean', 'required': False, 'default': False},
'interval': {'type': 'integer'},
'i2c_bus': {'type': 'integer'},
'tcpserver': {
@@ -105,11 +106,16 @@ class MqttNodesConfig(ConfigUnit):
'relay': {
'type': 'dict',
'schema': {
- 'device_type': {'type': 'string', 'allowed': ['lamp', 'pump', 'solenoid'], 'required': True},
+ 'device_type': {'type': 'string', 'allowed': ['lamp', 'pump', 'solenoid', 'cooler'], 'required': True},
'legacy_topics': {'type': 'boolean'}
}
},
- 'password': {'type': 'string'}
+ 'password': {'type': 'string'},
+ 'defines': {
+ 'type': 'dict',
+ 'keysrules': {'type': 'string'},
+ 'valuesrules': {'type': ['string', 'integer']}
+ }
}
}
}
@@ -163,3 +169,15 @@ class MqttNodesConfig(ConfigUnit):
else:
resdict[name] = node
return reslist if only_names else resdict
+
+ def node_uses_legacy_temphum_data_payload(self, node_id: str) -> bool:
+ try:
+ return self.get_node(node_id)['temphum']['legacy_payload']
+ except KeyError:
+ return False
+
+ def node_uses_legacy_relay_power_payload(self, node_id: str) -> bool:
+ try:
+ return self.get_node(node_id)['relay']['legacy_topics']
+ except KeyError:
+ return False
diff --git a/include/py/homekit/mqtt/_wrapper.py b/include/py/homekit/mqtt/_wrapper.py
index 3c2774c..5fc33fe 100644
--- a/include/py/homekit/mqtt/_wrapper.py
+++ b/include/py/homekit/mqtt/_wrapper.py
@@ -7,6 +7,8 @@ from ..util import strgen
class MqttWrapper(Mqtt):
_nodes: list[MqttNode]
+ _connect_callbacks: list[callable]
+ _disconnect_callbacks: list[callable]
def __init__(self,
client_id: str,
@@ -18,17 +20,30 @@ class MqttWrapper(Mqtt):
super().__init__(clean_session=clean_session,
client_id=client_id)
self._nodes = []
+ self._connect_callbacks = []
+ self._disconnect_callbacks = []
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)
+ for f in self._connect_callbacks:
+ try:
+ f()
+ except Exception as e:
+ self._logger.exception(e)
def on_disconnect(self, client: mqtt.Client, userdata, rc):
super().on_disconnect(client, userdata, rc)
for node in self._nodes:
node.on_disconnect()
+ for f in self._disconnect_callbacks:
+ try:
+ f()
+ except Exception as e:
+ self._logger.exception(e)
+
def on_message(self, client: mqtt.Client, userdata, msg):
try:
@@ -40,6 +55,12 @@ class MqttWrapper(Mqtt):
except Exception as e:
self._logger.exception(str(e))
+ def add_connect_callback(self, f: callable):
+ self._connect_callbacks.append(f)
+
+ def add_disconnect_callback(self, f: callable):
+ self._disconnect_callbacks.append(f)
+
def add_node(self, node: MqttNode):
self._nodes.append(node)
if self._connected:
diff --git a/include/py/homekit/mqtt/module/relay.py b/include/py/homekit/mqtt/module/relay.py
index e968031..5cbe09b 100644
--- a/include/py/homekit/mqtt/module/relay.py
+++ b/include/py/homekit/mqtt/module/relay.py
@@ -69,8 +69,7 @@ class MqttRelayModule(MqttModule):
mqtt.subscribe_module(self._get_switch_topic(), self)
mqtt.subscribe_module('relay/status', self)
- def switchpower(self,
- enable: bool):
+ def switchpower(self, enable: bool):
payload = MqttPowerSwitchPayload(secret=self._mqtt_node_ref.secret,
state=enable)
self._mqtt_node_ref.publish(self._get_switch_topic(),
diff --git a/include/py/homekit/mqtt/module/temphum.py b/include/py/homekit/mqtt/module/temphum.py
index fd02cca..6deccfe 100644
--- a/include/py/homekit/mqtt/module/temphum.py
+++ b/include/py/homekit/mqtt/module/temphum.py
@@ -10,8 +10,8 @@ MODULE_NAME = 'MqttTempHumModule'
DATA_TOPIC = 'temphum/data'
-class MqttTemphumDataPayload(MqttPayload):
- FORMAT = '=ddb'
+class MqttTemphumLegacyDataPayload(MqttPayload):
+ FORMAT = '=dd'
UNPACKER = {
'temp': two_digits_precision,
'rh': two_digits_precision
@@ -19,39 +19,26 @@ class MqttTemphumDataPayload(MqttPayload):
temp: float
rh: float
- error: int
-# 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()
-#
-# KBN_NH_1FL_INV = auto()
-# KBN_NH_1FL_CENTER = auto()
-# KBN_NH_1LF_KT = auto()
-# KBN_NH_1FL_DS = auto()
-# KBN_NH_1FS_EZ = auto()
-#
-# SPB_FLAT120_CABINET = auto()
+class MqttTemphumDataPayload(MqttTemphumLegacyDataPayload):
+ FORMAT = '=ddb'
+ error: int
class MqttTempHumModule(MqttModule):
+ _legacy_payload: bool
+
def __init__(self,
sensor: Optional[BaseSensor] = None,
+ legacy_payload=False,
write_to_database=False,
*args, **kwargs):
if sensor is not None:
kwargs['tick_interval'] = 10
super().__init__(*args, **kwargs)
self._sensor = sensor
+ self._legacy_payload = legacy_payload
def on_connect(self, mqtt: MqttNode):
super().on_connect(mqtt)
@@ -69,7 +56,7 @@ class MqttTempHumModule(MqttModule):
rh = self._sensor.humidity()
except:
error = 1
- pld = MqttTemphumDataPayload(temp=temp, rh=rh, error=error)
+ pld = self._get_data_payload_cls()(temp=temp, rh=rh, error=error)
self._mqtt_node_ref.publish(DATA_TOPIC, pld.pack())
def handle_payload(self,
@@ -77,6 +64,10 @@ class MqttTempHumModule(MqttModule):
topic: str,
payload: bytes) -> Optional[MqttPayload]:
if topic == DATA_TOPIC:
- message = MqttTemphumDataPayload.unpack(payload)
+ message = self._get_data_payload_cls().unpack(payload)
self._logger.debug(message)
return message
+
+ def _get_data_payload_cls(self):
+ return MqttTemphumLegacyDataPayload if self._legacy_payload else MqttTemphumDataPayload
+
diff --git a/include/py/homekit/pio/products.py b/include/py/homekit/pio/products.py
index a0e7a1f..5b40aae 100644
--- a/include/py/homekit/pio/products.py
+++ b/include/py/homekit/pio/products.py
@@ -3,6 +3,7 @@ import logging
from io import StringIO
from collections import OrderedDict
+from ..mqtt import MqttNodesConfig
_logger = logging.getLogger(__name__)
@@ -37,6 +38,8 @@ def platformio_ini(product_config: dict,
debug=False,
debug_network=False) -> str:
node_id = build_specific_defines['CONFIG_NODE_ID']
+ if node_id not in MqttNodesConfig().get_nodes().keys():
+ raise ValueError(f'node id "{node_id}" is not specified in the config!')
# defines
defines = {
diff --git a/include/py/homekit/util.py b/include/py/homekit/util.py
index 22bba86..3c73440 100644
--- a/include/py/homekit/util.py
+++ b/include/py/homekit/util.py
@@ -9,6 +9,7 @@ import logging
import string
import random
import re
+import os
from enum import Enum
from datetime import datetime
@@ -52,17 +53,21 @@ class Addr:
self.host = host
self.port = port
- @staticmethod
- def fromstring(addr: str) -> Addr:
- colons = addr.count(':')
- if colons != 1:
- raise ValueError('invalid host:port format')
-
- if not colons:
- host = addr
- port = None
+ @classmethod
+ def fromstring(cls, addr: str, port_required=True) -> Addr:
+ if port_required:
+ colons = addr.count(':')
+ if colons != 1:
+ raise ValueError('invalid host:port format')
+
+ if not colons:
+ host = addr
+ port = None
+ else:
+ host, port = addr.split(':')
else:
- host, port = addr.split(':')
+ port = None
+ host = addr
validate_ipv4_or_hostname(host, raise_exception=True)
@@ -73,12 +78,19 @@ class Addr:
return Addr(host, port)
+ @classmethod
+ def fromipstring(cls, addr: str) -> Addr:
+ return cls.fromstring(addr, port_required=False)
+
def __str__(self):
buf = self.host
if self.port is not None:
buf += ':'+str(self.port)
return buf
+ def __repr__(self):
+ return self.__str__()
+
def __iter__(self):
yield self.host
yield self.port
@@ -252,4 +264,10 @@ def next_tick_gen(freq):
t = time.time()
while True:
t += freq
- yield max(t - time.time(), 0) \ No newline at end of file
+ yield max(t - time.time(), 0)
+
+
+def homekit_path(*args) -> str:
+ return os.path.realpath(
+ os.path.join(os.path.dirname(__file__), '..', '..', '..', *args)
+ )