diff options
author | Evgeny Zinoviev <me@ch1p.io> | 2024-01-10 03:20:10 +0300 |
---|---|---|
committer | Evgeny Zinoviev <me@ch1p.io> | 2024-01-10 03:20:10 +0300 |
commit | 05c5d18f7619c28e620d42c0921f81ced780cc2d (patch) | |
tree | 26ab7a74daba5d0f37ddb0bbe467e84fdc7fb1b6 | |
parent | 54ddea4614dbd31dad577ae5fdb8ec4821490199 (diff) |
save
-rwxr-xr-x | bin/mqtt_node_util.py | 5 | ||||
-rw-r--r-- | bin/web_kbn.py | 22 | ||||
-rw-r--r-- | include/py/homekit/config/config.py | 19 | ||||
-rw-r--r-- | include/py/homekit/modem/config.py | 28 | ||||
-rw-r--r-- | include/py/homekit/mqtt/_config.py | 20 | ||||
-rw-r--r-- | include/py/homekit/mqtt/module/temphum.py | 39 | ||||
-rw-r--r-- | include/py/homekit/util.py | 31 | ||||
-rw-r--r-- | web/kbn_templates/base.html | 25 | ||||
-rw-r--r-- | web/kbn_templates/base.j2 | 44 | ||||
-rw-r--r-- | web/kbn_templates/index.j2 (renamed from web/kbn_templates/index.html) | 14 | ||||
-rw-r--r-- | web/kbn_templates/loading.j2 | 14 | ||||
-rw-r--r-- | web/kbn_templates/modems.j2 | 12 |
12 files changed, 191 insertions, 82 deletions
diff --git a/bin/mqtt_node_util.py b/bin/mqtt_node_util.py index c1d457c..5587739 100755 --- a/bin/mqtt_node_util.py +++ b/bin/mqtt_node_util.py @@ -48,7 +48,6 @@ if __name__ == '__main__': help='mqtt modules to include') parser.add_argument('--switch-relay', choices=[0, 1], type=int, help='send relay state') - parser.add_argument('--legacy-relay', action='store_true') parser.add_argument('--push-ota', type=str, metavar='OTA_FILENAME', help='push OTA, receives path to firmware.bin') parser.add_argument('--no-wait', action='store_true', @@ -80,8 +79,10 @@ if __name__ == '__main__': if arg.modules: for m in arg.modules: kwargs = {} - if m == 'relay' and arg.legacy_relay: + if m == 'relay' and MqttNodesConfig().node_uses_legacy_relay_power_payload(arg.node_id): kwargs['legacy_topics'] = True + if m == 'temphum' and MqttNodesConfig().node_uses_legacy_temphum_data_payload(arg.node_id): + kwargs['legacy_payload'] = True module_instance = mqtt_node.load_module(m, **kwargs) if m == 'relay' and arg.switch_relay is not None: relay_module = module_instance diff --git a/bin/web_kbn.py b/bin/web_kbn.py index e160fde..8b4ca6f 100644 --- a/bin/web_kbn.py +++ b/bin/web_kbn.py @@ -75,27 +75,35 @@ class WebSite(http.HTTPServer): self.app.router.add_static('/assets/', path=homekit_path('web', 'kbn_assets')) - self.get('/', self.get_index) - self.get('/modems', self.get_modems) + self.get('/main.cgi', self.get_index) + self.get('/modems.cgi', self.get_modems) async def render_page(self, req: http.Request, + template_name: str, + title: Optional[str] = None, context: Optional[dict] = None): if context is None: context = {} context = { **context, - 'head_static': get_head_static(), - 'title': 'this is title' + 'head_static': get_head_static() } - response = aiohttp_jinja2.render_template('index.html', req, context=context) + if title is not None: + context['title'] = title + response = aiohttp_jinja2.render_template(template_name+'.j2', req, context=context) return response async def get_index(self, req: http.Request): - return await self.render_page(req) + return await self.render_page(req, 'index', + title="Home web site") async def get_modems(self, req: http.Request): - pass + mc = ModemsConfig() + print(mc) + return await self.render_page(req, 'modems', + title='Состояние модемов', + context=dict(modems=ModemsConfig())) if __name__ == '__main__': diff --git a/include/py/homekit/config/config.py b/include/py/homekit/config/config.py index d424888..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): diff --git a/include/py/homekit/modem/config.py b/include/py/homekit/modem/config.py index 039d759..16d1ba0 100644 --- a/include/py/homekit/modem/config.py +++ b/include/py/homekit/modem/config.py @@ -1,5 +1,29 @@ -from ..config import ConfigUnit +from ..config import ConfigUnit, Translation +from typing import Optional class ModemsConfig(ConfigUnit): - pass + 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 e5f2c56..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': { @@ -109,7 +110,12 @@ class MqttNodesConfig(ConfigUnit): '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/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/util.py b/include/py/homekit/util.py index 2680c37..3c73440 100644 --- a/include/py/homekit/util.py +++ b/include/py/homekit/util.py @@ -53,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) @@ -74,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 diff --git a/web/kbn_templates/base.html b/web/kbn_templates/base.html deleted file mode 100644 index 43f7d2a..0000000 --- a/web/kbn_templates/base.html +++ /dev/null @@ -1,25 +0,0 @@ -<!doctype html> -<html> -<head> - <title>{{ title }}</title> - <meta http-equiv="content-type" content="text/html; charset=utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> - <script> - window.onerror = function(error) { - window.console && console.error(error); - } - </script> - {{ head_static | safe }} -</head> -<body> -<div class="container py-3"> - -{% block content %} {% endblock %} - -{% if js %} -<script>{{ js|raw }}</script> -{% endif %} - -</div> -</body> -</html> diff --git a/web/kbn_templates/base.j2 b/web/kbn_templates/base.j2 new file mode 100644 index 0000000..d43a08b --- /dev/null +++ b/web/kbn_templates/base.j2 @@ -0,0 +1,44 @@ +{% macro breadcrumbs(history) %} + <nav aria-label="breadcrumb"> + <ol class="breadcrumb"> + <li class="breadcrumb-item"><a href="main.cgi">Главная</a></li> + {% for item in history %} + <li class="breadcrumb-item"{% if loop.last %} aria-current="page"{% endif %}> + {% if item.link %}<a href="{{ item.link }}">{% endif %} + {% if item.html %} + {% raw %}{{ item.html }}{% endraw %} + {% else %} + {{ item.text }} + {% endif %} + {% if item.link %}</a>{% endif %} + </li> + {% endfor %} + </ol> + </nav> +{% endmacro %} + +<!doctype html> +<html> +<head> + <title>{{ title }}</title> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> + <script> + window.onerror = function(error) { + window.console && console.error(error); + } + </script> + {{ head_static | safe }} +</head> +<body> +<div class="container py-3"> + +{% block content %}{% endblock %} + +{% if js %} +<script>{{ js|raw }}</script> +{% endif %} + +</div> +</body> +</html> diff --git a/web/kbn_templates/index.html b/web/kbn_templates/index.j2 index 1921b87..e3ab421 100644 --- a/web/kbn_templates/index.html +++ b/web/kbn_templates/index.j2 @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "base.j2" %} {% block content %} <div class="container py-4"> @@ -16,16 +16,16 @@ <h6>Интернет</h6> <ul class="list-group list-group-flush"> - <li class="list-group-item"><a href="/modem/">Модемы</a></li> - <li class="list-group-item"><a href="/routing/">Маршрутизация</a></li> - <li class="list-group-item"><a href="/sms/">SMS-сообщения</a></li> + <li class="list-group-item"><a href="/modems.cgi">Модемы</a></li> + <li class="list-group-item"><a href="/routing.cgi">Маршрутизация</a></li> + <li class="list-group-item"><a href="/sms.cgi">SMS-сообщения</a></li> </ul> <h6 class="mt-4">Другое</h6> <ul class="list-group list-group-flush"> - <li class="list-group-item"><a href="/inverter/">Инвертор</a> (<a href="{{ grafana_inverter_url }}">Grafana</a>)</li> - <li class="list-group-item"><a href="/pump/">Насос</a></li> - <li class="list-group-item"><a href="/sensors/">Датчики</a> (<a href="{{ grafana_sensors_url }}">Grafana</a>)</li> + <li class="list-group-item"><a href="/inverter.cgi">Инвертор</a> (<a href="{{ grafana_inverter_url }}">Grafana</a>)</li> + <li class="list-group-item"><a href="/pump.cgi">Насос</a></li> + <li class="list-group-item"><a href="/sensors.cgi">Датчики</a> (<a href="{{ grafana_sensors_url }}">Grafana</a>)</li> </ul> <h6 class="mt-4"><a href="/cams/"><b>Все камеры</b></a> (<a href="/cams/?high=1">HQ</a>)</h6> diff --git a/web/kbn_templates/loading.j2 b/web/kbn_templates/loading.j2 new file mode 100644 index 0000000..d064a48 --- /dev/null +++ b/web/kbn_templates/loading.j2 @@ -0,0 +1,14 @@ +<div class="sk-fading-circle"> + <div class="sk-circle1 sk-circle"></div> + <div class="sk-circle2 sk-circle"></div> + <div class="sk-circle3 sk-circle"></div> + <div class="sk-circle4 sk-circle"></div> + <div class="sk-circle5 sk-circle"></div> + <div class="sk-circle6 sk-circle"></div> + <div class="sk-circle7 sk-circle"></div> + <div class="sk-circle8 sk-circle"></div> + <div class="sk-circle9 sk-circle"></div> + <div class="sk-circle10 sk-circle"></div> + <div class="sk-circle11 sk-circle"></div> + <div class="sk-circle12 sk-circle"></div> +</div>
\ No newline at end of file diff --git a/web/kbn_templates/modems.j2 b/web/kbn_templates/modems.j2 new file mode 100644 index 0000000..f148140 --- /dev/null +++ b/web/kbn_templates/modems.j2 @@ -0,0 +1,12 @@ +{% extends "base.j2" %} + +{% block content %} +{{ breadcrumbs([{'text': 'Модемы'}]) }} + +{% for modem in modems %} +<h6 class="text-primary{% if not loop.first %} mt-4{% endif %}">{{ modems.getfullname(modem) }}</h6> +<div id="modem_data_{{ modem }}"> + {% include "loading.j2" %} +</div> +{% endfor %} +{% endblock %}
\ No newline at end of file |