Skip to content

Commit

Permalink
Add support for MPTT sensors on ETT inverters
Browse files Browse the repository at this point in the history
Add support for MPTT sensors on ETT >25K inverters
  • Loading branch information
mletenay committed Dec 3, 2023
1 parent 78367bf commit 6a12808
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 11 deletions.
93 changes: 85 additions & 8 deletions goodwe/et.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,65 @@ class ET(Inverter):
Integer("meter_sw_version", 88, "Meter Software Version", "", Kind.GRID), # 36044
)

# Inverter's MPPT data
# Modbus registers from offset 0x89e5 (35301)
__all_sensors_mptt: Tuple[Sensor, ...] = (
Power4("ppv_total", 0, "PV Power Total", Kind.PV), # 35301
# 35303 PV channel RO U16 1 1 PV channel
Voltage("vpv5", 6, "PV5 Voltage", Kind.PV), # 35304
Current("ipv5", 8, "PV5 Current", Kind.PV), # 35305
Voltage("vpv6", 10, "PV6 Voltage", Kind.PV), # 35306
Current("ipv6", 12, "PV6 Current", Kind.PV), # 35307
Voltage("vpv7", 14, "PV7 Voltage", Kind.PV), # 35308
Current("ipv7", 16, "PV7 Current", Kind.PV), # 35309
Voltage("vpv8", 18, "PV8 Voltage", Kind.PV), # 35310
Current("ipv8", 20, "PV8 Current", Kind.PV), # 35311
Voltage("vpv9", 22, "PV9 Voltage", Kind.PV), # 35312
Current("ipv9", 24, "PV9 Current", Kind.PV), # 35313
Voltage("vpv10", 26, "PV10 Voltage", Kind.PV), # 35314
Current("ipv10", 28, "PV10 Current", Kind.PV), # 35315
Voltage("vpv11", 30, "PV11 Voltage", Kind.PV), # 35316
Current("ipv11", 32, "PV11 Current", Kind.PV), # 35317
Voltage("vpv12", 34, "PV12 Voltage", Kind.PV), # 35318
Current("ipv12", 36, "PV12 Current", Kind.PV), # 35319
Voltage("vpv13", 38, "PV13 Voltage", Kind.PV), # 35320
Current("ipv13", 40, "PV13 Current", Kind.PV), # 35321
Voltage("vpv14", 42, "PV14 Voltage", Kind.PV), # 35322
Current("ipv14", 44, "PV14 Current", Kind.PV), # 35323
Voltage("vpv15", 46, "PV15 Voltage", Kind.PV), # 35324
Current("ipv15", 48, "PV15 Current", Kind.PV), # 35325
Voltage("vpv16", 50, "PV16 Voltage", Kind.PV), # 35326
Current("ipv16", 52, "PV16 Current", Kind.PV), # 35327
# 35328 Warning Message
# 35330 Grid10minAvgVoltR
# 35331 Grid10minAvgVoltS
# 35332 Grid10minAvgVoltT
# 35333 Error Message Extend
# 35335 Warning Message Extend
Power("pmppt1", 72, "MPPT1 Power", Kind.PV), # 35337
Power("pmppt2", 74, "MPPT2 Power", Kind.PV), # 35338
Power("pmppt3", 76, "MPPT3 Power", Kind.PV), # 35339
Power("pmppt4", 78, "MPPT4 Power", Kind.PV), # 35340
Power("pmppt5", 80, "MPPT5 Power", Kind.PV), # 35341
Power("pmppt6", 82, "MPPT6 Power", Kind.PV), # 35342
Power("pmppt7", 84, "MPPT7 Power", Kind.PV), # 35343
Power("pmppt8", 86, "MPPT8 Power", Kind.PV), # 35344
Power("imppt1", 88, "MPPT1 Current", Kind.PV), # 35345
Power("imppt2", 90, "MPPT2 Current", Kind.PV), # 35346
Power("imppt3", 92, "MPPT3 Current", Kind.PV), # 35347
Power("imppt4", 94, "MPPT4 Current", Kind.PV), # 35348
Power("imppt5", 96, "MPPT5 Current", Kind.PV), # 35349
Power("imppt6", 98, "MPPT6 Current", Kind.PV), # 35350
Power("imppt7", 100, "MPPT7 Current", Kind.PV), # 35351
Power("imppt8", 102, "MPPT8 Current", Kind.PV), # 35352
Reactive4("reactive_power1", 104, "Reactive Power L1", Kind.GRID), # 36353/54
Reactive4("reactive_power2", 108, "Reactive Power L2", Kind.GRID), # 36355/56
Reactive4("reactive_power3", 112, "Reactive Power L2", Kind.GRID), # 36357/58
Apparent4("apparent_power1", 116, "Apparent Power L1", Kind.GRID), # 36359/60
Apparent4("apparent_power2", 120, "Apparent Power L2", Kind.GRID), # 36361/62
Apparent4("apparent_power3", 124, "Apparent Power L3", Kind.GRID), # 36363/64
)

# Modbus registers of inverter settings, offsets are modbus register addresses
__all_settings: Tuple[Sensor, ...] = (
Integer("comm_address", 45127, "Communication Address", ""),
Expand Down Expand Up @@ -342,12 +401,15 @@ def __init__(self, host: str, comm_addr: int = 0, timeout: int = 1, retries: int
self._READ_METER_DATA: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x8ca0, 0x2d)
self._READ_BATTERY_INFO: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x9088, 0x0018)
self._READ_BATTERY2_INFO: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x9858, 0x0016)
self._READ_MPTT_DATA: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x89a5, 0x3d)
self._has_battery: bool = True
self._has_battery2: bool = False
self._has_mptt: bool = False
self._sensors = self.__all_sensors
self._sensors_battery = self.__all_sensors_battery
self._sensors_battery2 = self.__all_sensors_battery2
self._sensors_meter = self.__all_sensors_meter
self._sensors_mptt = self.__all_sensors_mptt
self._settings: dict[str, Sensor] = {s.id_: s for s in self.__all_settings}

def _supports_eco_mode_v2(self) -> bool:
Expand Down Expand Up @@ -390,12 +452,15 @@ async def read_device_info(self):
self._sensors = tuple(filter(self._single_phase_only, self._sensors))
self._sensors_meter = tuple(filter(self._single_phase_only, self._sensors_meter))

if is_2_battery(self) or self.rated_power > 25000:
if is_2_battery(self) or self.rated_power >= 25000:
self._has_battery2 = True

if self.arm_version >= 19 or self.rated_power > 15000:
if self.rated_power >= 15000:
self._has_mptt = True

if self.arm_version >= 19 or self.rated_power >= 15000:
self._settings.update({s.id_: s for s in self.__settings_arm_fw_19})
if self.arm_version >= 22 or self.rated_power > 15000:
if self.arm_version >= 22 or self.rated_power >= 15000:
self._settings.update({s.id_: s for s in self.__settings_arm_fw_22})

async def read_runtime_data(self, include_unknown_sensors: bool = False) -> Dict[str, Any]:
Expand All @@ -422,6 +487,16 @@ async def read_runtime_data(self, include_unknown_sensors: bool = False) -> Dict

raw_data = await self._read_from_socket(self._READ_METER_DATA)
data.update(self._map_response(raw_data[5:-2], self._sensors_meter, include_unknown_sensors))

if self._has_mptt:
try:
raw_data = await self._read_from_socket(self._READ_MPTT_DATA)
data.update(self._map_response(raw_data[5:-2], self._sensors_mptt, include_unknown_sensors))
except RequestRejectedException as ex:
if ex.message == 'ILLEGAL DATA ADDRESS':
logger.warning("Cannot read MPPT values, disabling further attempts.")
self._has_mptt = False

return data

async def read_setting(self, setting_id: str) -> Any:
Expand Down Expand Up @@ -533,12 +608,14 @@ async def set_ongrid_battery_dod(self, dod: int) -> None:
await self.write_setting('battery_discharge_depth', 100 - dod)

def sensors(self) -> Tuple[Sensor, ...]:
result = self._sensors + self._sensors_meter
if self._has_battery:
result = result + self._sensors_battery
if self._has_battery2:
return self._sensors + self._sensors_battery + self._sensors_battery2 + self._sensors_meter
elif self._has_battery:
return self._sensors + self._sensors_battery + self._sensors_meter
else:
return self._sensors + self._sensors_meter
result = result + self._sensors_battery2
if self._has_mptt:
result = result + self._sensors_mptt
return result

def settings(self) -> Tuple[Sensor, ...]:
return tuple(self._settings.values())
Expand Down
2 changes: 1 addition & 1 deletion goodwe/inverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def __init__(self, host: str, comm_addr: int = 0, timeout: int = 1, retries: int

self.model_name: str | None = None
self.serial_number: str | None = None
self.rated_power: int | None = None
self.rated_power: int = 0
self.ac_output_type: int | None = None
self.firmware: str | None = None
self.arm_firmware: str | None = None
Expand Down
2 changes: 1 addition & 1 deletion tests/inverter_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
)

# Set the appropriate IP address
IP_ADDRESS = "192.168.1.14"
IP_ADDRESS = "192.168.2.14"
FAMILY = "ET" # One of ET, EH, ES, EM, DT, NS, XS or None to detect family automatically
COMM_ADDR = 0xf7 # Usually 0xf7 for ET/EH/EM/ES or 0x7f for DT/D-NS/XS, or None for default value
TIMEOUT = 1
Expand Down
1 change: 1 addition & 0 deletions tests/sample/et/GW25K-ET_mptt_data.hex
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
aa55f7037a0000021100020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009030903090300000000ffffffff00e8012b00000000000000000000000000030004000000000000000000000000000000000000000000000000000000000000026e
51 changes: 50 additions & 1 deletion tests/test_et.py
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,8 @@ def __init__(self, methodName='runTest'):
self.mock_response(self._READ_RUNNING_DATA, 'GW25K-ET_running_data.hex')
self.mock_response(self._READ_METER_DATA, 'GW25K-ET_meter_data.hex')
self.mock_response(self._READ_BATTERY_INFO, 'GW25K-ET_battery_info.hex')
# self.mock_response(self._READ_BATTERY_INFO2, 'GW25K-ET_battery2_info.hex')
self.mock_response(self._READ_MPTT_DATA, 'GW25K-ET_mptt_data.hex')

def test_GW25K_ET_device_info(self):
self.loop.run_until_complete(self.read_device_info())
Expand All @@ -912,7 +914,7 @@ def test_GW25K_ET_runtime_data(self):
self.sensor_map = {s.id_: s.unit for s in self.sensors()}

data = self.loop.run_until_complete(self.read_runtime_data(True))
self.assertEqual(174, len(data))
self.assertEqual(221, len(data))

self.assertSensor('timestamp', datetime.strptime('2023-12-03 14:07:07', '%Y-%m-%d %H:%M:%S'), '', data)
self.assertSensor('vpv1', 737.9, 'V', data)
Expand Down Expand Up @@ -1090,5 +1092,52 @@ def test_GW25K_ET_runtime_data(self):
self.assertSensor('meter_apparent_power_total', 1657, 'VA', data)
self.assertSensor('meter_type', 2, '', data)
self.assertSensor('meter_sw_version', 5, '', data)
self.assertSensor('ppv_total', 529, 'W', data)
self.assertSensor('vpv5', 0.0, 'V', data)
self.assertSensor('ipv5', 0.0, 'A', data)
self.assertSensor('vpv6', 0.0, 'V', data)
self.assertSensor('ipv6', 0.0, 'A', data)
self.assertSensor('vpv7', 0.0, 'V', data)
self.assertSensor('ipv7', 0.0, 'A', data)
self.assertSensor('vpv8', 0.0, 'V', data)
self.assertSensor('ipv8', 0.0, 'A', data)
self.assertSensor('vpv9', 0.0, 'V', data)
self.assertSensor('ipv9', 0.0, 'A', data)
self.assertSensor('vpv10', 0.0, 'V', data)
self.assertSensor('ipv10', 0.0, 'A', data)
self.assertSensor('vpv11', 0.0, 'V', data)
self.assertSensor('ipv11', 0.0, 'A', data)
self.assertSensor('vpv12', 0.0, 'V', data)
self.assertSensor('ipv12', 0.0, 'A', data)
self.assertSensor('vpv13', 0.0, 'V', data)
self.assertSensor('ipv13', 0.0, 'A', data)
self.assertSensor('vpv14', 0.0, 'V', data)
self.assertSensor('ipv14', 0.0, 'A', data)
self.assertSensor('vpv15', 0.0, 'V', data)
self.assertSensor('ipv15', 0.0, 'A', data)
self.assertSensor('vpv16', 0.0, 'V', data)
self.assertSensor('ipv16', 0.0, 'A', data)
self.assertSensor('pmppt1', 232, 'W', data)
self.assertSensor('pmppt2', 299, 'W', data)
self.assertSensor('pmppt3', 0, 'W', data)
self.assertSensor('pmppt4', 0, 'W', data)
self.assertSensor('pmppt5', 0, 'W', data)
self.assertSensor('pmppt6', 0, 'W', data)
self.assertSensor('pmppt7', 0, 'W', data)
self.assertSensor('pmppt8', 0, 'W', data)
self.assertSensor('imppt1', 3, 'W', data)
self.assertSensor('imppt2', 4, 'W', data)
self.assertSensor('imppt3', 0, 'W', data)
self.assertSensor('imppt4', 0, 'W', data)
self.assertSensor('imppt5', 0, 'W', data)
self.assertSensor('imppt6', 0, 'W', data)
self.assertSensor('imppt7', 0, 'W', data)
self.assertSensor('imppt8', 0, 'W', data)
self.assertSensor('reactive_power1', 0, 'var', data)
self.assertSensor('reactive_power2', 0, 'var', data)
self.assertSensor('reactive_power3', 0, 'var', data)
self.assertSensor('apparent_power1', 0, 'VA', data)
self.assertSensor('apparent_power2', 0, 'VA', data)
self.assertSensor('apparent_power3', 0, 'VA', data)

self.assertFalse(self.sensor_map, f"Some sensors were not tested {self.sensor_map}")

0 comments on commit 6a12808

Please sign in to comment.