goodwe 0.4.6__tar.gz → 0.4.7__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.4.6/goodwe.egg-info → goodwe-0.4.7}/PKG-INFO +1 -1
  2. goodwe-0.4.7/VERSION +1 -0
  3. {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/dt.py +7 -1
  4. {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/et.py +35 -3
  5. {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/model.py +4 -0
  6. {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/sensor.py +19 -0
  7. {goodwe-0.4.6 → goodwe-0.4.7/goodwe.egg-info}/PKG-INFO +1 -1
  8. {goodwe-0.4.6 → goodwe-0.4.7}/tests/test_dt.py +77 -16
  9. {goodwe-0.4.6 → goodwe-0.4.7}/tests/test_et.py +20 -37
  10. {goodwe-0.4.6 → goodwe-0.4.7}/tests/test_sensor.py +10 -0
  11. goodwe-0.4.6/VERSION +0 -1
  12. {goodwe-0.4.6 → goodwe-0.4.7}/LICENSE +0 -0
  13. {goodwe-0.4.6 → goodwe-0.4.7}/README.md +0 -0
  14. {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/__init__.py +0 -0
  15. {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/const.py +0 -0
  16. {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/es.py +0 -0
  17. {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/exceptions.py +0 -0
  18. {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/inverter.py +0 -0
  19. {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/modbus.py +0 -0
  20. {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/protocol.py +0 -0
  21. {goodwe-0.4.6 → goodwe-0.4.7}/goodwe.egg-info/SOURCES.txt +0 -0
  22. {goodwe-0.4.6 → goodwe-0.4.7}/goodwe.egg-info/dependency_links.txt +0 -0
  23. {goodwe-0.4.6 → goodwe-0.4.7}/goodwe.egg-info/top_level.txt +0 -0
  24. {goodwe-0.4.6 → goodwe-0.4.7}/pyproject.toml +0 -0
  25. {goodwe-0.4.6 → goodwe-0.4.7}/setup.cfg +0 -0
  26. {goodwe-0.4.6 → goodwe-0.4.7}/tests/test_es.py +0 -0
  27. {goodwe-0.4.6 → goodwe-0.4.7}/tests/test_modbus.py +0 -0
  28. {goodwe-0.4.6 → goodwe-0.4.7}/tests/test_protocol.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: goodwe
3
- Version: 0.4.6
3
+ Version: 0.4.7
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.4.7/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.4.7
@@ -35,6 +35,12 @@ class DT(Inverter):
35
35
  Calculated("ppv3",
36
36
  lambda data: round(read_voltage(data, 30107) * read_current(data, 30108)),
37
37
  "PV3 Power", "W", Kind.PV),
38
+ # ppv1 + ppv2 + ppv3
39
+ Calculated("ppv",
40
+ lambda data: (round(read_voltage(data, 30103) * read_current(data, 30104))) + (round(
41
+ read_voltage(data, 30105) * read_current(data, 30106))) + (round(
42
+ read_voltage(data, 30107) * read_current(data, 30108))),
43
+ "PV Power", "W", Kind.PV),
38
44
  # Voltage("vpv4", 14, "PV4 Voltage", Kind.PV),
39
45
  # Current("ipv4", 16, "PV4 Current", Kind.PV),
40
46
  # Voltage("vpv5", 14, "PV5 Voltage", Kind.PV),
@@ -63,7 +69,7 @@ class DT(Inverter):
63
69
  lambda data: round(read_voltage(data, 30120) * read_current(data, 30123)),
64
70
  "On-grid L3 Power", "W", Kind.AC),
65
71
  # 30127 reserved
66
- Power("ppv", 30128, "PV Power", Kind.PV),
72
+ PowerS("active_power", 30128, "Active Power", Kind.AC),
67
73
  Integer("work_mode", 30129, "Work Mode code"),
68
74
  Enum2("work_mode_label", 30129, WORK_MODES, "Work Mode"),
69
75
  Long("error_codes", 30130, "Error Codes"),
@@ -257,7 +257,8 @@ class ET(Inverter):
257
257
  Apparent4("meter_apparent_power_total", 36041, "Meter Apparent Power Total", Kind.GRID),
258
258
  Integer("meter_type", 36043, "Meter Type", "", Kind.GRID), # (0: Single phase, 1: 3P3W, 2: 3P4W, 3: HomeKit)
259
259
  Integer("meter_sw_version", 36044, "Meter Software Version", "", Kind.GRID),
260
- # Sensors added in some ARM fw update, read when flag _has_meter_extended is on
260
+
261
+ # Sensors added in some ARM fw update (or platform 745/753), read when flag _has_meter_extended is on
261
262
  Power4S("meter2_active_power", 36045, "Meter 2 Active Power", Kind.GRID),
262
263
  Float("meter2_e_total_exp", 36047, 1000, "Meter 2 Total Energy (export)", "kWh", Kind.GRID),
263
264
  Float("meter2_e_total_imp", 36049, 1000, "Meter 2 Total Energy (import)", "kWh", Kind.GRID),
@@ -268,6 +269,15 @@ class ET(Inverter):
268
269
  Current("meter_current1", 36055, "Meter L1 Current", Kind.GRID),
269
270
  Current("meter_current2", 36056, "Meter L2 Current", Kind.GRID),
270
271
  Current("meter_current3", 36057, "Meter L3 Current", Kind.GRID),
272
+
273
+ Energy8("meter_e_total_exp1", 36092, "Meter Total Energy (export) L1", Kind.GRID),
274
+ Energy8("meter_e_total_exp2", 36096, "Meter Total Energy (export) L2", Kind.GRID),
275
+ Energy8("meter_e_total_exp3", 36100, "Meter Total Energy (export) L3", Kind.GRID),
276
+ Energy8("meter_e_total_exp", 36104, "Meter Total Energy (export)", Kind.GRID),
277
+ Energy8("meter_e_total_imp1", 36108, "Meter Total Energy (import) L1", Kind.GRID),
278
+ Energy8("meter_e_total_imp2", 36112, "Meter Total Energy (import) L2", Kind.GRID),
279
+ Energy8("meter_e_total_imp3", 36116, "Meter Total Energy (import) L3", Kind.GRID),
280
+ Energy8("meter_e_total_imp", 36120, "Meter Total Energy (import)", Kind.GRID),
271
281
  )
272
282
 
273
283
  # Inverter's MPPT data
@@ -464,6 +474,7 @@ class ET(Inverter):
464
474
  self._READ_RUNNING_DATA: ProtocolCommand = self._read_command(0x891c, 0x007d)
465
475
  self._READ_METER_DATA: ProtocolCommand = self._read_command(0x8ca0, 0x2d)
466
476
  self._READ_METER_DATA_EXTENDED: ProtocolCommand = self._read_command(0x8ca0, 0x3a)
477
+ self._READ_METER_DATA_EXTENDED2: ProtocolCommand = self._read_command(0x8ca0, 0x7d)
467
478
  self._READ_BATTERY_INFO: ProtocolCommand = self._read_command(0x9088, 0x0018)
468
479
  self._READ_BATTERY2_INFO: ProtocolCommand = self._read_command(0x9858, 0x0016)
469
480
  self._READ_MPPT_DATA: ProtocolCommand = self._read_command(0x89e5, 0x3d)
@@ -472,6 +483,7 @@ class ET(Inverter):
472
483
  self._has_battery: bool = True
473
484
  self._has_battery2: bool = False
474
485
  self._has_meter_extended: bool = False
486
+ self._has_meter_extended2: bool = False
475
487
  self._has_mppt: bool = False
476
488
  self._sensors = self.__all_sensors
477
489
  self._sensors_battery = self.__all_sensors_battery
@@ -490,6 +502,11 @@ class ET(Inverter):
490
502
  """Filter to exclude extended meter sensors"""
491
503
  return s.offset < 36045
492
504
 
505
+ @staticmethod
506
+ def _not_extended_meter2(s: Sensor) -> bool:
507
+ """Filter to exclude extended meter sensors"""
508
+ return s.offset < 36058
509
+
493
510
  async def read_device_info(self):
494
511
  response = await self._read_from_socket(self._READ_DEVICE_VERSION_INFO)
495
512
  response = response.response_data()
@@ -520,9 +537,10 @@ class ET(Inverter):
520
537
  if is_2_battery(self) or self.rated_power >= 25000:
521
538
  self._has_battery2 = True
522
539
 
523
- if self.rated_power >= 15000:
540
+ if is_745_platform(self) or self.rated_power >= 15000:
524
541
  self._has_mppt = True
525
542
  self._has_meter_extended = True
543
+ self._has_meter_extended2 = True
526
544
  else:
527
545
  self._sensors_meter = tuple(filter(self._not_extended_meter, self._sensors_meter))
528
546
 
@@ -577,7 +595,21 @@ class ET(Inverter):
577
595
  else:
578
596
  raise ex
579
597
 
580
- if self._has_meter_extended:
598
+ if self._has_meter_extended2:
599
+ try:
600
+ response = await self._read_from_socket(self._READ_METER_DATA_EXTENDED2)
601
+ data.update(self._map_response(response, self._sensors_meter))
602
+ except RequestRejectedException as ex:
603
+ if ex.message == ILLEGAL_DATA_ADDRESS:
604
+ logger.info("Extended meter values not supported, disabling further attempts.")
605
+ self._has_meter_extended2 = False
606
+ self._sensors_meter = tuple(filter(self._not_extended_meter2, self._sensors_meter))
607
+ response = await self._read_from_socket(self._READ_METER_DATA_EXTENDED)
608
+ data.update(
609
+ self._map_response(response, self._sensors_meter))
610
+ else:
611
+ raise ex
612
+ elif self._has_meter_extended:
581
613
  try:
582
614
  response = await self._read_from_socket(self._READ_METER_DATA_EXTENDED)
583
615
  data.update(self._map_response(response, self._sensors_meter))
@@ -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)
@@ -197,6 +197,17 @@ class Energy4(Sensor):
197
197
  return float(value) / 10 if value is not None else None
198
198
 
199
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
209
+
210
+
200
211
  class Apparent(Sensor):
201
212
  """Sensor representing apparent power [VA] value encoded in 2 bytes"""
202
213
 
@@ -840,6 +851,14 @@ def read_bytes4_signed(buffer: ProtocolResponse, offset: int = None) -> int:
840
851
  return int.from_bytes(buffer.read(4), byteorder="big", signed=True)
841
852
 
842
853
 
854
+ def read_bytes8(buffer: ProtocolResponse, offset: int = None, undef: int = None) -> int:
855
+ """Retrieve 8 byte (unsigned int) value from buffer"""
856
+ if offset is not None:
857
+ buffer.seek(offset)
858
+ value = int.from_bytes(buffer.read(8), byteorder="big", signed=False)
859
+ return undef if value == 0xffffffffffffffff else value
860
+
861
+
843
862
  def read_decimal2(buffer: ProtocolResponse, scale: int, offset: int = None) -> float:
844
863
  """Retrieve 2 byte (signed float) value from buffer"""
845
864
  if offset is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: goodwe
3
- Version: 0.4.6
3
+ Version: 0.4.7
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
@@ -10,9 +10,9 @@ from goodwe.protocol import ProtocolCommand, ProtocolResponse
10
10
 
11
11
  class DtMock(TestCase, DT):
12
12
 
13
- def __init__(self, methodName='runTest'):
13
+ def __init__(self, methodName='runTest', port=8899):
14
14
  TestCase.__init__(self, methodName)
15
- DT.__init__(self, "localhost", 8899)
15
+ DT.__init__(self, "localhost", port)
16
16
  self.sensor_map = {s.id_: s.unit for s in self.sensors()}
17
17
  self._mock_responses = {}
18
18
 
@@ -52,7 +52,7 @@ class GW6000_DT_Test(DtMock):
52
52
  def test_GW6000_DT_runtime_data(self):
53
53
  self.loop.run_until_complete(self.read_device_info())
54
54
  data = self.loop.run_until_complete(self.read_runtime_data())
55
- self.assertEqual(40, len(data))
55
+ self.assertEqual(41, len(data))
56
56
 
57
57
  self.assertSensor('timestamp', datetime.strptime('2021-08-31 12:03:02', '%Y-%m-%d %H:%M:%S'), '', data)
58
58
  self.assertSensor('vpv1', 320.8, 'V', data)
@@ -64,6 +64,7 @@ class GW6000_DT_Test(DtMock):
64
64
  self.assertSensor('vpv3', None, 'V', data)
65
65
  self.assertSensor('ipv3', None, 'A', data)
66
66
  self.assertSensor('ppv3', None, 'W', data)
67
+ self.assertSensor('ppv', 2031, 'W', data)
67
68
  self.assertSensor('vline1', 0, 'V', data)
68
69
  self.assertSensor('vline2', 0, 'V', data)
69
70
  self.assertSensor('vline3', 0, 'V', data)
@@ -79,7 +80,7 @@ class GW6000_DT_Test(DtMock):
79
80
  self.assertSensor('pgrid1', 609, 'W', data)
80
81
  self.assertSensor('pgrid2', 597, 'W', data)
81
82
  self.assertSensor('pgrid3', 624, 'W', data)
82
- self.assertSensor('ppv', 1835, 'W', data)
83
+ self.assertSensor('active_power', 1835, 'W', data)
83
84
  self.assertSensor('work_mode', 1, '', data)
84
85
  self.assertSensor('work_mode_label', 'Normal', '', data)
85
86
  self.assertSensor('error_codes', 0, '', data)
@@ -135,7 +136,7 @@ class GW8K_DT_Test(DtMock):
135
136
  def test_GW8K_DT_runtime_data(self):
136
137
  self.loop.run_until_complete(self.read_device_info())
137
138
  data = self.loop.run_until_complete(self.read_runtime_data())
138
- self.assertEqual(40, len(data))
139
+ self.assertEqual(41, len(data))
139
140
 
140
141
  self.assertSensor('timestamp', datetime.strptime('2021-08-24 16:43:27', '%Y-%m-%d %H:%M:%S'), '', data)
141
142
  self.assertSensor('vpv1', 275.5, 'V', data)
@@ -144,6 +145,7 @@ class GW8K_DT_Test(DtMock):
144
145
  self.assertSensor('vpv2', 510.8, 'V', data)
145
146
  self.assertSensor('ipv2', 0.8, 'A', data)
146
147
  self.assertSensor('ppv2', 409, 'W', data)
148
+ self.assertSensor('ppv', 574, 'W', data)
147
149
  self.assertSensor('vline1', 413.7, 'V', data)
148
150
  self.assertSensor('vline2', 413.0, 'V', data)
149
151
  self.assertSensor('vline3', 408.0, 'V', data)
@@ -159,7 +161,7 @@ class GW8K_DT_Test(DtMock):
159
161
  self.assertSensor('pgrid1', 237, 'W', data)
160
162
  self.assertSensor('pgrid2', 240, 'W', data)
161
163
  self.assertSensor('pgrid3', 235, 'W', data)
162
- self.assertSensor('ppv', 643, 'W', data)
164
+ self.assertSensor('active_power', 643, 'W', data)
163
165
  self.assertSensor('work_mode', 1, '', data)
164
166
  self.assertSensor('work_mode_label', 'Normal', '', data)
165
167
  self.assertSensor('error_codes', 0, '', data)
@@ -199,7 +201,7 @@ class GW5000D_NS_Test(DtMock):
199
201
  def test_GW5000D_NS_runtime_data(self):
200
202
  self.loop.run_until_complete(self.read_device_info())
201
203
  data = self.loop.run_until_complete(self.read_runtime_data())
202
- self.assertEqual(30, len(data))
204
+ self.assertEqual(31, len(data))
203
205
 
204
206
  self.assertSensor('timestamp', datetime.strptime('2021-09-06 06:56:01', '%Y-%m-%d %H:%M:%S'), '', data)
205
207
  self.assertSensor('vpv1', 224.4, 'V', data)
@@ -208,12 +210,13 @@ class GW5000D_NS_Test(DtMock):
208
210
  self.assertSensor('vpv2', 291.8, 'V', data)
209
211
  self.assertSensor('ipv2', 0, 'A', data)
210
212
  self.assertSensor('ppv2', 0, 'W', data)
213
+ self.assertSensor('ppv', 0, 'W', data)
211
214
  self.assertSensor('vline1', 0, 'V', data)
212
215
  self.assertSensor('vgrid1', 240.5, 'V', data)
213
216
  self.assertSensor('igrid1', 0.0, 'A', data)
214
217
  self.assertSensor('fgrid1', 49.97, 'Hz', data)
215
218
  self.assertSensor('pgrid1', 0, 'W', data)
216
- self.assertSensor('ppv', 0, 'W', data)
219
+ self.assertSensor('active_power', 0, 'W', data)
217
220
  self.assertSensor('work_mode', 0, '', data)
218
221
  self.assertSensor('work_mode_label', 'Wait Mode', '', data)
219
222
  self.assertSensor('error_codes', 0, '', data)
@@ -262,7 +265,7 @@ class GW5000_MS_Test(DtMock):
262
265
  def test_GW5000_MS_runtime_data(self):
263
266
  self.loop.run_until_complete(self.read_device_info())
264
267
  data = self.loop.run_until_complete(self.read_runtime_data())
265
- self.assertEqual(33, len(data))
268
+ self.assertEqual(34, len(data))
266
269
 
267
270
  self.assertSensor('timestamp', datetime.strptime('2021-10-15 09:03:12', '%Y-%m-%d %H:%M:%S'), '', data)
268
271
  self.assertSensor('vpv1', 319.6, 'V', data)
@@ -274,12 +277,13 @@ class GW5000_MS_Test(DtMock):
274
277
  self.assertSensor('vpv3', 143.2, 'V', data)
275
278
  self.assertSensor('ipv3', 0.4, 'A', data)
276
279
  self.assertSensor('ppv3', 57, 'W', data)
280
+ self.assertSensor('ppv', 165, 'W', data)
277
281
  self.assertSensor('vline1', 0, 'V', data)
278
282
  self.assertSensor('vgrid1', 240.1, 'V', data)
279
283
  self.assertSensor('igrid1', 0.9, 'A', data)
280
284
  self.assertSensor('fgrid1', 49.98, 'Hz', data)
281
285
  self.assertSensor('pgrid1', 216, 'W', data)
282
- self.assertSensor('ppv', 295, 'W', data)
286
+ self.assertSensor('active_power', 295, 'W', data)
283
287
  self.assertSensor('work_mode', 1, '', data)
284
288
  self.assertSensor('work_mode_label', 'Normal', '', data)
285
289
  self.assertSensor('error_codes', 0, '', data)
@@ -318,7 +322,7 @@ class GW10K_MS_30_Test(DtMock):
318
322
  def test_GW10K_MS_30_runtime_data(self):
319
323
  self.loop.run_until_complete(self.read_device_info())
320
324
  data = self.loop.run_until_complete(self.read_runtime_data())
321
- self.assertEqual(33, len(data))
325
+ self.assertEqual(34, len(data))
322
326
 
323
327
  self.assertSensor('timestamp', datetime.strptime('2024-01-09 22:08:20', '%Y-%m-%d %H:%M:%S'), '', data)
324
328
  self.assertSensor('vpv1', 0.0, 'V', data)
@@ -330,12 +334,13 @@ class GW10K_MS_30_Test(DtMock):
330
334
  self.assertSensor('vpv3', 0.0, 'V', data)
331
335
  self.assertSensor('ipv3', 0.0, 'A', data)
332
336
  self.assertSensor('ppv3', 0, 'W', data)
337
+ self.assertSensor('ppv', 0, 'W', data)
333
338
  self.assertSensor('vline1', 0.0, 'V', data)
334
339
  self.assertSensor('vgrid1', 236.2, 'V', data)
335
340
  self.assertSensor('igrid1', 0.0, 'A', data)
336
341
  self.assertSensor('fgrid1', 50.0, 'Hz', data)
337
342
  self.assertSensor('pgrid1', 0, 'W', data)
338
- self.assertSensor('ppv', 0, 'W', data)
343
+ self.assertSensor('active_power', 0, 'W', data)
339
344
  self.assertSensor('work_mode', 0, '', data)
340
345
  self.assertSensor('work_mode_label', 'Wait Mode', '', data)
341
346
  self.assertSensor('error_codes', 0, '', data)
@@ -355,6 +360,60 @@ class GW10K_MS_30_Test(DtMock):
355
360
  self.assertSensor('derating_mode_label', '', '', data)
356
361
 
357
362
 
363
+ class GW10K_MS_TCP_Test(DtMock):
364
+
365
+ def __init__(self, methodName='runTest'):
366
+ DtMock.__init__(self, methodName, 502)
367
+ self.mock_response(self._READ_DEVICE_RUNNING_DATA, 'GW10K-MS-30_tcp_running_data.hex')
368
+
369
+ def test_GW10K_MS_TCP_runtime_data(self):
370
+ self.loop.run_until_complete(self.read_device_info())
371
+ data = self.loop.run_until_complete(self.read_runtime_data())
372
+ self.assertEqual(41, len(data))
373
+
374
+ self.assertSensor('timestamp', datetime.strptime('2024-06-02 09:07:17', '%Y-%m-%d %H:%M:%S'), '', data)
375
+ self.assertSensor('vpv1', 400.6, 'V', data)
376
+ self.assertSensor('ipv1', 6.9, 'A', data)
377
+ self.assertSensor('ppv1', 2764, 'W', data)
378
+ self.assertSensor('vpv2', 364.0, 'V', data)
379
+ self.assertSensor('ipv2', 3.6, 'A', data)
380
+ self.assertSensor('ppv2', 1310, 'W', data)
381
+ self.assertSensor('ppv', 6143, 'W', data)
382
+ self.assertSensor('vline1', 0, 'V', data)
383
+ self.assertSensor('vline2', 0, 'V', data)
384
+ self.assertSensor('vline3', 0, 'V', data)
385
+ self.assertSensor('vgrid1', 241.1, 'V', data)
386
+ self.assertSensor('vgrid2', 0, 'V', data)
387
+ self.assertSensor('vgrid3', 0, 'V', data)
388
+ self.assertSensor('igrid1', 24.7, 'A', data)
389
+ self.assertSensor('igrid2', 0, 'A', data)
390
+ self.assertSensor('igrid3', 0, 'A', data)
391
+ self.assertSensor('fgrid1', 49.98, 'Hz', data)
392
+ self.assertSensor('fgrid2', -0.01, 'Hz', data)
393
+ self.assertSensor('fgrid3', -0.01, 'Hz', data)
394
+ self.assertSensor('pgrid1', 5955, 'W', data)
395
+ self.assertSensor('pgrid2', 0, 'W', data)
396
+ self.assertSensor('pgrid3', 0, 'W', data)
397
+ self.assertSensor('active_power', 5914, 'W', data)
398
+ self.assertSensor('work_mode', 1, '', data)
399
+ self.assertSensor('work_mode_label', 'Normal', '', data)
400
+ self.assertSensor('error_codes', 0, '', data)
401
+ self.assertSensor('warning_code', 0, '', data)
402
+ self.assertSensor('apparent_power', 5957, 'VA', data)
403
+ self.assertSensor('reactive_power', -6, 'var', data)
404
+ self.assertSensor('temperature', 36.0, 'C', data)
405
+ self.assertSensor('e_day', 4.3, 'kWh', data)
406
+ self.assertSensor('e_total', 998.2, 'kWh', data)
407
+ self.assertSensor('h_total', 246, 'h', data)
408
+ self.assertSensor('safety_country', 32, '', data)
409
+ self.assertSensor('safety_country_label', '50Hz 230Vac Default', '', data)
410
+ self.assertSensor('funbit', 0, '', data)
411
+ self.assertSensor('vbus', 397.3, 'V', data)
412
+ self.assertSensor('vnbus', 0, 'V', data)
413
+ self.assertSensor('derating_mode', 0, '', data)
414
+ self.assertSensor('derating_mode_label', '', '', data)
415
+
416
+
358
417
  class GW20KAU_DT_Test(DtMock):
359
418
 
360
419
  def __init__(self, methodName='runTest'):
@@ -374,7 +433,7 @@ class GW20KAU_DT_Test(DtMock):
374
433
  def test_GW20KAU_DT_runtime_data(self):
375
434
  self.loop.run_until_complete(self.read_device_info())
376
435
  data = self.loop.run_until_complete(self.read_runtime_data())
377
- self.assertEqual(40, len(data))
436
+ self.assertEqual(41, len(data))
378
437
 
379
438
  self.assertSensor('timestamp', datetime.strptime('2022-10-21 19:23:42', '%Y-%m-%d %H:%M:%S'), '', data)
380
439
  self.assertSensor('vpv1', 390.5, 'V', data)
@@ -383,6 +442,7 @@ class GW20KAU_DT_Test(DtMock):
383
442
  self.assertSensor('vpv2', 351.6, 'V', data)
384
443
  self.assertSensor('ipv2', 7.1, 'A', data)
385
444
  self.assertSensor('ppv2', 2496, 'W', data)
445
+ self.assertSensor('ppv', 5151, 'W', data)
386
446
  self.assertSensor('vline1', 388.5, 'V', data)
387
447
  self.assertSensor('vline2', 391.7, 'V', data)
388
448
  self.assertSensor('vline3', 394.5, 'V', data)
@@ -398,7 +458,7 @@ class GW20KAU_DT_Test(DtMock):
398
458
  self.assertSensor('pgrid1', 1628, 'W', data)
399
459
  self.assertSensor('pgrid2', 1655, 'W', data)
400
460
  self.assertSensor('pgrid3', 1621, 'W', data)
401
- self.assertSensor('ppv', 4957, 'W', data)
461
+ self.assertSensor('active_power', 4957, 'W', data)
402
462
  self.assertSensor('work_mode', 1, '', data)
403
463
  self.assertSensor('work_mode_label', 'Normal', '', data)
404
464
  self.assertSensor('error_codes', 0, '', data)
@@ -437,7 +497,7 @@ class GW17K_DT_Test(DtMock):
437
497
  def test_GW20KAU_DT_runtime_data(self):
438
498
  self.loop.run_until_complete(self.read_device_info())
439
499
  data = self.loop.run_until_complete(self.read_runtime_data())
440
- self.assertEqual(40, len(data))
500
+ self.assertEqual(41, len(data))
441
501
 
442
502
  self.assertSensor('timestamp', datetime.strptime('2024-05-20 10:35:55', '%Y-%m-%d %H:%M:%S'), '', data)
443
503
  self.assertSensor('vpv1', 540.0, 'V', data)
@@ -446,6 +506,7 @@ class GW17K_DT_Test(DtMock):
446
506
  self.assertSensor('vpv2', 475.5, 'V', data)
447
507
  self.assertSensor('ipv2', 14.8, 'A', data)
448
508
  self.assertSensor('ppv2', 7037, 'W', data)
509
+ self.assertSensor('ppv', 12707, 'W', data)
449
510
  self.assertSensor('vline1', 413.0, 'V', data)
450
511
  self.assertSensor('vline2', 411.5, 'V', data)
451
512
  self.assertSensor('vline3', 409.5, 'V', data)
@@ -461,7 +522,7 @@ class GW17K_DT_Test(DtMock):
461
522
  self.assertSensor('pgrid1', 4166, 'W', data)
462
523
  self.assertSensor('pgrid2', 4170, 'W', data)
463
524
  self.assertSensor('pgrid3', 4153, 'W', data)
464
- self.assertSensor('ppv', 12470, 'W', data)
525
+ self.assertSensor('active_power', 12470, 'W', data)
465
526
  self.assertSensor('work_mode', 1, '', data)
466
527
  self.assertSensor('work_mode_label', 'Normal', '', data)
467
528
  self.assertSensor('error_codes', 0, '', data)
@@ -15,7 +15,7 @@ class EtMock(TestCase, ET):
15
15
  def __init__(self, methodName='runTest'):
16
16
  TestCase.__init__(self, methodName)
17
17
  ET.__init__(self, "localhost", 8899)
18
- self.sensor_map = {s.id_: s.unit for s in self.sensors()}
18
+ self.sensor_map = {s.id_: s for s in self.sensors()}
19
19
  self._mock_responses = {}
20
20
  self._list_of_requests = []
21
21
 
@@ -41,10 +41,11 @@ class EtMock(TestCase, ET):
41
41
  self._list_of_requests.append(command.request)
42
42
  return ProtocolResponse(bytes.fromhex("aa55f700010203040506070809"), command)
43
43
 
44
- def assertSensor(self, sensor, expected_value, expected_unit, data):
45
- self.assertEqual(expected_value, data.get(sensor))
46
- self.assertEqual(expected_unit, self.sensor_map.get(sensor))
47
- self.sensor_map.pop(sensor)
44
+ def assertSensor(self, sensor_name, expected_value, expected_unit, data):
45
+ self.assertEqual(expected_value, data.get(sensor_name))
46
+ sensor = self.sensor_map.get(sensor_name);
47
+ self.assertEqual(expected_unit, sensor.unit)
48
+ self.sensor_map.pop(sensor_name)
48
49
 
49
50
  @classmethod
50
51
  def setUpClass(cls):
@@ -81,13 +82,15 @@ class GW10K_ET_Test(EtMock):
81
82
  def test_GW10K_ET_runtime_data(self):
82
83
  # Reset sensors
83
84
  self.loop.run_until_complete(self.read_device_info())
84
- self.sensor_map = {s.id_: s.unit for s in self.sensors()}
85
+ self.sensor_map = {s.id_: s for s in self.sensors()}
85
86
 
86
87
  data = self.loop.run_until_complete(self.read_runtime_data())
87
88
  self.assertEqual(145, len(data))
88
89
 
90
+ self.assertEqual(36015, self.sensor_map.get("meter_e_total_exp").offset)
91
+
89
92
  # for sensor in self.sensors():
90
- # print(f"self.assertSensor('{sensor.id_}', {data[sensor.id_]}, '{self.sensor_map.get(sensor.id_)}', data)")
93
+ # print(f"self.assertSensor('{sensor.id_}', {data[sensor.id_]}, '{self.sensor_map.get(sensor.id_).unit}', data)")
91
94
 
92
95
  self.assertSensor('timestamp', datetime.strptime('2021-08-22 11:11:12', '%Y-%m-%d %H:%M:%S'), '', data)
93
96
  self.assertSensor('vpv1', 332.6, 'V', data)
@@ -386,7 +389,7 @@ class GW10K_ET_fw1023_Test(EtMock):
386
389
  def test_GW10K_ET_runtime_data_fw1023(self):
387
390
  # Reset sensors
388
391
  self.loop.run_until_complete(self.read_device_info())
389
- self.sensor_map = {s.id_: s.unit for s in self.sensors()}
392
+ self.sensor_map = {s.id_: s for s in self.sensors()}
390
393
 
391
394
  data = self.loop.run_until_complete(self.read_runtime_data())
392
395
  self.assertEqual(145, len(data))
@@ -596,7 +599,7 @@ class GEH10_1U_10_Test(EtMock):
596
599
  def test_GEH10_1U_10_runtime_data(self):
597
600
  # Reset sensors
598
601
  self.loop.run_until_complete(self.read_device_info())
599
- self.sensor_map = {s.id_: s.unit for s in self.sensors()}
602
+ self.sensor_map = {s.id_: s for s in self.sensors()}
600
603
 
601
604
  data = self.loop.run_until_complete(self.read_runtime_data())
602
605
  self.assertEqual(125, len(data))
@@ -760,6 +763,7 @@ class GW25K_ET_Test(EtMock):
760
763
  EtMock.__init__(self, methodName)
761
764
  self.mock_response(self._READ_DEVICE_VERSION_INFO, 'GW25K-ET_device_info.hex')
762
765
  self.mock_response(self._READ_RUNNING_DATA, 'GW25K-ET_running_data.hex')
766
+ self.mock_response(self._READ_METER_DATA_EXTENDED2, ILLEGAL_DATA_ADDRESS)
763
767
  self.mock_response(self._READ_METER_DATA_EXTENDED, 'GW25K-ET_meter_data.hex')
764
768
  self.mock_response(self._READ_BATTERY_INFO, 'GW25K-ET_battery_info.hex')
765
769
  self.mock_response(self._READ_MPPT_DATA, 'GW25K-ET_mppt_data.hex')
@@ -782,11 +786,14 @@ class GW25K_ET_Test(EtMock):
782
786
  def test_GW25K_ET_runtime_data(self):
783
787
  # Reset sensors
784
788
  self.loop.run_until_complete(self.read_device_info())
785
- self.sensor_map = {s.id_: s.unit for s in self.sensors()}
786
789
 
787
790
  data = self.loop.run_until_complete(self.read_runtime_data())
788
791
  self.assertEqual(237, len(data))
789
792
 
793
+ self.sensor_map = {s.id_: s for s in self.sensors()}
794
+
795
+ # self.assertEqual(36104, self.sensor_map.get("meter_e_total_exp").offset)
796
+
790
797
  self.assertSensor('timestamp', datetime.strptime('2023-12-03 14:07:07', '%Y-%m-%d %H:%M:%S'), '', data)
791
798
  self.assertSensor('vpv1', 737.9, 'V', data)
792
799
  self.assertSensor('ipv1', 1.4, 'A', data)
@@ -1036,6 +1043,7 @@ class GW29K9_ET_Test(EtMock):
1036
1043
  EtMock.__init__(self, methodName)
1037
1044
  self.mock_response(self._READ_DEVICE_VERSION_INFO, 'GW29K9-ET_device_info.hex')
1038
1045
  self.mock_response(self._READ_RUNNING_DATA, 'GW29K9-ET_running_data.hex')
1046
+ self.mock_response(self._READ_METER_DATA_EXTENDED2, ILLEGAL_DATA_ADDRESS)
1039
1047
  self.mock_response(self._READ_METER_DATA_EXTENDED, 'GW29K9-ET_meter_data.hex')
1040
1048
  self.mock_response(self._READ_BATTERY_INFO, 'GW29K9-ET_battery_info.hex')
1041
1049
  self.mock_response(self._READ_BATTERY2_INFO, 'GW29K9-ET_battery2_info.hex')
@@ -1059,11 +1067,12 @@ class GW29K9_ET_Test(EtMock):
1059
1067
  def test_GW29K9_ET_runtime_data(self):
1060
1068
  # Reset sensors
1061
1069
  self.loop.run_until_complete(self.read_device_info())
1062
- self.sensor_map = {s.id_: s.unit for s in self.sensors()}
1063
1070
 
1064
1071
  data = self.loop.run_until_complete(self.read_runtime_data())
1065
1072
  self.assertEqual(211, len(data))
1066
1073
 
1074
+ self.sensor_map = {s.id_: s for s in self.sensors()}
1075
+
1067
1076
  self.assertSensor('timestamp', datetime.strptime('2024-01-17 14:49:14', '%Y-%m-%d %H:%M:%S'), '', data)
1068
1077
  self.assertSensor('vpv1', 682.9, 'V', data)
1069
1078
  self.assertSensor('ipv1', 1.5, 'A', data)
@@ -1206,32 +1215,6 @@ class GW29K9_ET_Test(EtMock):
1206
1215
  self.assertSensor('meter_current1', 4.6, 'A', data)
1207
1216
  self.assertSensor('meter_current2', 6.0, 'A', data)
1208
1217
  self.assertSensor('meter_current3', 13.6, 'A', data)
1209
- self.assertSensor('battery_bms', None, '', data)
1210
- self.assertSensor('battery_index', None, '', data)
1211
- self.assertSensor('battery_status', None, '', data)
1212
- self.assertSensor('battery_temperature', None, 'C', data)
1213
- self.assertSensor('battery_charge_limit', None, 'A', data)
1214
- self.assertSensor('battery_discharge_limit', None, 'A', data)
1215
- self.assertSensor('battery_error_l', None, '', data)
1216
- self.assertSensor('battery_soc', None, '%', data)
1217
- self.assertSensor('battery_soh', None, '%', data)
1218
- self.assertSensor('battery_modules', None, '', data)
1219
- self.assertSensor('battery_warning_l', None, '', data)
1220
- self.assertSensor('battery_protocol', None, '', data)
1221
- self.assertSensor('battery_error_h', None, '', data)
1222
- self.assertSensor('battery_error', None, '', data)
1223
- self.assertSensor('battery_warning_h', None, '', data)
1224
- self.assertSensor('battery_warning', None, '', data)
1225
- self.assertSensor('battery_sw_version', None, '', data)
1226
- self.assertSensor('battery_hw_version', None, '', data)
1227
- self.assertSensor('battery_max_cell_temp_id', None, '', data)
1228
- self.assertSensor('battery_min_cell_temp_id', None, '', data)
1229
- self.assertSensor('battery_max_cell_voltage_id', None, '', data)
1230
- self.assertSensor('battery_min_cell_voltage_id', None, '', data)
1231
- self.assertSensor('battery_max_cell_temp', None, 'C', data)
1232
- self.assertSensor('battery_min_cell_temp', None, 'C', data)
1233
- self.assertSensor('battery_max_cell_voltage', None, 'V', data)
1234
- self.assertSensor('battery_min_cell_voltage', None, 'V', data)
1235
1218
  self.assertSensor('battery2_status', 0, '', data)
1236
1219
  self.assertSensor('battery2_temperature', 0.0, 'C', data)
1237
1220
  self.assertSensor('battery2_charge_limit', 0, 'A', data)
@@ -169,6 +169,16 @@ class TestUtils(TestCase):
169
169
  data = MockResponse("ffffffff")
170
170
  self.assertIsNone(testee.read(data))
171
171
 
172
+ def test_energy8(self):
173
+ testee = Energy8("", 0, "", None)
174
+
175
+ data = MockResponse("0000000000015b41")
176
+ self.assertEqual(888.97, testee.read(data))
177
+ data = MockResponse("0000000000038E6C")
178
+ self.assertEqual(2330.68, testee.read(data))
179
+ data = MockResponse("ffffffffffffffff")
180
+ self.assertIsNone(testee.read(data))
181
+
172
182
  def test_temp(self):
173
183
  testee = Temp("", 0, "", None)
174
184
 
goodwe-0.4.6/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.4.6
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
File without changes
File without changes