goodwe 0.3.4__tar.gz → 0.3.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {goodwe-0.3.4/goodwe.egg-info → goodwe-0.3.6}/PKG-INFO +1 -1
  2. goodwe-0.3.6/VERSION +1 -0
  3. {goodwe-0.3.4 → goodwe-0.3.6}/goodwe/dt.py +4 -0
  4. {goodwe-0.3.4 → goodwe-0.3.6}/goodwe/es.py +5 -6
  5. {goodwe-0.3.4 → goodwe-0.3.6}/goodwe/et.py +48 -10
  6. {goodwe-0.3.4 → goodwe-0.3.6}/goodwe/model.py +4 -0
  7. {goodwe-0.3.4 → goodwe-0.3.6}/goodwe/sensor.py +21 -2
  8. {goodwe-0.3.4 → goodwe-0.3.6/goodwe.egg-info}/PKG-INFO +1 -1
  9. {goodwe-0.3.4 → goodwe-0.3.6}/tests/test_dt.py +2 -2
  10. {goodwe-0.3.4 → goodwe-0.3.6}/tests/test_es.py +15 -1
  11. {goodwe-0.3.4 → goodwe-0.3.6}/tests/test_et.py +59 -51
  12. {goodwe-0.3.4 → goodwe-0.3.6}/tests/test_sensor.py +10 -0
  13. goodwe-0.3.4/VERSION +0 -1
  14. {goodwe-0.3.4 → goodwe-0.3.6}/LICENSE +0 -0
  15. {goodwe-0.3.4 → goodwe-0.3.6}/README.md +0 -0
  16. {goodwe-0.3.4 → goodwe-0.3.6}/goodwe/__init__.py +0 -0
  17. {goodwe-0.3.4 → goodwe-0.3.6}/goodwe/const.py +0 -0
  18. {goodwe-0.3.4 → goodwe-0.3.6}/goodwe/exceptions.py +0 -0
  19. {goodwe-0.3.4 → goodwe-0.3.6}/goodwe/inverter.py +0 -0
  20. {goodwe-0.3.4 → goodwe-0.3.6}/goodwe/modbus.py +0 -0
  21. {goodwe-0.3.4 → goodwe-0.3.6}/goodwe/protocol.py +0 -0
  22. {goodwe-0.3.4 → goodwe-0.3.6}/goodwe.egg-info/SOURCES.txt +0 -0
  23. {goodwe-0.3.4 → goodwe-0.3.6}/goodwe.egg-info/dependency_links.txt +0 -0
  24. {goodwe-0.3.4 → goodwe-0.3.6}/goodwe.egg-info/top_level.txt +0 -0
  25. {goodwe-0.3.4 → goodwe-0.3.6}/pyproject.toml +0 -0
  26. {goodwe-0.3.4 → goodwe-0.3.6}/setup.cfg +0 -0
  27. {goodwe-0.3.4 → goodwe-0.3.6}/tests/test_modbus.py +0 -0
  28. {goodwe-0.3.4 → goodwe-0.3.6}/tests/test_protocol.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: goodwe
3
- Version: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: Read data from GoodWe inverter via local network
5
5
  Home-page: https://github.com/marcelblijleven/goodwe
6
6
  Author: Martin Letenay, Marcel Blijleven
goodwe-0.3.6/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.6
@@ -110,6 +110,10 @@ class DT(Inverter):
110
110
  Integer("shadow_scan", 40326, "Shadow Scan", "", Kind.PV),
111
111
  Integer("grid_export", 40327, "Grid Export Enabled", "", Kind.GRID),
112
112
  Integer("grid_export_limit", 40328, "Grid Export Limit", "%", Kind.GRID),
113
+ Integer("start", 40330, "Start / Power On", "", Kind.GRID),
114
+ Integer("stop", 40331, "Stop / Power Off", "", Kind.GRID),
115
+ Integer("restart", 40332, "Restart", "", Kind.GRID),
116
+ Integer("grid_export_hw", 40345, "Grid Export Enabled (HW)", "", Kind.GRID),
113
117
  )
114
118
 
115
119
  # Settings for single phase inverters
@@ -178,11 +178,11 @@ class ES(Inverter):
178
178
  def _supports_eco_mode_v2(self) -> bool:
179
179
  if self.arm_version < 14:
180
180
  return False
181
- if "EMU" in self.serial_number:
181
+ if "EMU" in self.serial_number or "EMJ" in self.serial_number:
182
182
  return self.dsp1_version >= 11
183
- if "ESU" in self.serial_number:
183
+ if "ESU" in self.serial_number or "ESA" in self.serial_number:
184
184
  return self.dsp1_version >= 22
185
- if "BPS" in self.serial_number:
185
+ if "BPS" in self.serial_number or "BPU" in self.serial_number:
186
186
  return self.dsp1_version >= 10
187
187
  return False
188
188
 
@@ -192,7 +192,7 @@ class ES(Inverter):
192
192
  self.firmware = self._decode(response[0:5]).rstrip()
193
193
  self.model_name = self._decode(response[5:15]).rstrip()
194
194
  self.serial_number = self._decode(response[31:47])
195
- self.software_version = self._decode(response[51:63])
195
+ self.arm_firmware = self._decode(response[51:63]) # AKA software_version
196
196
  try:
197
197
  if len(self.firmware) >= 2:
198
198
  self.dsp1_version = int(self.firmware[0:2])
@@ -250,10 +250,9 @@ class ES(Inverter):
250
250
  # modbus can address/store only 16 bit values, read the other 8 bytes
251
251
  if self._is_modbus_setting(setting):
252
252
  response = await self._read_from_socket(ModbusReadCommand(self.comm_addr, setting.offset, 1))
253
- raw_value = setting.encode_value(value, response.response_data()[0:2])
254
253
  else:
255
254
  response = await self._read_from_socket(Aa55ReadCommand(setting.offset, 1))
256
- raw_value = setting.encode_value(value, response.response_data()[2:4])
255
+ raw_value = setting.encode_value(value, response.response_data()[0:2])
257
256
  else:
258
257
  raw_value = setting.encode_value(value)
259
258
  if len(raw_value) <= 2:
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import logging
4
4
  from typing import Tuple
5
5
 
6
- from .exceptions import RequestRejectedException
6
+ from .exceptions import RequestFailedException, RequestRejectedException
7
7
  from .inverter import Inverter
8
8
  from .inverter import OperationMode
9
9
  from .inverter import SensorKind as Kind
@@ -252,7 +252,8 @@ class ET(Inverter):
252
252
  Apparent4("meter_apparent_power_total", 36041, "Meter Apparent Power Total", Kind.GRID),
253
253
  Integer("meter_type", 36043, "Meter Type", "", Kind.GRID), # (0: Single phase, 1: 3P3W, 2: 3P4W, 3: HomeKit)
254
254
  Integer("meter_sw_version", 36044, "Meter Software Version", "", Kind.GRID),
255
- # Sensors added in some ARM fw update, read when flag _has_meter_extended is on
255
+
256
+ # Sensors added in some ARM fw update (or platform 745/753), read when flag _has_meter_extended is on
256
257
  Power4S("meter2_active_power", 36045, "Meter 2 Active Power", Kind.GRID),
257
258
  Float("meter2_e_total_exp", 36047, 1000, "Meter 2 Total Energy (export)", "kWh", Kind.GRID),
258
259
  Float("meter2_e_total_imp", 36049, 1000, "Meter 2 Total Energy (import)", "kWh", Kind.GRID),
@@ -263,6 +264,15 @@ class ET(Inverter):
263
264
  Current("meter_current1", 36055, "Meter L1 Current", Kind.GRID),
264
265
  Current("meter_current2", 36056, "Meter L2 Current", Kind.GRID),
265
266
  Current("meter_current3", 36057, "Meter L3 Current", Kind.GRID),
267
+
268
+ Energy8("meter_e_total_exp1", 36092, "Meter Total Energy (export) L1", Kind.GRID),
269
+ Energy8("meter_e_total_exp2", 36096, "Meter Total Energy (export) L2", Kind.GRID),
270
+ Energy8("meter_e_total_exp3", 36100, "Meter Total Energy (export) L3", Kind.GRID),
271
+ Energy8("meter_e_total_exp", 36104, "Meter Total Energy (export)", Kind.GRID),
272
+ Energy8("meter_e_total_imp1", 36108, "Meter Total Energy (import) L1", Kind.GRID),
273
+ Energy8("meter_e_total_imp2", 36112, "Meter Total Energy (import) L2", Kind.GRID),
274
+ Energy8("meter_e_total_imp3", 36116, "Meter Total Energy (import) L3", Kind.GRID),
275
+ Energy8("meter_e_total_imp", 36120, "Meter Total Energy (import)", Kind.GRID),
266
276
  )
267
277
 
268
278
  # Inverter's MPPT data
@@ -414,6 +424,7 @@ class ET(Inverter):
414
424
  self._READ_RUNNING_DATA: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x891c, 0x007d)
415
425
  self._READ_METER_DATA: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x8ca0, 0x2d)
416
426
  self._READ_METER_DATA_EXTENDED: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x8ca0, 0x3a)
427
+ self._READ_METER_DATA_EXTENDED2: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x8ca0, 0x7d)
417
428
  self._READ_BATTERY_INFO: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x9088, 0x0018)
418
429
  self._READ_BATTERY2_INFO: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x9858, 0x0016)
419
430
  self._READ_MPPT_DATA: ProtocolCommand = ModbusReadCommand(self.comm_addr, 0x89e5, 0x3d)
@@ -422,6 +433,7 @@ class ET(Inverter):
422
433
  self._has_battery: bool = True
423
434
  self._has_battery2: bool = False
424
435
  self._has_meter_extended: bool = False
436
+ self._has_meter_extended2: bool = False
425
437
  self._has_mppt: bool = False
426
438
  self._sensors = self.__all_sensors
427
439
  self._sensors_battery = self.__all_sensors_battery
@@ -440,6 +452,11 @@ class ET(Inverter):
440
452
  """Filter to exclude extended meter sensors"""
441
453
  return s.offset < 36045
442
454
 
455
+ @staticmethod
456
+ def _not_extended_meter2(s: Sensor) -> bool:
457
+ """Filter to exclude extended meter sensors"""
458
+ return s.offset < 36058
459
+
443
460
  async def read_device_info(self):
444
461
  response = await self._read_from_socket(self._READ_DEVICE_VERSION_INFO)
445
462
  response = response.response_data()
@@ -470,9 +487,10 @@ class ET(Inverter):
470
487
  if is_2_battery(self) or self.rated_power >= 25000:
471
488
  self._has_battery2 = True
472
489
 
473
- if self.rated_power >= 15000:
490
+ if is_745_platform(self) or self.rated_power >= 15000:
474
491
  self._has_mppt = True
475
492
  self._has_meter_extended = True
493
+ self._has_meter_extended2 = True
476
494
  else:
477
495
  self._sensors_meter = tuple(filter(self._not_extended_meter, self._sensors_meter))
478
496
 
@@ -482,8 +500,11 @@ class ET(Inverter):
482
500
  self._settings.update({s.id_: s for s in self.__settings_arm_fw_19})
483
501
  except RequestRejectedException as ex:
484
502
  if ex.message == 'ILLEGAL DATA ADDRESS':
485
- logger.debug("Cannot read EcoModeV2 settings, using to EcoModeV1.")
503
+ logger.debug("EcoModeV2 settings not supported, switching to EcoModeV1.")
486
504
  self._has_eco_mode_v2 = False
505
+ except RequestFailedException as ex:
506
+ logger.debug("Cannot read EcoModeV2 settings, switching to EcoModeV1.")
507
+ self._has_eco_mode_v2 = False
487
508
 
488
509
  # Check and add Peak Shaving settings added in (ETU fw 22)
489
510
  try:
@@ -491,8 +512,11 @@ class ET(Inverter):
491
512
  self._settings.update({s.id_: s for s in self.__settings_arm_fw_22})
492
513
  except RequestRejectedException as ex:
493
514
  if ex.message == 'ILLEGAL DATA ADDRESS':
494
- logger.debug("Cannot read PeakShaving setting, disabling it.")
515
+ logger.debug("PeakShaving setting not supported, disabling it.")
495
516
  self._has_peak_shaving = False
517
+ except RequestFailedException as ex:
518
+ logger.debug("Cannot read _has_peak_shaving settings, disabling it.")
519
+ self._has_peak_shaving = False
496
520
 
497
521
  async def read_runtime_data(self) -> Dict[str, Any]:
498
522
  response = await self._read_from_socket(self._READ_RUNNING_DATA)
@@ -505,7 +529,7 @@ class ET(Inverter):
505
529
  data.update(self._map_response(response, self._sensors_battery))
506
530
  except RequestRejectedException as ex:
507
531
  if ex.message == 'ILLEGAL DATA ADDRESS':
508
- logger.warning("Cannot read battery values, disabling further attempts.")
532
+ logger.warning("Battery values not supported, disabling further attempts.")
509
533
  self._has_battery = False
510
534
  else:
511
535
  raise ex
@@ -516,18 +540,32 @@ class ET(Inverter):
516
540
  self._map_response(response, self._sensors_battery2))
517
541
  except RequestRejectedException as ex:
518
542
  if ex.message == 'ILLEGAL DATA ADDRESS':
519
- logger.warning("Cannot read battery 2 values, disabling further attempts.")
543
+ logger.warning("Battery 2 values not supported, disabling further attempts.")
520
544
  self._has_battery2 = False
521
545
  else:
522
546
  raise ex
523
547
 
524
- if self._has_meter_extended:
548
+ if self._has_meter_extended2:
549
+ try:
550
+ response = await self._read_from_socket(self._READ_METER_DATA_EXTENDED2)
551
+ data.update(self._map_response(response, self._sensors_meter))
552
+ except RequestRejectedException as ex:
553
+ if ex.message == 'ILLEGAL DATA ADDRESS':
554
+ logger.info("Extended meter values not supported, disabling further attempts.")
555
+ self._has_meter_extended2 = False
556
+ self._sensors_meter = tuple(filter(self._not_extended_meter2, self._sensors_meter))
557
+ response = await self._read_from_socket(self._READ_METER_DATA_EXTENDED)
558
+ data.update(
559
+ self._map_response(response, self._sensors_meter))
560
+ else:
561
+ raise ex
562
+ elif self._has_meter_extended:
525
563
  try:
526
564
  response = await self._read_from_socket(self._READ_METER_DATA_EXTENDED)
527
565
  data.update(self._map_response(response, self._sensors_meter))
528
566
  except RequestRejectedException as ex:
529
567
  if ex.message == 'ILLEGAL DATA ADDRESS':
530
- logger.warning("Cannot read extended meter values, disabling further attempts.")
568
+ logger.warning("Extended meter values not supported, disabling further attempts.")
531
569
  self._has_meter_extended = False
532
570
  self._sensors_meter = tuple(filter(self._not_extended_meter, self._sensors_meter))
533
571
  response = await self._read_from_socket(self._READ_METER_DATA)
@@ -545,7 +583,7 @@ class ET(Inverter):
545
583
  data.update(self._map_response(response, self._sensors_mppt))
546
584
  except RequestRejectedException as ex:
547
585
  if ex.message == 'ILLEGAL DATA ADDRESS':
548
- logger.warning("Cannot read MPPT values, disabling further attempts.")
586
+ logger.warning("MPPT values not supported, disabling further attempts.")
549
587
  self._has_mppt = False
550
588
  else:
551
589
  raise ex
@@ -48,3 +48,7 @@ def is_2_battery(inverter: Inverter) -> bool:
48
48
  def is_745_platform(inverter: Inverter) -> bool:
49
49
  return any(model in inverter.serial_number for model in PLATFORM_745_LV_MODELS) or any(
50
50
  model in inverter.serial_number for model in PLATFORM_745_HV_MODELS)
51
+
52
+
53
+ def is_753_platform(inverter: Inverter) -> bool:
54
+ return any(model in inverter.serial_number for model in PLATFORM_753_MODELS)
@@ -183,7 +183,7 @@ class Energy(Sensor):
183
183
 
184
184
  def read_value(self, data: ProtocolResponse):
185
185
  value = read_bytes2(data)
186
- return float(value) / 10 if value else None
186
+ return float(value) / 10 if value is not None else None
187
187
 
188
188
 
189
189
  class Energy4(Sensor):
@@ -194,7 +194,18 @@ class Energy4(Sensor):
194
194
 
195
195
  def read_value(self, data: ProtocolResponse):
196
196
  value = read_bytes4(data)
197
- return float(value) / 10 if value else None
197
+ return float(value) / 10 if value is not None else None
198
+
199
+
200
+ class Energy8(Sensor):
201
+ """Sensor representing energy [kWh] value encoded in 8 bytes"""
202
+
203
+ def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind]):
204
+ super().__init__(id_, offset, name, 8, "kWh", kind)
205
+
206
+ def read_value(self, data: ProtocolResponse):
207
+ value = read_bytes8(data)
208
+ return float(value) / 100 if value is not None else None
198
209
 
199
210
 
200
211
  class Apparent(Sensor):
@@ -816,6 +827,14 @@ def read_bytes4_signed(buffer: ProtocolResponse, offset: int = None) -> int:
816
827
  return int.from_bytes(buffer.read(4), byteorder="big", signed=True)
817
828
 
818
829
 
830
+ def read_bytes8(buffer: ProtocolResponse, offset: int = None, undef: int = None) -> int:
831
+ """Retrieve 8 byte (unsigned int) value from buffer"""
832
+ if offset is not None:
833
+ buffer.seek(offset)
834
+ value = int.from_bytes(buffer.read(8), byteorder="big", signed=False)
835
+ return undef if value == 0xffffffffffffffff else value
836
+
837
+
819
838
  def read_decimal2(buffer: ProtocolResponse, scale: int, offset: int = None) -> float:
820
839
  """Retrieve 2 byte (signed float) value from buffer"""
821
840
  if offset is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: goodwe
3
- Version: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: Read data from GoodWe inverter via local network
5
5
  Home-page: https://github.com/marcelblijleven/goodwe
6
6
  Author: Martin Letenay, Marcel Blijleven
@@ -101,7 +101,7 @@ class GW6000_DT_Test(DtMock):
101
101
  self.assertFalse(self.sensor_map, f"Some sensors were not tested {self.sensor_map}")
102
102
 
103
103
  def test_GW6000_DT_setting(self):
104
- self.assertEqual(4, len(self.settings()))
104
+ self.assertEqual(8, len(self.settings()))
105
105
  settings = {s.id_: s for s in self.settings()}
106
106
  self.assertEqual('Timestamp', type(settings.get("time")).__name__)
107
107
  self.assertEqual('Integer', type(settings.get("grid_export")).__name__)
@@ -221,7 +221,7 @@ class GW5000D_NS_Test(DtMock):
221
221
  self.assertSensor("apparent_power", -1, "VA", data),
222
222
  self.assertSensor("reactive_power", -1, "var", data),
223
223
  self.assertSensor('temperature', 1.4, 'C', data)
224
- self.assertSensor('e_day', None, 'kWh', data)
224
+ self.assertSensor('e_day', 0, 'kWh', data)
225
225
  self.assertSensor('e_total', 881.7, 'kWh', data)
226
226
  self.assertSensor('h_total', 955, 'h', data)
227
227
  self.assertSensor('safety_country', 73, '', data)
@@ -7,7 +7,7 @@ from goodwe import DISCOVERY_COMMAND
7
7
  from goodwe.es import ES
8
8
  from goodwe.exceptions import RequestFailedException
9
9
  from goodwe.inverter import OperationMode
10
- from goodwe.protocol import ProtocolCommand, ProtocolResponse
10
+ from goodwe.protocol import Aa55ReadCommand, ProtocolCommand, ProtocolResponse
11
11
 
12
12
 
13
13
  class EsMock(TestCase, ES):
@@ -26,6 +26,8 @@ class EsMock(TestCase, ES):
26
26
  root_dir = os.path.dirname(os.path.abspath(__file__))
27
27
  filename = self._mock_responses.get(command)
28
28
  if filename is not None:
29
+ if filename.startswith('aa55'):
30
+ return ProtocolResponse(bytes.fromhex(filename), command)
29
31
  with open(root_dir + '/sample/es/' + filename, 'r') as f:
30
32
  response = bytes.fromhex(f.read())
31
33
  if not command.validator(response):
@@ -52,6 +54,8 @@ class GW5048D_ES_Test(EsMock):
52
54
  self.mock_response(self._READ_DEVICE_VERSION_INFO, 'GW5048D-ES_device_info.hex')
53
55
  self.mock_response(self._READ_DEVICE_RUNNING_DATA, 'GW5048D-ES_running_data.hex')
54
56
  self.mock_response(self._READ_DEVICE_SETTINGS_DATA, 'GW5048D-ES_settings_data.hex')
57
+ self.mock_response(Aa55ReadCommand(1793, 1), 'aa557fc0019a08000000000000007f0360')
58
+ self.mock_response(Aa55ReadCommand(1800, 1), 'aa557fc0019a02007f035a')
55
59
 
56
60
  def test_GW5048D_ES_device_info(self):
57
61
  self.loop.run_until_complete(self.read_device_info())
@@ -163,6 +167,16 @@ class GW5048D_ES_Test(EsMock):
163
167
  data = self.loop.run_until_complete(self.read_setting('grid_export_limit'))
164
168
  self.assertEqual(10000, data)
165
169
 
170
+ data = self.loop.run_until_complete(self.read_setting('eco_mode_1'))
171
+ self.assertEqual(
172
+ "EcoModeV1(id_='eco_mode_1', offset=1793, name='Eco Mode Group 1', size_=8, unit='', kind=<SensorKind.BAT: 4>)",
173
+ repr(data))
174
+ data = self.loop.run_until_complete(self.read_setting('eco_mode_2_switch'))
175
+ self.assertEqual(0, data)
176
+
177
+ def test_write_setting(self):
178
+ self.loop.run_until_complete(self.write_setting('eco_mode_2_switch', 0))
179
+
166
180
 
167
181
  class GW5048_EM_Test(EsMock):
168
182
 
@@ -14,7 +14,7 @@ class EtMock(TestCase, ET):
14
14
  def __init__(self, methodName='runTest'):
15
15
  TestCase.__init__(self, methodName)
16
16
  ET.__init__(self, "localhost")
17
- self.sensor_map = {s.id_: s.unit for s in self.sensors()}
17
+ self.sensor_map = {s.id_: s for s in self.sensors()}
18
18
  self._mock_responses = {}
19
19
  self._list_of_requests = []
20
20
 
@@ -28,6 +28,8 @@ class EtMock(TestCase, ET):
28
28
  if filename is not None:
29
29
  if 'ILLEGAL DATA ADDRESS' == filename:
30
30
  raise RequestRejectedException('ILLEGAL DATA ADDRESS')
31
+ if 'NO RESPONSE' == filename:
32
+ raise RequestFailedException()
31
33
  with open(root_dir + '/sample/et/' + filename, 'r') as f:
32
34
  response = bytes.fromhex(f.read())
33
35
  if not command.validator(response):
@@ -38,10 +40,11 @@ class EtMock(TestCase, ET):
38
40
  self._list_of_requests.append(command.request)
39
41
  return ProtocolResponse(bytes.fromhex("aa55f700010203040506070809"), command)
40
42
 
41
- def assertSensor(self, sensor, expected_value, expected_unit, data):
42
- self.assertEqual(expected_value, data.get(sensor))
43
- self.assertEqual(expected_unit, self.sensor_map.get(sensor))
44
- self.sensor_map.pop(sensor)
43
+ def assertSensor(self, sensor_name, expected_value, expected_unit, data):
44
+ self.assertEqual(expected_value, data.get(sensor_name))
45
+ sensor = self.sensor_map.get(sensor_name)
46
+ self.assertEqual(expected_unit, sensor.unit)
47
+ self.sensor_map.pop(sensor_name)
45
48
 
46
49
  @classmethod
47
50
  def setUpClass(cls):
@@ -78,13 +81,15 @@ class GW10K_ET_Test(EtMock):
78
81
  def test_GW10K_ET_runtime_data(self):
79
82
  # Reset sensors
80
83
  self.loop.run_until_complete(self.read_device_info())
81
- self.sensor_map = {s.id_: s.unit for s in self.sensors()}
84
+ self.sensor_map = {s.id_: s for s in self.sensors()}
82
85
 
83
86
  data = self.loop.run_until_complete(self.read_runtime_data())
84
87
  self.assertEqual(145, len(data))
85
88
 
89
+ self.assertEqual(36015, self.sensor_map.get("meter_e_total_exp").offset)
90
+
86
91
  # for sensor in self.sensors():
87
- # print(f"self.assertSensor('{sensor.id_}', {data[sensor.id_]}, '{self.sensor_map.get(sensor.id_)}', data)")
92
+ # print(f"self.assertSensor('{sensor.id_}', {data[sensor.id_]}, '{self.sensor_map.get(sensor.id_).unit}', data)")
88
93
 
89
94
  self.assertSensor('timestamp', datetime.strptime('2021-08-22 11:11:12', '%Y-%m-%d %H:%M:%S'), '', data)
90
95
  self.assertSensor('vpv1', 332.6, 'V', data)
@@ -164,7 +169,7 @@ class GW10K_ET_Test(EtMock):
164
169
  self.assertSensor('h_total', 9246, 'h', data)
165
170
  self.assertSensor("e_day_exp", 9.8, 'kWh', data)
166
171
  self.assertSensor("e_total_imp", 58.0, 'kWh', data)
167
- self.assertSensor("e_day_imp", None, 'kWh', data)
172
+ self.assertSensor("e_day_imp", 0, 'kWh', data)
168
173
  self.assertSensor("e_load_total", 8820.2, 'kWh', data)
169
174
  self.assertSensor("e_load_day", 11.6, 'kWh', data)
170
175
  self.assertSensor("e_bat_charge_total", 2758.1, 'kWh', data)
@@ -457,14 +462,14 @@ class GW6000_EH_Test(EtMock):
457
462
  self.assertSensor("e_total_exp", 58.6, 'kWh', data)
458
463
  self.assertSensor('h_total', 33, 'h', data)
459
464
  self.assertSensor("e_day_exp", 21.6, 'kWh', data)
460
- self.assertSensor("e_total_imp", None, 'kWh', data)
461
- self.assertSensor("e_day_imp", None, 'kWh', data)
465
+ self.assertSensor("e_total_imp", 0, 'kWh', data)
466
+ self.assertSensor("e_day_imp", 0, 'kWh', data)
462
467
  self.assertSensor("e_load_total", 70.1, 'kWh', data)
463
468
  self.assertSensor("e_load_day", 27.1, 'kWh', data)
464
- self.assertSensor("e_bat_charge_total", None, 'kWh', data)
465
- self.assertSensor("e_bat_charge_day", None, 'kWh', data)
466
- self.assertSensor("e_bat_discharge_total", None, 'kWh', data)
467
- self.assertSensor("e_bat_discharge_day", None, 'kWh', data)
469
+ self.assertSensor("e_bat_charge_total", 0, 'kWh', data)
470
+ self.assertSensor("e_bat_charge_day", 0, 'kWh', data)
471
+ self.assertSensor("e_bat_discharge_total", 0, 'kWh', data)
472
+ self.assertSensor("e_bat_discharge_day", 0, 'kWh', data)
468
473
  self.assertSensor('diagnose_result', 117983303, '', data)
469
474
  self.assertSensor('diagnose_result_label',
470
475
  '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',
@@ -486,7 +491,7 @@ class GEH10_1U_10_Test(EtMock):
486
491
  def test_GEH10_1U_10_runtime_data(self):
487
492
  # Reset sensors
488
493
  self.loop.run_until_complete(self.read_device_info())
489
- self.sensor_map = {s.id_: s.unit for s in self.sensors()}
494
+ self.sensor_map = {s.id_: s for s in self.sensors()}
490
495
 
491
496
  data = self.loop.run_until_complete(self.read_runtime_data())
492
497
  self.assertEqual(125, len(data))
@@ -558,8 +563,8 @@ class GEH10_1U_10_Test(EtMock):
558
563
  self.assertSensor('e_total_exp', 10273.3, 'kWh', data)
559
564
  self.assertSensor('h_total', 3256, 'h', data)
560
565
  self.assertSensor('e_day_exp', 16.6, 'kWh', data)
561
- self.assertSensor('e_total_imp', None, 'kWh', data)
562
- self.assertSensor('e_day_imp', None, 'kWh', data)
566
+ self.assertSensor('e_total_imp', 0, 'kWh', data)
567
+ self.assertSensor('e_day_imp', 0, 'kWh', data)
563
568
  self.assertSensor('e_load_total', 4393.9, 'kWh', data)
564
569
  self.assertSensor('e_load_day', 10.7, 'kWh', data)
565
570
  self.assertSensor('e_bat_charge_total', 141.9, 'kWh', data)
@@ -650,6 +655,7 @@ class GW25K_ET_Test(EtMock):
650
655
  EtMock.__init__(self, methodName)
651
656
  self.mock_response(self._READ_DEVICE_VERSION_INFO, 'GW25K-ET_device_info.hex')
652
657
  self.mock_response(self._READ_RUNNING_DATA, 'GW25K-ET_running_data.hex')
658
+ self.mock_response(self._READ_METER_DATA_EXTENDED2, 'ILLEGAL DATA ADDRESS')
653
659
  self.mock_response(self._READ_METER_DATA_EXTENDED, 'GW25K-ET_meter_data.hex')
654
660
  self.mock_response(self._READ_BATTERY_INFO, 'GW25K-ET_battery_info.hex')
655
661
  self.mock_response(self._READ_MPPT_DATA, 'GW25K-ET_mppt_data.hex')
@@ -672,11 +678,14 @@ class GW25K_ET_Test(EtMock):
672
678
  def test_GW25K_ET_runtime_data(self):
673
679
  # Reset sensors
674
680
  self.loop.run_until_complete(self.read_device_info())
675
- self.sensor_map = {s.id_: s.unit for s in self.sensors()}
676
681
 
677
682
  data = self.loop.run_until_complete(self.read_runtime_data())
678
683
  self.assertEqual(237, len(data))
679
684
 
685
+ self.sensor_map = {s.id_: s for s in self.sensors()}
686
+
687
+ # self.assertEqual(36104, self.sensor_map.get("meter_e_total_exp").offset)
688
+
680
689
  self.assertSensor('timestamp', datetime.strptime('2023-12-03 14:07:07', '%Y-%m-%d %H:%M:%S'), '', data)
681
690
  self.assertSensor('vpv1', 737.9, 'V', data)
682
691
  self.assertSensor('ipv1', 1.4, 'A', data)
@@ -771,7 +780,7 @@ class GW25K_ET_Test(EtMock):
771
780
  self.assertSensor('e_bat_charge_total', 91.3, 'kWh', data)
772
781
  self.assertSensor('e_bat_charge_day', 11.0, 'kWh', data)
773
782
  self.assertSensor('e_bat_discharge_total', 69.6, 'kWh', data)
774
- self.assertSensor('e_bat_discharge_day', None, 'kWh', data)
783
+ self.assertSensor('e_bat_discharge_day', 0, 'kWh', data)
775
784
  self.assertSensor('diagnose_result', 33816960, '', data)
776
785
  self.assertSensor('diagnose_result_label',
777
786
  'BMS: Discharge current low, APP: Discharge current too low, BMS: Charge disabled, PF value set',
@@ -926,6 +935,7 @@ class GW29K9_ET_Test(EtMock):
926
935
  EtMock.__init__(self, methodName)
927
936
  self.mock_response(self._READ_DEVICE_VERSION_INFO, 'GW29K9-ET_device_info.hex')
928
937
  self.mock_response(self._READ_RUNNING_DATA, 'GW29K9-ET_running_data.hex')
938
+ self.mock_response(self._READ_METER_DATA_EXTENDED2, 'ILLEGAL DATA ADDRESS')
929
939
  self.mock_response(self._READ_METER_DATA_EXTENDED, 'GW29K9-ET_meter_data.hex')
930
940
  self.mock_response(self._READ_BATTERY_INFO, 'GW29K9-ET_battery_info.hex')
931
941
  self.mock_response(self._READ_BATTERY2_INFO, 'GW29K9-ET_battery2_info.hex')
@@ -949,11 +959,12 @@ class GW29K9_ET_Test(EtMock):
949
959
  def test_GW29K9_ET_runtime_data(self):
950
960
  # Reset sensors
951
961
  self.loop.run_until_complete(self.read_device_info())
952
- self.sensor_map = {s.id_: s.unit for s in self.sensors()}
953
962
 
954
963
  data = self.loop.run_until_complete(self.read_runtime_data())
955
964
  self.assertEqual(211, len(data))
956
965
 
966
+ self.sensor_map = {s.id_: s for s in self.sensors()}
967
+
957
968
  self.assertSensor('timestamp', datetime.strptime('2024-01-17 14:49:14', '%Y-%m-%d %H:%M:%S'), '', data)
958
969
  self.assertSensor('vpv1', 682.9, 'V', data)
959
970
  self.assertSensor('ipv1', 1.5, 'A', data)
@@ -1042,13 +1053,13 @@ class GW29K9_ET_Test(EtMock):
1042
1053
  self.assertSensor('h_total', 1175, 'h', data)
1043
1054
  self.assertSensor('e_day_exp', 1.2, 'kWh', data)
1044
1055
  self.assertSensor('e_total_imp', 8.7, 'kWh', data)
1045
- self.assertSensor('e_day_imp', None, 'kWh', data)
1056
+ self.assertSensor('e_day_imp', 0, 'kWh', data)
1046
1057
  self.assertSensor('e_load_total', 10742.2, 'kWh', data)
1047
1058
  self.assertSensor('e_load_day', 43.8, 'kWh', data)
1048
- self.assertSensor('e_bat_charge_total', None, 'kWh', data)
1049
- self.assertSensor('e_bat_charge_day', None, 'kWh', data)
1050
- self.assertSensor('e_bat_discharge_total', None, 'kWh', data)
1051
- self.assertSensor('e_bat_discharge_day', None, 'kWh', data)
1059
+ self.assertSensor('e_bat_charge_total', 0, 'kWh', data)
1060
+ self.assertSensor('e_bat_charge_day', 0, 'kWh', data)
1061
+ self.assertSensor('e_bat_discharge_total', 0, 'kWh', data)
1062
+ self.assertSensor('e_bat_discharge_day', 0, 'kWh', data)
1052
1063
  self.assertSensor('diagnose_result', 33816782, '', data)
1053
1064
  self.assertSensor('diagnose_result_label',
1054
1065
  'Battery SOC low, Battery SOC in back, BMS: Discharge disabled, '
@@ -1096,32 +1107,6 @@ class GW29K9_ET_Test(EtMock):
1096
1107
  self.assertSensor('meter_current1', 4.6, 'A', data)
1097
1108
  self.assertSensor('meter_current2', 6.0, 'A', data)
1098
1109
  self.assertSensor('meter_current3', 13.6, 'A', data)
1099
- self.assertSensor('battery_bms', None, '', data)
1100
- self.assertSensor('battery_index', None, '', data)
1101
- self.assertSensor('battery_status', None, '', data)
1102
- self.assertSensor('battery_temperature', None, 'C', data)
1103
- self.assertSensor('battery_charge_limit', None, 'A', data)
1104
- self.assertSensor('battery_discharge_limit', None, 'A', data)
1105
- self.assertSensor('battery_error_l', None, '', data)
1106
- self.assertSensor('battery_soc', None, '%', data)
1107
- self.assertSensor('battery_soh', None, '%', data)
1108
- self.assertSensor('battery_modules', None, '', data)
1109
- self.assertSensor('battery_warning_l', None, '', data)
1110
- self.assertSensor('battery_protocol', None, '', data)
1111
- self.assertSensor('battery_error_h', None, '', data)
1112
- self.assertSensor('battery_error', None, '', data)
1113
- self.assertSensor('battery_warning_h', None, '', data)
1114
- self.assertSensor('battery_warning', None, '', data)
1115
- self.assertSensor('battery_sw_version', None, '', data)
1116
- self.assertSensor('battery_hw_version', None, '', data)
1117
- self.assertSensor('battery_max_cell_temp_id', None, '', data)
1118
- self.assertSensor('battery_min_cell_temp_id', None, '', data)
1119
- self.assertSensor('battery_max_cell_voltage_id', None, '', data)
1120
- self.assertSensor('battery_min_cell_voltage_id', None, '', data)
1121
- self.assertSensor('battery_max_cell_temp', None, 'C', data)
1122
- self.assertSensor('battery_min_cell_temp', None, 'C', data)
1123
- self.assertSensor('battery_max_cell_voltage', None, 'V', data)
1124
- self.assertSensor('battery_min_cell_voltage', None, 'V', data)
1125
1110
  self.assertSensor('battery2_status', 0, '', data)
1126
1111
  self.assertSensor('battery2_temperature', 0.0, 'C', data)
1127
1112
  self.assertSensor('battery2_charge_limit', 0, 'A', data)
@@ -1196,3 +1181,26 @@ class GW29K9_ET_Test(EtMock):
1196
1181
  self.assertSensor('apparent_power3', 0, 'VA', data)
1197
1182
 
1198
1183
  self.assertFalse(self.sensor_map, f"Some sensors were not tested {self.sensor_map}")
1184
+
1185
+
1186
+ class GW5K_BT_Test(EtMock):
1187
+
1188
+ def __init__(self, methodName='runTest'):
1189
+ EtMock.__init__(self, methodName)
1190
+ self.mock_response(self._READ_DEVICE_VERSION_INFO, 'GW5K-BT_device_info.hex')
1191
+ self.mock_response(ModbusReadCommand(self.comm_addr, 47547, 6), 'NO RESPONSE')
1192
+
1193
+ def test_GW5K_BT_device_info(self):
1194
+ self.loop.run_until_complete(self.read_device_info())
1195
+ self.assertEqual('GW5K-BT', self.model_name)
1196
+ self.assertEqual('95000BTU203W0000', self.serial_number)
1197
+ self.assertEqual(5000, self.rated_power)
1198
+ self.assertEqual(0, self.modbus_version)
1199
+ self.assertEqual(254, self.ac_output_type)
1200
+ self.assertEqual(3, self.dsp1_version)
1201
+ self.assertEqual(3, self.dsp2_version)
1202
+ self.assertEqual(124, self.dsp_svn_version)
1203
+ self.assertEqual(11, self.arm_version)
1204
+ self.assertEqual(147, self.arm_svn_version)
1205
+ self.assertEqual('04029-03-S10', self.firmware)
1206
+ self.assertEqual('02041-11-S00', self.arm_firmware)
@@ -155,6 +155,16 @@ class TestUtils(TestCase):
155
155
  data = MockResponse("ffffffff")
156
156
  self.assertIsNone(testee.read(data))
157
157
 
158
+ def test_energy8(self):
159
+ testee = Energy8("", 0, "", None)
160
+
161
+ data = MockResponse("0000000000015b41")
162
+ self.assertEqual(888.97, testee.read(data))
163
+ data = MockResponse("0000000000038E6C")
164
+ self.assertEqual(2330.68, testee.read(data))
165
+ data = MockResponse("ffffffffffffffff")
166
+ self.assertIsNone(testee.read(data))
167
+
158
168
  def test_timestamp(self):
159
169
  testee = Timestamp("", 0, "", None)
160
170
 
goodwe-0.3.4/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.3.4
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes