1+ import os .path
2+
3+ from ha_mqtt_discoverable import Settings , DeviceInfo
4+ from ha_mqtt_discoverable .sensors import SensorInfo , Sensor
5+
6+ from pylontech .pylontech import PylontechModule , Pylontech , PylontechStackData
7+ from pylontechpoller .tools import minimize
8+ from pylontechpoller .reporter import Reporter
9+
10+ import paho .mqtt .client as mqtt
11+
12+
13+ class MqttReporter (Reporter ):
14+ def __init__ (self , mqtt_host , mqtt_port , mqtt_login , mqtt_password ):
15+ if os .path .exists (mqtt_password ):
16+ with open (mqtt_password , 'r' ) as file :
17+ mqtt_password = file .read ().strip ()
18+
19+ client = mqtt .Client (client_id = "pylontech-poller" )
20+ client .username_pw_set (mqtt_login , mqtt_password )
21+ client .connect (mqtt_host , mqtt_port )
22+ client .loop_start ()
23+ self .mqtt_settings = Settings .MQTT (client = client )
24+ # client.enable_logger(logger)
25+
26+ # self.mqtt_settings = Settings.MQTT(host=mqtt_host, port=mqtt_port, username=mqtt_login, password=mqtt_password,
27+ # client_name="pylontech-poller")
28+
29+ self .device_info = DeviceInfo (name = "Pylontech Battery Stack" , identifiers = "pylontech_battery_stack" )
30+
31+ self .hass_stack_disbalance_info = SensorInfo (
32+ name = "Stack Disbalance" ,
33+ device_class = "voltage" ,
34+ unique_id = "stack_disbalance" ,
35+ unit_of_measurement = "V" ,
36+ suggested_display_precision = 3 ,
37+ device = self .device_info ,
38+ icon = "mdi:scale-unbalanced" ,
39+
40+ )
41+ self .hass_stack_disbalance_settings = Settings (mqtt = self .mqtt_settings , entity = self .hass_stack_disbalance_info )
42+ self .hass_stack_disbalance = Sensor (self .hass_stack_disbalance_settings )
43+
44+ self .hass_max_battery_disbalance_info = SensorInfo (
45+ name = "Max Battery Disbalance" ,
46+ device_class = "voltage" ,
47+ unique_id = "max_battery_disbalance" ,
48+ unit_of_measurement = "V" ,
49+ suggested_display_precision = 3 ,
50+ device = self .device_info ,
51+ icon = "mdi:scale-unbalanced" ,
52+ )
53+ self .hass_max_battery_disbalance_settings = Settings (mqtt = self .mqtt_settings ,
54+ entity = self .hass_max_battery_disbalance_info )
55+ self .hass_max_battery_disbalance = Sensor (self .hass_max_battery_disbalance_settings )
56+
57+ self .hass_max_disbalance_id = Sensor (Settings (mqtt = self .mqtt_settings , entity = SensorInfo (
58+ name = "Max disbalance ID" ,
59+ unique_id = f"max_battery_disbalance_id" ,
60+ device = self .device_info ,
61+ icon = "mdi:battery-alert" ,
62+
63+ )))
64+ self .bats = {}
65+
66+ def report_meta (self , meta : PylontechStackData , p : Pylontech ):
67+ moduledata = { m ["n" ] : m for m in minimize ( next (p .poll_parameters (meta .range ())) )["modules" ]}
68+ cells = {}
69+
70+ for id in meta .ids :
71+ m = meta .modules [id ]
72+ device_info = DeviceInfo (
73+ name = f"Pylontech Battery { id } " ,
74+ identifiers = [f"pylontech_battery_{ m .serial } " , f"pylontech_battery_{ id } " , ],
75+ manufacturer = m .manufacturer_info ,
76+ sw_version = "." .join ([str (x ) for x in m .fw_version ]),
77+ model = m .device_name
78+ )
79+ mdata = moduledata [id ]
80+ for cn , c in enumerate (mdata ["cv" ]):
81+ cells [f"cell_{ cn } _voltage" ] = Sensor (Settings (mqtt = self .mqtt_settings , entity = SensorInfo (
82+ name = f"Cell { cn } Voltage" ,
83+ device_class = "voltage" ,
84+ unique_id = f"cell_voltage_{ id } _{ cn } " ,
85+ unit_of_measurement = "V" ,
86+ suggested_display_precision = 3 ,
87+ device = device_info ,
88+ entity_category = "diagnostic" ,
89+ icon = "mdi:gauge" ,
90+ )))
91+
92+ self .bats [id ] = {
93+ "bat_soc" : Sensor (Settings (mqtt = self .mqtt_settings , entity = SensorInfo (
94+ name = "SoC" ,
95+ device_class = "battery" ,
96+ unique_id = f"battery_soc_{ id } " ,
97+ unit_of_measurement = "%" ,
98+ suggested_display_precision = 1 ,
99+ device = device_info
100+ ))),
101+ "bat_disbalance" : Sensor (Settings (mqtt = self .mqtt_settings , entity = SensorInfo (
102+ name = "Cell Disbalance" ,
103+ device_class = "voltage" ,
104+ unique_id = f"battery_disbalance_{ id } " ,
105+ unit_of_measurement = "V" ,
106+ suggested_display_precision = 3 ,
107+ device = device_info ,
108+ icon = "mdi:scale-unbalanced" ,
109+ ))),
110+ "bat_voltage" : Sensor (Settings (mqtt = self .mqtt_settings , entity = SensorInfo (
111+ name = "Voltage" ,
112+ device_class = "voltage" ,
113+ unique_id = f"battery_voltage_{ id } " ,
114+ unit_of_measurement = "V" ,
115+ suggested_display_precision = 3 ,
116+ device = device_info ,
117+ icon = "mdi:gauge" ,
118+ ))),
119+ "bat_current" : Sensor (Settings (mqtt = self .mqtt_settings , entity = SensorInfo (
120+ name = "Current" ,
121+ device_class = "current" ,
122+ unique_id = f"battery_current_{ id } " ,
123+ unit_of_measurement = "A" ,
124+ suggested_display_precision = 3 ,
125+ device = device_info ,
126+ icon = "mdi:current-dc" ,
127+ ))),
128+ "bat_power" : Sensor (Settings (mqtt = self .mqtt_settings , entity = SensorInfo (
129+ name = "Power" ,
130+ device_class = "power" ,
131+ unique_id = f"battery_power_{ id } " ,
132+ unit_of_measurement = "W" ,
133+ suggested_display_precision = 2 ,
134+ device = device_info ,
135+ icon = "mdi:battery-charging" ,
136+ ))),
137+ "bat_cycle" : Sensor (Settings (mqtt = self .mqtt_settings , entity = SensorInfo (
138+ name = "Cycle" ,
139+ unique_id = f"battery_cycle_{ id } " ,
140+ device = device_info ,
141+ icon = "mdi:battery-sync" ,
142+ ))),
143+ "bat_temp" : Sensor (Settings (mqtt = self .mqtt_settings , entity = SensorInfo (
144+ name = "Temperature" ,
145+ device_class = "temperature" ,
146+ unique_id = f"battery_temperature_{ id } " ,
147+ unit_of_measurement = "C" ,
148+ suggested_display_precision = 1 ,
149+ device = device_info ,
150+ ))),
151+ } | cells
152+
153+ def report_state (self , state ):
154+ md = state ["max_module_disbalance" ]
155+ self .hass_stack_disbalance .set_state (state ["stack_disbalance" ])
156+ self .hass_max_battery_disbalance .set_state (md [1 ])
157+ self .hass_max_disbalance_id .set_state (md [0 ])
158+
159+ for b in state ["modules" ]:
160+ s = self .bats [b ["n" ]]
161+ s ["bat_disbalance" ].set_state (b ["disbalance" ])
162+ s ["bat_voltage" ].set_state (b ["v" ])
163+ s ["bat_current" ].set_state (b ["current" ])
164+ s ["bat_soc" ].set_state (int (b ["soc" ] * 1000 ) / 10.0 )
165+ s ["bat_power" ].set_state (b ["pw" ])
166+ s ["bat_cycle" ].set_state (b ["cycle" ])
167+ s ["bat_temp" ].set_state (b ["tempavg" ])
168+ for cn , c in enumerate (b ["cv" ]):
169+ s [f"cell_{ cn } _voltage" ].set_state (c )
0 commit comments