140 lines
4.3 KiB
Python
Executable File
140 lines
4.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import logging
|
|
import include_homekit
|
|
|
|
from aiohttp import web
|
|
from homekit import http
|
|
from homekit.config import config, AppConfigUnit
|
|
from homekit.mqtt import MqttPayload, MqttWrapper, MqttNode, MqttModule, MqttNodesConfig
|
|
from homekit.mqtt.module.relay import MqttRelayState, MqttRelayModule, MqttPowerStatusPayload
|
|
from homekit.mqtt.module.diagnostics import InitialDiagnosticsPayload, DiagnosticsPayload
|
|
from typing import Optional, Union
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
mqtt: Optional[MqttWrapper] = None
|
|
mqtt_nodes: dict[str, MqttNode] = {}
|
|
relay_modules: dict[str, Union[MqttRelayModule, MqttModule]] = {}
|
|
relay_states: dict[str, MqttRelayState] = {}
|
|
mqtt_nodes_config = MqttNodesConfig()
|
|
|
|
|
|
class RelayMqttHttpProxyConfig(AppConfigUnit):
|
|
NAME = 'relay_mqtt_http_proxy'
|
|
|
|
@classmethod
|
|
def schema(cls) -> Optional[dict]:
|
|
return {
|
|
'relay_nodes': {
|
|
'type': 'list',
|
|
'required': True,
|
|
'schema': {
|
|
'type': 'string'
|
|
}
|
|
},
|
|
'listen_addr': cls._addr_schema(required=True)
|
|
}
|
|
|
|
@staticmethod
|
|
def custom_validator(data):
|
|
relay_node_names = mqtt_nodes_config.get_nodes(filters=('relay',), only_names=True)
|
|
for node in data['relay_nodes']:
|
|
if node not in relay_node_names:
|
|
raise ValueError(f'unknown relay node "{node}"')
|
|
|
|
|
|
def on_mqtt_message(node: MqttNode,
|
|
message: MqttPayload):
|
|
try:
|
|
is_legacy = mqtt_nodes_config[node.id]['relay']['legacy_topics']
|
|
logger.debug(f'on_mqtt_message: relay {node.id} uses legacy topic names')
|
|
except KeyError:
|
|
is_legacy = False
|
|
kwargs = {}
|
|
|
|
if isinstance(message, InitialDiagnosticsPayload) or isinstance(message, DiagnosticsPayload):
|
|
kwargs['rssi'] = message.rssi
|
|
if is_legacy:
|
|
kwargs['enabled'] = message.flags.state
|
|
|
|
if not is_legacy and isinstance(message, MqttPowerStatusPayload):
|
|
kwargs['enabled'] = message.opened
|
|
|
|
if len(kwargs):
|
|
logger.debug(f'on_mqtt_message: {node.id}: going to update relay state: {str(kwargs)}')
|
|
if node.id not in relay_states:
|
|
relay_states[node.id] = MqttRelayState()
|
|
relay_states[node.id].update(**kwargs)
|
|
|
|
|
|
# -=-=-=-=-=-=- #
|
|
# Web interface #
|
|
# -=-=-=-=-=-=- #
|
|
|
|
routes = web.RouteTableDef()
|
|
|
|
|
|
async def _relay_on_off(self,
|
|
enable: Optional[bool],
|
|
req: web.Request):
|
|
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 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
|
|
|
|
node.secret = node_secret
|
|
relay_module.switchpower(enable)
|
|
return self.ok()
|
|
|
|
|
|
@routes.get('/relay/{id}/on')
|
|
async def relay_on(self, req: web.Request):
|
|
return await self._relay_on_off(True, req)
|
|
|
|
|
|
@routes.get('/relay/{id}/off')
|
|
async def relay_off(self, req: web.Request):
|
|
return await self._relay_on_off(False, req)
|
|
|
|
|
|
@routes.get('/relay/{id}/toggle')
|
|
async def relay_toggle(self, req: web.Request):
|
|
return await self._relay_on_off(None, req)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
config.load_app(RelayMqttHttpProxyConfig)
|
|
|
|
mqtt = MqttWrapper(client_id='relay_mqtt_http_proxy',
|
|
randomize_client_id=True)
|
|
for node_id in config.app_config['relay_nodes']:
|
|
node_data = mqtt_nodes_config.get_node(node_id)
|
|
mqtt_node = MqttNode(node_id=node_id)
|
|
module_kwargs = {}
|
|
try:
|
|
if node_data['relay']['legacy_topics']:
|
|
module_kwargs['legacy_topics'] = True
|
|
except KeyError:
|
|
pass
|
|
relay_modules[node_id] = mqtt_node.load_module('relay', **module_kwargs)
|
|
if 'legacy_topics' in module_kwargs:
|
|
mqtt_node.load_module('diagnostics')
|
|
mqtt_node.add_payload_callback(on_mqtt_message)
|
|
mqtt.add_node(mqtt_node)
|
|
mqtt_nodes[node_id] = mqtt_node
|
|
|
|
mqtt.connect_and_loop(loop_forever=False)
|
|
|
|
try:
|
|
http.serve(config.app_config['listen_addr'], routes=routes)
|
|
except KeyboardInterrupt:
|
|
mqtt.disconnect()
|