From 405a17a9fdd420faa7af90f769e72eb21fda73ce Mon Sep 17 00:00:00 2001 From: Evgeny Zinoviev Date: Wed, 13 Sep 2023 09:34:49 +0300 Subject: [PATCH 1/4] save --- bin/web_kbn.py | 58 +++++++++++++++++++++++++++++ include/py/homekit/config/config.py | 2 - include/py/homekit/util.py | 9 ++++- requirements.txt | 5 ++- web/kbn_templates/base.html | 23 ++++++++++++ 5 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 bin/web_kbn.py create mode 100644 web/kbn_templates/base.html diff --git a/bin/web_kbn.py b/bin/web_kbn.py new file mode 100644 index 0000000..b66e2a5 --- /dev/null +++ b/bin/web_kbn.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +import asyncio +import jinja2 +import aiohttp_jinja2 +import os +import __py_include + +from typing import Optional +from homekit.config import config, AppConfigUnit +from homekit.util import homekit_path +from aiohttp import web +from homekit import http + + +class WebKbnConfig(AppConfigUnit): + NAME = 'web_kbn' + + @classmethod + def schema(cls) -> Optional[dict]: + return { + 'listen_addr': cls._addr_schema(required=True) + } + + +class WebSite(http.HTTPServer): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + aiohttp_jinja2.setup( + self.app, + loader=jinja2.FileSystemLoader(homekit_path('web', 'kbn_templates')) + ) + + self.get('/', self.get_index) + + @staticmethod + async def get_index(req: http.Request): + # context = { + # 'username': request.match_info.get("username", ""), + # 'current_date': 'January 27, 2017' + # } + # response = aiohttp_jinja2.render_template("example.html", request, + # context=context) + # return response + + message = "nothing here, keep lurking" + return http.Response(text=message, content_type='text/plain') + + +if __name__ == '__main__': + config.load_app(WebKbnConfig) + + loop = asyncio.get_event_loop() + # print(config.app_config) + + print(config.app_config['listen_addr'].host) + server = WebSite(config.app_config['listen_addr']) + server.run() diff --git a/include/py/homekit/config/config.py b/include/py/homekit/config/config.py index 773de1e..7d30a77 100644 --- a/include/py/homekit/config/config.py +++ b/include/py/homekit/config/config.py @@ -277,9 +277,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/util.py b/include/py/homekit/util.py index 22bba86..2680c37 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 @@ -252,4 +253,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) + ) diff --git a/requirements.txt b/requirements.txt index 521ae41..66e8379 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,4 +17,7 @@ cerberus~=1.3.4 # following can be installed from debian repositories # matplotlib~=3.5.0 -Pillow==9.5.0 \ No newline at end of file +Pillow==9.5.0 + +jinja2~=3.1.2 +aiohttp-jinja2~=1.5.1 \ No newline at end of file diff --git a/web/kbn_templates/base.html b/web/kbn_templates/base.html new file mode 100644 index 0000000..e567a90 --- /dev/null +++ b/web/kbn_templates/base.html @@ -0,0 +1,23 @@ + + + + {{ title }} + + + + {{ head_static }} + + +
+ +{% if js %} + +{% endif %} + +
+ + From 3623e770b6b25fcaa1c8d76b9d3dafefec480876 Mon Sep 17 00:00:00 2001 From: Evgeny Zinoviev Date: Sun, 24 Sep 2023 02:49:12 +0300 Subject: [PATCH 2/4] mqtt: various fixes --- bin/mqtt_node_util.py | 51 ++++++++++++++++++++----- include/py/homekit/mqtt/_config.py | 2 +- include/py/homekit/mqtt/_wrapper.py | 21 ++++++++++ include/py/homekit/mqtt/module/relay.py | 3 +- include/py/homekit/pio/products.py | 3 ++ 5 files changed, 67 insertions(+), 13 deletions(-) diff --git a/bin/mqtt_node_util.py b/bin/mqtt_node_util.py index cf451fd..c1d457c 100755 --- a/bin/mqtt_node_util.py +++ b/bin/mqtt_node_util.py @@ -7,12 +7,37 @@ from typing import Optional from argparse import ArgumentParser, ArgumentError from homekit.config import config -from homekit.mqtt import MqttNode, MqttWrapper, get_mqtt_modules -from homekit.mqtt import MqttNodesConfig +from homekit.mqtt import MqttNode, MqttWrapper, get_mqtt_modules, MqttNodesConfig +from homekit.mqtt.module.relay import MqttRelayModule +from homekit.mqtt.module.ota import MqttOtaModule mqtt_node: Optional[MqttNode] = None mqtt: Optional[MqttWrapper] = None +relay_module: Optional[MqttOtaModule] = None +relay_val = None + +ota_module: Optional[MqttRelayModule] = None +ota_val = False + +no_wait = False +stop_loop = False + + +def on_mqtt_connect(): + global stop_loop + + if relay_module: + relay_module.switchpower(relay_val == 1) + + if ota_val: + if not os.path.exists(arg.push_ota): + raise OSError(f'--push-ota: file \"{arg.push_ota}\" does not exists') + ota_module.push_ota(arg.push_ota, 1) + + if no_wait: + stop_loop = True + if __name__ == '__main__': nodes_config = MqttNodesConfig() @@ -26,15 +51,21 @@ if __name__ == '__main__': 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', + help='execute command and exit') config.load_app(parser=parser, no_config=True) arg = parser.parse_args() + if arg.no_wait: + no_wait = True + if arg.switch_relay is not None and 'relay' not in arg.modules: raise ArgumentError(None, '--relay is only allowed when \'relay\' module included in --modules') mqtt = MqttWrapper(randomize_client_id=True, client_id='mqtt_node_util') + mqtt.add_connect_callback(on_mqtt_connect) mqtt_node = MqttNode(node_id=arg.node_id, node_secret=nodes_config.get_node(arg.node_id)['password']) @@ -42,6 +73,8 @@ if __name__ == '__main__': # must-have modules ota_module = mqtt_node.load_module('ota') + ota_val = arg.push_ota + mqtt_node.load_module('diagnostics') if arg.modules: @@ -51,18 +84,16 @@ if __name__ == '__main__': kwargs['legacy_topics'] = True module_instance = mqtt_node.load_module(m, **kwargs) if m == 'relay' and arg.switch_relay is not None: - module_instance.switchpower(arg.switch_relay == 1) + relay_module = module_instance + relay_val = arg.switch_relay try: mqtt.connect_and_loop(loop_forever=False) - - if arg.push_ota: - if not os.path.exists(arg.push_ota): - raise OSError(f'--push-ota: file \"{arg.push_ota}\" does not exists') - ota_module.push_ota(arg.push_ota, 1) - - while True: + while not stop_loop: sleep(0.1) except KeyboardInterrupt: + pass + + finally: mqtt.disconnect() diff --git a/include/py/homekit/mqtt/_config.py b/include/py/homekit/mqtt/_config.py index 9ba9443..e5f2c56 100644 --- a/include/py/homekit/mqtt/_config.py +++ b/include/py/homekit/mqtt/_config.py @@ -105,7 +105,7 @@ 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'} } }, 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/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 = { From 54ddea4614dbd31dad577ae5fdb8ec4821490199 Mon Sep 17 00:00:00 2001 From: Evgeny Zinoviev Date: Sun, 24 Sep 2023 03:35:51 +0300 Subject: [PATCH 3/4] save --- bin/web_kbn.py | 75 +++++++++++++++--- include/py/homekit/modem/__init__.py | 1 + include/py/homekit/modem/config.py | 5 ++ localwebsite/handlers/ModemHandler.php | 6 -- localwebsite/htdocs/assets/modem.js | 29 ------- .../htdocs/assets => web/kbn_assets}/app.css | 0 .../htdocs/assets => web/kbn_assets}/app.js | 32 +++++++- .../kbn_assets}/bootstrap.min.css | 0 .../kbn_assets}/bootstrap.min.js | 0 .../h265webjs-v20221106-reminified.js | 0 .../h265webjs-dist/h265webjs-v20221106.js | 0 .../missile-120func-v20221120.js | 0 .../missile-120func-v20221120.wasm | Bin .../h265webjs-dist/missile-120func.js | 0 .../h265webjs-dist/missile-256mb-v20221120.js | 0 .../missile-256mb-v20221120.wasm | Bin .../h265webjs-dist/missile-256mb.js | 0 .../h265webjs-dist/missile-512mb-v20221120.js | 0 .../missile-512mb-v20221120.wasm | Bin .../h265webjs-dist/missile-512mb.js | 0 .../h265webjs-dist/missile-format.js | 0 .../h265webjs-dist/missile-v20221120.js | 0 .../h265webjs-dist/missile-v20221120.wasm | Bin .../kbn_assets}/h265webjs-dist/missile.js | 0 .../kbn_assets}/h265webjs-dist/raw-parser.js | 0 .../h265webjs-dist/worker-fetch-dist.js | 0 .../h265webjs-dist/worker-parse-dist.js | 0 .../htdocs/assets => web/kbn_assets}/hls.js | 0 .../assets => web/kbn_assets}/inverter.js | 0 .../assets => web/kbn_assets}/polyfills.js | 0 web/kbn_templates/base.html | 4 +- web/kbn_templates/index.html | 39 +++++++++ 32 files changed, 142 insertions(+), 49 deletions(-) create mode 100644 include/py/homekit/modem/__init__.py create mode 100644 include/py/homekit/modem/config.py delete mode 100644 localwebsite/htdocs/assets/modem.js rename {localwebsite/htdocs/assets => web/kbn_assets}/app.css (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/app.js (94%) rename {localwebsite/htdocs/assets => web/kbn_assets}/bootstrap.min.css (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/bootstrap.min.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/h265webjs-v20221106-reminified.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/h265webjs-v20221106.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/missile-120func-v20221120.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/missile-120func-v20221120.wasm (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/missile-120func.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/missile-256mb-v20221120.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/missile-256mb-v20221120.wasm (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/missile-256mb.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/missile-512mb-v20221120.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/missile-512mb-v20221120.wasm (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/missile-512mb.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/missile-format.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/missile-v20221120.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/missile-v20221120.wasm (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/missile.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/raw-parser.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/worker-fetch-dist.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/h265webjs-dist/worker-parse-dist.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/hls.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/inverter.js (100%) rename {localwebsite/htdocs/assets => web/kbn_assets}/polyfills.js (100%) create mode 100644 web/kbn_templates/index.html diff --git a/bin/web_kbn.py b/bin/web_kbn.py index b66e2a5..e160fde 100644 --- a/bin/web_kbn.py +++ b/bin/web_kbn.py @@ -5,11 +5,13 @@ import aiohttp_jinja2 import os import __py_include +from io import StringIO from typing import Optional from homekit.config import config, AppConfigUnit from homekit.util import homekit_path from aiohttp import web from homekit import http +from homekit.modem import ModemsConfig class WebKbnConfig(AppConfigUnit): @@ -18,10 +20,50 @@ class WebKbnConfig(AppConfigUnit): @classmethod def schema(cls) -> Optional[dict]: return { - 'listen_addr': cls._addr_schema(required=True) + 'listen_addr': cls._addr_schema(required=True), + 'assets_public_path': {'type': 'string'} } +STATIC_FILES = [ + 'bootstrap.min.css', + 'bootstrap.min.js', + 'polyfills.js', + 'app.js', + 'app.css' +] + + +def get_js_link(file, version) -> str: + if version: + file += f'?version={version}' + return f'' + + +def get_css_link(file, version) -> str: + if version: + file += f'?version={version}' + return f'' + + +def get_head_static() -> str: + buf = StringIO() + for file in STATIC_FILES: + v = 1 + try: + q_ind = file.index('?') + v = file[q_ind+1:] + file = file[:file.index('?')] + except ValueError: + pass + + if file.endswith('.js'): + buf.write(get_js_link(file, v)) + else: + buf.write(get_css_link(file, v)) + return buf.getvalue() + + class WebSite(http.HTTPServer): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -31,20 +73,29 @@ class WebSite(http.HTTPServer): loader=jinja2.FileSystemLoader(homekit_path('web', 'kbn_templates')) ) + self.app.router.add_static('/assets/', path=homekit_path('web', 'kbn_assets')) + self.get('/', self.get_index) + self.get('/modems', self.get_modems) - @staticmethod - async def get_index(req: http.Request): - # context = { - # 'username': request.match_info.get("username", ""), - # 'current_date': 'January 27, 2017' - # } - # response = aiohttp_jinja2.render_template("example.html", request, - # context=context) - # return response + async def render_page(self, + req: http.Request, + context: Optional[dict] = None): + if context is None: + context = {} + context = { + **context, + 'head_static': get_head_static(), + 'title': 'this is title' + } + response = aiohttp_jinja2.render_template('index.html', req, context=context) + return response - message = "nothing here, keep lurking" - return http.Response(text=message, content_type='text/plain') + async def get_index(self, req: http.Request): + return await self.render_page(req) + + async def get_modems(self, req: http.Request): + pass if __name__ == '__main__': 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..039d759 --- /dev/null +++ b/include/py/homekit/modem/config.py @@ -0,0 +1,5 @@ +from ..config import ConfigUnit + + +class ModemsConfig(ConfigUnit): + pass diff --git a/localwebsite/handlers/ModemHandler.php b/localwebsite/handlers/ModemHandler.php index b54b82c..fb91084 100644 --- a/localwebsite/handlers/ModemHandler.php +++ b/localwebsite/handlers/ModemHandler.php @@ -7,12 +7,6 @@ use libphonenumber\PhoneNumberUtil; class ModemHandler extends RequestHandler { - public function __construct() - { - parent::__construct(); - $this->tpl->add_static('modem.js'); - } - public function GET_status_page() { global $config; diff --git a/localwebsite/htdocs/assets/modem.js b/localwebsite/htdocs/assets/modem.js deleted file mode 100644 index 9fdb91d..0000000 --- a/localwebsite/htdocs/assets/modem.js +++ /dev/null @@ -1,29 +0,0 @@ -var ModemStatus = { - _modems: [], - - init: function(modems) { - for (var i = 0; i < modems.length; i++) { - var modem = modems[i]; - this._modems.push(new ModemStatusUpdater(modem)); - } - } -}; - - -function ModemStatusUpdater(id) { - this.id = id; - this.elem = ge('modem_data_'+id); - this.fetch(); -} -extend(ModemStatusUpdater.prototype, { - fetch: function() { - ajax.get('/modem/get.ajax', { - id: this.id - }).then(({response}) => { - var {html} = response; - this.elem.innerHTML = html; - - // TODO enqueue rerender - }); - }, -}); \ No newline at end of file diff --git a/localwebsite/htdocs/assets/app.css b/web/kbn_assets/app.css similarity index 100% rename from localwebsite/htdocs/assets/app.css rename to web/kbn_assets/app.css diff --git a/localwebsite/htdocs/assets/app.js b/web/kbn_assets/app.js similarity index 94% rename from localwebsite/htdocs/assets/app.js rename to web/kbn_assets/app.js index 37f1307..c187f89 100644 --- a/localwebsite/htdocs/assets/app.js +++ b/web/kbn_assets/app.js @@ -316,4 +316,34 @@ window.Cameras = { return video.canPlayType('application/vnd.apple.mpegurl'); }, }; -})(); \ No newline at end of file +})(); + + +var ModemStatus = { + _modems: [], + + init: function(modems) { + for (var i = 0; i < modems.length; i++) { + var modem = modems[i]; + this._modems.push(new ModemStatusUpdater(modem)); + } + } +}; + +function ModemStatusUpdater(id) { + this.id = id; + this.elem = ge('modem_data_'+id); + this.fetch(); +} +extend(ModemStatusUpdater.prototype, { + fetch: function() { + ajax.get('/modem/get.ajax', { + id: this.id + }).then(({response}) => { + var {html} = response; + this.elem.innerHTML = html; + + // TODO enqueue rerender + }); + }, +}); \ No newline at end of file diff --git a/localwebsite/htdocs/assets/bootstrap.min.css b/web/kbn_assets/bootstrap.min.css similarity index 100% rename from localwebsite/htdocs/assets/bootstrap.min.css rename to web/kbn_assets/bootstrap.min.css diff --git a/localwebsite/htdocs/assets/bootstrap.min.js b/web/kbn_assets/bootstrap.min.js similarity index 100% rename from localwebsite/htdocs/assets/bootstrap.min.js rename to web/kbn_assets/bootstrap.min.js diff --git a/localwebsite/htdocs/assets/h265webjs-dist/h265webjs-v20221106-reminified.js b/web/kbn_assets/h265webjs-dist/h265webjs-v20221106-reminified.js similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/h265webjs-v20221106-reminified.js rename to web/kbn_assets/h265webjs-dist/h265webjs-v20221106-reminified.js diff --git a/localwebsite/htdocs/assets/h265webjs-dist/h265webjs-v20221106.js b/web/kbn_assets/h265webjs-dist/h265webjs-v20221106.js similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/h265webjs-v20221106.js rename to web/kbn_assets/h265webjs-dist/h265webjs-v20221106.js diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile-120func-v20221120.js b/web/kbn_assets/h265webjs-dist/missile-120func-v20221120.js similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/missile-120func-v20221120.js rename to web/kbn_assets/h265webjs-dist/missile-120func-v20221120.js diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile-120func-v20221120.wasm b/web/kbn_assets/h265webjs-dist/missile-120func-v20221120.wasm similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/missile-120func-v20221120.wasm rename to web/kbn_assets/h265webjs-dist/missile-120func-v20221120.wasm diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile-120func.js b/web/kbn_assets/h265webjs-dist/missile-120func.js similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/missile-120func.js rename to web/kbn_assets/h265webjs-dist/missile-120func.js diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile-256mb-v20221120.js b/web/kbn_assets/h265webjs-dist/missile-256mb-v20221120.js similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/missile-256mb-v20221120.js rename to web/kbn_assets/h265webjs-dist/missile-256mb-v20221120.js diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile-256mb-v20221120.wasm b/web/kbn_assets/h265webjs-dist/missile-256mb-v20221120.wasm similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/missile-256mb-v20221120.wasm rename to web/kbn_assets/h265webjs-dist/missile-256mb-v20221120.wasm diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile-256mb.js b/web/kbn_assets/h265webjs-dist/missile-256mb.js similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/missile-256mb.js rename to web/kbn_assets/h265webjs-dist/missile-256mb.js diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile-512mb-v20221120.js b/web/kbn_assets/h265webjs-dist/missile-512mb-v20221120.js similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/missile-512mb-v20221120.js rename to web/kbn_assets/h265webjs-dist/missile-512mb-v20221120.js diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile-512mb-v20221120.wasm b/web/kbn_assets/h265webjs-dist/missile-512mb-v20221120.wasm similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/missile-512mb-v20221120.wasm rename to web/kbn_assets/h265webjs-dist/missile-512mb-v20221120.wasm diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile-512mb.js b/web/kbn_assets/h265webjs-dist/missile-512mb.js similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/missile-512mb.js rename to web/kbn_assets/h265webjs-dist/missile-512mb.js diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile-format.js b/web/kbn_assets/h265webjs-dist/missile-format.js similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/missile-format.js rename to web/kbn_assets/h265webjs-dist/missile-format.js diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile-v20221120.js b/web/kbn_assets/h265webjs-dist/missile-v20221120.js similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/missile-v20221120.js rename to web/kbn_assets/h265webjs-dist/missile-v20221120.js diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile-v20221120.wasm b/web/kbn_assets/h265webjs-dist/missile-v20221120.wasm similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/missile-v20221120.wasm rename to web/kbn_assets/h265webjs-dist/missile-v20221120.wasm diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile.js b/web/kbn_assets/h265webjs-dist/missile.js similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/missile.js rename to web/kbn_assets/h265webjs-dist/missile.js diff --git a/localwebsite/htdocs/assets/h265webjs-dist/raw-parser.js b/web/kbn_assets/h265webjs-dist/raw-parser.js similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/raw-parser.js rename to web/kbn_assets/h265webjs-dist/raw-parser.js diff --git a/localwebsite/htdocs/assets/h265webjs-dist/worker-fetch-dist.js b/web/kbn_assets/h265webjs-dist/worker-fetch-dist.js similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/worker-fetch-dist.js rename to web/kbn_assets/h265webjs-dist/worker-fetch-dist.js diff --git a/localwebsite/htdocs/assets/h265webjs-dist/worker-parse-dist.js b/web/kbn_assets/h265webjs-dist/worker-parse-dist.js similarity index 100% rename from localwebsite/htdocs/assets/h265webjs-dist/worker-parse-dist.js rename to web/kbn_assets/h265webjs-dist/worker-parse-dist.js diff --git a/localwebsite/htdocs/assets/hls.js b/web/kbn_assets/hls.js similarity index 100% rename from localwebsite/htdocs/assets/hls.js rename to web/kbn_assets/hls.js diff --git a/localwebsite/htdocs/assets/inverter.js b/web/kbn_assets/inverter.js similarity index 100% rename from localwebsite/htdocs/assets/inverter.js rename to web/kbn_assets/inverter.js diff --git a/localwebsite/htdocs/assets/polyfills.js b/web/kbn_assets/polyfills.js similarity index 100% rename from localwebsite/htdocs/assets/polyfills.js rename to web/kbn_assets/polyfills.js diff --git a/web/kbn_templates/base.html b/web/kbn_templates/base.html index e567a90..43f7d2a 100644 --- a/web/kbn_templates/base.html +++ b/web/kbn_templates/base.html @@ -9,11 +9,13 @@ window.console && console.error(error); } - {{ head_static }} + {{ head_static | safe }}
+{% block content %} {% endblock %} + {% if js %} {% endif %} diff --git a/web/kbn_templates/index.html b/web/kbn_templates/index.html new file mode 100644 index 0000000..1921b87 --- /dev/null +++ b/web/kbn_templates/index.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} + +{% block content %} +
+ + + + + + + + +
Интернет
+ + +
Другое
+ + +
Все камеры (HQ)
+ +
+{% endblock %} \ No newline at end of file From 05c5d18f7619c28e620d42c0921f81ced780cc2d Mon Sep 17 00:00:00 2001 From: Evgeny Zinoviev Date: Wed, 10 Jan 2024 03:20:10 +0300 Subject: [PATCH 4/4] save --- bin/mqtt_node_util.py | 5 ++- bin/web_kbn.py | 22 +++++++---- include/py/homekit/config/config.py | 19 ++++++++-- include/py/homekit/modem/config.py | 28 +++++++++++++- include/py/homekit/mqtt/_config.py | 20 +++++++++- include/py/homekit/mqtt/module/temphum.py | 41 ++++++++------------ include/py/homekit/util.py | 29 +++++++++----- web/kbn_templates/base.html | 25 ------------ web/kbn_templates/base.j2 | 44 ++++++++++++++++++++++ web/kbn_templates/{index.html => index.j2} | 14 +++---- web/kbn_templates/loading.j2 | 14 +++++++ web/kbn_templates/modems.j2 | 12 ++++++ 12 files changed, 191 insertions(+), 82 deletions(-) delete mode 100644 web/kbn_templates/base.html create mode 100644 web/kbn_templates/base.j2 rename web/kbn_templates/{index.html => index.j2} (63%) create mode 100644 web/kbn_templates/loading.j2 create mode 100644 web/kbn_templates/modems.j2 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 + + +class MqttTemphumDataPayload(MqttTemphumLegacyDataPayload): + FORMAT = '=ddb' 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 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') + @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 + 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 @@ - - - - {{ title }} - - - - {{ head_static | safe }} - - -
- -{% block content %} {% endblock %} - -{% if js %} - -{% endif %} - -
- - 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) %} + +{% endmacro %} + + + + + {{ title }} + + + + {{ head_static | safe }} + + +
+ +{% block content %}{% endblock %} + +{% if js %} + +{% endif %} + +
+ + diff --git a/web/kbn_templates/index.html b/web/kbn_templates/index.j2 similarity index 63% rename from web/kbn_templates/index.html rename to 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 %}
@@ -16,16 +16,16 @@
Интернет
Другое
Все камеры (HQ)
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 @@ +
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 %} +
{{ modems.getfullname(modem) }}
+
+ {% include "loading.j2" %} +
+{% endfor %} +{% endblock %} \ No newline at end of file