Merge branch 'mqtt-refactoring' of ch1p.io:homekit into mqtt-refactoring
This commit is contained in:
commit
27234de929
@ -14,7 +14,7 @@ apscheduler~=3.9.1
|
||||
psutil~=5.9.1
|
||||
aioshutil~=1.1
|
||||
scikit-image~=0.19.3
|
||||
|
||||
cerberus~=1.3.4
|
||||
# following can be installed from debian repositories
|
||||
# matplotlib~=3.5.0
|
||||
|
||||
|
@ -65,7 +65,7 @@ class ESP32CameraNodeServer(MediaNodeServer):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load('camera_node')
|
||||
config.load_app('camera_node')
|
||||
|
||||
recorder_kwargs = {}
|
||||
camera_type = CameraType(config['camera']['type'])
|
||||
|
@ -76,7 +76,7 @@ class ESP32CamCaptureDiffNode:
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load('esp32cam_capture_diff_node')
|
||||
config.load_app('esp32cam_capture_diff_node')
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
ESP32CamCaptureDiffNode()
|
||||
|
@ -13,7 +13,7 @@ if __name__ == '__main__':
|
||||
if not os.getegid() == 0:
|
||||
sys.exit('Must be run as root.')
|
||||
|
||||
config.load()
|
||||
config.load_app()
|
||||
|
||||
try:
|
||||
s = RelayServer(pinname=config.get('relayd.pin'),
|
||||
|
@ -1 +1,11 @@
|
||||
from .config import ConfigStore, config, is_development_mode, setup_logging
|
||||
from .config import (
|
||||
Config,
|
||||
config,
|
||||
is_development_mode,
|
||||
setup_logging,
|
||||
app_config
|
||||
)
|
||||
from .validators import validate
|
||||
from ._configs import (
|
||||
LinuxBoardsConfig
|
||||
)
|
5
src/home/config/_configs.py
Normal file
5
src/home/config/_configs.py
Normal file
@ -0,0 +1,5 @@
|
||||
from .config import ConfigUnit
|
||||
|
||||
|
||||
class LinuxBoardsConfig(ConfigUnit):
|
||||
NAME = 'linux_boards'
|
@ -3,45 +3,116 @@ import yaml
|
||||
import logging
|
||||
import os
|
||||
|
||||
from . import validators
|
||||
from os.path import join, isdir, isfile
|
||||
from typing import Optional, Any, MutableMapping
|
||||
from argparse import ArgumentParser
|
||||
from ..util import parse_addr
|
||||
|
||||
|
||||
def _get_config_path(name: str) -> str:
|
||||
formats = ['toml', 'yaml']
|
||||
|
||||
dirname = join(os.environ['HOME'], '.config', name)
|
||||
|
||||
if isdir(dirname):
|
||||
for fmt in formats:
|
||||
filename = join(dirname, f'config.{fmt}')
|
||||
if isfile(filename):
|
||||
return filename
|
||||
|
||||
raise IOError(f'config not found in {dirname}')
|
||||
|
||||
else:
|
||||
filenames = [join(os.environ['HOME'], '.config', f'{name}.{format}') for format in formats]
|
||||
for file in filenames:
|
||||
if isfile(file):
|
||||
return file
|
||||
|
||||
raise IOError(f'config not found')
|
||||
_my_validators = {}
|
||||
|
||||
|
||||
class ConfigStore:
|
||||
def _get_validator(name: str) -> Optional[callable]:
|
||||
if hasattr(validators, f'{name}_validator'):
|
||||
return getattr(validators, f'{name}_validator')
|
||||
if name in _my_validators:
|
||||
return _my_validators[name]
|
||||
return None
|
||||
|
||||
|
||||
def add_validator(name: str, f: callable):
|
||||
_my_validators[name] = f
|
||||
|
||||
|
||||
class ConfigUnit:
|
||||
NAME = 'dumb'
|
||||
|
||||
data: MutableMapping[str, Any]
|
||||
|
||||
@classmethod
|
||||
def get_config_path(cls, name=None) -> str:
|
||||
if name is None:
|
||||
name = cls.NAME
|
||||
if name is None:
|
||||
raise ValueError('get_config_path: name is none')
|
||||
|
||||
dirnames = (
|
||||
join(os.environ['HOME'], '.config', 'homekit'),
|
||||
'/etc/homekit'
|
||||
)
|
||||
|
||||
for dirname in dirnames:
|
||||
if isdir(dirname):
|
||||
for fmt in ('toml', 'yaml'):
|
||||
filename = join(dirname, f'{name}.{fmt}')
|
||||
if isfile(filename):
|
||||
return filename
|
||||
|
||||
raise IOError(f'config \'{name}\' not found')
|
||||
|
||||
def __init__(self, name=None):
|
||||
self.data = {}
|
||||
|
||||
if self.NAME != 'dumb':
|
||||
self.load_from(self.get_config_path())
|
||||
self.validate()
|
||||
|
||||
elif name is not None:
|
||||
self.NAME = name
|
||||
|
||||
def load_from(self, path: str):
|
||||
if path.endswith('.toml'):
|
||||
self.data = toml.load(path)
|
||||
elif path.endswith('.yaml'):
|
||||
with open(path, 'r') as fd:
|
||||
self.data = yaml.safe_load(fd)
|
||||
|
||||
def validate(self):
|
||||
v = _get_validator(self.NAME)
|
||||
v(self.data)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.data[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
raise NotImplementedError('overwriting config values is prohibited')
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.data
|
||||
|
||||
def get(self, key: str, default=None):
|
||||
cur = self.data
|
||||
pts = key.split('.')
|
||||
for i in range(len(pts)):
|
||||
k = pts[i]
|
||||
if i < len(pts)-1:
|
||||
if k not in cur:
|
||||
raise KeyError(f'key {k} not found')
|
||||
else:
|
||||
return cur[k] if k in cur else default
|
||||
cur = self.data[k]
|
||||
raise KeyError(f'option {key} not found')
|
||||
|
||||
def get_addr(self, key: str):
|
||||
return parse_addr(self.get(key))
|
||||
|
||||
def items(self):
|
||||
return self.data.items()
|
||||
|
||||
|
||||
class Config:
|
||||
app_name: Optional[str]
|
||||
app_config: ConfigUnit
|
||||
|
||||
def __init__(self):
|
||||
self.data = {}
|
||||
self.app_name = None
|
||||
self.app_config = ConfigUnit()
|
||||
|
||||
def load(self, name: Optional[str] = None,
|
||||
use_cli=True,
|
||||
parser: ArgumentParser = None):
|
||||
def load_app(self,
|
||||
name: Optional[str] = None,
|
||||
use_cli=True,
|
||||
parser: ArgumentParser = None):
|
||||
self.app_name = name
|
||||
|
||||
if (name is None) and (not use_cli):
|
||||
@ -75,65 +146,32 @@ class ConfigStore:
|
||||
log_default_fmt = args.log_default_fmt
|
||||
|
||||
if not no_config and path is None:
|
||||
path = _get_config_path(name)
|
||||
path = ConfigUnit.get_config_path(name=name)
|
||||
|
||||
if no_config:
|
||||
self.data = {}
|
||||
else:
|
||||
if path.endswith('.toml'):
|
||||
self.data = toml.load(path)
|
||||
elif path.endswith('.yaml'):
|
||||
with open(path, 'r') as fd:
|
||||
self.data = yaml.safe_load(fd)
|
||||
if not no_config:
|
||||
self.app_config.load_from(path)
|
||||
|
||||
if 'logging' in self:
|
||||
if not log_file and 'file' in self['logging']:
|
||||
log_file = self['logging']['file']
|
||||
if log_default_fmt and 'default_fmt' in self['logging']:
|
||||
log_default_fmt = self['logging']['default_fmt']
|
||||
if 'logging' in self.app_config:
|
||||
if not log_file and 'file' in self.app_config['logging']:
|
||||
log_file = self.app_config['logging']['file']
|
||||
if log_default_fmt and 'default_fmt' in self.app_config['logging']:
|
||||
log_default_fmt = self.app_config['logging']['default_fmt']
|
||||
|
||||
setup_logging(log_verbose, log_file, log_default_fmt)
|
||||
|
||||
if use_cli:
|
||||
return args
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.data[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
raise NotImplementedError('overwriting config values is prohibited')
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.data
|
||||
|
||||
def get(self, key: str, default=None):
|
||||
cur = self.data
|
||||
pts = key.split('.')
|
||||
for i in range(len(pts)):
|
||||
k = pts[i]
|
||||
if i < len(pts)-1:
|
||||
if k not in cur:
|
||||
raise KeyError(f'key {k} not found')
|
||||
else:
|
||||
return cur[k] if k in cur else default
|
||||
cur = self.data[k]
|
||||
raise KeyError(f'option {key} not found')
|
||||
|
||||
def get_addr(self, key: str):
|
||||
return parse_addr(self.get(key))
|
||||
|
||||
def items(self):
|
||||
return self.data.items()
|
||||
|
||||
|
||||
config = ConfigStore()
|
||||
config = Config()
|
||||
app_config = config.app_config
|
||||
|
||||
|
||||
def is_development_mode() -> bool:
|
||||
if 'HK_MODE' in os.environ and os.environ['HK_MODE'] == 'dev':
|
||||
return True
|
||||
|
||||
return ('logging' in config) and ('verbose' in config['logging']) and (config['logging']['verbose'] is True)
|
||||
return ('logging' in config.app_config) and ('verbose' in config.app_config['logging']) and (config.app_config['logging']['verbose'] is True)
|
||||
|
||||
|
||||
def setup_logging(verbose=False, log_file=None, default_fmt=False):
|
||||
|
2
src/home/config/validators/__init__.py
Normal file
2
src/home/config/validators/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from ._validators import *
|
||||
from ._util import validate
|
11
src/home/config/validators/_util.py
Normal file
11
src/home/config/validators/_util.py
Normal file
@ -0,0 +1,11 @@
|
||||
import inspect
|
||||
|
||||
from cerberus import Validator, DocumentError
|
||||
|
||||
|
||||
def validate(schema, data):
|
||||
v = Validator(schema)
|
||||
if not v.validate(data):
|
||||
frame = inspect.currentframe().f_back
|
||||
caller_name = frame.f_code.co_name
|
||||
raise DocumentError(f'{caller_name}: failed to validate data: ' + v.errors)
|
32
src/home/config/validators/_validators.py
Normal file
32
src/home/config/validators/_validators.py
Normal file
@ -0,0 +1,32 @@
|
||||
from ._util import validate
|
||||
|
||||
__all__ = [
|
||||
'linux_boards_validator'
|
||||
]
|
||||
|
||||
|
||||
def linux_boards_validator(data) -> None:
|
||||
validate({
|
||||
'type': 'dict',
|
||||
'valuesrules': {
|
||||
'type': 'dict',
|
||||
'schema': {
|
||||
'mdns': {'type': 'string', 'required': True},
|
||||
'board': {'type': 'string', 'required': True},
|
||||
'network': {'type': 'list', 'required': True, 'empty': False},
|
||||
'ram': {'type': 'integer', 'required': True},
|
||||
'ext_hdd': {
|
||||
'type': 'list',
|
||||
'schema': {
|
||||
'type': 'dict',
|
||||
'schema': {
|
||||
'mountpoint': {'type': 'string', 'required': True},
|
||||
'size': {'type': 'integer', 'required': True}
|
||||
}
|
||||
},
|
||||
},
|
||||
'services': {'type': 'list', 'empty': False},
|
||||
'online': {'type': 'boolean', 'required': True}
|
||||
}
|
||||
}
|
||||
}, data)
|
@ -14,13 +14,17 @@ class MqttNode:
|
||||
_modules: List[MqttModule]
|
||||
_module_subscriptions: dict[str, MqttModule]
|
||||
_node_id: str
|
||||
_node_secret: str
|
||||
_payload_callbacks: list[callable]
|
||||
_wrapper: Optional[MqttWrapper]
|
||||
|
||||
def __init__(self, node_id: str):
|
||||
def __init__(self,
|
||||
node_id: str,
|
||||
node_secret: Optional[str] = None):
|
||||
self._modules = []
|
||||
self._module_subscriptions = {}
|
||||
self._node_id = node_id
|
||||
self._node_secret = node_secret
|
||||
self._payload_callbacks = []
|
||||
self._logger = logging.getLogger(self.__class__.__name__)
|
||||
self._wrapper = None
|
||||
@ -42,7 +46,7 @@ class MqttNode:
|
||||
payload = self._module_subscriptions[topic].handle_payload(self, topic, payload)
|
||||
if isinstance(payload, MqttPayload):
|
||||
for f in self._payload_callbacks:
|
||||
f(payload)
|
||||
f(self, payload)
|
||||
|
||||
def load_module(self, module_name: str, *args, **kwargs) -> MqttModule:
|
||||
module = importlib.import_module(f'..module.{module_name}', __name__)
|
||||
@ -78,3 +82,11 @@ class MqttNode:
|
||||
@property
|
||||
def id(self) -> str:
|
||||
return self._node_id
|
||||
|
||||
@property
|
||||
def secret(self) -> str:
|
||||
return self._node_secret
|
||||
|
||||
@secret.setter
|
||||
def secret(self, secret: str) -> None:
|
||||
self._node_secret = secret
|
||||
|
@ -9,11 +9,15 @@ from ..util import strgen
|
||||
class MqttWrapper(Mqtt):
|
||||
_nodes: list[MqttNode]
|
||||
|
||||
def __init__(self, topic_prefix='hk', randomize_client_id=False):
|
||||
def __init__(self,
|
||||
topic_prefix='hk',
|
||||
randomize_client_id=False,
|
||||
clean_session=True):
|
||||
client_id = config['mqtt']['client_id']
|
||||
if randomize_client_id:
|
||||
client_id += '_'+strgen(6)
|
||||
super().__init__(clean_session=True, client_id=client_id)
|
||||
super().__init__(clean_session=clean_session,
|
||||
client_id=client_id)
|
||||
self._nodes = []
|
||||
self._topic_prefix = topic_prefix
|
||||
|
||||
|
@ -41,7 +41,7 @@ class OtaPayload(MqttPayload):
|
||||
|
||||
|
||||
class MqttOtaModule(MqttModule):
|
||||
_ota_request: Optional[tuple[str, str, int]]
|
||||
_ota_request: Optional[tuple[str, int]]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@ -52,9 +52,9 @@ class MqttOtaModule(MqttModule):
|
||||
mqtt.subscribe_module("otares", self)
|
||||
|
||||
if self._ota_request is not None:
|
||||
secret, filename, qos = self._ota_request
|
||||
filename, qos = self._ota_request
|
||||
self._ota_request = None
|
||||
self.do_push_ota(secret, filename, qos)
|
||||
self.do_push_ota(self._mqtt_node_ref.secret, filename, qos)
|
||||
|
||||
def handle_payload(self, mqtt: MqttNode, topic: str, payload: bytes) -> Optional[MqttPayload]:
|
||||
if topic == 'otares':
|
||||
@ -69,10 +69,9 @@ class MqttOtaModule(MqttModule):
|
||||
qos=qos)
|
||||
|
||||
def push_ota(self,
|
||||
secret: str,
|
||||
filename: str,
|
||||
qos: int):
|
||||
if not self._initialized:
|
||||
self._ota_request = (secret, filename, qos)
|
||||
self._ota_request = (filename, qos)
|
||||
else:
|
||||
self.do_push_ota(secret, filename, qos)
|
||||
self.do_push_ota(filename, qos)
|
||||
|
@ -64,9 +64,9 @@ class MqttRelayModule(MqttModule):
|
||||
mqtt.subscribe_module('relay/status', self)
|
||||
|
||||
def switchpower(self,
|
||||
enable: bool,
|
||||
secret: str):
|
||||
payload = MqttPowerSwitchPayload(secret=secret, state=enable)
|
||||
enable: bool):
|
||||
payload = MqttPowerSwitchPayload(secret=self._mqtt_node_ref.secret,
|
||||
state=enable)
|
||||
self._mqtt_node_ref.publish('relay/switch', payload=payload.pack())
|
||||
|
||||
def handle_payload(self, mqtt: MqttNode, topic: str, payload: bytes) -> Optional[MqttPayload]:
|
||||
|
@ -1,8 +1,8 @@
|
||||
from enum import auto
|
||||
# from enum import auto
|
||||
from .._node import MqttNode
|
||||
from .._module import MqttModule
|
||||
from .._payload import MqttPayload
|
||||
from ...util import HashableEnum
|
||||
# from ...util import HashableEnum
|
||||
from typing import Optional
|
||||
from ...temphum import BaseSensor
|
||||
|
||||
@ -24,30 +24,31 @@ class MqttTemphumDataPayload(MqttPayload):
|
||||
error: int
|
||||
|
||||
|
||||
class MqttTempHumNodes(HashableEnum):
|
||||
KBN_SH_HALL = auto()
|
||||
KBN_SH_BATHROOM = auto()
|
||||
KBN_SH_LIVINGROOM = auto()
|
||||
KBN_SH_BEDROOM = auto()
|
||||
|
||||
KBN_BH_2FL = auto()
|
||||
KBN_BH_2FL_STREET = auto()
|
||||
KBN_BH_1FL_LIVINGROOM = auto()
|
||||
KBN_BH_1FL_BEDROOM = auto()
|
||||
KBN_BH_1FL_BATHROOM = auto()
|
||||
|
||||
KBN_NH_1FL_INV = auto()
|
||||
KBN_NH_1FL_CENTER = auto()
|
||||
KBN_NH_1LF_KT = auto()
|
||||
KBN_NH_1FL_DS = auto()
|
||||
KBN_NH_1FS_EZ = auto()
|
||||
|
||||
SPB_FLAT120_CABINET = auto()
|
||||
# class MqttTempHumNodes(HashableEnum):
|
||||
# KBN_SH_HALL = auto()
|
||||
# KBN_SH_BATHROOM = auto()
|
||||
# KBN_SH_LIVINGROOM = auto()
|
||||
# KBN_SH_BEDROOM = auto()
|
||||
#
|
||||
# KBN_BH_2FL = auto()
|
||||
# KBN_BH_2FL_STREET = auto()
|
||||
# KBN_BH_1FL_LIVINGROOM = auto()
|
||||
# KBN_BH_1FL_BEDROOM = auto()
|
||||
# KBN_BH_1FL_BATHROOM = auto()
|
||||
#
|
||||
# KBN_NH_1FL_INV = auto()
|
||||
# KBN_NH_1FL_CENTER = auto()
|
||||
# KBN_NH_1LF_KT = auto()
|
||||
# KBN_NH_1FL_DS = auto()
|
||||
# KBN_NH_1FS_EZ = auto()
|
||||
#
|
||||
# SPB_FLAT120_CABINET = auto()
|
||||
|
||||
|
||||
class MqttTempHumModule(MqttModule):
|
||||
def __init__(self,
|
||||
sensor: Optional[BaseSensor] = None,
|
||||
write_to_database=False,
|
||||
*args, **kwargs):
|
||||
if sensor is not None:
|
||||
kwargs['tick_interval'] = 10
|
||||
|
@ -44,7 +44,7 @@ flags_map = {
|
||||
}
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
config.load('inverter_bot')
|
||||
config.load_app('inverter_bot')
|
||||
|
||||
bot.initialize()
|
||||
bot.lang.ru(
|
||||
|
@ -8,10 +8,10 @@ if __name__ == '__main__':
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument('mode', type=str, choices=('sender', 'receiver'), nargs=1)
|
||||
|
||||
config.load('inverter_mqtt_util', parser=parser)
|
||||
config.load_app('inverter_mqtt_util', parser=parser)
|
||||
arg = parser.parse_args()
|
||||
|
||||
mqtt = MqttWrapper()
|
||||
mqtt = MqttWrapper(clean_session=arg.mode[0] != 'receiver')
|
||||
node = MqttNode(node_id='inverter')
|
||||
module_kwargs = {}
|
||||
if arg.mode[0] == 'sender':
|
||||
|
@ -556,7 +556,7 @@ logger = logging.getLogger(__name__)
|
||||
# --------------------
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load('ipcam_server')
|
||||
config.load_app('ipcam_server')
|
||||
|
||||
open_database()
|
||||
|
||||
|
@ -23,14 +23,14 @@ if __name__ == '__main__':
|
||||
parser.add_argument('--node-secret', type=str,
|
||||
help='node admin password')
|
||||
|
||||
config.load('mqtt_util', parser=parser)
|
||||
config.load_app('mqtt_util', parser=parser)
|
||||
arg = parser.parse_args()
|
||||
|
||||
if (arg.switch_relay is not None or arg.node_secret is not None) and 'relay' not in arg.modules:
|
||||
raise ArgumentError(None, '--relay is only allowed when \'relay\' module included in --modules')
|
||||
|
||||
mqtt = MqttWrapper(randomize_client_id=True)
|
||||
mqtt_node = MqttNode(node_id=arg.node_id)
|
||||
mqtt_node = MqttNode(node_id=arg.node_id, node_secret=arg.node_secret)
|
||||
|
||||
mqtt.add_node(mqtt_node)
|
||||
|
||||
@ -44,9 +44,7 @@ if __name__ == '__main__':
|
||||
if m == 'relay' and arg.switch_relay is not None:
|
||||
if not arg.node_secret:
|
||||
raise ArgumentError(None, '--switch-relay requires --node-secret')
|
||||
module_instance.switchpower(mqtt_node,
|
||||
arg.switch_relay == 1,
|
||||
arg.node_secret)
|
||||
module_instance.switchpower(arg.switch_relay == 1)
|
||||
|
||||
mqtt.configure_tls()
|
||||
try:
|
||||
@ -58,7 +56,7 @@ if __name__ == '__main__':
|
||||
if not arg.node_secret:
|
||||
raise ArgumentError(None, 'pushing OTA requires --node-secret')
|
||||
|
||||
ota_module.push_ota(arg.node_secret, arg.push_ota, 1)
|
||||
ota_module.push_ota(arg.push_ota, 1)
|
||||
|
||||
while True:
|
||||
sleep(0.1)
|
||||
|
@ -54,7 +54,7 @@ def main(mac: str,
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load('openwrt_log_analyzer')
|
||||
config.load_app('openwrt_log_analyzer')
|
||||
for ap in config['openwrt_log_analyzer']['aps']:
|
||||
state_file = config['simple_state']['file']
|
||||
state_file = state_file.replace('.txt', f'-{ap}.txt')
|
||||
|
@ -46,7 +46,7 @@ if __name__ == '__main__':
|
||||
parser.add_argument('--access-point', type=int, required=True,
|
||||
help='access point number')
|
||||
|
||||
arg = config.load('openwrt_logger', parser=parser)
|
||||
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})
|
||||
|
@ -41,7 +41,7 @@ from telegram.ext import (
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
config.load('polaris_kettle_bot')
|
||||
config.load_app('polaris_kettle_bot')
|
||||
|
||||
primary_choices = (70, 80, 90, 100)
|
||||
all_choices = range(
|
||||
|
@ -75,7 +75,7 @@ def main():
|
||||
parser.add_argument('-t', '--temperature', dest='temp', type=int, default=tempmax,
|
||||
choices=range(tempmin, tempmax+tempstep, tempstep))
|
||||
|
||||
arg = config.load('polaris_kettle_util', use_cli=True, parser=parser)
|
||||
arg = config.load_app('polaris_kettle_util', use_cli=True, parser=parser)
|
||||
|
||||
if arg.mode == 'mqtt':
|
||||
server = MqttServer()
|
||||
|
@ -16,7 +16,7 @@ from home.mqtt.module.temphum import MqttTemphumDataPayload
|
||||
from home.mqtt.module.diagnostics import InitialDiagnosticsPayload, DiagnosticsPayload
|
||||
|
||||
|
||||
config.load('pump_bot')
|
||||
config.load_app('pump_bot')
|
||||
|
||||
mqtt: Optional[MqttWrapper] = None
|
||||
mqtt_node: Optional[MqttNode] = None
|
||||
@ -208,7 +208,7 @@ def markup(ctx: Optional[bot.Context]) -> Optional[ReplyKeyboardMarkup]:
|
||||
return ReplyKeyboardMarkup(buttons, one_time_keyboard=False)
|
||||
|
||||
|
||||
def mqtt_payload_callback(payload: MqttPayload):
|
||||
def mqtt_payload_callback(mqtt_node: MqttNode, payload: MqttPayload):
|
||||
global watering_mcu_status
|
||||
|
||||
types_the_node_can_send = (
|
||||
|
@ -13,7 +13,7 @@ from home.mqtt.module.relay import MqttRelayState
|
||||
from home.mqtt.module.diagnostics import InitialDiagnosticsPayload, DiagnosticsPayload
|
||||
|
||||
|
||||
config.load('pump_mqtt_bot')
|
||||
config.load_app('pump_mqtt_bot')
|
||||
|
||||
bot.initialize()
|
||||
bot.lang.ru(
|
||||
|
@ -6,13 +6,12 @@ from functools import partial
|
||||
|
||||
from home.config import config
|
||||
from home.telegram import bot
|
||||
from home.mqtt import MqttRelay, MqttRelayState
|
||||
from home.mqtt.esp import MqttEspDevice
|
||||
from home.mqtt.payload import MqttPayload
|
||||
from home.mqtt.payload.relay import InitialDiagnosticsPayload, DiagnosticsPayload
|
||||
from home.mqtt import MqttPayload, MqttNode, MqttWrapper
|
||||
from home.mqtt.module.relay import MqttRelayModule, MqttRelayState
|
||||
from home.mqtt.module.diagnostics import InitialDiagnosticsPayload, DiagnosticsPayload
|
||||
|
||||
|
||||
config.load('relay_mqtt_bot')
|
||||
config.load_app('relay_mqtt_bot')
|
||||
|
||||
bot.initialize()
|
||||
bot.lang.ru(
|
||||
@ -34,7 +33,11 @@ status_emoji = {
|
||||
'on': '✅',
|
||||
'off': '❌'
|
||||
}
|
||||
mqtt_relay: Optional[MqttRelay] = None
|
||||
|
||||
|
||||
# mqtt_relay: Optional[MqttRelayModule] = None
|
||||
mqtt: Optional[MqttWrapper] = None
|
||||
relay_nodes: dict[str, MqttRelayModule] = {}
|
||||
relay_states: dict[str, MqttRelayState] = {}
|
||||
|
||||
|
||||
@ -43,23 +46,24 @@ class UserAction(Enum):
|
||||
OFF = 'off'
|
||||
|
||||
|
||||
def on_mqtt_message(home_id, message: MqttPayload):
|
||||
def on_mqtt_message(node: MqttNode,
|
||||
message: MqttPayload):
|
||||
if isinstance(message, InitialDiagnosticsPayload) or isinstance(message, DiagnosticsPayload):
|
||||
kwargs = dict(rssi=message.rssi, enabled=message.flags.state)
|
||||
if isinstance(message, InitialDiagnosticsPayload):
|
||||
kwargs['fw_version'] = message.fw_version
|
||||
if home_id not in relay_states:
|
||||
relay_states[home_id] = MqttRelayState()
|
||||
relay_states[home_id].update(**kwargs)
|
||||
if node.id not in relay_states:
|
||||
relay_states[node.id] = MqttRelayState()
|
||||
relay_states[node.id].update(**kwargs)
|
||||
|
||||
|
||||
def enable_handler(home_id: str, ctx: bot.Context) -> None:
|
||||
mqtt_relay.set_power(home_id, True)
|
||||
def enable_handler(node_id: str, ctx: bot.Context) -> None:
|
||||
relay_nodes[node_id].switchpower(True)
|
||||
ctx.reply(ctx.lang('done'))
|
||||
|
||||
|
||||
def disable_handler(home_id: str, ctx: bot.Context) -> None:
|
||||
mqtt_relay.set_power(home_id, False)
|
||||
def disable_handler(node_id: str, ctx: bot.Context) -> None:
|
||||
relay_nodes[node_id].switchpower(False)
|
||||
ctx.reply(ctx.lang('done'))
|
||||
|
||||
|
||||
@ -86,9 +90,13 @@ def markup(ctx: Optional[bot.Context]) -> Optional[ReplyKeyboardMarkup]:
|
||||
|
||||
if __name__ == '__main__':
|
||||
devices = []
|
||||
mqtt = MqttWrapper()
|
||||
for device_id, data in config['relays'].items():
|
||||
devices.append(MqttEspDevice(id=device_id,
|
||||
secret=data['secret']))
|
||||
mqtt_node = MqttNode(node_id=device_id, node_secret=data['secret'])
|
||||
relay_nodes[device_id] = mqtt_node.load_module('relay')
|
||||
mqtt_node.add_payload_callback(on_mqtt_message)
|
||||
mqtt.add_node(mqtt_node)
|
||||
|
||||
labels = data['labels']
|
||||
bot.lang.ru(**{device_id: labels['ru']})
|
||||
bot.lang.en(**{device_id: labels['en']})
|
||||
@ -101,12 +109,9 @@ if __name__ == '__main__':
|
||||
messages.append(f'{type_emoji}{status_emoji[action.value]} {labels[_lang]}')
|
||||
bot.handler(texts=messages)(partial(enable_handler if action == UserAction.ON else disable_handler, device_id))
|
||||
|
||||
mqtt_relay = MqttRelay(devices=devices)
|
||||
mqtt_relay.set_message_callback(on_mqtt_message)
|
||||
mqtt_relay.configure_tls()
|
||||
mqtt_relay.connect_and_loop(loop_forever=False)
|
||||
mqtt.configure_tls()
|
||||
mqtt.connect_and_loop(loop_forever=False)
|
||||
|
||||
# bot.enable_logging(BotType.RELAY_MQTT)
|
||||
bot.run(start_handler=start)
|
||||
|
||||
mqtt_relay.disconnect()
|
||||
mqtt.disconnect()
|
||||
|
@ -1,17 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
from home import http
|
||||
from home.config import config
|
||||
from home.mqtt import MqttRelay, MqttRelayState
|
||||
from home.mqtt.esp import MqttEspDevice
|
||||
from home.mqtt.payload import MqttPayload
|
||||
from home.mqtt.payload.relay import InitialDiagnosticsPayload, DiagnosticsPayload
|
||||
from typing import Optional
|
||||
from home.mqtt import MqttPayload, MqttWrapper, MqttNode, MqttModule
|
||||
from home.mqtt.module.relay import MqttRelayState, MqttRelayModule
|
||||
from home.mqtt.module.diagnostics import InitialDiagnosticsPayload, DiagnosticsPayload
|
||||
from typing import Optional, Union
|
||||
|
||||
mqtt_relay: Optional[MqttRelay] = None
|
||||
mqtt: Optional[MqttWrapper] = None
|
||||
mqtt_nodes: dict[str, MqttNode] = {}
|
||||
relay_modules: dict[str, Union[MqttRelayModule, MqttModule]] = {}
|
||||
relay_states: dict[str, MqttRelayState] = {}
|
||||
|
||||
|
||||
def on_mqtt_message(device_id, message: MqttPayload):
|
||||
def on_mqtt_message(node: MqttNode,
|
||||
message: MqttPayload):
|
||||
if isinstance(message, InitialDiagnosticsPayload) or isinstance(message, DiagnosticsPayload):
|
||||
kwargs = dict(rssi=message.rssi, enabled=message.flags.state)
|
||||
if device_id not in relay_states:
|
||||
@ -29,17 +31,22 @@ class RelayMqttHttpProxy(http.HTTPServer):
|
||||
async def _relay_on_off(self,
|
||||
enable: Optional[bool],
|
||||
req: http.Request):
|
||||
device_id = req.match_info['id']
|
||||
device_secret = req.query['secret']
|
||||
node_id = req.match_info['id']
|
||||
node_secret = req.query['secret']
|
||||
|
||||
node = mqtt_nodes[node_id]
|
||||
relay_module = relay_modules[node_id]
|
||||
|
||||
if enable is None:
|
||||
if device_id in relay_states and relay_states[device_id].ever_updated:
|
||||
cur_state = relay_states[device_id].enabled
|
||||
if node_id in relay_states and relay_states[node_id].ever_updated:
|
||||
cur_state = relay_states[node_id].enabled
|
||||
else:
|
||||
cur_state = False
|
||||
enable = not cur_state
|
||||
|
||||
mqtt_relay.set_power(device_id, enable, device_secret)
|
||||
if not node.secret:
|
||||
node.secret = node_secret
|
||||
relay_module.switchpower(enable)
|
||||
return self.ok()
|
||||
|
||||
async def relay_on(self, req: http.Request):
|
||||
@ -53,15 +60,22 @@ class RelayMqttHttpProxy(http.HTTPServer):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load('relay_mqtt_http_proxy')
|
||||
config.load_app('relay_mqtt_http_proxy')
|
||||
|
||||
mqtt_relay = MqttRelay(devices=[MqttEspDevice(id=device_id) for device_id in config.get('relay.devices')])
|
||||
mqtt_relay.configure_tls()
|
||||
mqtt_relay.set_message_callback(on_mqtt_message)
|
||||
mqtt_relay.connect_and_loop(loop_forever=False)
|
||||
mqtt = MqttWrapper()
|
||||
for device_id, data in config['relays'].items():
|
||||
mqtt_node = MqttNode(node_id=device_id)
|
||||
relay_modules[device_id] = mqtt_node.load_module('relay')
|
||||
mqtt_nodes[device_id] = mqtt_node
|
||||
mqtt_node.add_payload_callback(on_mqtt_message)
|
||||
mqtt.add_node(mqtt_node)
|
||||
mqtt_node.add_payload_callback(on_mqtt_message)
|
||||
|
||||
mqtt.configure_tls()
|
||||
mqtt.connect_and_loop(loop_forever=False)
|
||||
|
||||
proxy = RelayMqttHttpProxy(config.get_addr('server.listen'))
|
||||
try:
|
||||
proxy.run()
|
||||
except KeyboardInterrupt:
|
||||
mqtt_relay.disconnect()
|
||||
mqtt.disconnect()
|
||||
|
@ -23,7 +23,7 @@ from home.api.types import (
|
||||
TemperatureSensorLocation
|
||||
)
|
||||
|
||||
config.load('sensors_bot')
|
||||
config.load_app('sensors_bot')
|
||||
bot.initialize()
|
||||
|
||||
bot.lang.ru(
|
||||
|
@ -1,58 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
import json
|
||||
|
||||
from home.util import parse_addr, MySimpleSocketClient
|
||||
from home.mqtt import Mqtt, poll_tick
|
||||
from home.mqtt.payload.sensors import Temperature
|
||||
from home.config import config
|
||||
|
||||
|
||||
class MqttClient(Mqtt):
|
||||
def __init__(self):
|
||||
super().__init__(self)
|
||||
self._home_id = config['mqtt']['home_id']
|
||||
|
||||
def poll(self):
|
||||
freq = int(config['mqtt']['sensors']['poll_freq'])
|
||||
self._logger.debug(f'freq={freq}')
|
||||
|
||||
g = poll_tick(freq)
|
||||
while True:
|
||||
time.sleep(next(g))
|
||||
for k, v in config['mqtt']['sensors']['si7021'].items():
|
||||
host, port = parse_addr(v['addr'])
|
||||
self.publish_si7021(host, port, k)
|
||||
|
||||
def publish_si7021(self, host: str, port: int, name: str):
|
||||
self._logger.debug(f"publish_si7021/{name}: {host}:{port}")
|
||||
|
||||
try:
|
||||
now = time.time()
|
||||
socket = MySimpleSocketClient(host, port)
|
||||
|
||||
socket.write('read')
|
||||
response = json.loads(socket.read().strip())
|
||||
|
||||
temp = response['temp']
|
||||
humidity = response['humidity']
|
||||
|
||||
self._logger.debug(f'publish_si7021/{name}: temp={temp} humidity={humidity}')
|
||||
|
||||
pld = Temperature(time=round(now),
|
||||
temp=temp,
|
||||
rh=humidity)
|
||||
self._client.publish(f'hk/{self._home_id}/si7021/{name}',
|
||||
payload=pld.pack(),
|
||||
qos=1)
|
||||
except Exception as e:
|
||||
self._logger.exception(e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load('sensors_mqtt_sender')
|
||||
|
||||
client = MqttClient()
|
||||
client.configure_tls()
|
||||
client.connect_and_loop(loop_forever=False)
|
||||
client.poll()
|
@ -23,7 +23,7 @@ from telegram import ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardBu
|
||||
|
||||
from PIL import Image
|
||||
|
||||
config.load('sound_bot')
|
||||
config.load_app('sound_bot')
|
||||
|
||||
nodes = {}
|
||||
for nodename, nodecfg in config['nodes'].items():
|
||||
|
@ -77,7 +77,7 @@ if __name__ == '__main__':
|
||||
if not os.getegid() == 0:
|
||||
raise RuntimeError("Must be run as root.")
|
||||
|
||||
config.load('sound_node')
|
||||
config.load_app('sound_node')
|
||||
|
||||
storage = SoundRecordStorage(config['node']['storage'])
|
||||
|
||||
|
@ -14,7 +14,7 @@ if __name__ == '__main__':
|
||||
if not os.getegid() == 0:
|
||||
sys.exit('Must be run as root.')
|
||||
|
||||
config.load('sound_sensor_node')
|
||||
config.load_app('sound_sensor_node')
|
||||
|
||||
kwargs = {}
|
||||
if 'delay' in config['node']:
|
||||
|
@ -159,7 +159,7 @@ def api_error_handler(exc, name, req: RequestParams):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load('sound_sensor_server')
|
||||
config.load_app('sound_sensor_server')
|
||||
|
||||
hc = HitCounter()
|
||||
api = WebAPIClient(timeout=(10, 60))
|
||||
|
@ -3,7 +3,7 @@
|
||||
from home.config import config
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load('ssh_tunnels_config_util')
|
||||
config.load_app('ssh_tunnels_config_util')
|
||||
|
||||
network_prefix = config['network']
|
||||
hostnames = []
|
||||
|
@ -63,7 +63,7 @@ async def run_server(host, port):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load()
|
||||
config.load_app()
|
||||
|
||||
if 'measure_delay' in config['sensor']:
|
||||
delay = float(config['sensor']['measure_delay'])
|
||||
|
@ -2,18 +2,8 @@
|
||||
import paho.mqtt.client as mqtt
|
||||
import re
|
||||
|
||||
from home.mqtt import Mqtt
|
||||
from home.config import config
|
||||
from home.mqtt.payload.sensors import Temperature
|
||||
from home.api.types import TemperatureSensorLocation
|
||||
from home.database import SensorsDatabase
|
||||
|
||||
|
||||
def get_sensor_type(sensor: str) -> TemperatureSensorLocation:
|
||||
for item in TemperatureSensorLocation:
|
||||
if sensor == item.name.lower():
|
||||
return item
|
||||
raise ValueError(f'unexpected sensor value: {sensor}')
|
||||
from home.mqtt import MqttWrapper, MqttNode
|
||||
|
||||
|
||||
class MqttServer(Mqtt):
|
||||
@ -47,7 +37,12 @@ class MqttServer(Mqtt):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load('sensors_mqtt_receiver')
|
||||
config.load_app('temphum_mqtt_receiver')
|
||||
|
||||
server = MqttServer()
|
||||
server.connect_and_loop()
|
||||
mqtt = MqttWrapper(clean_session=False)
|
||||
node = MqttNode(node_id='+')
|
||||
node.load_module('temphum', write_to_database=True)
|
||||
mqtt.add_node(node)
|
||||
|
||||
mqtt.configure_tls()
|
||||
mqtt.connect_and_loop()
|
@ -63,7 +63,7 @@ async def run_server(host, port):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load()
|
||||
config.load_app()
|
||||
|
||||
if 'measure_delay' in config['sensor']:
|
||||
delay = float(config['sensor']['measure_delay'])
|
||||
|
9
src/test_new_config.py
Normal file
9
src/test_new_config.py
Normal file
@ -0,0 +1,9 @@
|
||||
from home.config import config, app_config, LinuxBoardsConfig
|
||||
from pprint import pprint
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load_app(name=False)
|
||||
|
||||
lbc = LinuxBoardsConfig()
|
||||
pprint(lbc.data)
|
@ -231,7 +231,7 @@ if __name__ == '__main__':
|
||||
_app_name = 'web_api'
|
||||
if is_development_mode():
|
||||
_app_name += '_dev'
|
||||
config.load(_app_name)
|
||||
config.load_app(_app_name)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
[Unit]
|
||||
Description=sensors mqtt receiver
|
||||
Description=temphum mqtt receiver
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=user
|
||||
Group=user
|
||||
Restart=on-failure
|
||||
ExecStart=python3 /home/user/home/src/sensors_mqtt_receiver.py
|
||||
ExecStart=python3 /home/user/home/src/temphum_mqtt_receiver.py
|
||||
WorkingDirectory=/home/user
|
||||
|
||||
[Install]
|
||||
|
@ -1,13 +0,0 @@
|
||||
[Unit]
|
||||
Description=Sensors MQTT sender
|
||||
After=temphumd.service
|
||||
|
||||
[Service]
|
||||
User=user
|
||||
Group=user
|
||||
Restart=on-failure
|
||||
ExecStart=/home/user/homekit/src/sensors_mqtt_sender.py
|
||||
WorkingDirectory=/home/user
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -12,7 +12,7 @@ from src.home.mqtt.relay import MQTTRelayClient
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load('test_mqtt_relay_server')
|
||||
config.load_app('test_mqtt_relay_server')
|
||||
relay = MQTTRelayClient('test')
|
||||
relay.configure_tls()
|
||||
relay.connect_and_loop()
|
||||
|
@ -18,7 +18,7 @@ if __name__ == '__main__':
|
||||
parser.add_argument('--off', action='store_true')
|
||||
parser.add_argument('--stat', action='store_true')
|
||||
|
||||
config.load('test_mqtt_relay', parser=parser)
|
||||
config.load_app('test_mqtt_relay', parser=parser)
|
||||
arg = parser.parse_args()
|
||||
|
||||
relay = MQTTRelayController('test')
|
||||
|
@ -28,7 +28,7 @@ if __name__ == '__main__':
|
||||
parser.add_argument('--decr', type=str)
|
||||
# parser.add_argument('--dump-config', action='store_true')
|
||||
|
||||
args = config.load('test_amixer', parser=parser)
|
||||
args = config.load_app('test_amixer', parser=parser)
|
||||
|
||||
# if args.dump_config:
|
||||
# print(config.data)
|
||||
|
@ -13,7 +13,7 @@ from src.home.config import config
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load('test_api')
|
||||
config.load_app('test_api')
|
||||
|
||||
api = WebAPIClient()
|
||||
print(api.log_bot_request(BotType.ADMIN, 1, "test_api.py"))
|
||||
|
@ -21,7 +21,7 @@ if __name__ == '__main__':
|
||||
parser.add_argument('--status', action='store_true',
|
||||
help='print status and exit')
|
||||
|
||||
arg = config.load(False, parser=parser)
|
||||
arg = config.load_app(False, parser=parser)
|
||||
cam = esp32.WebClient(addr=parse_addr(arg.addr))
|
||||
|
||||
if arg.status:
|
||||
|
@ -372,5 +372,5 @@ def main():
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load('test_inverter_monitor')
|
||||
config.load_app('test_inverter_monitor')
|
||||
main()
|
||||
|
@ -77,5 +77,5 @@ def cleanup_job():
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load('ipcam_server')
|
||||
config.load_app('ipcam_server')
|
||||
cleanup_job()
|
||||
|
@ -64,7 +64,7 @@ def api_success_handler(response, name, req: RequestParams):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load('test_record_upload')
|
||||
config.load_app('test_record_upload')
|
||||
|
||||
nodes = {}
|
||||
for name, addr in config['nodes'].items():
|
||||
|
@ -56,7 +56,7 @@ def hits_sender():
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load('test_api')
|
||||
config.load_app('test_api')
|
||||
|
||||
hc = HitCounter()
|
||||
api = WebAPIClient()
|
||||
|
@ -20,7 +20,7 @@ async def main():
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load('test_telegram_aio_send_photo')
|
||||
config.load_app('test_telegram_aio_send_photo')
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
asyncio.ensure_future(main())
|
||||
|
Loading…
x
Reference in New Issue
Block a user