diff --git a/grobro/grobro/client.py b/grobro/grobro/client.py index 65f015e..c21dfd2 100644 --- a/grobro/grobro/client.py +++ b/grobro/grobro/client.py @@ -371,7 +371,7 @@ def __on_message(self, client, userdata, msg: MQTTMessage): self._client.publish(topic, smart_meter["data"], retain=PUBLISH_SENSORS_RETAINED) return - # NOAH/NEXA-specific message types (FE19 config, 0103 holding regs, etc.) + # NOAH/NEXA-specific message types (FE19 config, etc.) noah_msg = parser.parse_noah_message(unscrambled) if noah_msg and noah_msg.get("message_type") == 0xFE19: if device_id.startswith("0PVP") or device_id.startswith("0HVR"): @@ -411,14 +411,16 @@ def __on_message(self, client, userdata, msg: MQTTMessage): LOG.info("Modbus message from unknown device type: %s", device_id) return - if ( - modbus_message.function - == GrowattModbusFunction.READ_SINGLE_REGISTER + if modbus_message.function in ( + GrowattModbusFunction.READ_SINGLE_REGISTER, + GrowattModbusFunction.READ_HOLDING_REGISTER, ): state = HomeAssistantHoldingRegisterInput(device_id=modbus_device_id) for name, register in known_registers.holding_registers.items(): data_raw = modbus_message.get_data(register.growatt.position) + if data_raw is None: + continue value = register.growatt.data.parse(data_raw) if value is None: continue diff --git a/grobro/grobro/parser.py b/grobro/grobro/parser.py index d3e63d8..5e15636 100644 --- a/grobro/grobro/parser.py +++ b/grobro/grobro/parser.py @@ -167,25 +167,6 @@ def parse_config_ack(data: bytes): # The 2-byte type/subtype field starts at payload offset 14. -def parse_noah_0103(data: bytes) -> dict: - """ - NOAH type 0x0103 — Holding register dump. - Payload: 14 zero bytes + 16B device serial (offset 14) + register data. - """ - payload = data[24:] - device_id = payload[14:30].rstrip(b"\x00").decode("ascii", errors="replace") - reg_data = payload[30:] - registers = [] - for i in range(0, len(reg_data) - 1, 2): - registers.append(struct.unpack_from(">H", reg_data, i)[0]) - return { - "message_type": 0x0103, - "device_id": device_id, - "registers": registers, - "register_count": len(registers), - } - - def parse_noah_0110(data: bytes) -> dict: """ NOAH type 0x0110 — Preset-multiple register response/ack. @@ -315,7 +296,6 @@ def parse_noah_6f64(data: bytes) -> dict: NOAH_DECODERS = { - 0x0103: parse_noah_0103, 0x0110: parse_noah_0110, 0x0125: parse_noah_0125, 0xFE18: parse_noah_fe18, diff --git a/grobro/model/modbus_message.py b/grobro/model/modbus_message.py index c23f093..474e17d 100644 --- a/grobro/model/modbus_message.py +++ b/grobro/model/modbus_message.py @@ -116,7 +116,7 @@ class GrowattModbusMessage(BaseModel): - B - 1 byte modbus device address (seems to be constant 1 in mqtt) - B - 1 byte function - 30s - 30 byte zero-padded device id - - optional GrowattModbusMetadata - only present when function == READ_INPUT_REGISTER + - optional GrowattModbusMetadata - present for functions READ_INPUT_REGISTER and READ_HOLDING_REGISTER - N register blocks """ @@ -163,12 +163,17 @@ def parse_grobro(buffer) -> Optional["GrowattModbusMessage"]: offset = 38 metadata = None - if function == GrowattModbusFunction.READ_INPUT_REGISTER: + if function in ( + GrowattModbusFunction.READ_INPUT_REGISTER, + GrowattModbusFunction.READ_HOLDING_REGISTER, + ): metadata = GrowattMetadata.parse_grobro(buffer[offset:]) offset += metadata.size() while len(buffer) > offset + 6: block = GrowattModbusBlock.parse_grobro(buffer[offset:]) + if block is None: + return None register_blocks.append(block) offset += block.size() diff --git a/tests/model/test_commands.py b/tests/model/test_commands.py index c5dd205..37c4f11 100644 --- a/tests/model/test_commands.py +++ b/tests/model/test_commands.py @@ -4,6 +4,7 @@ from grobro.grobro import parser from grobro.model.modbus_message import ( + GrowattModbusFunction, GrowattModbusMessage, ) from grobro.model.modbus_function import GrowattModbusFunctionSingle @@ -229,10 +230,13 @@ def test_noah_preset_multiple_charge_limit(): def test_noah_type0103_holding_registers(): data = (DATA_DIR / "NoahType0103_HoldingRegs.bin").read_bytes() unscrambled = parser.unscramble(data) - result = parser.parse_noah_0103(unscrambled) - assert result["message_type"] == 0x0103 - assert result["device_id"] == NOAH_TEST_DEVICE_ID - assert result["register_count"] > 0 + result = GrowattModbusMessage.parse_grobro(unscrambled) + assert result is not None + assert result.device_id == NOAH_TEST_DEVICE_ID + assert result.function == GrowattModbusFunction.READ_HOLDING_REGISTER + assert result.metadata is not None + assert result.metadata.device_sn == NOAH_TEST_DEVICE_ID + assert len(result.register_blocks) > 0 def test_noah_type0110_response():