diff options
author | Evgeny Zinoviev <me@ch1p.io> | 2022-06-28 03:22:30 +0300 |
---|---|---|
committer | Evgeny Zinoviev <me@ch1p.io> | 2022-06-30 03:47:49 +0300 |
commit | 8f20c9b825cabab7a3f0f5dd2cfe000cc7f72c28 (patch) | |
tree | b5d7446e7b2fcfd42b1e5029aeef33ecb5f9715f /src/home | |
parent | ee09bc98aedfc6a65a5026432b399345a30a39c8 (diff) |
polaris pwk 1725cgld full support
- significant improvements, correctnesses and stability fixes in
protocol implementation
- correct handling of device appearances and disappearances
- flawlessly functioning telegram bot that re-renders kettle's state
(temperature and other) in real time
Diffstat (limited to 'src/home')
-rw-r--r-- | src/home/api/errors/api_response_error.py | 4 | ||||
-rw-r--r-- | src/home/api/types/types.py | 1 | ||||
-rw-r--r-- | src/home/api/web_api_client.py | 20 | ||||
-rw-r--r-- | src/home/audio/amixer.py | 4 | ||||
-rw-r--r-- | src/home/bot/__init__.py | 2 | ||||
-rw-r--r-- | src/home/bot/lang.py | 13 | ||||
-rw-r--r-- | src/home/bot/wrapper.py | 34 | ||||
-rw-r--r-- | src/home/camera/util.py | 3 | ||||
-rw-r--r-- | src/home/database/bots.py | 10 | ||||
-rw-r--r-- | src/home/media/node_client.py | 6 | ||||
-rw-r--r-- | src/home/media/record.py | 6 | ||||
-rw-r--r-- | src/home/media/record_client.py | 18 | ||||
-rw-r--r-- | src/home/media/storage.py | 4 | ||||
-rw-r--r-- | src/home/telegram/telegram.py | 3 | ||||
-rw-r--r-- | src/home/util.py | 6 |
15 files changed, 84 insertions, 50 deletions
diff --git a/src/home/api/errors/api_response_error.py b/src/home/api/errors/api_response_error.py index 6910b2d..85d788b 100644 --- a/src/home/api/errors/api_response_error.py +++ b/src/home/api/errors/api_response_error.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, List class ApiResponseError(Exception): @@ -6,7 +6,7 @@ class ApiResponseError(Exception): status_code: int, error_type: str, error_message: str, - error_stacktrace: Optional[list[str]] = None): + error_stacktrace: Optional[List[str]] = None): super().__init__() self.status_code = status_code self.error_message = error_message diff --git a/src/home/api/types/types.py b/src/home/api/types/types.py index b6233e6..4d8b4ff 100644 --- a/src/home/api/types/types.py +++ b/src/home/api/types/types.py @@ -7,6 +7,7 @@ class BotType(Enum): SENSORS = auto() ADMIN = auto() SOUND = auto() + POLARIS_KETTLE = auto() class TemperatureSensorLocation(Enum): diff --git a/src/home/api/web_api_client.py b/src/home/api/web_api_client.py index 34d080c..d6c9dc7 100644 --- a/src/home/api/web_api_client.py +++ b/src/home/api/web_api_client.py @@ -6,7 +6,7 @@ import logging from collections import namedtuple from datetime import datetime from enum import Enum, auto -from typing import Optional, Callable, Union +from typing import Optional, Callable, Union, List, Tuple, Dict from requests.auth import HTTPBasicAuth from .errors import ApiResponseError @@ -28,13 +28,13 @@ class HTTPMethod(Enum): class WebAPIClient: token: str - timeout: Union[float, tuple[float, float]] + timeout: Union[float, Tuple[float, float]] basic_auth: Optional[HTTPBasicAuth] do_async: bool async_error_handler: Optional[Callable] async_success_handler: Optional[Callable] - def __init__(self, timeout: Union[float, tuple[float, float]] = 5): + def __init__(self, timeout: Union[float, Tuple[float, float]] = 5): self.token = config['api']['token'] self.timeout = timeout self.basic_auth = None @@ -66,7 +66,7 @@ class WebAPIClient: }) def log_openwrt(self, - lines: list[tuple[int, str]]): + lines: List[Tuple[int, str]]): return self._post('logs/openwrt', { 'logs': stringify(lines) }) @@ -81,14 +81,14 @@ class WebAPIClient: return [(datetime.fromtimestamp(date), temp, hum) for date, temp, hum in data] def add_sound_sensor_hits(self, - hits: list[tuple[str, int]]): + hits: List[Tuple[str, int]]): return self._post('sound_sensors/hits/', { 'hits': stringify(hits) }) def get_sound_sensor_hits(self, location: SoundSensorLocation, - after: datetime) -> list[dict]: + after: datetime) -> List[dict]: return self._process_sound_sensor_hits_data(self._get('sound_sensors/hits/', { 'after': int(after.timestamp()), 'location': location.value @@ -100,13 +100,13 @@ class WebAPIClient: 'location': location.value })) - def recordings_list(self, extended=False, as_objects=False) -> Union[list[str], list[dict], list[RecordFile]]: + def recordings_list(self, extended=False, as_objects=False) -> Union[List[str], List[dict], List[RecordFile]]: files = self._get('recordings/list/', {'extended': int(extended)})['data'] if as_objects: return MediaNodeClient.record_list_from_serialized(files) return files - def _process_sound_sensor_hits_data(self, data: list[dict]) -> list[dict]: + def _process_sound_sensor_hits_data(self, data: List[dict]) -> List[dict]: for item in data: item['time'] = datetime.fromtimestamp(item['time']) return data @@ -124,7 +124,7 @@ class WebAPIClient: name: str, params: dict, method: HTTPMethod, - files: Optional[dict[str, str]] = None): + files: Optional[Dict[str, str]] = None): if not self.do_async: return self._make_request(name, params, method, files) else: @@ -136,7 +136,7 @@ class WebAPIClient: name: str, params: dict, method: HTTPMethod = HTTPMethod.GET, - files: Optional[dict[str, str]] = None) -> Optional[any]: + files: Optional[Dict[str, str]] = None) -> Optional[any]: domain = config['api']['host'] kwargs = {} diff --git a/src/home/audio/amixer.py b/src/home/audio/amixer.py index 0ab2c64..53e6bce 100644 --- a/src/home/audio/amixer.py +++ b/src/home/audio/amixer.py @@ -2,7 +2,7 @@ import subprocess from ..config import config from threading import Lock -from typing import Union +from typing import Union, List _lock = Lock() @@ -16,7 +16,7 @@ def has_control(s: str) -> bool: return False -def get_caps(s: str) -> list[str]: +def get_caps(s: str) -> List[str]: for control in config['amixer']['controls']: if control['name'] == s: return control['caps'] diff --git a/src/home/bot/__init__.py b/src/home/bot/__init__.py index 5e68af7..0d93af3 100644 --- a/src/home/bot/__init__.py +++ b/src/home/bot/__init__.py @@ -1,6 +1,6 @@ from .reporting import ReportingHelper from .lang import LangPack -from .wrapper import Wrapper, Context, text_filter +from .wrapper import Wrapper, Context, text_filter, handlermethod from .store import Store from .errors import * from .util import command_usage, user_any_name
\ No newline at end of file diff --git a/src/home/bot/lang.py b/src/home/bot/lang.py index 2f10358..624c748 100644 --- a/src/home/bot/lang.py +++ b/src/home/bot/lang.py @@ -1,6 +1,8 @@ +from __future__ import annotations + import logging -from typing import Union, Optional +from typing import Union, Optional, List, Dict logger = logging.getLogger(__name__) @@ -24,7 +26,7 @@ class LangStrings(dict): class LangPack: - strings: dict[str, LangStrings[str, str]] + strings: Dict[str, LangStrings[str, str]] default_lang: str def __init__(self): @@ -57,11 +59,14 @@ class LangPack: return result @property - def languages(self) -> list[str]: + def languages(self) -> List[str]: return list(self.strings.keys()) def get(self, key: str, lang: str, *args) -> str: - return self.strings[lang][key] % args + if args: + return self.strings[lang][key] % args + else: + return self.strings[lang][key] def __call__(self, *args, **kwargs): return self.strings[self.default_lang][args[0]] diff --git a/src/home/bot/wrapper.py b/src/home/bot/wrapper.py index 8ebde4f..5f399ce 100644 --- a/src/home/bot/wrapper.py +++ b/src/home/bot/wrapper.py @@ -8,6 +8,7 @@ from telegram import ( ReplyKeyboardMarkup, CallbackQuery, User, + Message, ) from telegram.ext import ( Updater, @@ -22,7 +23,7 @@ from telegram.ext import ( ) from telegram.error import TimedOut from ..config import config -from typing import Optional, Union +from typing import Optional, Union, List, Tuple from .store import Store from .lang import LangPack from ..api.types import BotType @@ -110,7 +111,7 @@ class Context: kwargs = dict(parse_mode=ParseMode.HTML) if not isinstance(markup, IgnoreMarkup): kwargs['reply_markup'] = markup - self._update.message.reply_text(text, **kwargs) + return self._update.message.reply_text(text, **kwargs) def reply_exc(self, e: Exception) -> None: self.reply(exc2text(e)) @@ -133,7 +134,7 @@ class Context: return self._update.callback_query @property - def args(self) -> Optional[list[str]]: + def args(self) -> Optional[List[str]]: return self._callback_context.args @property @@ -157,6 +158,25 @@ class Context: return self._update.callback_query and self._update.callback_query.data and self._update.callback_query.data != '' +def handlermethod(f: callable): + def _handler(self, update: Update, context: CallbackContext, *args, **kwargs): + ctx = Context(update, + callback_context=context, + markup_getter=self.markup, + lang=self.lang, + store=self.store) + try: + return f(self, ctx, *args, **kwargs) + except Exception as e: + if not self.exception_handler(e, ctx) and not isinstance(e, TimedOut): + logger.exception(e) + if not ctx.is_callback_context(): + ctx.reply_exc(e) + else: + self.notify_user(ctx.user_id, exc2text(e)) + return _handler + + class Wrapper: store: Optional[Store] updater: Updater @@ -252,7 +272,7 @@ class Wrapper: def exception_handler(self, e: Exception, ctx: Context) -> Optional[bool]: pass - def notify_all(self, text_getter: callable, exclude: tuple[int] = ()) -> None: + def notify_all(self, text_getter: callable, exclude: Tuple[int] = ()) -> None: if 'notify_users' not in config['bot']: logger.error('notify_all() called but no notify_users directive found in the config') return @@ -280,6 +300,12 @@ class Wrapper: def send_file(self, user_id, **kwargs): self.updater.bot.send_document(chat_id=user_id, **kwargs) + def edit_message_text(self, user_id, message_id, *args, **kwargs): + self.updater.bot.edit_message_text(chat_id=user_id, message_id=message_id, parse_mode='HTML', *args, **kwargs) + + def delete_message(self, user_id, message_id): + self.updater.bot.delete_message(chat_id=user_id, message_id=message_id) + # # Language Selection # diff --git a/src/home/camera/util.py b/src/home/camera/util.py index 39bfcd3..97f35aa 100644 --- a/src/home/camera/util.py +++ b/src/home/camera/util.py @@ -3,6 +3,7 @@ import os.path import logging import psutil +from typing import List, Tuple from ..util import chunks from ..config import config @@ -62,7 +63,7 @@ async def ffmpeg_cut(input: str, _logger.info(f'ffmpeg_cut({input}): OK') -def dvr_scan_timecodes(timecodes: str) -> list[tuple[int, int]]: +def dvr_scan_timecodes(timecodes: str) -> List[Tuple[int, int]]: tc_backup = timecodes timecodes = timecodes.split(',') diff --git a/src/home/database/bots.py b/src/home/database/bots.py index bc490e1..99befc0 100644 --- a/src/home/database/bots.py +++ b/src/home/database/bots.py @@ -5,7 +5,7 @@ from ..api.types import ( BotType, SoundSensorLocation ) -from typing import Optional +from typing import Optional, List, Tuple from datetime import datetime from html import escape @@ -37,7 +37,7 @@ class BotsDatabase(MySQLDatabase): self.commit() def add_openwrt_logs(self, - lines: list[tuple[datetime, str]]): + lines: List[Tuple[datetime, str]]): now = datetime.now() with self.cursor() as cursor: for line in lines: @@ -47,7 +47,7 @@ class BotsDatabase(MySQLDatabase): self.commit() def add_sound_hits(self, - hits: list[tuple[SoundSensorLocation, int]], + hits: List[Tuple[SoundSensorLocation, int]], time: datetime): with self.cursor() as cursor: for loc, count in hits: @@ -58,7 +58,7 @@ class BotsDatabase(MySQLDatabase): def get_sound_hits(self, location: SoundSensorLocation, after: Optional[datetime] = None, - last: Optional[int] = None) -> list[dict]: + last: Optional[int] = None) -> List[dict]: with self.cursor(dictionary=True) as cursor: sql = "SELECT `time`, hits FROM sound_hits WHERE location=%s" args = [location.name.lower()] @@ -84,7 +84,7 @@ class BotsDatabase(MySQLDatabase): def get_openwrt_logs(self, filter_text: str, min_id: int, - limit: int = None) -> list[OpenwrtLogRecord]: + limit: int = None) -> List[OpenwrtLogRecord]: tz = pytz.timezone('Europe/Moscow') with self.cursor(dictionary=True) as cursor: sql = "SELECT * FROM openwrt WHERE text LIKE %s AND id > %s" diff --git a/src/home/media/node_client.py b/src/home/media/node_client.py index 4430962..eb39898 100644 --- a/src/home/media/node_client.py +++ b/src/home/media/node_client.py @@ -2,7 +2,7 @@ import requests import shutil import logging -from typing import Optional, Union +from typing import Optional, Union, List from .storage import RecordFile from ..util import Addr from ..api.errors import ApiResponseError @@ -25,7 +25,7 @@ class MediaNodeClient: def record_download(self, record_id: int, output: str): return self._call(f'record/download/{record_id}/', save_to=output) - def storage_list(self, extended=False, as_objects=False) -> Union[list[str], list[dict], list[RecordFile]]: + def storage_list(self, extended=False, as_objects=False) -> Union[List[str], List[dict], List[RecordFile]]: r = self._call('storage/list/', params={'extended': int(extended)}) files = r['files'] if as_objects: @@ -33,7 +33,7 @@ class MediaNodeClient: return files @staticmethod - def record_list_from_serialized(files: Union[list[str], list[dict]]): + def record_list_from_serialized(files: Union[List[str], List[dict]]): new_files = [] for f in files: kwargs = {'remote': True} diff --git a/src/home/media/record.py b/src/home/media/record.py index fdb8382..cd7447a 100644 --- a/src/home/media/record.py +++ b/src/home/media/record.py @@ -5,7 +5,7 @@ import time import subprocess import signal -from typing import Optional +from typing import Optional, List, Dict from ..util import find_child_processes, Addr from ..config import config from .storage import RecordFile, RecordStorage @@ -22,7 +22,7 @@ class RecordHistoryItem: request_time: float start_time: float stop_time: float - relations: list[int] + relations: List[int] status: RecordStatus error: Optional[Exception] file: Optional[RecordFile] @@ -76,7 +76,7 @@ class RecordingNotFoundError(Exception): class RecordHistory: - history: dict[int, RecordHistoryItem] + history: Dict[int, RecordHistoryItem] def __init__(self): self.history = {} diff --git a/src/home/media/record_client.py b/src/home/media/record_client.py index f264155..322495c 100644 --- a/src/home/media/record_client.py +++ b/src/home/media/record_client.py @@ -7,7 +7,7 @@ from tempfile import gettempdir from .record import RecordStatus from .node_client import SoundNodeClient, MediaNodeClient, CameraNodeClient from ..util import Addr -from typing import Optional, Callable +from typing import Optional, Callable, Dict class RecordClient: @@ -15,14 +15,14 @@ class RecordClient: interrupted: bool logger: logging.Logger - clients: dict[str, MediaNodeClient] - awaiting: dict[str, dict[int, Optional[dict]]] + clients: Dict[str, MediaNodeClient] + awaiting: Dict[str, Dict[int, Optional[dict]]] error_handler: Optional[Callable] finished_handler: Optional[Callable] download_on_finish: bool def __init__(self, - nodes: dict[str, Addr], + nodes: Dict[str, Addr], error_handler: Optional[Callable] = None, finished_handler: Optional[Callable] = None, download_on_finish=False): @@ -50,7 +50,7 @@ class RecordClient: self.stop() self.logger.exception(exc) - def make_clients(self, nodes: dict[str, Addr]): + def make_clients(self, nodes: Dict[str, Addr]): pass def stop(self): @@ -148,9 +148,9 @@ class RecordClient: class SoundRecordClient(RecordClient): DOWNLOAD_EXTENSION = 'mp3' - # clients: dict[str, SoundNodeClient] + # clients: Dict[str, SoundNodeClient] - def make_clients(self, nodes: dict[str, Addr]): + def make_clients(self, nodes: Dict[str, Addr]): for node, addr in nodes.items(): self.clients[node] = SoundNodeClient(addr) self.awaiting[node] = {} @@ -158,9 +158,9 @@ class SoundRecordClient(RecordClient): class CameraRecordClient(RecordClient): DOWNLOAD_EXTENSION = 'mp4' - # clients: dict[str, CameraNodeClient] + # clients: Dict[str, CameraNodeClient] - def make_clients(self, nodes: dict[str, Addr]): + def make_clients(self, nodes: Dict[str, Addr]): for node, addr in nodes.items(): self.clients[node] = CameraNodeClient(addr) self.awaiting[node] = {}
\ No newline at end of file diff --git a/src/home/media/storage.py b/src/home/media/storage.py index 08ba06a..dd74ff8 100644 --- a/src/home/media/storage.py +++ b/src/home/media/storage.py @@ -3,7 +3,7 @@ import re import shutil import logging -from typing import Optional, Union +from typing import Optional, Union, List from datetime import datetime from ..util import strgen @@ -149,7 +149,7 @@ class RecordStorage: self.root = root - def getfiles(self, as_objects=False) -> Union[list[str], list[RecordFile]]: + def getfiles(self, as_objects=False) -> Union[List[str], List[RecordFile]]: files = [] for name in os.listdir(self.root): path = os.path.join(self.root, name) diff --git a/src/home/telegram/telegram.py b/src/home/telegram/telegram.py index 9c7ea73..2f94f93 100644 --- a/src/home/telegram/telegram.py +++ b/src/home/telegram/telegram.py @@ -1,6 +1,7 @@ import requests import logging +from typing import Tuple from ..config import config @@ -29,7 +30,7 @@ def send_photo(filename: str): def _send_telegram_data(text: str, parse_mode: str = None, - disable_web_page_preview: bool = False) -> tuple[dict, str]: + disable_web_page_preview: bool = False) -> Tuple[dict, str]: data = { 'chat_id': config['telegram']['chat_id'], 'text': text diff --git a/src/home/util.py b/src/home/util.py index 9dd84f6..5050ebb 100644 --- a/src/home/util.py +++ b/src/home/util.py @@ -9,7 +9,7 @@ import random from enum import Enum from datetime import datetime -from typing import Tuple, Optional +from typing import Tuple, Optional, List Addr = Tuple[str, int] # network address type (host, port) @@ -96,7 +96,7 @@ def send_datagram(message: str, addr: Addr) -> None: sock.sendto(message.encode(), addr) -def format_tb(exc) -> Optional[list[str]]: +def format_tb(exc) -> Optional[List[str]]: tb = traceback.format_tb(exc.__traceback__) if not tb: return None @@ -120,7 +120,7 @@ class ChildProcessInfo: self.cmd = cmd -def find_child_processes(ppid: int) -> list[ChildProcessInfo]: +def find_child_processes(ppid: int) -> List[ChildProcessInfo]: p = subprocess.run(['pgrep', '-P', str(ppid), '--list-full'], capture_output=True) if p.returncode != 0: raise OSError(f'pgrep returned {p.returncode}') |