2024-12-07 15:22:08 +01:00
import json
import logging
2024-12-08 11:51:05 +01:00
import sys
from collections . abc import Callable
from typing import Any
2024-12-07 15:22:08 +01:00
import paho . mqtt . client as mqtt
from . screen import Screen
from . select import Selector
logger = logging . getLogger ( __name__ )
class HAClient :
2024-12-08 10:43:31 +01:00
def __init__ (
self ,
entity : str ,
secondary_entities : list [ str ] = [ ] ,
2024-12-08 11:51:05 +01:00
mqtt_config : dict [ str , str ] = dict ( ) ,
2024-12-08 10:43:31 +01:00
) - > None :
2024-12-07 15:22:08 +01:00
self . entity = entity
self . secondary_entities = secondary_entities
2024-12-08 11:17:25 +01:00
self . config = mqtt_config
2024-12-07 15:22:08 +01:00
self . state_topic = " oin/state "
self . availability_topic = " oin/availability "
self . client = mqtt . Client ( mqtt . CallbackAPIVersion . VERSION2 )
2024-12-08 11:17:25 +01:00
username = self . config . get ( " username " , None )
logger . debug ( f " Setting up MQTT with user < { username } > " )
2024-12-08 10:43:31 +01:00
self . client . username_pw_set (
2024-12-08 11:17:25 +01:00
username = username ,
2024-12-08 10:43:31 +01:00
password = self . config . get ( " password " , None ) ,
)
2024-12-07 15:22:08 +01:00
self . screen = Screen ( )
self . selector = Selector ( self . send_data )
@property
2024-12-08 10:43:31 +01:00
def ha_options ( self ) - > dict [ str , str | dict [ str , str ] ] :
2024-12-07 15:22:08 +01:00
return {
" dev " : {
" ids " : " oin " ,
" name " : " Oin " ,
} ,
" o " : {
" name " : " Oin " ,
} ,
" availability_topic " : self . availability_topic ,
" state_topic " : self . state_topic ,
" cmps " : self . selector . ha_options ,
}
2024-12-08 10:43:31 +01:00
def connect ( self ) - > None :
2024-12-07 15:22:08 +01:00
self . client . will_set ( self . availability_topic , " offline " , retain = True )
2024-12-08 11:17:25 +01:00
host = self . config . get ( " host " )
port = self . config . get ( " port " , 1883 )
2024-12-08 11:51:05 +01:00
logger . debug ( f " Connecting to < { host } > on port < { port } >. " )
2024-12-08 11:17:25 +01:00
self . client . connect ( host , port )
2024-12-07 15:22:08 +01:00
self . subscribe ( entity_topic ( self . entity ) , self . state_update )
2024-12-08 11:51:05 +01:00
self . subscribe (
[ entity_topic ( entity ) for entity in self . secondary_entities ] ,
self . secondary_state_update ,
)
2024-12-07 15:22:08 +01:00
self . publish ( " homeassistant/device/oin/config " , self . ha_options , retain = True )
self . client . publish ( self . availability_topic , " online " , retain = True )
2024-12-08 11:51:05 +01:00
def publish ( self , topic : str , data : Any , * * kwargs ) - > mqtt . MQTTMessageInfo :
2024-12-07 15:22:08 +01:00
logger . debug ( f " Sending message on topic < { topic } >: { json . dumps ( data ) } " )
2024-12-08 11:51:05 +01:00
return self . client . publish ( topic , json . dumps ( data ) , * * kwargs )
def subscribe ( self , topic : str | list [ str ] , callback : Callable ) - > None :
logger . debug ( f " Subscribing to < { topic } >. " )
match topic :
case str ( ) :
self . client . message_callback_add ( topic , callback )
code , _ = self . client . subscribe ( topic )
case list ( ) :
for top in topic :
self . client . message_callback_add ( top , callback )
code , _ = self . client . subscribe ( [ ( top , 0 ) for top in topic ] )
2024-12-07 15:22:08 +01:00
2024-12-08 11:51:05 +01:00
if code != 0 :
logger . error ( f " Failed subscribing to topic < { topic } > with code < { code } >. " )
sys . exit ( 1 )
2024-12-07 15:22:08 +01:00
2024-12-08 11:51:05 +01:00
def loop ( self ) - > mqtt . MQTTErrorCode :
logger . info ( " Starting MQTT client loop. " )
code = self . client . loop_forever ( retry_first_connection = True )
2024-12-07 15:22:08 +01:00
2024-12-08 11:51:05 +01:00
if code != 0 :
logger . error ( " MQTT client loop failed with code < {code} >. " )
2024-12-07 15:22:08 +01:00
2024-12-08 11:51:05 +01:00
def state_update (
self , client : mqtt . Client , userdata : Any , message : mqtt . MQTTMessage
) - > None :
2024-12-07 15:22:08 +01:00
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 (
2024-12-08 11:51:05 +01:00
self , client : mqtt . Client , userdata : Any , message : mqtt . MQTTMessage
) - > None :
2024-12-07 15:22:08 +01:00
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 ( ) }
2024-12-08 11:51:05 +01:00
def send_data ( self , data : Any ) - > mqtt . MQTTMessageInfo :
return self . publish ( self . state_topic , data )
2024-12-07 15:22:08 +01:00
2024-12-08 11:51:05 +01:00
def parse ( message : mqtt . MQTTMessage ) - > Any :
2024-12-07 15:22:08 +01:00
return json . loads ( message . payload . decode ( ) )
2024-12-08 11:51:05 +01:00
def entity_topic ( entity : str , subtopic : str = " # " ) - > str :
2024-12-07 15:22:08 +01:00
topic = entity . replace ( " . " , " / " )
return f " homeassistant/ { topic } / { subtopic } "