wip
This commit is contained in:
parent
7058d0f506
commit
da5db8bc28
@ -2,16 +2,18 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import jinja2
|
import jinja2
|
||||||
import aiohttp_jinja2
|
import aiohttp_jinja2
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import __py_include
|
import __py_include
|
||||||
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from typing import Optional
|
from typing import Optional, Union
|
||||||
from homekit.config import config, AppConfigUnit
|
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 aiohttp import web
|
||||||
from homekit import http
|
from homekit import http
|
||||||
from homekit.modem import ModemsConfig
|
from homekit.modem import ModemsConfig, E3372, MacroNetWorkType
|
||||||
|
|
||||||
|
|
||||||
class WebKbnConfig(AppConfigUnit):
|
class WebKbnConfig(AppConfigUnit):
|
||||||
@ -49,7 +51,7 @@ def get_css_link(file, version) -> str:
|
|||||||
def get_head_static() -> str:
|
def get_head_static() -> str:
|
||||||
buf = StringIO()
|
buf = StringIO()
|
||||||
for file in STATIC_FILES:
|
for file in STATIC_FILES:
|
||||||
v = 1
|
v = 2
|
||||||
try:
|
try:
|
||||||
q_ind = file.index('?')
|
q_ind = file.index('?')
|
||||||
v = file[q_ind+1:]
|
v = file[q_ind+1:]
|
||||||
@ -64,19 +66,52 @@ def get_head_static() -> str:
|
|||||||
return buf.getvalue()
|
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):
|
class WebSite(http.HTTPServer):
|
||||||
|
_modems_config: ModemsConfig
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self._modems_config = ModemsConfig()
|
||||||
|
|
||||||
aiohttp_jinja2.setup(
|
aiohttp_jinja2.setup(
|
||||||
self.app,
|
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.app.router.add_static('/assets/', path=homekit_path('web', 'kbn_assets'))
|
||||||
|
|
||||||
self.get('/main.cgi', self.get_index)
|
self.get('/main.cgi', self.get_index)
|
||||||
self.get('/modems.cgi', self.get_modems)
|
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,
|
async def render_page(self,
|
||||||
req: http.Request,
|
req: http.Request,
|
||||||
@ -99,19 +134,50 @@ class WebSite(http.HTTPServer):
|
|||||||
title="Home web site")
|
title="Home web site")
|
||||||
|
|
||||||
async def get_modems(self, req: http.Request):
|
async def get_modems(self, req: http.Request):
|
||||||
mc = ModemsConfig()
|
|
||||||
print(mc)
|
|
||||||
return await self.render_page(req, 'modems',
|
return await self.render_page(req, 'modems',
|
||||||
title='Состояние модемов',
|
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__':
|
if __name__ == '__main__':
|
||||||
config.load_app(WebKbnConfig)
|
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 = WebSite(config.app_config['listen_addr'])
|
||||||
server.run()
|
server.run()
|
||||||
|
@ -78,6 +78,9 @@ class BaseConfigUnit(ABC):
|
|||||||
|
|
||||||
raise KeyError(f'option {key} not found')
|
raise KeyError(f'option {key} not found')
|
||||||
|
|
||||||
|
def getkeys(self):
|
||||||
|
return list(self._data.keys())
|
||||||
|
|
||||||
|
|
||||||
class ConfigUnit(BaseConfigUnit):
|
class ConfigUnit(BaseConfigUnit):
|
||||||
NAME = 'dumb'
|
NAME = 'dumb'
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
from .http import serve, ok, routes, HTTPServer
|
from .http import serve, ok, routes, HTTPServer, HTTPMethod
|
||||||
from aiohttp.web import FileResponse, StreamResponse, Request, Response
|
from aiohttp.web import FileResponse, StreamResponse, Request, Response
|
@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from aiohttp.web import Response
|
from aiohttp.web import Response
|
||||||
from aiohttp.web_exceptions import HTTPNotFound
|
from aiohttp.web_exceptions import HTTPNotFound
|
||||||
@ -104,3 +105,8 @@ class HTTPServer:
|
|||||||
|
|
||||||
def plain(self, text: str):
|
def plain(self, text: str):
|
||||||
return Response(text=text, content_type='text/plain')
|
return Response(text=text, content_type='text/plain')
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPMethod(Enum):
|
||||||
|
GET = 'GET'
|
||||||
|
POST = 'POST'
|
||||||
|
@ -1 +1,2 @@
|
|||||||
from .config import ModemsConfig
|
from .config import ModemsConfig
|
||||||
|
from .e3372 import E3372, MacroNetWorkType
|
||||||
|
253
include/py/homekit/modem/e3372.py
Normal file
253
include/py/homekit/modem/e3372.py
Normal file
@ -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
|
@ -12,7 +12,7 @@ import re
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
from zlib import adler32
|
from zlib import adler32
|
||||||
|
|
||||||
@ -255,6 +255,25 @@ def filesize_fmt(num, suffix="B") -> str:
|
|||||||
return f"{num:.1f} Yi{suffix}"
|
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):
|
class HashableEnum(Enum):
|
||||||
def hash(self) -> int:
|
def hash(self) -> int:
|
||||||
return adler32(self.name.encode())
|
return adler32(self.name.encode())
|
||||||
|
@ -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 '?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -7,65 +7,6 @@ use libphonenumber\PhoneNumberUtil;
|
|||||||
class ModemHandler extends RequestHandler
|
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() {
|
public function GET_routing_smallhome_page() {
|
||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
@ -233,32 +174,6 @@ class ModemHandler extends RequestHandler
|
|||||||
$go_back();
|
$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() {
|
protected static function getCurrentUpstream() {
|
||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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>
|
|
9
test/test_modems.py
Executable file
9
test/test_modems.py
Executable file
@ -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'])
|
@ -14,7 +14,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** spinner.twig **/
|
/** spinner.j2 **/
|
||||||
|
|
||||||
.sk-fading-circle {
|
.sk-fading-circle {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
@ -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 = {
|
var ModemStatus = {
|
||||||
_modems: [],
|
_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
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
@ -35,9 +35,9 @@
|
|||||||
|
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
{% if js %}
|
<script>
|
||||||
<script>{{ js|raw }}</script>
|
{% block js %}{% endblock %}
|
||||||
{% endif %}
|
</script>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
13
web/kbn_templates/modem_data.j2
Normal file
13
web/kbn_templates/modem_data.j2
Normal file
@ -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>
|
18
web/kbn_templates/modem_verbose.j2
Normal file
18
web/kbn_templates/modem_verbose.j2
Normal file
@ -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 %}
|
@ -10,3 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
ModemStatus.init({{ modems.getkeys()|tojson }});
|
||||||
|
{% endblock %}
|
@ -1,5 +1,5 @@
|
|||||||
<div class="signal_level">
|
<div class="signal_level">
|
||||||
{% for i in 0..4 %}
|
{% for i in range(5) %}
|
||||||
<div{% if i < level %} class="yes"{% endif %}></div>
|
<div{% if i < level %} class="yes"{% endif %}></div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
Loading…
x
Reference in New Issue
Block a user