From 750f3b3660e38189c0f47d9e652c4e8cc6e70d72 Mon Sep 17 00:00:00 2001 From: mle Date: Sun, 5 May 2024 00:43:30 +0200 Subject: [PATCH] Fix decoding of 0xFFFF power values Decoding of unsigned 0xFFFF res 0xFFFFFFFF cannot be always 0 or None. For calculations 0 is proper, but for energy values None is better since it clearly represents value not provided. (Without it intermitent dropouts of energy values on DT inverters create mess in energy summaries in HA) --- goodwe/es.py | 4 ++-- goodwe/et.py | 16 ++++++++-------- goodwe/sensor.py | 23 ++++++++++++----------- tests/test_dt.py | 6 +++--- tests/test_et.py | 30 +++++++++++++++--------------- tests/test_sensor.py | 4 ++-- 6 files changed, 42 insertions(+), 41 deletions(-) diff --git a/goodwe/es.py b/goodwe/es.py index a9e544e..33c4d18 100644 --- a/goodwe/es.py +++ b/goodwe/es.py @@ -94,7 +94,7 @@ class ES(Inverter): Power("pback_up", 81, "Back-up Power", Kind.UPS), # pload + pback_up Calculated("plant_power", - lambda data: round(read_bytes2(data, 47) + read_bytes2(data, 81)), + lambda data: round(read_bytes2(data, 47, 0) + read_bytes2(data, 81, 0)), "Plant Power", "W", Kind.AC), Decimal("meter_power_factor", 83, 1000, "Meter Power Factor", "", Kind.GRID), # modbus 0x531 # Integer("xx85", 85, "Unknown sensor@85"), @@ -134,7 +134,7 @@ class ES(Inverter): Integer("charge_i", 26, "Charge Current", "A", ), Integer("discharge_i", 28, "Discharge Current", "A", ), Decimal("discharge_v", 30, 10, "Discharge Voltage", "V"), - Calculated("dod", lambda data: 100 - read_bytes2(data, 32), "Depth of Discharge", "%"), + Calculated("dod", lambda data: 100 - read_bytes2(data, 32, 0), "Depth of Discharge", "%"), Integer("battery_activated", 34, "Battery Activated"), Integer("bp_off_grid_charge", 36, "BP Off-grid Charge"), Integer("bp_pv_discharge", 38, "BP PV Discharge"), diff --git a/goodwe/et.py b/goodwe/et.py index 76115ab..96a138c 100644 --- a/goodwe/et.py +++ b/goodwe/et.py @@ -35,10 +35,10 @@ class ET(Inverter): # ppv1 + ppv2 + ppv3 + ppv4 Calculated("ppv", lambda data: - max(0, read_bytes4(data, 35105)) + - max(0, read_bytes4(data, 35109)) + - max(0, read_bytes4(data, 35113)) + - max(0, read_bytes4(data, 35117)), + max(0, read_bytes4(data, 35105, 0)) + + max(0, read_bytes4(data, 35109, 0)) + + max(0, read_bytes4(data, 35113, 0)) + + max(0, read_bytes4(data, 35117, 0)), "PV Power", "W", Kind.PV), ByteH("pv4_mode", 35119, "PV4 Mode code", "", Kind.PV), EnumH("pv4_mode_label", 35119, PV_MODES, "PV4 Mode", Kind.PV), @@ -145,10 +145,10 @@ class ET(Inverter): # ppv1 + ppv2 + ppv3 + ppv4 + pbattery1 - active_power Calculated("house_consumption", lambda data: - read_bytes4(data, 35105) + - read_bytes4(data, 35109) + - read_bytes4(data, 35113) + - read_bytes4(data, 35117) + + read_bytes4(data, 35105, 0) + + read_bytes4(data, 35109, 0) + + read_bytes4(data, 35113, 0) + + read_bytes4(data, 35117, 0) + read_bytes4_signed(data, 35182) - read_bytes2_signed(data, 35140), "House Consumption", "W", Kind.AC), diff --git a/goodwe/sensor.py b/goodwe/sensor.py index b0bb8e5..2cc04e9 100644 --- a/goodwe/sensor.py +++ b/goodwe/sensor.py @@ -183,7 +183,7 @@ def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind]) def read_value(self, data: ProtocolResponse): value = read_bytes2(data) - return float(value) / 10 + return float(value) / 10 if value else None class Energy4(Sensor): @@ -194,7 +194,7 @@ def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind]) def read_value(self, data: ProtocolResponse): value = read_bytes4(data) - return float(value) / 10 + return float(value) / 10 if value else None class Apparent(Sensor): @@ -308,7 +308,7 @@ def __init__(self, id_: str, offset: int, name: str, unit: str = "", kind: Optio super().__init__(id_, offset, name, 2, unit, kind) def read_value(self, data: ProtocolResponse): - return read_bytes2(data) + return read_bytes2(data, None, 0) def encode_value(self, value: Any, register_value: bytes = None) -> bytes: return int.to_bytes(int(value), length=2, byteorder="big", signed=False) @@ -334,7 +334,7 @@ def __init__(self, id_: str, offset: int, name: str, unit: str = "", kind: Optio super().__init__(id_, offset, name, 4, unit, kind) def read_value(self, data: ProtocolResponse): - return read_bytes4(data) + return read_bytes4(data, None, 0) def encode_value(self, value: Any, register_value: bytes = None) -> bytes: return int.to_bytes(int(value), length=4, byteorder="big", signed=False) @@ -414,7 +414,7 @@ def read_value(self, data: ProtocolResponse): class EnumL(Sensor): - """Sensor representing label from enumeration encoded in 1 bytes (low 8 bits of 16bit register)""" + """Sensor representing label from enumeration encoded in 1 byte (low 8 bits of 16bit register)""" def __init__(self, id_: str, offset: int, labels: Dict, name: str, kind: Optional[SensorKind] = None): super().__init__(id_, offset, name, 1, "", kind) @@ -433,7 +433,7 @@ def __init__(self, id_: str, offset: int, labels: Dict, name: str, kind: Optiona self._labels: Dict = labels def read_value(self, data: ProtocolResponse): - return self._labels.get(read_bytes2(data)) + return self._labels.get(read_bytes2(data, None, 0)) class EnumBitmap4(Sensor): @@ -464,7 +464,8 @@ def read_value(self, data: ProtocolResponse) -> Any: raise NotImplementedError() def read(self, data: ProtocolResponse): - return decode_bitmap(read_bytes2(data, self.offset) << 16 + read_bytes2(data, self._offsetL), self._labels) + return decode_bitmap(read_bytes2(data, self.offset, 0) << 16 + read_bytes2(data, self._offsetL, 0), + self._labels) class EnumCalculated(Sensor): @@ -785,12 +786,12 @@ def read_byte(buffer: ProtocolResponse, offset: int = None) -> int: return int.from_bytes(buffer.read(1), byteorder="big", signed=True) -def read_bytes2(buffer: ProtocolResponse, offset: int = None) -> int: +def read_bytes2(buffer: ProtocolResponse, offset: int = None, undef: int = None) -> int: """Retrieve 2 byte (unsigned int) value from buffer""" if offset is not None: buffer.seek(offset) value = int.from_bytes(buffer.read(2), byteorder="big", signed=False) - return value if value != 0xffff else 0 + return undef if value == 0xffff else value def read_bytes2_signed(buffer: ProtocolResponse, offset: int = None) -> int: @@ -800,12 +801,12 @@ def read_bytes2_signed(buffer: ProtocolResponse, offset: int = None) -> int: return int.from_bytes(buffer.read(2), byteorder="big", signed=True) -def read_bytes4(buffer: ProtocolResponse, offset: int = None) -> int: +def read_bytes4(buffer: ProtocolResponse, offset: int = None, undef: int = None) -> int: """Retrieve 4 byte (unsigned int) value from buffer""" if offset is not None: buffer.seek(offset) value = int.from_bytes(buffer.read(4), byteorder="big", signed=False) - return value if value != 0xffffffff else 0 + return undef if value == 0xffffffff else value def read_bytes4_signed(buffer: ProtocolResponse, offset: int = None) -> int: diff --git a/tests/test_dt.py b/tests/test_dt.py index d6ec51e..47f5f52 100644 --- a/tests/test_dt.py +++ b/tests/test_dt.py @@ -167,8 +167,8 @@ def test_GW8K_DT_runtime_data(self): self.assertSensor("apparent_power", 0, "VA", data), self.assertSensor("reactive_power", 0, "var", data), self.assertSensor('temperature', 45.3, 'C', data) - self.assertSensor('e_day', 0.0, 'kWh', data) - self.assertSensor('e_total', 0.0, 'kWh', data) + self.assertSensor('e_day', None, 'kWh', data) + self.assertSensor('e_total', None, 'kWh', data) self.assertSensor('h_total', 0, 'h', data) self.assertSensor('safety_country', 32, '', data) self.assertSensor('safety_country_label', '50Hz 230Vac Default', '', data) @@ -221,7 +221,7 @@ def test_GW5000D_NS_runtime_data(self): self.assertSensor("apparent_power", -1, "VA", data), self.assertSensor("reactive_power", -1, "var", data), self.assertSensor('temperature', 1.4, 'C', data) - self.assertSensor('e_day', 0.0, 'kWh', data) + self.assertSensor('e_day', None, 'kWh', data) self.assertSensor('e_total', 881.7, 'kWh', data) self.assertSensor('h_total', 955, 'h', data) self.assertSensor('safety_country', 73, '', data) diff --git a/tests/test_et.py b/tests/test_et.py index 27958fa..f6a9823 100644 --- a/tests/test_et.py +++ b/tests/test_et.py @@ -164,7 +164,7 @@ def test_GW10K_ET_runtime_data(self): self.assertSensor('h_total', 9246, 'h', data) self.assertSensor("e_day_exp", 9.8, 'kWh', data) self.assertSensor("e_total_imp", 58.0, 'kWh', data) - self.assertSensor("e_day_imp", 0.0, 'kWh', data) + self.assertSensor("e_day_imp", None, 'kWh', data) self.assertSensor("e_load_total", 8820.2, 'kWh', data) self.assertSensor("e_load_day", 11.6, 'kWh', data) self.assertSensor("e_bat_charge_total", 2758.1, 'kWh', data) @@ -457,14 +457,14 @@ def test_GW6000_EH_runtime_data(self): self.assertSensor("e_total_exp", 58.6, 'kWh', data) self.assertSensor('h_total', 33, 'h', data) self.assertSensor("e_day_exp", 21.6, 'kWh', data) - self.assertSensor("e_total_imp", 0.0, 'kWh', data) - self.assertSensor("e_day_imp", 0.0, 'kWh', data) + self.assertSensor("e_total_imp", None, 'kWh', data) + self.assertSensor("e_day_imp", None, 'kWh', data) self.assertSensor("e_load_total", 70.1, 'kWh', data) self.assertSensor("e_load_day", 27.1, 'kWh', data) - self.assertSensor("e_bat_charge_total", 0.0, 'kWh', data) - self.assertSensor("e_bat_charge_day", 0.0, 'kWh', data) - self.assertSensor("e_bat_discharge_total", 0.0, 'kWh', data) - self.assertSensor("e_bat_discharge_day", 0.0, 'kWh', data) + self.assertSensor("e_bat_charge_total", None, 'kWh', data) + self.assertSensor("e_bat_charge_day", None, 'kWh', data) + self.assertSensor("e_bat_discharge_total", None, 'kWh', data) + self.assertSensor("e_bat_discharge_day", None, 'kWh', data) self.assertSensor('diagnose_result', 117983303, '', data) self.assertSensor('diagnose_result_label', 'Battery voltage low, Battery SOC low, Battery SOC in back, Discharge Driver On, Self-use load light, Battery Disconnected, Self-use off, Export power limit set, PF value set, Real power limit set', @@ -558,8 +558,8 @@ def test_GEH10_1U_10_runtime_data(self): self.assertSensor('e_total_exp', 10273.3, 'kWh', data) self.assertSensor('h_total', 3256, 'h', data) self.assertSensor('e_day_exp', 16.6, 'kWh', data) - self.assertSensor('e_total_imp', 0.0, 'kWh', data) - self.assertSensor('e_day_imp', 0.0, 'kWh', data) + self.assertSensor('e_total_imp', None, 'kWh', data) + self.assertSensor('e_day_imp', None, 'kWh', data) self.assertSensor('e_load_total', 4393.9, 'kWh', data) self.assertSensor('e_load_day', 10.7, 'kWh', data) self.assertSensor('e_bat_charge_total', 141.9, 'kWh', data) @@ -771,7 +771,7 @@ def test_GW25K_ET_runtime_data(self): self.assertSensor('e_bat_charge_total', 91.3, 'kWh', data) self.assertSensor('e_bat_charge_day', 11.0, 'kWh', data) self.assertSensor('e_bat_discharge_total', 69.6, 'kWh', data) - self.assertSensor('e_bat_discharge_day', 0.0, 'kWh', data) + self.assertSensor('e_bat_discharge_day', None, 'kWh', data) self.assertSensor('diagnose_result', 33816960, '', data) self.assertSensor('diagnose_result_label', 'BMS: Discharge current low, APP: Discharge current too low, BMS: Charge disabled, PF value set', @@ -1042,13 +1042,13 @@ def test_GW29K9_ET_runtime_data(self): self.assertSensor('h_total', 1175, 'h', data) self.assertSensor('e_day_exp', 1.2, 'kWh', data) self.assertSensor('e_total_imp', 8.7, 'kWh', data) - self.assertSensor('e_day_imp', 0.0, 'kWh', data) + self.assertSensor('e_day_imp', None, 'kWh', data) self.assertSensor('e_load_total', 10742.2, 'kWh', data) self.assertSensor('e_load_day', 43.8, 'kWh', data) - self.assertSensor('e_bat_charge_total', 0.0, 'kWh', data) - self.assertSensor('e_bat_charge_day', 0.0, 'kWh', data) - self.assertSensor('e_bat_discharge_total', 0.0, 'kWh', data) - self.assertSensor('e_bat_discharge_day', 0.0, 'kWh', data) + self.assertSensor('e_bat_charge_total', None, 'kWh', data) + self.assertSensor('e_bat_charge_day', None, 'kWh', data) + self.assertSensor('e_bat_discharge_total', None, 'kWh', data) + self.assertSensor('e_bat_discharge_day', None, 'kWh', data) self.assertSensor('diagnose_result', 33816782, '', data) self.assertSensor('diagnose_result_label', 'Battery SOC low, Battery SOC in back, BMS: Discharge disabled, ' diff --git a/tests/test_sensor.py b/tests/test_sensor.py index 6e5423f..193b7be 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -130,7 +130,7 @@ def test_power4(self): self.assertEqual(4294967293, testee.read(data)) data = MockResponse("ffffffff") - self.assertEqual(0, testee.read(data)) + self.assertIsNone(testee.read(data)) def test_power4_signed(self): testee = Power4S("", 0, "", None) @@ -153,7 +153,7 @@ def test_energy4(self): data = MockResponse("00020972") self.assertEqual(13349.0, testee.read(data)) data = MockResponse("ffffffff") - self.assertEqual(0.0, testee.read(data)) + self.assertIsNone(testee.read(data)) def test_timestamp(self): testee = Timestamp("", 0, "", None)