summaryrefslogtreecommitdiff
path: root/include/py
diff options
context:
space:
mode:
Diffstat (limited to 'include/py')
-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
6 files changed, 286 insertions, 4 deletions
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())