aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Sorokin <me@ch1p.io>2024-01-16 02:05:00 +0300
committerEvgeny Sorokin <me@ch1p.io>2024-01-16 02:05:00 +0300
commitda5db8bc280deab0e2081f39d2f32aabb2372afe (patch)
tree913c2f8c985b16b550d0ec58ba3ca469bf5a43fd
parent7058d0f5063dc9b065248d0a906cf874788caecf (diff)
wip
-rw-r--r--bin/web_kbn.py90
-rw-r--r--include/py/homekit/config/config.py3
-rw-r--r--include/py/homekit/http/__init__.py4
-rw-r--r--include/py/homekit/http/http.py6
-rw-r--r--include/py/homekit/modem/__init__.py3
-rw-r--r--include/py/homekit/modem/e3372.py253
-rw-r--r--include/py/homekit/util.py21
-rw-r--r--localwebsite/classes/E3372.php310
-rw-r--r--localwebsite/handlers/ModemHandler.php85
-rw-r--r--localwebsite/templates-web/modem_data.twig14
-rw-r--r--localwebsite/templates-web/modem_status_page.twig19
-rw-r--r--localwebsite/templates-web/modem_verbose_page.twig15
-rw-r--r--localwebsite/templates-web/spinner.twig14
-rwxr-xr-xtest/test_modems.py9
-rw-r--r--web/kbn_assets/app.css2
-rw-r--r--web/kbn_assets/app.js38
-rw-r--r--web/kbn_templates/base.j26
-rw-r--r--web/kbn_templates/modem_data.j213
-rw-r--r--web/kbn_templates/modem_verbose.j218
-rw-r--r--web/kbn_templates/modems.j24
-rw-r--r--web/kbn_templates/signal_level.j2 (renamed from localwebsite/templates-web/signal_level.twig)2
21 files changed, 433 insertions, 496 deletions
diff --git a/bin/web_kbn.py b/bin/web_kbn.py
index 8b4ca6f..75437f1 100644
--- a/bin/web_kbn.py
+++ b/bin/web_kbn.py
@@ -2,16 +2,18 @@
import asyncio
import jinja2
import aiohttp_jinja2
+import json
import os
+import re
import __py_include
from io import StringIO
-from typing import Optional
+from typing import Optional, Union
from homekit.config import config, AppConfigUnit
-from homekit.util import homekit_path
+from homekit.util import homekit_path, filesize_fmt, seconds_to_human_readable_string
from aiohttp import web
from homekit import http
-from homekit.modem import ModemsConfig
+from homekit.modem import ModemsConfig, E3372, MacroNetWorkType
class WebKbnConfig(AppConfigUnit):
@@ -49,7 +51,7 @@ def get_css_link(file, version) -> str:
def get_head_static() -> str:
buf = StringIO()
for file in STATIC_FILES:
- v = 1
+ v = 2
try:
q_ind = file.index('?')
v = file[q_ind+1:]
@@ -64,19 +66,52 @@ def get_head_static() -> str:
return buf.getvalue()
+def get_modem_data(modem_cfg: dict, get_raw=False) -> Union[dict, tuple]:
+ cl = E3372(modem_cfg['ip'], legacy_token_auth=modem_cfg['legacy_auth'])
+
+ signal = cl.device_signal
+ status = cl.monitoring_status
+ traffic = cl.traffic_stats
+
+ if get_raw:
+ device_info = cl.device_information
+ dialup_conn = cl.dialup_connection
+ return signal, status, traffic, device_info, dialup_conn
+ else:
+ network_type_label = re.sub('^MACRO_NET_WORK_TYPE(_EX)?_', '', MacroNetWorkType(int(status['CurrentNetworkType'])).name)
+ return {
+ 'type': network_type_label,
+ 'level': int(status['SignalIcon']) if 'SignalIcon' in status else 0,
+ 'rssi': signal['rssi'],
+ 'sinr': signal['sinr'],
+ 'connected_time': seconds_to_human_readable_string(int(traffic['CurrentConnectTime'])),
+ 'downloaded': filesize_fmt(int(traffic['CurrentDownload'])),
+ 'uploaded': filesize_fmt(int(traffic['CurrentUpload']))
+ }
+
+
class WebSite(http.HTTPServer):
+ _modems_config: ModemsConfig
+
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
+ self._modems_config = ModemsConfig()
+
aiohttp_jinja2.setup(
self.app,
- loader=jinja2.FileSystemLoader(homekit_path('web', 'kbn_templates'))
+ loader=jinja2.FileSystemLoader(homekit_path('web', 'kbn_templates')),
+ autoescape=jinja2.select_autoescape(['html', 'xml']),
)
+ env = aiohttp_jinja2.get_env(self.app)
+ env.filters['tojson'] = lambda obj: json.dumps(obj, separators=(',', ':'))
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)
+ self.get('/modems/info.ajx', self.get_modems_ajax)
+ self.get('/modems/verbose.cgi', self.get_modems_verbose)
async def render_page(self,
req: http.Request,
@@ -99,19 +134,50 @@ class WebSite(http.HTTPServer):
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()))
+ context=dict(modems=self._modems_config))
+
+ async def get_modems_ajax(self, req: http.Request):
+ modem = req.query.get('id', None)
+ if modem not in self._modems_config.getkeys():
+ raise ValueError('invalid modem id')
+
+ modem_cfg = self._modems_config.get(modem)
+ loop = asyncio.get_event_loop()
+ modem_data = await loop.run_in_executor(None, lambda: get_modem_data(modem_cfg))
+
+ html = aiohttp_jinja2.render_string('modem_data.j2', req, context=dict(
+ modem_data=modem_data,
+ modem=modem
+ ))
+
+ return self.ok({'html': html})
+
+ async def get_modems_verbose(self, req: http.Request):
+ modem = req.query.get('id', None)
+ if modem not in self._modems_config.getkeys():
+ raise ValueError('invalid modem id')
+
+ modem_cfg = self._modems_config.get(modem)
+ loop = asyncio.get_event_loop()
+ signal, status, traffic, device, dialup_conn = await loop.run_in_executor(None, lambda: get_modem_data(modem_cfg, True))
+ data = [
+ ['Signal', signal],
+ ['Connection', status],
+ ['Traffic', traffic],
+ ['Device info', device],
+ ['Dialup connection', dialup_conn]
+ ]
+
+ modem_name = self._modems_config.getfullname(modem)
+ return await self.render_page(req, 'modem_verbose',
+ title=f'Подробная информация о модеме "{modem_name}"',
+ context=dict(data=data, modem_name=modem_name))
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 abdedad..eb2ad82 100644
--- a/include/py/homekit/config/config.py
+++ b/include/py/homekit/config/config.py
@@ -78,6 +78,9 @@ class BaseConfigUnit(ABC):
raise KeyError(f'option {key} not found')
+ def getkeys(self):
+ return list(self._data.keys())
+
class ConfigUnit(BaseConfigUnit):
NAME = 'dumb'
diff --git a/include/py/homekit/http/__init__.py b/include/py/homekit/http/__init__.py
index 6030e95..d019e4c 100644
--- a/include/py/homekit/http/__init__.py
+++ b/include/py/homekit/http/__init__.py
@@ -1,2 +1,2 @@
-from .http import serve, ok, routes, HTTPServer
-from aiohttp.web import FileResponse, StreamResponse, Request, Response
+from .http import serve, ok, routes, HTTPServer, HTTPMethod
+from aiohttp.web import FileResponse, StreamResponse, Request, Response \ No newline at end of file
diff --git a/include/py/homekit/http/http.py b/include/py/homekit/http/http.py
index 3e70751..9b76d9a 100644
--- a/include/py/homekit/http/http.py
+++ b/include/py/homekit/http/http.py
@@ -1,6 +1,7 @@
import logging
import asyncio
+from enum import Enum
from aiohttp import web
from aiohttp.web import Response
from aiohttp.web_exceptions import HTTPNotFound
@@ -104,3 +105,8 @@ class HTTPServer:
def plain(self, text: str):
return Response(text=text, content_type='text/plain')
+
+
+class HTTPMethod(Enum):
+ GET = 'GET'
+ POST = 'POST'
diff --git a/include/py/homekit/modem/__init__.py b/include/py/homekit/modem/__init__.py
index 20e75b7..ea0930e 100644
--- a/include/py/homekit/modem/__init__.py
+++ b/include/py/homekit/modem/__init__.py
@@ -1 +1,2 @@
-from .config import ModemsConfig \ No newline at end of file
+from .config import ModemsConfig
+from .e3372 import E3372, MacroNetWorkType
diff --git a/include/py/homekit/modem/e3372.py b/include/py/homekit/modem/e3372.py
new file mode 100644
index 0000000..f68db5a
--- /dev/null
+++ b/include/py/homekit/modem/e3372.py
@@ -0,0 +1,253 @@
+import requests
+import xml.etree.ElementTree as ElementTree
+
+from ..util import Addr
+from enum import Enum
+from ..http import HTTPMethod
+from typing import Union
+
+
+class Error(Enum):
+ ERROR_SYSTEM_NO_SUPPORT = 100002
+ ERROR_SYSTEM_NO_RIGHTS = 100003
+ ERROR_SYSTEM_BUSY = 100004
+ ERROR_LOGIN_USERNAME_WRONG = 108001
+ ERROR_LOGIN_PASSWORD_WRONG = 108002
+ ERROR_LOGIN_ALREADY_LOGIN = 108003
+ ERROR_LOGIN_USERNAME_PWD_WRONG = 108006
+ ERROR_LOGIN_USERNAME_PWD_ORERRUN = 108007
+ ERROR_LOGIN_TOUCH_ALREADY_LOGIN = 108009
+ ERROR_VOICE_BUSY = 120001
+ ERROR_WRONG_TOKEN = 125001
+ ERROR_WRONG_SESSION = 125002
+ ERROR_WRONG_SESSION_TOKEN = 125003
+
+
+class WifiStatus(Enum):
+ WIFI_CONNECTING = '900'
+ WIFI_CONNECTED = '901'
+ WIFI_DISCONNECTED = '902'
+ WIFI_DISCONNECTING = '903'
+
+
+class Cradle(Enum):
+ CRADLE_CONNECTING = '900'
+ CRADLE_CONNECTED = '901'
+ CRADLE_DISCONNECTED = '902'
+ CRADLE_DISCONNECTING = '903'
+ CRADLE_CONNECTFAILED = '904'
+ CRADLE_CONNECTSTATUSNULL = '905'
+ CRANDLE_CONNECTSTATUSERRO = '906'
+
+
+class MacroEVDOLevel(Enum):
+ MACRO_EVDO_LEVEL_ZERO = '0'
+ MACRO_EVDO_LEVEL_ONE = '1'
+ MACRO_EVDO_LEVEL_TWO = '2'
+ MACRO_EVDO_LEVEL_THREE = '3'
+ MACRO_EVDO_LEVEL_FOUR = '4'
+ MACRO_EVDO_LEVEL_FIVE = '5'
+
+
+class MacroNetWorkType(Enum):
+ MACRO_NET_WORK_TYPE_NOSERVICE = 0
+ MACRO_NET_WORK_TYPE_GSM = 1
+ MACRO_NET_WORK_TYPE_GPRS = 2
+ MACRO_NET_WORK_TYPE_EDGE = 3
+ MACRO_NET_WORK_TYPE_WCDMA = 4
+ MACRO_NET_WORK_TYPE_HSDPA = 5
+ MACRO_NET_WORK_TYPE_HSUPA = 6
+ MACRO_NET_WORK_TYPE_HSPA = 7
+ MACRO_NET_WORK_TYPE_TDSCDMA = 8
+ MACRO_NET_WORK_TYPE_HSPA_PLUS = 9
+ MACRO_NET_WORK_TYPE_EVDO_REV_0 = 10
+ MACRO_NET_WORK_TYPE_EVDO_REV_A = 11
+ MACRO_NET_WORK_TYPE_EVDO_REV_B = 12
+ MACRO_NET_WORK_TYPE_1xRTT = 13
+ MACRO_NET_WORK_TYPE_UMB = 14
+ MACRO_NET_WORK_TYPE_1xEVDV = 15
+ MACRO_NET_WORK_TYPE_3xRTT = 16
+ MACRO_NET_WORK_TYPE_HSPA_PLUS_64QAM = 17
+ MACRO_NET_WORK_TYPE_HSPA_PLUS_MIMO = 18
+ MACRO_NET_WORK_TYPE_LTE = 19
+ MACRO_NET_WORK_TYPE_EX_NOSERVICE = 0
+ MACRO_NET_WORK_TYPE_EX_GSM = 1
+ MACRO_NET_WORK_TYPE_EX_GPRS = 2
+ MACRO_NET_WORK_TYPE_EX_EDGE = 3
+ MACRO_NET_WORK_TYPE_EX_IS95A = 21
+ MACRO_NET_WORK_TYPE_EX_IS95B = 22
+ MACRO_NET_WORK_TYPE_EX_CDMA_1x = 23
+ MACRO_NET_WORK_TYPE_EX_EVDO_REV_0 = 24
+ MACRO_NET_WORK_TYPE_EX_EVDO_REV_A = 25
+ MACRO_NET_WORK_TYPE_EX_EVDO_REV_B = 26
+ MACRO_NET_WORK_TYPE_EX_HYBRID_CDMA_1x = 27
+ MACRO_NET_WORK_TYPE_EX_HYBRID_EVDO_REV_0 = 28
+ MACRO_NET_WORK_TYPE_EX_HYBRID_EVDO_REV_A = 29
+ MACRO_NET_WORK_TYPE_EX_HYBRID_EVDO_REV_B = 30
+ MACRO_NET_WORK_TYPE_EX_EHRPD_REL_0 = 31
+ MACRO_NET_WORK_TYPE_EX_EHRPD_REL_A = 32
+ MACRO_NET_WORK_TYPE_EX_EHRPD_REL_B = 33
+ MACRO_NET_WORK_TYPE_EX_HYBRID_EHRPD_REL_0 = 34
+ MACRO_NET_WORK_TYPE_EX_HYBRID_EHRPD_REL_A = 35
+ MACRO_NET_WORK_TYPE_EX_HYBRID_EHRPD_REL_B = 36
+ MACRO_NET_WORK_TYPE_EX_WCDMA = 41
+ MACRO_NET_WORK_TYPE_EX_HSDPA = 42
+ MACRO_NET_WORK_TYPE_EX_HSUPA = 43
+ MACRO_NET_WORK_TYPE_EX_HSPA = 44
+ MACRO_NET_WORK_TYPE_EX_HSPA_PLUS = 45
+ MACRO_NET_WORK_TYPE_EX_DC_HSPA_PLUS = 46
+ MACRO_NET_WORK_TYPE_EX_TD_SCDMA = 61
+ MACRO_NET_WORK_TYPE_EX_TD_HSDPA = 62
+ MACRO_NET_WORK_TYPE_EX_TD_HSUPA = 63
+ MACRO_NET_WORK_TYPE_EX_TD_HSPA = 64
+ MACRO_NET_WORK_TYPE_EX_TD_HSPA_PLUS = 65
+ MACRO_NET_WORK_TYPE_EX_802_16E = 81
+ MACRO_NET_WORK_TYPE_EX_LTE = 101
+
+
+def post_data_to_xml(data: dict, depth: int = 1) -> str:
+ if depth == 1:
+ return '<?xml version: "1.0" encoding="UTF-8"?>'+post_data_to_xml({'request': data}, depth+1)
+
+ items = []
+ for k, v in data.items():
+ if isinstance(v, dict):
+ v = post_data_to_xml(v, depth+1)
+ elif isinstance(v, list):
+ raise TypeError('list type is unsupported here')
+ items.append(f'<{k}>{v}</{k}>')
+
+ return ''.join(items)
+
+
+class E3372:
+ _addr: Addr
+ _need_auth: bool
+ _legacy_token_auth: bool
+ _get_raw_data: bool
+ _headers: dict[str, str]
+ _authorized: bool
+
+ def __init__(self,
+ addr: Addr,
+ need_auth: bool = True,
+ legacy_token_auth: bool = False,
+ get_raw_data: bool = False):
+ self._addr = addr
+ self._need_auth = need_auth
+ self._legacy_token_auth = legacy_token_auth
+ self._get_raw_data = get_raw_data
+ self._authorized = False
+ self._headers = {}
+
+ @property
+ def device_information(self):
+ self.auth()
+ return self.request('device/information')
+
+ @property
+ def device_signal(self):
+ self.auth()
+ return self.request('device/signal')
+
+ @property
+ def monitoring_status(self):
+ self.auth()
+ return self.request('monitoring/status')
+
+ @property
+ def notifications(self):
+ self.auth()
+ return self.request('monitoring/check-notifications')
+
+ @property
+ def dialup_connection(self):
+ self.auth()
+ return self.request('dialup/connection')
+
+ @property
+ def traffic_stats(self):
+ self.auth()
+ return self.request('monitoring/traffic-statistics')
+
+ @property
+ def sms_count(self):
+ self.auth()
+ return self.request('sms/sms-count')
+
+ def sms_send(self, phone: str, text: str):
+ self.auth()
+ return self.request('sms/send-sms', HTTPMethod.POST, {
+ 'Index': -1,
+ 'Phones': {
+ 'Phone': phone
+ },
+ 'Sca': '',
+ 'Content': text,
+ 'Length': -1,
+ 'Reserved': 1,
+ 'Date': -1
+ })
+
+ def sms_list(self, page: int = 1, count: int = 20, outbox: bool = False):
+ self.auth()
+ xml = self.request('sms/sms-list', HTTPMethod.POST, {
+ 'PageIndex': page,
+ 'ReadCount': count,
+ 'BoxType': 1 if not outbox else 2,
+ 'SortType': 0,
+ 'Ascending': 0,
+ 'UnreadPreferred': 1 if not outbox else 0
+ }, return_body=True)
+
+ root = ElementTree.fromstring(xml)
+ messages = []
+ for message_elem in root.find('Messages').findall('Message'):
+ message_dict = {child.tag: child.text for child in message_elem}
+ messages.append(message_dict)
+ return messages
+
+ def auth(self):
+ if self._authorized:
+ return
+
+ if not self._legacy_token_auth:
+ data = self.request('webserver/SesTokInfo')
+ self._headers = {
+ 'Cookie': data['SesInfo'],
+ '__RequestVerificationToken': data['TokInfo'],
+ 'Content-Type': 'text/xml'
+ }
+ else:
+ data = self.request('webserver/token')
+ self._headers = {
+ '__RequestVerificationToken': data['token'],
+ 'Content-Type': 'text/xml'
+ }
+
+ self._authorized = True
+
+ def request(self,
+ method: str,
+ http_method: HTTPMethod = HTTPMethod.GET,
+ data: dict = {},
+ return_body: bool = False) -> Union[str, dict]:
+ url = f'http://{self._addr}/api/{method}'
+ if http_method == HTTPMethod.POST:
+ data = post_data_to_xml(data)
+ f = requests.post
+ else:
+ data = None
+ f = requests.get
+ r = f(url, data=data, headers=self._headers)
+ r.raise_for_status()
+ r.encoding = 'utf-8'
+
+ if return_body:
+ return r.text
+
+ root = ElementTree.fromstring(r.text)
+ data_dict = {}
+ for elem in root:
+ data_dict[elem.tag] = elem.text
+ return data_dict
diff --git a/include/py/homekit/util.py b/include/py/homekit/util.py
index 3c73440..f267488 100644
--- a/include/py/homekit/util.py
+++ b/include/py/homekit/util.py
@@ -12,7 +12,7 @@ import re
import os
from enum import Enum
-from datetime import datetime
+from datetime import datetime, timedelta
from typing import Optional, List
from zlib import adler32
@@ -255,6 +255,25 @@ def filesize_fmt(num, suffix="B") -> str:
return f"{num:.1f} Yi{suffix}"
+def seconds_to_human_readable_string(seconds: int) -> str:
+ duration = timedelta(seconds=seconds)
+ days, remainder = divmod(duration.total_seconds(), 86400)
+ hours, remainder = divmod(remainder, 3600)
+ minutes, seconds = divmod(remainder, 60)
+
+ parts = []
+ if days > 0:
+ parts.append(f"{int(days)} day{'s' if days > 1 else ''}")
+ if hours > 0:
+ parts.append(f"{int(hours)} hour{'s' if hours > 1 else ''}")
+ if minutes > 0:
+ parts.append(f"{int(minutes)} minute{'s' if minutes > 1 else ''}")
+ if seconds > 0:
+ parts.append(f"{int(seconds)} second{'s' if seconds > 1 else ''}")
+
+ return ' '.join(parts)
+
+
class HashableEnum(Enum):
def hash(self) -> int:
return adler32(self.name.encode())
diff --git a/localwebsite/classes/E3372.php b/localwebsite/classes/E3372.php
deleted file mode 100644
index a3ce80c..0000000
--- a/localwebsite/classes/E3372.php
+++ /dev/null
@@ -1,310 +0,0 @@
-<?php
-
-class E3372
-{
-
- const WIFI_CONNECTING = '900';
- const WIFI_CONNECTED = '901';
- const WIFI_DISCONNECTED = '902';
- const WIFI_DISCONNECTING = '903';
-
- const CRADLE_CONNECTING = '900';
- const CRADLE_CONNECTED = '901';
- const CRADLE_DISCONNECTED = '902';
- const CRADLE_DISCONNECTING = '903';
- const CRADLE_CONNECTFAILED = '904';
- const CRADLE_CONNECTSTATUSNULL = '905';
- const CRANDLE_CONNECTSTATUSERRO = '906';
-
- const MACRO_EVDO_LEVEL_ZERO = '0';
- const MACRO_EVDO_LEVEL_ONE = '1';
- const MACRO_EVDO_LEVEL_TWO = '2';
- const MACRO_EVDO_LEVEL_THREE = '3';
- const MACRO_EVDO_LEVEL_FOUR = '4';
- const MACRO_EVDO_LEVEL_FIVE = '5';
-
- // CurrentNetworkType
- const MACRO_NET_WORK_TYPE_NOSERVICE = 0;
- const MACRO_NET_WORK_TYPE_GSM = 1;
- const MACRO_NET_WORK_TYPE_GPRS = 2;
- const MACRO_NET_WORK_TYPE_EDGE = 3;
- const MACRO_NET_WORK_TYPE_WCDMA = 4;
- const MACRO_NET_WORK_TYPE_HSDPA = 5;
- const MACRO_NET_WORK_TYPE_HSUPA = 6;
- const MACRO_NET_WORK_TYPE_HSPA = 7;
- const MACRO_NET_WORK_TYPE_TDSCDMA = 8;
- const MACRO_NET_WORK_TYPE_HSPA_PLUS = 9;
- const MACRO_NET_WORK_TYPE_EVDO_REV_0 = 10;
- const MACRO_NET_WORK_TYPE_EVDO_REV_A = 11;
- const MACRO_NET_WORK_TYPE_EVDO_REV_B = 12;
- const MACRO_NET_WORK_TYPE_1xRTT = 13;
- const MACRO_NET_WORK_TYPE_UMB = 14;
- const MACRO_NET_WORK_TYPE_1xEVDV = 15;
- const MACRO_NET_WORK_TYPE_3xRTT = 16;
- const MACRO_NET_WORK_TYPE_HSPA_PLUS_64QAM = 17;
- const MACRO_NET_WORK_TYPE_HSPA_PLUS_MIMO = 18;
- const MACRO_NET_WORK_TYPE_LTE = 19;
- const MACRO_NET_WORK_TYPE_EX_NOSERVICE = 0;
- const MACRO_NET_WORK_TYPE_EX_GSM = 1;
- const MACRO_NET_WORK_TYPE_EX_GPRS = 2;
- const MACRO_NET_WORK_TYPE_EX_EDGE = 3;
- const MACRO_NET_WORK_TYPE_EX_IS95A = 21;
- const MACRO_NET_WORK_TYPE_EX_IS95B = 22;
- const MACRO_NET_WORK_TYPE_EX_CDMA_1x = 23;
- const MACRO_NET_WORK_TYPE_EX_EVDO_REV_0 = 24;
- const MACRO_NET_WORK_TYPE_EX_EVDO_REV_A = 25;
- const MACRO_NET_WORK_TYPE_EX_EVDO_REV_B = 26;
- const MACRO_NET_WORK_TYPE_EX_HYBRID_CDMA_1x = 27;
- const MACRO_NET_WORK_TYPE_EX_HYBRID_EVDO_REV_0 = 28;
- const MACRO_NET_WORK_TYPE_EX_HYBRID_EVDO_REV_A = 29;
- const MACRO_NET_WORK_TYPE_EX_HYBRID_EVDO_REV_B = 30;
- const MACRO_NET_WORK_TYPE_EX_EHRPD_REL_0 = 31;
- const MACRO_NET_WORK_TYPE_EX_EHRPD_REL_A = 32;
- const MACRO_NET_WORK_TYPE_EX_EHRPD_REL_B = 33;
- const MACRO_NET_WORK_TYPE_EX_HYBRID_EHRPD_REL_0 = 34;
- const MACRO_NET_WORK_TYPE_EX_HYBRID_EHRPD_REL_A = 35;
- const MACRO_NET_WORK_TYPE_EX_HYBRID_EHRPD_REL_B = 36;
- const MACRO_NET_WORK_TYPE_EX_WCDMA = 41;
- const MACRO_NET_WORK_TYPE_EX_HSDPA = 42;
- const MACRO_NET_WORK_TYPE_EX_HSUPA = 43;
- const MACRO_NET_WORK_TYPE_EX_HSPA = 44;
- const MACRO_NET_WORK_TYPE_EX_HSPA_PLUS = 45;
- const MACRO_NET_WORK_TYPE_EX_DC_HSPA_PLUS = 46;
- const MACRO_NET_WORK_TYPE_EX_TD_SCDMA = 61;
- const MACRO_NET_WORK_TYPE_EX_TD_HSDPA = 62;
- const MACRO_NET_WORK_TYPE_EX_TD_HSUPA = 63;
- const MACRO_NET_WORK_TYPE_EX_TD_HSPA = 64;
- const MACRO_NET_WORK_TYPE_EX_TD_HSPA_PLUS = 65;
- const MACRO_NET_WORK_TYPE_EX_802_16E = 81;
- const MACRO_NET_WORK_TYPE_EX_LTE = 101;
-
-
- const ERROR_SYSTEM_NO_SUPPORT = 100002;
- const ERROR_SYSTEM_NO_RIGHTS = 100003;
- const ERROR_SYSTEM_BUSY = 100004;
- const ERROR_LOGIN_USERNAME_WRONG = 108001;
- const ERROR_LOGIN_PASSWORD_WRONG = 108002;
- const ERROR_LOGIN_ALREADY_LOGIN = 108003;
- const ERROR_LOGIN_USERNAME_PWD_WRONG = 108006;
- const ERROR_LOGIN_USERNAME_PWD_ORERRUN = 108007;
- const ERROR_LOGIN_TOUCH_ALREADY_LOGIN = 108009;
- const ERROR_VOICE_BUSY = 120001;
- const ERROR_WRONG_TOKEN = 125001;
- const ERROR_WRONG_SESSION = 125002;
- const ERROR_WRONG_SESSION_TOKEN = 125003;
-
- private string $host;
- private array $headers = [];
- private bool $authorized = false;
- private bool $useLegacyTokenAuth = false;
-
- public function __construct(string $host, bool $legacy_token_auth = false) {
- $this->host = $host;
- $this->useLegacyTokenAuth = $legacy_token_auth;
- }
-
- public function auth() {
- if ($this->authorized)
- return;
-
- if (!$this->useLegacyTokenAuth) {
- $data = $this->request('webserver/SesTokInfo');
- $this->headers = [
- 'Cookie: '.$data['SesInfo'],
- '__RequestVerificationToken: '.$data['TokInfo'],
- 'Content-Type: text/xml'
- ];
- } else {
- $data = $this->request('webserver/token');
- $this->headers = [
- '__RequestVerificationToken: '.$data['token'],
- 'Content-Type: text/xml'
- ];
- }
- $this->authorized = true;
- }
-
- public function getDeviceInformation() {
- $this->auth();
- return $this->request('device/information');
- }
-
- public function getDeviceSignal() {
- $this->auth();
- return $this->request('device/signal');
- }
-
- public function getMonitoringStatus() {
- $this->auth();
- return $this->request('monitoring/status');
- }
-
- public function getNotifications() {
- $this->auth();
- return $this->request('monitoring/check-notifications');
- }
-
- public function getDialupConnection() {
- $this->auth();
- return $this->request('dialup/connection');
- }
-
- public function getTrafficStats() {
- $this->auth();
- return $this->request('monitoring/traffic-statistics');
- }
-
- public function getSMSCount() {
- $this->auth();
- return $this->request('sms/sms-count');
- }
-
- public function sendSMS(string $phone, string $text) {
- $this->auth();
- return $this->request('sms/send-sms', 'POST', [
- 'Index' => -1,
- 'Phones' => [
- 'Phone' => $phone
- ],
- 'Sca' => '',
- 'Content' => $text,
- 'Length' => -1,
- 'Reserved' => 1,
- 'Date' => -1
- ]);
- }
-
- public function getSMSList(int $page = 1, int $count = 20, bool $outbox = false) {
- $this->auth();
- $xml = $this->request('sms/sms-list', 'POST', [
- 'PageIndex' => $page,
- 'ReadCount' => $count,
- 'BoxType' => !$outbox ? 1 : 2,
- 'SortType' => 0,
- 'Ascending' => 0,
- 'UnreadPreferred' => !$outbox ? 1 : 0
- ], true);
- $xml = simplexml_load_string($xml);
-
- $messages = [];
- foreach ($xml->Messages->Message as $message) {
- $dt = DateTime::createFromFormat("Y-m-d H:i:s", (string)$message->Date);
- $messages[] = [
- 'date' => (string)$message->Date,
- 'timestamp' => $dt->getTimestamp(),
- 'phone' => (string)$message->Phone,
- 'content' => (string)$message->Content
- ];
- }
- return $messages;
- }
-
- private function xmlToAssoc(string $xml): array {
- $xml = new SimpleXMLElement($xml);
- $data = [];
- foreach ($xml as $name => $value) {
- $data[$name] = (string)$value;
- }
- return $data;
- }
-
- private function request(string $method, string $http_method = 'GET', array $data = [], bool $return_body = false) {
- $ch = curl_init();
- $url = 'http://'.$this->host.'/api/'.$method;
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- if (!empty($this->headers))
- curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
- if ($http_method == 'POST') {
- curl_setopt($ch, CURLOPT_POST, true);
-
- $post_data = $this->postDataToXML($data);
- // debugLog('post_data:', $post_data);
-
- if (!empty($data))
- curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
- }
- $body = curl_exec($ch);
-
- $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- if ($code != 200)
- throw new Exception('e3372 host returned code '.$code);
-
- curl_close($ch);
- return $return_body ? $body : $this->xmlToAssoc($body);
- }
-
- private function postDataToXML(array $data, int $depth = 1): string {
- if ($depth == 1)
- return '<?xml version: "1.0" encoding="UTF-8"?>'.$this->postDataToXML(['request' => $data], $depth+1);
-
- $items = [];
- foreach ($data as $key => $value) {
- if (is_array($value))
- $value = $this->postDataToXML($value, $depth+1);
- $items[] = "<{$key}>{$value}</{$key}>";
- }
-
- return implode('', $items);
- }
-
- public static function getNetworkTypeLabel($type): string {
- switch ((int)$type) {
- case self::MACRO_NET_WORK_TYPE_NOSERVICE: return 'NOSERVICE';
- case self::MACRO_NET_WORK_TYPE_GSM: return 'GSM';
- case self::MACRO_NET_WORK_TYPE_GPRS: return 'GPRS';
- case self::MACRO_NET_WORK_TYPE_EDGE: return 'EDGE';
- case self::MACRO_NET_WORK_TYPE_WCDMA: return 'WCDMA';
- case self::MACRO_NET_WORK_TYPE_HSDPA: return 'HSDPA';
- case self::MACRO_NET_WORK_TYPE_HSUPA: return 'HSUPA';
- case self::MACRO_NET_WORK_TYPE_HSPA: return 'HSPA';
- case self::MACRO_NET_WORK_TYPE_TDSCDMA: return 'TDSCDMA';
- case self::MACRO_NET_WORK_TYPE_HSPA_PLUS: return 'HSPA_PLUS';
- case self::MACRO_NET_WORK_TYPE_EVDO_REV_0: return 'EVDO_REV_0';
- case self::MACRO_NET_WORK_TYPE_EVDO_REV_A: return 'EVDO_REV_A';
- case self::MACRO_NET_WORK_TYPE_EVDO_REV_B: return 'EVDO_REV_B';
- case self::MACRO_NET_WORK_TYPE_1xRTT: return '1xRTT';
- case self::MACRO_NET_WORK_TYPE_UMB: return 'UMB';
- case self::MACRO_NET_WORK_TYPE_1xEVDV: return '1xEVDV';
- case self::MACRO_NET_WORK_TYPE_3xRTT: return '3xRTT';
- case self::MACRO_NET_WORK_TYPE_HSPA_PLUS_64QAM: return 'HSPA_PLUS_64QAM';
- case self::MACRO_NET_WORK_TYPE_HSPA_PLUS_MIMO: return 'HSPA_PLUS_MIMO';
- case self::MACRO_NET_WORK_TYPE_LTE: return 'LTE';
- case self::MACRO_NET_WORK_TYPE_EX_NOSERVICE: return 'NOSERVICE';
- case self::MACRO_NET_WORK_TYPE_EX_GSM: return 'GSM';
- case self::MACRO_NET_WORK_TYPE_EX_GPRS: return 'GPRS';
- case self::MACRO_NET_WORK_TYPE_EX_EDGE: return 'EDGE';
- case self::MACRO_NET_WORK_TYPE_EX_IS95A: return 'IS95A';
- case self::MACRO_NET_WORK_TYPE_EX_IS95B: return 'IS95B';
- case self::MACRO_NET_WORK_TYPE_EX_CDMA_1x: return 'CDMA_1x';
- case self::MACRO_NET_WORK_TYPE_EX_EVDO_REV_0: return 'EVDO_REV_0';
- case self::MACRO_NET_WORK_TYPE_EX_EVDO_REV_A: return 'EVDO_REV_A';
- case self::MACRO_NET_WORK_TYPE_EX_EVDO_REV_B: return 'EVDO_REV_B';
- case self::MACRO_NET_WORK_TYPE_EX_HYBRID_CDMA_1x: return 'HYBRID_CDMA_1x';
- case self::MACRO_NET_WORK_TYPE_EX_HYBRID_EVDO_REV_0: return 'HYBRID_EVDO_REV_0';
- case self::MACRO_NET_WORK_TYPE_EX_HYBRID_EVDO_REV_A: return 'HYBRID_EVDO_REV_A';
- case self::MACRO_NET_WORK_TYPE_EX_HYBRID_EVDO_REV_B: return 'HYBRID_EVDO_REV_B';
- case self::MACRO_NET_WORK_TYPE_EX_EHRPD_REL_0: return 'EHRPD_REL_0';
- case self::MACRO_NET_WORK_TYPE_EX_EHRPD_REL_A: return 'EHRPD_REL_A';
- case self::MACRO_NET_WORK_TYPE_EX_EHRPD_REL_B: return 'EHRPD_REL_B';
- case self::MACRO_NET_WORK_TYPE_EX_HYBRID_EHRPD_REL_0: return 'HYBRID_EHRPD_REL_0';
- case self::MACRO_NET_WORK_TYPE_EX_HYBRID_EHRPD_REL_A: return 'HYBRID_EHRPD_REL_A';
- case self::MACRO_NET_WORK_TYPE_EX_HYBRID_EHRPD_REL_B: return 'HYBRID_EHRPD_REL_B';
- case self::MACRO_NET_WORK_TYPE_EX_WCDMA: return 'WCDMA';
- case self::MACRO_NET_WORK_TYPE_EX_HSDPA: return 'HSDPA';
- case self::MACRO_NET_WORK_TYPE_EX_HSUPA: return 'HSUPA';
- case self::MACRO_NET_WORK_TYPE_EX_HSPA: return 'HSPA';
- case self::MACRO_NET_WORK_TYPE_EX_HSPA_PLUS: return 'HSPA_PLUS';
- case self::MACRO_NET_WORK_TYPE_EX_DC_HSPA_PLUS: return 'DC_HSPA_PLUS';
- case self::MACRO_NET_WORK_TYPE_EX_TD_SCDMA: return 'TD_SCDMA';
- case self::MACRO_NET_WORK_TYPE_EX_TD_HSDPA: return 'TD_HSDPA';
- case self::MACRO_NET_WORK_TYPE_EX_TD_HSUPA: return 'TD_HSUPA';
- case self::MACRO_NET_WORK_TYPE_EX_TD_HSPA: return 'TD_HSPA';
- case self::MACRO_NET_WORK_TYPE_EX_TD_HSPA_PLUS: return 'TD_HSPA_PLUS';
- case self::MACRO_NET_WORK_TYPE_EX_802_16E: return '802_16E';
- case self::MACRO_NET_WORK_TYPE_EX_LTE: return 'LTE';
- default: return '?';
- }
- }
-
-}
diff --git a/localwebsite/handlers/ModemHandler.php b/localwebsite/handlers/ModemHandler.php
index 6743fe9..8179620 100644
--- a/localwebsite/handlers/ModemHandler.php
+++ b/localwebsite/handlers/ModemHandler.php
@@ -7,65 +7,6 @@ use libphonenumber\PhoneNumberUtil;
class ModemHandler extends RequestHandler
{
- public function GET_status_page() {
- global $config;
-
- $this->tpl->set([
- 'modems' => $config['modems'],
- 'js_modems' => array_keys($config['modems']),
- ]);
-
- $this->tpl->set_title('Состояние модемов');
- $this->tpl->render_page('modem_status_page.twig');
- }
-
- public function GET_status_get_ajax() {
- global $config;
- list($id) = $this->input('id');
- if (!isset($config['modems'][$id]))
- ajax_error('invalid modem id: '.$id);
-
- $modem_data = self::getModemData(
- $config['modems'][$id]['ip'],
- $config['modems'][$id]['legacy_token_auth']);
-
- ajax_ok([
- 'html' => $this->tpl->render('modem_data.twig', [
- 'loading' => false,
- 'modem' => $id,
- 'modem_data' => $modem_data
- ])
- ]);
- }
-
- public function GET_verbose_page() {
- global $config;
-
- list($modem) = $this->input('modem');
- if (!$modem)
- $modem = array_key_first($config['modems']);
-
- list($signal, $status, $traffic, $device, $dialup_conn) = self::getModemData(
- $config['modems'][$modem]['ip'],
- $config['modems'][$modem]['legacy_token_auth'],
- true);
-
- $data = [
- ['Signal', $signal],
- ['Connection', $status],
- ['Traffic', $traffic],
- ['Device info', $device],
- ['Dialup connection', $dialup_conn]
- ];
- $this->tpl->set([
- 'data' => $data,
- 'modem_name' => $config['modems'][$modem]['label'],
- ]);
- $this->tpl->set_title('Подробная информация о модеме '.$modem);
- $this->tpl->render_page('modem_verbose_page.twig');
- }
-
-
public function GET_routing_smallhome_page() {
global $config;
@@ -233,32 +174,6 @@ class ModemHandler extends RequestHandler
$go_back();
}
- protected static function getModemData(string $ip,
- bool $need_auth = true,
- bool $get_raw_data = false): array {
- $modem = new E3372($ip, $need_auth);
-
- $signal = $modem->getDeviceSignal();
- $status = $modem->getMonitoringStatus();
- $traffic = $modem->getTrafficStats();
-
- if ($get_raw_data) {
- $device_info = $modem->getDeviceInformation();
- $dialup_conn = $modem->getDialupConnection();
- return [$signal, $status, $traffic, $device_info, $dialup_conn];
- } else {
- return [
- 'type' => e3372::getNetworkTypeLabel($status['CurrentNetworkType']),
- 'level' => $status['SignalIcon'] ?? 0,
- 'rssi' => $signal['rssi'],
- 'sinr' => $signal['sinr'],
- 'connected_time' => secondsToTime($traffic['CurrentConnectTime']),
- 'downloaded' => bytesToUnitsLabel(gmp_init($traffic['CurrentDownload'])),
- 'uploaded' => bytesToUnitsLabel(gmp_init($traffic['CurrentUpload'])),
- ];
- }
- }
-
protected static function getCurrentUpstream() {
global $config;
diff --git a/localwebsite/templates-web/modem_data.twig b/localwebsite/templates-web/modem_data.twig
deleted file mode 100644
index a2c00e5..0000000
--- a/localwebsite/templates-web/modem_data.twig
+++ /dev/null
@@ -1,14 +0,0 @@
-{% if not loading %}
- <span class="text-secondary">Сигнал:</span> {% include 'signal_level.twig' with {'level': modem_data.level} %}<br>
- <span class="text-secondary">Тип сети:</span> <b>{{ modem_data.type }}</b><br>
- <span class="text-secondary">RSSI:</span> {{ modem_data.rssi }}<br/>
- {% if modem_data.sinr %}
- <span class="text-secondary">SINR:</span> {{ modem_data.sinr }}<br/>
- {% endif %}
- <span class="text-secondary">Время соединения:</span> {{ modem_data.connected_time }}<br>
- <span class="text-secondary">Принято/передано:</span> {{ modem_data.downloaded }} / {{ modem_data.uploaded }}
- <br>
- <a href="/modem/verbose/?modem={{ modem }}">Подробная информация</a>
-{% else %}
- {% include 'spinner.twig' %}
-{% endif %} \ No newline at end of file
diff --git a/localwebsite/templates-web/modem_status_page.twig b/localwebsite/templates-web/modem_status_page.twig
deleted file mode 100644
index 3f20b86..0000000
--- a/localwebsite/templates-web/modem_status_page.twig
+++ /dev/null
@@ -1,19 +0,0 @@
-{% include 'bc.twig' with {
- history: [
- {text: "Модемы" }
- ]
-} %}
-
-{% for modem_key, modem in modems %}
- <h6 class="text-primary{% if not loop.first %} mt-4{% endif %}">{{ modem.label }}</h6>
- <div id="modem_data_{{ modem_key }}">
- {% include 'modem_data.twig' with {
- loading: true,
- modem: modem_key
- } %}
- </div>
-{% endfor %}
-
-{% js %}
-ModemStatus.init({{ js_modems|json_encode|raw }});
-{% endjs %}
diff --git a/localwebsite/templates-web/modem_verbose_page.twig b/localwebsite/templates-web/modem_verbose_page.twig
deleted file mode 100644
index 3b4c25e..0000000
--- a/localwebsite/templates-web/modem_verbose_page.twig
+++ /dev/null
@@ -1,15 +0,0 @@
-{% include 'bc.twig' with {
- history: [
- {link: '/modem/', text: "Модемы" },
- {text: modem_name}
- ]
-} %}
-
-{% for item in data %}
- {% set item_name = item[0] %}
- {% set item_data = item[1] %}
- <h6 class="text-primary mt-4">{{ item_name }}</h6>
- {% for k, v in item_data %}
- {{ k }} = {{ v }}<br>
- {% endfor %}
-{% endfor %} \ No newline at end of file
diff --git a/localwebsite/templates-web/spinner.twig b/localwebsite/templates-web/spinner.twig
deleted file mode 100644
index 2d629ea..0000000
--- a/localwebsite/templates-web/spinner.twig
+++ /dev/null
@@ -1,14 +0,0 @@
-<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/test/test_modems.py b/test/test_modems.py
new file mode 100755
index 0000000..39981f7
--- /dev/null
+++ b/test/test_modems.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python3
+import __py_include
+from homekit.modem import E3372, ModemsConfig
+
+
+if __name__ == '__main__':
+ mc = ModemsConfig()
+ modem = mc.get('mts-azov')
+ cl = E3372(modem['ip'], legacy_token_auth=modem['legacy_auth'])
diff --git a/web/kbn_assets/app.css b/web/kbn_assets/app.css
index 3146bcf..1a4697a 100644
--- a/web/kbn_assets/app.css
+++ b/web/kbn_assets/app.css
@@ -14,7 +14,7 @@
}
-/** spinner.twig **/
+/** spinner.j2 **/
.sk-fading-circle {
margin-top: 10px;
diff --git a/web/kbn_assets/app.js b/web/kbn_assets/app.js
index c187f89..eaac003 100644
--- a/web/kbn_assets/app.js
+++ b/web/kbn_assets/app.js
@@ -319,6 +319,26 @@ window.Cameras = {
})();
+class ModemStatusUpdater {
+ constructor(id) {
+ this.id = id;
+ this.elem = ge('modem_data_'+id);
+ this.fetch()
+ }
+
+ fetch() {
+ ajax.get('/modems/info.ajx', {
+ id: this.id
+ }).then(({response}) => {
+ const {html} = response;
+ this.elem.innerHTML = html;
+
+ // TODO enqueue rerender
+ });
+ }
+}
+
+
var ModemStatus = {
_modems: [],
@@ -329,21 +349,3 @@ var ModemStatus = {
}
}
};
-
-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/web/kbn_templates/base.j2 b/web/kbn_templates/base.j2
index d43a08b..e2e29e3 100644
--- a/web/kbn_templates/base.j2
+++ b/web/kbn_templates/base.j2
@@ -35,9 +35,9 @@
{% block content %}{% endblock %}
-{% if js %}
-<script>{{ js|raw }}</script>
-{% endif %}
+<script>
+{% block js %}{% endblock %}
+</script>
</div>
</body>
diff --git a/web/kbn_templates/modem_data.j2 b/web/kbn_templates/modem_data.j2
new file mode 100644
index 0000000..7f97b77
--- /dev/null
+++ b/web/kbn_templates/modem_data.j2
@@ -0,0 +1,13 @@
+{% with level=modem_data.level %}
+ <span class="text-secondary">Сигнал:</span> {% include 'signal_level.j2' %}<br>
+{% endwith %}
+
+<span class="text-secondary">Тип сети:</span> <b>{{ modem_data.type }}</b><br>
+<span class="text-secondary">RSSI:</span> {{ modem_data.rssi }}<br/>
+{% if modem_data.sinr %}
+<span class="text-secondary">SINR:</span> {{ modem_data.sinr }}<br/>
+{% endif %}
+<span class="text-secondary">Время соединения:</span> {{ modem_data.connected_time }}<br>
+<span class="text-secondary">Принято/передано:</span> {{ modem_data.downloaded }} / {{ modem_data.uploaded }}
+<br>
+<a href="/modems/verbose.cgi?id={{ modem }}">Подробная информация</a>
diff --git a/web/kbn_templates/modem_verbose.j2 b/web/kbn_templates/modem_verbose.j2
new file mode 100644
index 0000000..7c6c930
--- /dev/null
+++ b/web/kbn_templates/modem_verbose.j2
@@ -0,0 +1,18 @@
+{% extends "base.j2" %}
+
+{% block content %}
+{{ breadcrumbs([
+ {'link': '/modems.cgi', 'text': "Модемы"},
+ {'text': modem_name}
+]) }}
+
+{% for item in data %}
+ {% set item_name = item[0] %}
+ {% set item_data = item[1] %}
+ <h6 class="text-primary mt-4">{{ item_name }}</h6>
+ {% for k, v in item_data.items() %}
+ {{ k }} = {{ v }}<br>
+ {% endfor %}
+{% endfor %}
+
+{% endblock %} \ No newline at end of file
diff --git a/web/kbn_templates/modems.j2 b/web/kbn_templates/modems.j2
index f148140..4ff9cf8 100644
--- a/web/kbn_templates/modems.j2
+++ b/web/kbn_templates/modems.j2
@@ -9,4 +9,8 @@
{% include "loading.j2" %}
</div>
{% endfor %}
+{% endblock %}
+
+{% block js %}
+ModemStatus.init({{ modems.getkeys()|tojson }});
{% endblock %} \ No newline at end of file
diff --git a/localwebsite/templates-web/signal_level.twig b/web/kbn_templates/signal_level.j2
index 9498482..93c9abf 100644
--- a/localwebsite/templates-web/signal_level.twig
+++ b/web/kbn_templates/signal_level.j2
@@ -1,5 +1,5 @@
<div class="signal_level">
- {% for i in 0..4 %}
+ {% for i in range(5) %}
<div{% if i < level %} class="yes"{% endif %}></div>
{% endfor %}
</div> \ No newline at end of file