diff --git a/grobro/ha/client.py b/grobro/ha/client.py index 92e2965..7bc3416 100644 --- a/grobro/ha/client.py +++ b/grobro/ha/client.py @@ -221,7 +221,7 @@ def __init__(self, mqtt_config: model.MQTTConfig): self._client.connect(mqtt_config.host, mqtt_config.port, 60) # Subscriptions - for cmd_type in ["number", "button", "switch", "config"]: + for cmd_type in ["number", "time", "button", "switch", "config"]: for action in ["set", "read"]: topic = f"{HA_BASE_TOPIC}/{cmd_type}/grobro/+/+/{action}" self._client.subscribe(topic) @@ -373,7 +373,7 @@ def __on_connect(self, client, userdata, flags, reason_code, properties): def __on_message(self, client, userdata, msg: mqtt.MQTTMessage): parts = msg.topic.removeprefix(f"{HA_BASE_TOPIC}/").split("/") - if len(parts) != 5 or parts[0] not in {"number", "button", "switch", "config"}: + if len(parts) != 5 or parts[0] not in {"number", "time", "button", "switch", "config"}: return cmd_type, _, device_id, cmd_name, action = parts @@ -387,7 +387,7 @@ def __on_message(self, client, userdata, msg: mqtt.MQTTMessage): # Buttons if cmd_type == "button": if cmd_name == "read_all": - # Send all modbus reads first + # Send all modbus reads first for name, register in known_registers.holding_registers.items(): if name.startswith("slot"): try: @@ -395,59 +395,95 @@ def __on_message(self, client, userdata, msg: mqtt.MQTTMessage): continue except ValueError: continue + pos = register.growatt.position - self.on_command(make_modbus_command( - device_id, - GrowattModbusFunction.READ_SINGLE_REGISTER, - pos.register_no, - )) + self.on_command( + make_modbus_command( + device_id, + GrowattModbusFunction.READ_SINGLE_REGISTER, + pos.register_no, + ) + ) # Queue config reads if self.on_config_read: with self._config_read_lock: q = self._config_read_queues.setdefault(device_id, deque()) + for cfg in known_registers.config_registers.values(): q.append(cfg.growatt.register_no) # give the datalogger time to answer modbus reads Timer( - 3.0, # small delay is enough + 3.0, self.__kickoff_next_config_read, args=(device_id,), ).start() return - + if action == "read": pos = known_registers.holding_registers[cmd_name].growatt.position - self.on_command(make_modbus_command( - device_id, GrowattModbusFunction.READ_SINGLE_REGISTER, pos.register_no - )) + + self.on_command( + make_modbus_command( + device_id, + GrowattModbusFunction.READ_SINGLE_REGISTER, + pos.register_no, + ) + ) + return - # Number / Switch - if cmd_type in {"number", "switch"} and action == "set": - raw_value = msg.payload.decode() + # Number / Switch / Time + if cmd_type in {"number", "switch", "time"} and action == "set": + raw_value = msg.payload.decode().strip() + if cmd_type == "switch": parsed_value = 1 if raw_value.upper() == "ON" else 0 - elif "_start_time" in cmd_name or "_end_time" in cmd_name: - hour, minute = divmod(int(raw_value), 100) + + elif cmd_type == "time": + hour, minute = map(int, raw_value.split(":")[:2]) parsed_value = (hour * 256) + minute + else: parsed_value = int(raw_value) pos = known_registers.holding_registers[cmd_name].growatt.position - LOG.debug("Setting %s register %s to value %s", cmd_name, pos.register_no, parsed_value) + + LOG.debug( + "Setting %s register %s to value %s", + cmd_name, + pos.register_no, + parsed_value, + ) # write - self.on_command(make_modbus_command( - device_id, GrowattModbusFunction.PRESET_SINGLE_REGISTER, pos.register_no, parsed_value - )) + self.on_command( + make_modbus_command( + device_id, + GrowattModbusFunction.PRESET_SINGLE_REGISTER, + pos.register_no, + parsed_value, + ) + ) + # read-after-write - LOG.debug("Triggering read-after-write for Command %s register %s", cmd_name, pos.register_no) - self.on_command(make_modbus_command( - device_id, GrowattModbusFunction.READ_SINGLE_REGISTER, pos.register_no - )) + LOG.debug( + "Triggering read-after-write for Command %s register %s", + cmd_name, + pos.register_no, + ) + + self.on_command( + make_modbus_command( + device_id, + GrowattModbusFunction.READ_SINGLE_REGISTER, + pos.register_no, + ) + ) + + return # Config if parts[0] == "config" and action == "set": diff --git a/grobro/model/growatt_nexa_registers.json b/grobro/model/growatt_nexa_registers.json index fc6e4bc..fe75c6f 100644 --- a/grobro/model/growatt_nexa_registers.json +++ b/grobro/model/growatt_nexa_registers.json @@ -163,10 +163,7 @@ "homeassistant": { "publish": true, "name": "Slot 1 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -184,10 +181,7 @@ "homeassistant": { "publish": true, "name": "Slot 1 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, @@ -263,10 +257,7 @@ "homeassistant": { "publish": true, "name": "Slot 2 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -284,10 +275,7 @@ "homeassistant": { "publish": true, "name": "Slot 2 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, @@ -363,10 +351,7 @@ "homeassistant": { "publish": true, "name": "Slot 3 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -384,10 +369,7 @@ "homeassistant": { "publish": true, "name": "Slot 3 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, @@ -463,10 +445,7 @@ "homeassistant": { "publish": true, "name": "Slot 4 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -484,10 +463,7 @@ "homeassistant": { "publish": true, "name": "Slot 4 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, @@ -563,10 +539,7 @@ "homeassistant": { "publish": true, "name": "Slot 5 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -584,10 +557,7 @@ "homeassistant": { "publish": true, "name": "Slot 5 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, @@ -663,10 +633,7 @@ "homeassistant": { "publish": true, "name": "Slot 6 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -684,10 +651,7 @@ "homeassistant": { "publish": true, "name": "Slot 6 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, @@ -763,10 +727,7 @@ "homeassistant": { "publish": true, "name": "Slot 7 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -784,10 +745,7 @@ "homeassistant": { "publish": true, "name": "Slot 7 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, @@ -863,10 +821,7 @@ "homeassistant": { "publish": true, "name": "Slot 8 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -884,10 +839,7 @@ "homeassistant": { "publish": true, "name": "Slot 8 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, @@ -963,10 +915,7 @@ "homeassistant": { "publish": true, "name": "Slot 9 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -984,10 +933,7 @@ "homeassistant": { "publish": true, "name": "Slot 9 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, diff --git a/grobro/model/growatt_noah_registers.json b/grobro/model/growatt_noah_registers.json index 6349be0..42ce95a 100644 --- a/grobro/model/growatt_noah_registers.json +++ b/grobro/model/growatt_noah_registers.json @@ -76,10 +76,7 @@ "homeassistant": { "publish": true, "name": "Slot 1 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -97,10 +94,7 @@ "homeassistant": { "publish": true, "name": "Slot 1 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, @@ -176,10 +170,7 @@ "homeassistant": { "publish": true, "name": "Slot 2 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -197,10 +188,7 @@ "homeassistant": { "publish": true, "name": "Slot 2 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, @@ -276,10 +264,7 @@ "homeassistant": { "publish": true, "name": "Slot 3 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -297,10 +282,7 @@ "homeassistant": { "publish": true, "name": "Slot 3 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, @@ -376,10 +358,7 @@ "homeassistant": { "publish": true, "name": "Slot 4 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -397,10 +376,7 @@ "homeassistant": { "publish": true, "name": "Slot 4 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, @@ -476,10 +452,7 @@ "homeassistant": { "publish": true, "name": "Slot 5 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -497,10 +470,7 @@ "homeassistant": { "publish": true, "name": "Slot 5 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, @@ -576,10 +546,7 @@ "homeassistant": { "publish": true, "name": "Slot 6 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -597,10 +564,7 @@ "homeassistant": { "publish": true, "name": "Slot 6 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, @@ -676,10 +640,7 @@ "homeassistant": { "publish": true, "name": "Slot 7 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -697,10 +658,7 @@ "homeassistant": { "publish": true, "name": "Slot 7 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, @@ -776,10 +734,7 @@ "homeassistant": { "publish": true, "name": "Slot 8 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -797,10 +752,7 @@ "homeassistant": { "publish": true, "name": "Slot 8 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, @@ -876,10 +828,7 @@ "homeassistant": { "publish": true, "name": "Slot 9 Start Time", - "type": "number", - "min": 0, - "max": 2358, - "step": 1, + "type": "time", "icon": "mdi:clock-start" } }, @@ -897,10 +846,7 @@ "homeassistant": { "publish": true, "name": "Slot 9 End Time", - "type": "number", - "min": 0, - "max": 2359, - "step": 1, + "type": "time", "icon": "mdi:clock-end" } }, diff --git a/grobro/model/growatt_registers.py b/grobro/model/growatt_registers.py index 4a32d24..b72b3c3 100644 --- a/grobro/model/growatt_registers.py +++ b/grobro/model/growatt_registers.py @@ -55,9 +55,9 @@ def parse(self, data_raw: bytes): return round(value, 3) elif self.data_type == GrowattRegisterDataTypes.TIME_HHMM: value = struct.unpack(unpack_type, data_raw)[0] - h = value // 256 - m = value % 256 - return (h * 100) + m + hour = (value >> 8) & 0xFF + minute = value & 0xFF + return f"{hour:02d}:{minute:02d}" elif self.data_type in [GrowattRegisterDataTypes.INT, GrowattRegisterDataTypes.SIGNED_INT]: value = struct.unpack(unpack_type, data_raw)[0] return value