diff options
-rw-r--r-- | doc/relay_mqtt_http_proxy.md | 20 | ||||
-rw-r--r-- | src/home/mqtt/relay.py | 8 | ||||
-rw-r--r-- | src/relay_mqtt_http_proxy.py | 68 |
3 files changed, 93 insertions, 3 deletions
diff --git a/doc/relay_mqtt_http_proxy.md b/doc/relay_mqtt_http_proxy.md new file mode 100644 index 0000000..dfd0c90 --- /dev/null +++ b/doc/relay_mqtt_http_proxy.md @@ -0,0 +1,20 @@ +## config example + +``` +[mqtt] +host = "mqtt.solarmon.ru" +port = 8883 + +client_id = "relay_mqtt_util" +username = "" +password = "" + +[relay] +devices = ['id1', 'id2'] + +[server] +listen = "0.0.0.0:8821" + +[logging] +verbose = false +```
\ No newline at end of file diff --git a/src/home/mqtt/relay.py b/src/home/mqtt/relay.py index 0c6084f..53d43e4 100644 --- a/src/home/mqtt/relay.py +++ b/src/home/mqtt/relay.py @@ -86,11 +86,13 @@ class MQTTRelay(MQTTBase): except Exception as e: self._logger.exception(str(e)) - def set_power(self, device_id, enable: bool): + def set_power(self, device_id, enable: bool, secret=None): device = next(d for d in self._devices if d.id == device_id) - assert device.secret is not None, 'device secret not specified' + secret = secret if secret else device.secret + + assert secret is not None, 'device secret not specified' - payload = PowerPayload(secret=device.secret, + payload = PowerPayload(secret=secret, state=enable) self._client.publish(f'hk/{device.id}/relay/power', payload=payload.pack(), diff --git a/src/relay_mqtt_http_proxy.py b/src/relay_mqtt_http_proxy.py new file mode 100644 index 0000000..ab8a138 --- /dev/null +++ b/src/relay_mqtt_http_proxy.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +from home import http +from home.config import config +from home.mqtt import MQTTRelay, MQTTRelayDevice, MQTTRelayState +from home.mqtt.payload import MQTTPayload +from home.mqtt.payload.relay import InitialStatPayload, StatPayload +from typing import Optional +from home.util import parse_addr + +mqtt_relay: Optional[MQTTRelay] = None +relay_states: dict[str, MQTTRelayState] = {} + + +def on_mqtt_message(device_id, message: MQTTPayload): + if isinstance(message, InitialStatPayload) or isinstance(message, StatPayload): + kwargs = dict(rssi=message.rssi, enabled=message.flags.state) + if device_id not in relay_states: + relay_states[device_id] = MQTTRelayState() + relay_states[device_id].update(**kwargs) + + +class RelayMqttHttpProxy(http.HTTPServer): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.get('/relay/{id}/on', self.relay_on) + self.get('/relay/{id}/off', self.relay_off) + self.get('/relay/{id}/toggle', self.relay_toggle) + + async def _relay_on_off(self, + enable: Optional[bool], + req: http.Request): + device_id = req.match_info['id'] + device_secret = req.query['secret'] + + if enable is None: + if device_id in relay_states and relay_states[device_id].ever_updated: + cur_state = relay_states[device_id].enabled + else: + cur_state = False + enable = not cur_state + + mqtt_relay.set_power(device_id, enable, device_secret) + return self.ok() + + async def relay_on(self, req: http.Request): + return await self._relay_on_off(True, req) + + async def relay_off(self, req: http.Request): + return await self._relay_on_off(False, req) + + async def relay_toggle(self, req: http.Request): + return await self._relay_on_off(None, req) + + +if __name__ == '__main__': + config.load('relay_mqtt_http_proxy') + + mqtt_relay = MQTTRelay(devices=[MQTTRelayDevice(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) + + proxy = RelayMqttHttpProxy(parse_addr(config.get('server.listen'))) + try: + proxy.run() + except KeyboardInterrupt: + mqtt_relay.disconnect() + |