aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2023-06-10 22:29:24 +0300
committerEvgeny Zinoviev <me@ch1p.io>2023-06-10 22:29:24 +0300
commit3790c2205396cf860738f297e6ddc49cd2b2a03f (patch)
treeb1ba6f227d3389071dd27346adcd24a593f8b327
parent2631c58961c2f5ec90be560a8f5152fe27339a90 (diff)
new config: port openwrt_logger and webapiclient
-rw-r--r--doc/openwrt_logger.md28
-rw-r--r--src/home/api/__init__.py12
-rw-r--r--src/home/api/__init__.pyi3
-rw-r--r--src/home/api/config.py15
-rw-r--r--src/home/api/web_api_client.py32
-rw-r--r--src/home/database/_base.py9
-rw-r--r--src/home/database/simple_state.py14
-rw-r--r--src/home/database/sqlite.py6
-rw-r--r--src/home/telegram/_botutil.py2
-rw-r--r--src/home/telegram/bot.py4
-rwxr-xr-xsrc/inverter_bot.py4
-rwxr-xr-xsrc/openwrt_log_analyzer.py2
-rwxr-xr-xsrc/openwrt_logger.py37
-rwxr-xr-xsrc/sensors_bot.py4
-rwxr-xr-xsrc/sound_bot.py6
-rwxr-xr-xsrc/sound_sensor_server.py6
-rwxr-xr-xtest/test_api.py4
-rwxr-xr-xtest/test_record_upload.py4
-rwxr-xr-xtest/test_sound_server_api.py4
19 files changed, 124 insertions, 72 deletions
diff --git a/doc/openwrt_logger.md b/doc/openwrt_logger.md
new file mode 100644
index 0000000..1179c8b
--- /dev/null
+++ b/doc/openwrt_logger.md
@@ -0,0 +1,28 @@
+# openwrt_logger.py
+
+This script is supposed to be run by cron every 5 minutes or so.
+It looks for new lines in log file and sends them to remote server.
+
+OpenWRT must have remote logging enabled (UDP; IP of host this script is launched on; port 514)
+
+`/etc/rsyslog.conf` contains following (assuming `192.168.1.1` is the router IP):
+
+```
+$ModLoad imudp
+$UDPServerRun 514
+:fromhost-ip, isequal, "192.168.1.1" /var/log/openwrt.log
+& ~
+```
+
+Also comment out the following line:
+```
+$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
+```
+
+Cron line example:
+```
+* * * * * /home/user/homekit/src/openwrt_logger.py --access-point 1 --file /var/wrtlogfs/openwrt-5.log >/dev/null
+```
+
+`/var/wrtlogfs` is recommended to be tmpfs, to avoid writes on mmc card, in case
+you use arm sbcs as I do. \ No newline at end of file
diff --git a/src/home/api/__init__.py b/src/home/api/__init__.py
index 782a61e..d641f62 100644
--- a/src/home/api/__init__.py
+++ b/src/home/api/__init__.py
@@ -1,11 +1,19 @@
import importlib
-__all__ = ['WebAPIClient', 'RequestParams']
+__all__ = [
+ # web_api_client.py
+ 'WebApiClient',
+ 'RequestParams',
+
+ # config.py
+ 'WebApiConfig'
+]
def __getattr__(name):
if name in __all__:
- module = importlib.import_module(f'.web_api_client', __name__)
+ file = 'config' if name == 'WebApiConfig' else 'web_api_client'
+ module = importlib.import_module(f'.{file}', __name__)
return getattr(module, name)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/src/home/api/__init__.pyi b/src/home/api/__init__.pyi
index 1b812d6..5b98161 100644
--- a/src/home/api/__init__.pyi
+++ b/src/home/api/__init__.pyi
@@ -1,4 +1,5 @@
from .web_api_client import (
RequestParams as RequestParams,
- WebAPIClient as WebAPIClient
+ WebApiClient as WebApiClient
)
+from .config import WebApiConfig as WebApiConfig
diff --git a/src/home/api/config.py b/src/home/api/config.py
new file mode 100644
index 0000000..00c1097
--- /dev/null
+++ b/src/home/api/config.py
@@ -0,0 +1,15 @@
+from ..config import ConfigUnit
+from typing import Optional, Union
+
+
+class WebApiConfig(ConfigUnit):
+ NAME = 'web_api'
+
+ @classmethod
+ def schema(cls) -> Optional[dict]:
+ return {
+ 'listen_addr': cls._addr_schema(required=True),
+ 'host': cls._addr_schema(required=True),
+ 'token': dict(type='string', required=True),
+ 'recordings_dir': dict(type='string', required=True)
+ } \ No newline at end of file
diff --git a/src/home/api/web_api_client.py b/src/home/api/web_api_client.py
index 6677182..15c1915 100644
--- a/src/home/api/web_api_client.py
+++ b/src/home/api/web_api_client.py
@@ -9,13 +9,15 @@ from enum import Enum, auto
from typing import Optional, Callable, Union, List, Tuple, Dict
from requests.auth import HTTPBasicAuth
+from .config import WebApiConfig
from .errors import ApiResponseError
from .types import *
from ..config import config
from ..util import stringify
from ..media import RecordFile, MediaNodeClient
-logger = logging.getLogger(__name__)
+_logger = logging.getLogger(__name__)
+_config = WebApiConfig()
RequestParams = namedtuple('RequestParams', 'params, files, method')
@@ -26,7 +28,7 @@ class HTTPMethod(Enum):
POST = auto()
-class WebAPIClient:
+class WebApiClient:
token: str
timeout: Union[float, Tuple[float, float]]
basic_auth: Optional[HTTPBasicAuth]
@@ -35,22 +37,22 @@ class WebAPIClient:
async_success_handler: Optional[Callable]
def __init__(self, timeout: Union[float, Tuple[float, float]] = 5):
- self.token = config['api']['token']
+ self.token = config['token']
self.timeout = timeout
self.basic_auth = None
self.do_async = False
self.async_error_handler = None
self.async_success_handler = None
- if 'basic_auth' in config['api']:
- ba = config['api']['basic_auth']
- col = ba.index(':')
-
- user = ba[:col]
- pw = ba[col+1:]
-
- logger.debug(f'enabling basic auth: {user}:{pw}')
- self.basic_auth = HTTPBasicAuth(user, pw)
+ # if 'basic_auth' in config['api']:
+ # ba = config['api']['basic_auth']
+ # col = ba.index(':')
+ #
+ # user = ba[:col]
+ # pw = ba[col+1:]
+ #
+ # _logger.debug(f'enabling basic auth: {user}:{pw}')
+ # self.basic_auth = HTTPBasicAuth(user, pw)
# api methods
# -----------
@@ -152,7 +154,7 @@ class WebAPIClient:
params: dict,
method: HTTPMethod = HTTPMethod.GET,
files: Optional[Dict[str, str]] = None) -> Optional[any]:
- domain = config['api']['host']
+ domain = config['host']
kwargs = {}
if self.basic_auth is not None:
@@ -196,7 +198,7 @@ class WebAPIClient:
try:
f.close()
except Exception as exc:
- logger.exception(exc)
+ _logger.exception(exc)
pass
def _make_request_in_thread(self, name, params, method, files):
@@ -204,7 +206,7 @@ class WebAPIClient:
result = self._make_request(name, params, method, files)
self._report_async_success(result, name, RequestParams(params=params, method=method, files=files))
except Exception as e:
- logger.exception(e)
+ _logger.exception(e)
self._report_async_error(e, name, RequestParams(params=params, method=method, files=files))
def enable_async(self,
diff --git a/src/home/database/_base.py b/src/home/database/_base.py
new file mode 100644
index 0000000..c01e62b
--- /dev/null
+++ b/src/home/database/_base.py
@@ -0,0 +1,9 @@
+import os
+
+
+def get_data_root_directory(name: str) -> str:
+ return os.path.join(
+ os.environ['HOME'],
+ '.config',
+ 'homekit',
+ 'data') \ No newline at end of file
diff --git a/src/home/database/simple_state.py b/src/home/database/simple_state.py
index cada9c8..2b8ebe7 100644
--- a/src/home/database/simple_state.py
+++ b/src/home/database/simple_state.py
@@ -2,24 +2,26 @@ import os
import json
import atexit
+from ._base import get_data_root_directory
+
class SimpleState:
def __init__(self,
- file: str,
- default: dict = None,
- **kwargs):
+ name: str,
+ default: dict = None):
if default is None:
default = {}
elif type(default) is not dict:
raise TypeError('default must be dictionary')
- if not os.path.exists(file):
+ path = os.path.join(get_data_root_directory(), name)
+ if not os.path.exists(path):
self._data = default
else:
- with open(file, 'r') as f:
+ with open(path, 'r') as f:
self._data = json.loads(f.read())
- self._file = file
+ self._file = path
atexit.register(self.__cleanup)
def __cleanup(self):
diff --git a/src/home/database/sqlite.py b/src/home/database/sqlite.py
index 8c6145c..0af1f54 100644
--- a/src/home/database/sqlite.py
+++ b/src/home/database/sqlite.py
@@ -2,15 +2,13 @@ import sqlite3
import os.path
import logging
+from ._base import get_data_root_directory
from ..config import config, is_development_mode
def _get_database_path(name: str) -> str:
return os.path.join(
- os.environ['HOME'],
- '.config',
- 'homekit',
- 'data',
+ get_data_root_directory(),
f'{name}.db')
diff --git a/src/home/telegram/_botutil.py b/src/home/telegram/_botutil.py
index 6d1ee8f..b551a55 100644
--- a/src/home/telegram/_botutil.py
+++ b/src/home/telegram/_botutil.py
@@ -3,7 +3,7 @@ import traceback
from html import escape
from telegram import User
-from home.api import WebAPIClient as APIClient
+from home.api import WebApiClient as APIClient
from home.api.types import BotType
from home.api.errors import ApiResponseError
diff --git a/src/home/telegram/bot.py b/src/home/telegram/bot.py
index 7e22263..e6ebc6e 100644
--- a/src/home/telegram/bot.py
+++ b/src/home/telegram/bot.py
@@ -21,7 +21,7 @@ from telegram.ext.filters import BaseFilter
from telegram.error import TimedOut
from home.config import config
-from home.api import WebAPIClient
+from home.api import WebApiClient
from home.api.types import BotType
from ._botlang import lang, languages
@@ -522,7 +522,7 @@ def _logging_callback_handler(update: Update, context: CallbackContext):
def enable_logging(bot_type: BotType):
- api = WebAPIClient(timeout=3)
+ api = WebApiClient(timeout=3)
api.enable_async()
global _reporting
diff --git a/src/inverter_bot.py b/src/inverter_bot.py
index d35e606..1dd167e 100755
--- a/src/inverter_bot.py
+++ b/src/inverter_bot.py
@@ -28,7 +28,7 @@ from home.inverter.types import (
)
from home.database.inverter_time_formats import FormatDate
from home.api.types import BotType
-from home.api import WebAPIClient
+from home.api import WebApiClient
from telegram import ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
@@ -718,7 +718,7 @@ class ConsumptionConversation(bot.conversation):
message = ctx.reply(ctx.lang('consumption_request_sent'),
markup=bot.IgnoreMarkup())
- api = WebAPIClient(timeout=60)
+ api = WebApiClient(timeout=60)
method = 'inverter_get_consumed_energy' if state == self.TOTAL else 'inverter_get_grid_consumed_energy'
try:
diff --git a/src/openwrt_log_analyzer.py b/src/openwrt_log_analyzer.py
index 35b755f..c1c4fbe 100755
--- a/src/openwrt_log_analyzer.py
+++ b/src/openwrt_log_analyzer.py
@@ -59,7 +59,7 @@ if __name__ == '__main__':
state_file = config['simple_state']['file']
state_file = state_file.replace('.txt', f'-{ap}.txt')
- state = SimpleState(file=state_file,
+ state = SimpleState(name=state_file,
default={'last_id': 0})
max_last_id = 0
diff --git a/src/openwrt_logger.py b/src/openwrt_logger.py
index 97fe7a9..82f11ac 100755
--- a/src/openwrt_logger.py
+++ b/src/openwrt_logger.py
@@ -2,29 +2,19 @@
import os
from datetime import datetime
-from typing import Tuple, List
+from typing import Tuple, List, Optional
from argparse import ArgumentParser
-from home.config import config
+from home.config import config, AppConfigUnit
from home.database import SimpleState
-from home.api import WebAPIClient
+from home.api import WebApiClient
-f"""
-This script is supposed to be run by cron every 5 minutes or so.
-It looks for new lines in log file and sends them to remote server.
-OpenWRT must have remote logging enabled (UDP; IP of host this script is launched on; port 514)
-
-/etc/rsyslog.conf contains following (assuming 192.168.1.1 is the router IP):
-
-$ModLoad imudp
-$UDPServerRun 514
-:fromhost-ip, isequal, "192.168.1.1" /var/log/openwrt.log
-& ~
-
-Also comment out the following line:
-$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
-
-"""
+class OpenwrtLoggerConfig(AppConfigUnit):
+ @classmethod
+ def schema(cls) -> Optional[dict]:
+ return dict(
+ database_name_template=dict(type='string', required=True)
+ )
def parse_line(line: str) -> Tuple[int, str]:
@@ -46,11 +36,10 @@ if __name__ == '__main__':
parser.add_argument('--access-point', type=int, required=True,
help='access point number')
- arg = config.load_app('openwrt_logger', parser=parser)
-
- state = SimpleState(file=config['simple_state']['file'].replace('{ap}', str(arg.access_point)),
- default={'seek': 0, 'size': 0})
+ arg = config.load_app(OpenwrtLoggerConfig, parser=parser)
+ state = SimpleState(name=config.app_config['database_name_template'].replace('{ap}', str(arg.access_point)),
+ default=dict(seek=0, size=0))
fsize = os.path.getsize(arg.file)
if fsize < state['size']:
state['seek'] = 0
@@ -79,5 +68,5 @@ if __name__ == '__main__':
except ValueError:
lines.append((0, line))
- api = WebAPIClient()
+ api = WebApiClient()
api.log_openwrt(lines, arg.access_point)
diff --git a/src/sensors_bot.py b/src/sensors_bot.py
index 152dd24..441c212 100755
--- a/src/sensors_bot.py
+++ b/src/sensors_bot.py
@@ -17,7 +17,7 @@ from telegram import ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardBu
from home.config import config
from home.telegram import bot
from home.util import chunks, MySimpleSocketClient
-from home.api import WebAPIClient
+from home.api import WebApiClient
from home.api.types import (
BotType,
TemperatureSensorLocation
@@ -111,7 +111,7 @@ def callback_handler(ctx: bot.Context) -> None:
sensor = TemperatureSensorLocation[match.group(1).upper()]
hours = int(match.group(2))
- api = WebAPIClient(timeout=20)
+ api = WebApiClient(timeout=20)
data = api.get_sensors_data(sensor, hours)
title = ctx.lang(sensor.name.lower()) + ' (' + ctx.lang('n_hrs', hours) + ')'
diff --git a/src/sound_bot.py b/src/sound_bot.py
index 32371bd..bc9edce 100755
--- a/src/sound_bot.py
+++ b/src/sound_bot.py
@@ -9,7 +9,7 @@ from html import escape
from typing import Optional, List, Dict, Tuple
from home.config import config
-from home.api import WebAPIClient
+from home.api import WebApiClient
from home.api.types import SoundSensorLocation, BotType
from home.api.errors import ApiResponseError
from home.media import SoundNodeClient, SoundRecordClient, SoundRecordFile, CameraNodeClient
@@ -734,7 +734,7 @@ def sound_sensors_last_24h(ctx: bot.Context):
ctx.answer()
- cl = WebAPIClient()
+ cl = WebApiClient()
data = cl.get_sound_sensor_hits(location=SoundSensorLocation[node.upper()],
after=datetime.now() - timedelta(hours=24))
@@ -757,7 +757,7 @@ def sound_sensors_last_anything(ctx: bot.Context):
ctx.answer()
- cl = WebAPIClient()
+ cl = WebApiClient()
data = cl.get_last_sound_sensor_hits(location=SoundSensorLocation[node.upper()],
last=20)
diff --git a/src/sound_sensor_server.py b/src/sound_sensor_server.py
index b660210..3446b80 100755
--- a/src/sound_sensor_server.py
+++ b/src/sound_sensor_server.py
@@ -7,7 +7,7 @@ from typing import Optional, List, Dict, Tuple
from functools import partial
from home.config import config
from home.util import Addr
-from home.api import WebAPIClient, RequestParams
+from home.api import WebApiClient, RequestParams
from home.api.types import SoundSensorLocation
from home.soundsensor import SoundSensorServer, SoundSensorHitHandler
from home.media import MediaNodeType, SoundRecordClient, CameraRecordClient, RecordClient
@@ -120,7 +120,7 @@ def hits_sender():
sleep(5)
-api: Optional[WebAPIClient] = None
+api: Optional[WebApiClient] = None
hc: Optional[HitCounter] = None
record_clients: Dict[MediaNodeType, RecordClient] = {}
@@ -162,7 +162,7 @@ if __name__ == '__main__':
config.load_app('sound_sensor_server')
hc = HitCounter()
- api = WebAPIClient(timeout=(10, 60))
+ api = WebApiClient(timeout=(10, 60))
api.enable_async(error_handler=api_error_handler)
t = threading.Thread(target=hits_sender)
diff --git a/test/test_api.py b/test/test_api.py
index e80eb4c..ecf8764 100755
--- a/test/test_api.py
+++ b/test/test_api.py
@@ -7,7 +7,7 @@ sys.path.extend([
)
])
-from src.home.api import WebAPIClient
+from src.home.api import WebApiClient
from src.home.api.types import BotType
from src.home.config import config
@@ -15,5 +15,5 @@ from src.home.config import config
if __name__ == '__main__':
config.load_app('test_api')
- api = WebAPIClient()
+ api = WebApiClient()
print(api.log_bot_request(BotType.ADMIN, 1, "test_api.py"))
diff --git a/test/test_record_upload.py b/test/test_record_upload.py
index 835504f..c0daceb 100755
--- a/test/test_record_upload.py
+++ b/test/test_record_upload.py
@@ -10,7 +10,7 @@ sys.path.extend([
import time
-from src.home.api import WebAPIClient, RequestParams
+from src.home.api import WebApiClient, RequestParams
from src.home.config import config
from src.home.media import SoundRecordClient
from src.home.util import Addr
@@ -74,7 +74,7 @@ if __name__ == '__main__':
finished_handler=record_finished,
download_on_finish=True)
- api = WebAPIClient()
+ api = WebApiClient()
api.enable_async(error_handler=api_error_handler,
success_handler=api_success_handler)
diff --git a/test/test_sound_server_api.py b/test/test_sound_server_api.py
index 5295a5d..77fe1ba 100755
--- a/test/test_sound_server_api.py
+++ b/test/test_sound_server_api.py
@@ -10,7 +10,7 @@ import threading
from time import sleep
from src.home.config import config
-from src.home.api import WebAPIClient
+from src.home.api import WebApiClient
from src.home.api.types import SoundSensorLocation
from typing import List, Tuple
@@ -59,7 +59,7 @@ if __name__ == '__main__':
config.load_app('test_api')
hc = HitCounter()
- api = WebAPIClient()
+ api = WebApiClient()
hc.add('spb1', 1)
# hc.add('big_house', 123)