Skip to content

Commit

Permalink
Fix decoding of 0xFFFF power values
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
mletenay committed May 4, 2024
1 parent e424733 commit 750f3b3
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 41 deletions.
4 changes: 2 additions & 2 deletions goodwe/es.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -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"),
Expand Down
16 changes: 8 additions & 8 deletions goodwe/et.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down
23 changes: 12 additions & 11 deletions goodwe/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down
6 changes: 3 additions & 3 deletions tests/test_dt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
30 changes: 15 additions & 15 deletions tests/test_et.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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, '
Expand Down
4 changes: 2 additions & 2 deletions tests/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down

0 comments on commit 750f3b3

Please sign in to comment.