aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2023-09-13 09:34:49 +0300
committerEvgeny Sorokin <me@ch1p.io>2024-01-13 00:57:00 +0000
commit7058d0f5063dc9b065248d0a906cf874788caecf (patch)
tree26468a5d5a7f8c05804911b3fbf649d952bfd8f1
parent57955b596485ecce1ffd4395e23c078358cc5ddd (diff)
save
-rwxr-xr-xbin/mqtt_node_util.py56
-rw-r--r--bin/web_kbn.py117
-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
-rw-r--r--localwebsite/handlers/ModemHandler.php6
-rw-r--r--localwebsite/htdocs/assets/modem.js29
-rw-r--r--requirements.txt5
-rw-r--r--web/kbn_assets/app.css (renamed from localwebsite/htdocs/assets/app.css)0
-rw-r--r--web/kbn_assets/app.js (renamed from localwebsite/htdocs/assets/app.js)32
-rw-r--r--web/kbn_assets/bootstrap.min.css (renamed from localwebsite/htdocs/assets/bootstrap.min.css)0
-rw-r--r--web/kbn_assets/bootstrap.min.js (renamed from localwebsite/htdocs/assets/bootstrap.min.js)0
-rw-r--r--web/kbn_assets/h265webjs-dist/h265webjs-v20221106-reminified.js (renamed from localwebsite/htdocs/assets/h265webjs-dist/h265webjs-v20221106-reminified.js)0
-rw-r--r--web/kbn_assets/h265webjs-dist/h265webjs-v20221106.js (renamed from localwebsite/htdocs/assets/h265webjs-dist/h265webjs-v20221106.js)0
-rw-r--r--web/kbn_assets/h265webjs-dist/missile-120func-v20221120.js (renamed from localwebsite/htdocs/assets/h265webjs-dist/missile-120func-v20221120.js)0
-rw-r--r--web/kbn_assets/h265webjs-dist/missile-120func-v20221120.wasm (renamed from localwebsite/htdocs/assets/h265webjs-dist/missile-120func-v20221120.wasm)bin2190151 -> 2190151 bytes
-rw-r--r--web/kbn_assets/h265webjs-dist/missile-120func.js (renamed from localwebsite/htdocs/assets/h265webjs-dist/missile-120func.js)0
-rw-r--r--web/kbn_assets/h265webjs-dist/missile-256mb-v20221120.js (renamed from localwebsite/htdocs/assets/h265webjs-dist/missile-256mb-v20221120.js)0
-rw-r--r--web/kbn_assets/h265webjs-dist/missile-256mb-v20221120.wasm (renamed from localwebsite/htdocs/assets/h265webjs-dist/missile-256mb-v20221120.wasm)bin2108889 -> 2108889 bytes
-rw-r--r--web/kbn_assets/h265webjs-dist/missile-256mb.js (renamed from localwebsite/htdocs/assets/h265webjs-dist/missile-256mb.js)0
-rw-r--r--web/kbn_assets/h265webjs-dist/missile-512mb-v20221120.js (renamed from localwebsite/htdocs/assets/h265webjs-dist/missile-512mb-v20221120.js)0
-rw-r--r--web/kbn_assets/h265webjs-dist/missile-512mb-v20221120.wasm (renamed from localwebsite/htdocs/assets/h265webjs-dist/missile-512mb-v20221120.wasm)bin2108889 -> 2108889 bytes
-rw-r--r--web/kbn_assets/h265webjs-dist/missile-512mb.js (renamed from localwebsite/htdocs/assets/h265webjs-dist/missile-512mb.js)0
-rw-r--r--web/kbn_assets/h265webjs-dist/missile-format.js (renamed from localwebsite/htdocs/assets/h265webjs-dist/missile-format.js)0
-rw-r--r--web/kbn_assets/h265webjs-dist/missile-v20221120.js (renamed from localwebsite/htdocs/assets/h265webjs-dist/missile-v20221120.js)0
-rw-r--r--web/kbn_assets/h265webjs-dist/missile-v20221120.wasm (renamed from localwebsite/htdocs/assets/h265webjs-dist/missile-v20221120.wasm)bin2108891 -> 2108891 bytes
-rw-r--r--web/kbn_assets/h265webjs-dist/missile.js (renamed from localwebsite/htdocs/assets/h265webjs-dist/missile.js)0
-rw-r--r--web/kbn_assets/h265webjs-dist/raw-parser.js (renamed from localwebsite/htdocs/assets/h265webjs-dist/raw-parser.js)0
-rw-r--r--web/kbn_assets/h265webjs-dist/worker-fetch-dist.js (renamed from localwebsite/htdocs/assets/h265webjs-dist/worker-fetch-dist.js)0
-rw-r--r--web/kbn_assets/h265webjs-dist/worker-parse-dist.js (renamed from localwebsite/htdocs/assets/h265webjs-dist/worker-parse-dist.js)0
-rw-r--r--web/kbn_assets/hls.js (renamed from localwebsite/htdocs/assets/hls.js)0
-rw-r--r--web/kbn_assets/inverter.js (renamed from localwebsite/htdocs/assets/inverter.js)0
-rw-r--r--web/kbn_assets/polyfills.js (renamed from localwebsite/htdocs/assets/polyfills.js)0
-rw-r--r--web/kbn_templates/base.j244
-rw-r--r--web/kbn_templates/index.j239
-rw-r--r--web/kbn_templates/loading.j214
-rw-r--r--web/kbn_templates/modems.j212
43 files changed, 439 insertions, 94 deletions
diff --git a/bin/mqtt_node_util.py b/bin/mqtt_node_util.py
index cf451fd..5587739 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()
@@ -23,18 +48,23 @@ 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',
+ 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,27 +72,29 @@ 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:
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:
- 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/bin/web_kbn.py b/bin/web_kbn.py
new file mode 100644
index 0000000..8b4ca6f
--- /dev/null
+++ b/bin/web_kbn.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+import asyncio
+import jinja2
+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):
+ NAME = 'web_kbn'
+
+ @classmethod
+ def schema(cls) -> Optional[dict]:
+ return {
+ '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'<script src="{config.app_config["assets_public_path"]}/{file}" type="text/javascript"></script>'
+
+
+def get_css_link(file, version) -> str:
+ if version:
+ file += f'?version={version}'
+ return f'<link rel="stylesheet" type="text/css" href="{config.app_config["assets_public_path"]}/{file}">'
+
+
+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)
+
+ aiohttp_jinja2.setup(
+ self.app,
+ loader=jinja2.FileSystemLoader(homekit_path('web', 'kbn_templates'))
+ )
+
+ self.app.router.add_static('/assets/', path=homekit_path('web', 'kbn_assets'))
+
+ 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()
+ }
+ 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, 'index',
+ title="Home web site")
+
+ async def get_modems(self, req: http.Request):
+ mc = ModemsConfig()
+ print(mc)
+ return await self.render_page(req, 'modems',
+ title='Состояние модемов',
+ context=dict(modems=ModemsConfig()))
+
+
+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 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)
+ )
diff --git a/localwebsite/handlers/ModemHandler.php b/localwebsite/handlers/ModemHandler.php
index 23e4c9a..6743fe9 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/requirements.txt b/requirements.txt
index 6067436..8fa67c3 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/localwebsite/htdocs/assets/app.css b/web/kbn_assets/app.css
index 3146bcf..3146bcf 100644
--- a/localwebsite/htdocs/assets/app.css
+++ b/web/kbn_assets/app.css
diff --git a/localwebsite/htdocs/assets/app.js b/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
index edfbbb0..edfbbb0 100644
--- a/localwebsite/htdocs/assets/bootstrap.min.css
+++ b/web/kbn_assets/bootstrap.min.css
diff --git a/localwebsite/htdocs/assets/bootstrap.min.js b/web/kbn_assets/bootstrap.min.js
index aed031f..aed031f 100644
--- a/localwebsite/htdocs/assets/bootstrap.min.js
+++ b/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
index 9a9f036..9a9f036 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/h265webjs-v20221106-reminified.js
+++ b/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
index e877ade..e877ade 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/h265webjs-v20221106.js
+++ b/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
index fd26bc7..fd26bc7 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/missile-120func-v20221120.js
+++ b/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
index de5b4f7..de5b4f7 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/missile-120func-v20221120.wasm
+++ b/web/kbn_assets/h265webjs-dist/missile-120func-v20221120.wasm
Binary files differ
diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile-120func.js b/web/kbn_assets/h265webjs-dist/missile-120func.js
index fd26bc7..fd26bc7 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/missile-120func.js
+++ b/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
index fb8f13d..fb8f13d 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/missile-256mb-v20221120.js
+++ b/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
index ee7d92a..ee7d92a 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/missile-256mb-v20221120.wasm
+++ b/web/kbn_assets/h265webjs-dist/missile-256mb-v20221120.wasm
Binary files differ
diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile-256mb.js b/web/kbn_assets/h265webjs-dist/missile-256mb.js
index fb8f13d..fb8f13d 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/missile-256mb.js
+++ b/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
index 49ec3b6..49ec3b6 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/missile-512mb-v20221120.js
+++ b/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
index 71432e4..71432e4 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/missile-512mb-v20221120.wasm
+++ b/web/kbn_assets/h265webjs-dist/missile-512mb-v20221120.wasm
Binary files differ
diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile-512mb.js b/web/kbn_assets/h265webjs-dist/missile-512mb.js
index 49ec3b6..49ec3b6 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/missile-512mb.js
+++ b/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
index 8f7eddf..8f7eddf 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/missile-format.js
+++ b/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
index c498b84..c498b84 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/missile-v20221120.js
+++ b/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
index 629ce98..629ce98 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/missile-v20221120.wasm
+++ b/web/kbn_assets/h265webjs-dist/missile-v20221120.wasm
Binary files differ
diff --git a/localwebsite/htdocs/assets/h265webjs-dist/missile.js b/web/kbn_assets/h265webjs-dist/missile.js
index c498b84..c498b84 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/missile.js
+++ b/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
index edc91a3..edc91a3 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/raw-parser.js
+++ b/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
index e845d0e..e845d0e 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/worker-fetch-dist.js
+++ b/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
index 2e5d0ea..2e5d0ea 100644
--- a/localwebsite/htdocs/assets/h265webjs-dist/worker-parse-dist.js
+++ b/web/kbn_assets/h265webjs-dist/worker-parse-dist.js
diff --git a/localwebsite/htdocs/assets/hls.js b/web/kbn_assets/hls.js
index ce60c4f..ce60c4f 100644
--- a/localwebsite/htdocs/assets/hls.js
+++ b/web/kbn_assets/hls.js
diff --git a/localwebsite/htdocs/assets/inverter.js b/web/kbn_assets/inverter.js
index 72d985c..72d985c 100644
--- a/localwebsite/htdocs/assets/inverter.js
+++ b/web/kbn_assets/inverter.js
diff --git a/localwebsite/htdocs/assets/polyfills.js b/web/kbn_assets/polyfills.js
index e851999..e851999 100644
--- a/localwebsite/htdocs/assets/polyfills.js
+++ b/web/kbn_assets/polyfills.js
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.j2 b/web/kbn_templates/index.j2
new file mode 100644
index 0000000..e3ab421
--- /dev/null
+++ b/web/kbn_templates/index.j2
@@ -0,0 +1,39 @@
+{% extends "base.j2" %}
+
+{% block content %}
+<div class="container py-4">
+ <nav aria-label="breadcrumb">
+ <ol class="breadcrumb">
+ <li class="breadcrumb-item active" aria-current="page">Главная</li>
+ </ol>
+ </nav>
+
+<!-- {% if auth_user %}-->
+<!-- <div class="mb-4 alert alert-secondary">-->
+<!-- Вы авторизованы как <b>{{ auth_user.username }}</b>. <a href="/deauth/">Выйти</a>-->
+<!-- </div>-->
+<!-- {% endif %}-->
+
+ <h6>Интернет</h6>
+ <ul class="list-group list-group-flush">
+ <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.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>
+ <ul class="list-group list-group-flush">
+ {% for id, name in cameras %}
+ <li class="list-group-item"><a href="/cams/{{ id }}/">{{ name }}</a> (<a href="/cams/{{ id }}/?high=1">HQ</a>)</li>
+ {% endfor %}
+ <li class="list-group-item"><a href="/cams/stat/">Статистика</a></li>
+ </ul>
+</div>
+{% endblock %} \ No newline at end of file
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