oin/oin_thermostat/mqtt.py

127 lines
4.2 KiB
Python

import json
import logging
import paho.mqtt.client as mqtt
from .screen import Screen
from .select import Selector
logger = logging.getLogger(__name__)
class HAClient:
def __init__(
self,
entity: str,
secondary_entities: list[str] = [],
config: dict = dict(),
) -> None:
self.entity = entity
self.secondary_entities = secondary_entities
self.config = config
self.state_topic = "oin/state"
self.availability_topic = "oin/availability"
self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
self.client.username_pw_set(
username=self.config.get("username", None),
password=self.config.get("password", None),
)
self.screen = Screen()
self.selector = Selector(self.send_data)
@property
def ha_options(self) -> dict[str, str | dict[str, str]]:
return {
"dev": {
"ids": "oin",
"name": "Oin",
},
"o": {
"name": "Oin",
},
"availability_topic": self.availability_topic,
"state_topic": self.state_topic,
"cmps": self.selector.ha_options,
}
def connect(self) -> None:
logger.debug("Connecting to HA...")
self.client.will_set(self.availability_topic, "offline", retain=True)
self.client.connect(self.config.get("host"), self.config.get("port", 1883))
self.subscribe(entity_topic(self.entity), self.state_update)
for entity in self.secondary_entities:
self.subscribe(entity_topic(entity, "state"), self.secondary_state_update)
self.publish("homeassistant/device/oin/config", self.ha_options, retain=True)
self.client.publish(self.availability_topic, "online", retain=True)
def publish(self, topic, data, **kwargs):
logger.debug(f"Sending message on topic <{topic}>: {json.dumps(data)}")
self.client.publish(topic, json.dumps(data), **kwargs)
def subscribe(self, topic, callback):
logger.debug(f"Subscribe to <{topic}>")
self.client.subscribe(topic)
self.client.message_callback_add(topic, callback)
def unsubscribe(self, topic):
logger.debug(f"Unsubscribe from <{topic}>")
self.client.unsubscribe(topic)
def loop(self):
logger.info("Starting MQTT client loop")
self.client.loop_forever()
def state_update(self, client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
logger.debug(f"Message received on topic <{message.topic}>: {message.payload}.")
subtopic = message.topic.rsplit("/", maxsplit=1)[1]
match subtopic:
case "current_temperature":
self.screen.value = parse(message)
case "temperature":
if (value := parse(message)) != self.selector.temperature:
self.screen.tmp_value = value
self.selector.temperature = value
case "hvac_action":
self.screen.mode = parse(message)
case "preset_modes":
if (value := parse(message)) != self.selector.preset_modes:
self.selector.preset_modes = value
case "preset_mode":
if (value := parse(message)) != self.selector.mode:
self.selector.mode = value
case "state":
match message.payload.decode():
case "heat":
self.selector.switch = True
case "off":
self.selector.switch = False
def secondary_state_update(
self, client: mqtt.Client, userdata, message: mqtt.MQTTMessage
):
logger.debug(f"Message received on topic <{message.topic}>: {message.payload}.")
_, grp, ent, subtopic = message.topic.split("/")
idx = self.secondary_entities.index(f"{grp}.{ent}")
if subtopic == "state":
self.screen.secondary |= {idx: message.payload.decode()}
def send_data(self, data):
self.publish(self.state_topic, data)
def parse(message):
return json.loads(message.payload.decode())
def entity_topic(entity, subtopic="#"):
topic = entity.replace(".", "/")
return f"homeassistant/{topic}/{subtopic}"