save
This commit is contained in:
parent
54ddea4614
commit
05c5d18f76
@ -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
|
||||
|
@ -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__':
|
||||
|
@ -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):
|
||||
|
@ -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']
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -53,8 +53,9 @@ class Addr:
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
||||
@staticmethod
|
||||
def fromstring(addr: str) -> Addr:
|
||||
@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')
|
||||
@ -64,6 +65,9 @@ class Addr:
|
||||
port = None
|
||||
else:
|
||||
host, port = addr.split(':')
|
||||
else:
|
||||
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
|
||||
|
@ -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>
|
44
web/kbn_templates/base.j2
Normal file
44
web/kbn_templates/base.j2
Normal file
@ -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>
|
@ -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>
|
14
web/kbn_templates/loading.j2
Normal file
14
web/kbn_templates/loading.j2
Normal file
@ -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>
|
12
web/kbn_templates/modems.j2
Normal file
12
web/kbn_templates/modems.j2
Normal file
@ -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 %}
|
Loading…
x
Reference in New Issue
Block a user