goodwe 0.4.5__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.5/goodwe.egg-info → goodwe-0.4.7}/PKG-INFO +1 -1
  2. goodwe-0.4.7/VERSION +1 -0
  3. {goodwe-0.4.5 → goodwe-0.4.7}/goodwe/dt.py +12 -1
  4. {goodwe-0.4.5 → goodwe-0.4.7}/goodwe/es.py +9 -6
  5. {goodwe-0.4.5 → goodwe-0.4.7}/goodwe/et.py +40 -5
  6. {goodwe-0.4.5 → goodwe-0.4.7}/goodwe/model.py +4 -0
  7. {goodwe-0.4.5 → goodwe-0.4.7}/goodwe/protocol.py +26 -4
  8. {goodwe-0.4.5 → goodwe-0.4.7}/goodwe/sensor.py +23 -4
  9. {goodwe-0.4.5 → goodwe-0.4.7/goodwe.egg-info}/PKG-INFO +1 -1
  10. {goodwe-0.4.5 → goodwe-0.4.7}/tests/test_dt.py +78 -17
  11. {goodwe-0.4.5 → goodwe-0.4.7}/tests/test_et.py +23 -40
  12. {goodwe-0.4.5 → goodwe-0.4.7}/tests/test_sensor.py +24 -0
  13. goodwe-0.4.5/VERSION +0 -1
  14. {goodwe-0.4.5 → goodwe-0.4.7}/LICENSE +0 -0
  15. {goodwe-0.4.5 → goodwe-0.4.7}/README.md +0 -0
  16. {goodwe-0.4.5 → goodwe-0.4.7}/goodwe/__init__.py +0 -0
  17. {goodwe-0.4.5 → goodwe-0.4.7}/goodwe/const.py +0 -0
  18. {goodwe-0.4.5 → goodwe-0.4.7}/goodwe/exceptions.py +0 -0
  19. {goodwe-0.4.5 → goodwe-0.4.7}/goodwe/inverter.py +0 -0
  20. {goodwe-0.4.5 → goodwe-0.4.7}/goodwe/modbus.py +0 -0
  21. {goodwe-0.4.5 → goodwe-0.4.7}/goodwe.egg-info/SOURCES.txt +0 -0
  22. {goodwe-0.4.5 → goodwe-0.4.7}/goodwe.egg-info/dependency_links.txt +0 -0
  23. {goodwe-0.4.5 → goodwe-0.4.7}/goodwe.egg-info/top_level.txt +0 -0
  24. {goodwe-0.4.5 → goodwe-0.4.7}/pyproject.toml +0 -0
  25. {goodwe-0.4.5 → goodwe-0.4.7}/setup.cfg +0 -0
  26. {goodwe-0.4.5 → goodwe-0.4.7}/tests/test_es.py +0 -0
  27. {goodwe-0.4.5 → goodwe-0.4.7}/tests/test_modbus.py +0 -0
  28. {goodwe-0.4.5 → 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.5
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"),
@@ -114,6 +120,10 @@ class DT(Inverter):
114
120
  Integer("shadow_scan", 40326, "Shadow Scan", "", Kind.PV),
115
121
  Integer("grid_export", 40327, "Grid Export Enabled", "", Kind.GRID),
116
122
  Integer("grid_export_limit", 40328, "Grid Export Limit", "%", Kind.GRID),
123
+ Integer("start", 40330, "Start / Power On", "", Kind.GRID),
124
+ Integer("stop", 40331, "Stop / Power Off", "", Kind.GRID),
125
+ Integer("restart", 40332, "Restart", "", Kind.GRID),
126
+ Integer("grid_export_hw", 40345, "Grid Export Enabled (HW)", "", Kind.GRID),
117
127
  )
118
128
 
119
129
  # Settings for single phase inverters
@@ -196,6 +206,7 @@ class DT(Inverter):
196
206
  if ex.message == ILLEGAL_DATA_ADDRESS:
197
207
  logger.debug("Unsupported setting %s", setting.id_)
198
208
  self._settings.pop(setting.id_, None)
209
+ raise ValueError(f'Unknown setting "{setting.id_}"')
199
210
  return None
200
211
 
201
212
  async def write_setting(self, setting_id: str, value: Any):
@@ -174,11 +174,11 @@ class ES(Inverter):
174
174
  def _supports_eco_mode_v2(self) -> bool:
175
175
  if self.arm_version < 14:
176
176
  return False
177
- if "EMU" in self.serial_number:
177
+ if "EMU" in self.serial_number or "EMJ" in self.serial_number:
178
178
  return self.dsp1_version >= 11
179
- if "ESU" in self.serial_number:
179
+ if "ESU" in self.serial_number or "ESA" in self.serial_number:
180
180
  return self.dsp1_version >= 22
181
- if "BPS" in self.serial_number:
181
+ if "BPS" in self.serial_number or "BPU" in self.serial_number:
182
182
  return self.dsp1_version >= 10
183
183
  return False
184
184
 
@@ -188,7 +188,7 @@ class ES(Inverter):
188
188
  self.firmware = self._decode(response[0:5]).rstrip()
189
189
  self.model_name = self._decode(response[5:15]).rstrip()
190
190
  self.serial_number = self._decode(response[31:47])
191
- self.software_version = self._decode(response[51:63])
191
+ self.arm_firmware = self._decode(response[51:63]) # AKA software_version
192
192
  try:
193
193
  if len(self.firmware) >= 2:
194
194
  self.dsp1_version = int(self.firmware[0:2])
@@ -220,9 +220,12 @@ class ES(Inverter):
220
220
  elif setting_id.startswith("modbus"):
221
221
  response = await self._read_from_socket(self._read_command(int(setting_id[7:]), 1))
222
222
  return int.from_bytes(response.read(2), byteorder="big", signed=True)
223
- else:
223
+ elif setting_id in self._settings:
224
+ logger.debug("Reading setting %s", setting_id)
224
225
  all_settings = await self.read_settings_data()
225
226
  return all_settings.get(setting_id)
227
+ else:
228
+ raise ValueError(f'Unknown setting "{setting_id}"')
226
229
 
227
230
  async def _read_setting(self, setting: Sensor) -> Any:
228
231
  count = (setting.size_ + (setting.size_ % 2)) // 2
@@ -296,7 +299,7 @@ class ES(Inverter):
296
299
  try:
297
300
  mode = OperationMode(mode_id)
298
301
  except ValueError:
299
- logger.debug("Unknown work_mode value %d", mode_id)
302
+ logger.debug("Unknown work_mode value %s", mode_id)
300
303
  return None
301
304
  if OperationMode.ECO != mode:
302
305
  return mode
@@ -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
@@ -332,7 +342,7 @@ class ET(Inverter):
332
342
  # Modbus registers of inverter settings, offsets are modbus register addresses
333
343
  __all_settings: Tuple[Sensor, ...] = (
334
344
  Integer("comm_address", 45127, "Communication Address", ""),
335
- Integer("modbus_baud_rate", 45132, "Modbus Baud rate", ""),
345
+ Long("modbus_baud_rate", 45132, "Modbus Baud rate", ""),
336
346
  Timestamp("time", 45200, "Inverter time"),
337
347
 
338
348
  Integer("sensitivity_check", 45246, "Sensitivity Check Mode", "", Kind.AC),
@@ -357,6 +367,8 @@ class ET(Inverter):
357
367
  Integer("work_mode", 47000, "Work Mode", "", Kind.AC),
358
368
  Integer("dred", 47010, "DRED/Remote Shutdown", "", Kind.AC),
359
369
 
370
+ Integer("meter_target_power_offset", 47120, "Meter Target Power Offset", "W", Kind.AC),
371
+
360
372
  Integer("battery_soc_protection", 47500, "Battery SoC Protection", "", Kind.BAT),
361
373
 
362
374
  Integer("grid_export", 47509, "Grid Export Enabled", "", Kind.GRID),
@@ -462,6 +474,7 @@ class ET(Inverter):
462
474
  self._READ_RUNNING_DATA: ProtocolCommand = self._read_command(0x891c, 0x007d)
463
475
  self._READ_METER_DATA: ProtocolCommand = self._read_command(0x8ca0, 0x2d)
464
476
  self._READ_METER_DATA_EXTENDED: ProtocolCommand = self._read_command(0x8ca0, 0x3a)
477
+ self._READ_METER_DATA_EXTENDED2: ProtocolCommand = self._read_command(0x8ca0, 0x7d)
465
478
  self._READ_BATTERY_INFO: ProtocolCommand = self._read_command(0x9088, 0x0018)
466
479
  self._READ_BATTERY2_INFO: ProtocolCommand = self._read_command(0x9858, 0x0016)
467
480
  self._READ_MPPT_DATA: ProtocolCommand = self._read_command(0x89e5, 0x3d)
@@ -470,6 +483,7 @@ class ET(Inverter):
470
483
  self._has_battery: bool = True
471
484
  self._has_battery2: bool = False
472
485
  self._has_meter_extended: bool = False
486
+ self._has_meter_extended2: bool = False
473
487
  self._has_mppt: bool = False
474
488
  self._sensors = self.__all_sensors
475
489
  self._sensors_battery = self.__all_sensors_battery
@@ -488,6 +502,11 @@ class ET(Inverter):
488
502
  """Filter to exclude extended meter sensors"""
489
503
  return s.offset < 36045
490
504
 
505
+ @staticmethod
506
+ def _not_extended_meter2(s: Sensor) -> bool:
507
+ """Filter to exclude extended meter sensors"""
508
+ return s.offset < 36058
509
+
491
510
  async def read_device_info(self):
492
511
  response = await self._read_from_socket(self._READ_DEVICE_VERSION_INFO)
493
512
  response = response.response_data()
@@ -518,9 +537,10 @@ class ET(Inverter):
518
537
  if is_2_battery(self) or self.rated_power >= 25000:
519
538
  self._has_battery2 = True
520
539
 
521
- if self.rated_power >= 15000:
540
+ if is_745_platform(self) or self.rated_power >= 15000:
522
541
  self._has_mppt = True
523
542
  self._has_meter_extended = True
543
+ self._has_meter_extended2 = True
524
544
  else:
525
545
  self._sensors_meter = tuple(filter(self._not_extended_meter, self._sensors_meter))
526
546
 
@@ -575,7 +595,21 @@ class ET(Inverter):
575
595
  else:
576
596
  raise ex
577
597
 
578
- 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:
579
613
  try:
580
614
  response = await self._read_from_socket(self._READ_METER_DATA_EXTENDED)
581
615
  data.update(self._map_response(response, self._sensors_meter))
@@ -626,6 +660,7 @@ class ET(Inverter):
626
660
  if ex.message == ILLEGAL_DATA_ADDRESS:
627
661
  logger.debug("Unsupported setting %s", setting.id_)
628
662
  self._settings.pop(setting.id_, None)
663
+ raise ValueError(f'Unknown setting "{setting.id_}"')
629
664
  return None
630
665
 
631
666
  async def write_setting(self, setting_id: str, value: Any):
@@ -685,7 +720,7 @@ class ET(Inverter):
685
720
  try:
686
721
  mode = OperationMode(mode_id)
687
722
  except ValueError:
688
- logger.debug("Unknown work_mode value %d", mode_id)
723
+ logger.debug("Unknown work_mode value %s", mode_id)
689
724
  return None
690
725
  if OperationMode.ECO != mode:
691
726
  return mode
@@ -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)
@@ -482,7 +482,7 @@ class Aa55ProtocolCommand(ProtocolCommand):
482
482
  The last 2 bytes are again plain checksum of header+payload.
483
483
  """
484
484
 
485
- def __init__(self, payload: str, response_type: str):
485
+ def __init__(self, payload: str, response_type: str, offset: int = 0, value: int = 0):
486
486
  super().__init__(
487
487
  bytes.fromhex(
488
488
  "AA55C07F"
@@ -491,6 +491,8 @@ class Aa55ProtocolCommand(ProtocolCommand):
491
491
  ),
492
492
  lambda x: self._validate_aa55_response(x, response_type),
493
493
  )
494
+ self.first_address: int = offset
495
+ self.value = value
494
496
 
495
497
  @staticmethod
496
498
  def _checksum(data: bytes) -> bytes:
@@ -534,6 +536,17 @@ class Aa55ProtocolCommand(ProtocolCommand):
534
536
  """Trim raw response from header and checksum data"""
535
537
  return raw_response[7:-2]
536
538
 
539
+ def __repr__(self):
540
+ if self.request[4] == 1:
541
+ if self.request[5] == 2:
542
+ return f'READ device info ({self.request.hex()})'
543
+ elif self.request[5] == 6:
544
+ return f'READ runtime data ({self.request.hex()})'
545
+ elif self.request[5] == 9:
546
+ return f'READ settings ({self.request.hex()})'
547
+ else:
548
+ return self.request.hex()
549
+
537
550
 
538
551
  class Aa55ReadCommand(Aa55ProtocolCommand):
539
552
  """
@@ -541,7 +554,13 @@ class Aa55ReadCommand(Aa55ProtocolCommand):
541
554
  """
542
555
 
543
556
  def __init__(self, offset: int, count: int):
544
- super().__init__("011A03" + "{:04x}".format(offset) + "{:02x}".format(count), "019A")
557
+ super().__init__("011A03" + "{:04x}".format(offset) + "{:02x}".format(count), "019A", offset, count)
558
+
559
+ def __repr__(self):
560
+ if self.value > 1:
561
+ return f'READ {self.value} registers from {self.first_address} ({self.request.hex()})'
562
+ else:
563
+ return f'READ register {self.first_address} ({self.request.hex()})'
545
564
 
546
565
 
547
566
  class Aa55WriteCommand(Aa55ProtocolCommand):
@@ -550,7 +569,10 @@ class Aa55WriteCommand(Aa55ProtocolCommand):
550
569
  """
551
570
 
552
571
  def __init__(self, register: int, value: int):
553
- super().__init__("023905" + "{:04x}".format(register) + "01" + "{:04x}".format(value), "02B9")
572
+ super().__init__("023905" + "{:04x}".format(register) + "01" + "{:04x}".format(value), "02B9", register, value)
573
+
574
+ def __repr__(self):
575
+ return f'WRITE {self.value} to register {self.first_address} ({self.request.hex()})'
554
576
 
555
577
 
556
578
  class Aa55WriteMultiCommand(Aa55ProtocolCommand):
@@ -560,7 +582,7 @@ class Aa55WriteMultiCommand(Aa55ProtocolCommand):
560
582
 
561
583
  def __init__(self, offset: int, values: bytes):
562
584
  super().__init__("02390B" + "{:04x}".format(offset) + "{:02x}".format(len(values)) + values.hex(),
563
- "02B9")
585
+ "02B9", offset, len(values) // 2)
564
586
 
565
587
 
566
588
  class ModbusRtuProtocolCommand(ProtocolCommand):
@@ -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
 
@@ -364,7 +375,7 @@ class Decimal(Sensor):
364
375
  return read_decimal2(data, self.scale)
365
376
 
366
377
  def encode_value(self, value: Any, register_value: bytes = None) -> bytes:
367
- return int.to_bytes(int(value * self.scale), length=2, byteorder="big", signed=True)
378
+ return int.to_bytes(int(float(value) * self.scale), length=2, byteorder="big", signed=True)
368
379
 
369
380
 
370
381
  class Float(Sensor):
@@ -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:
@@ -868,7 +887,7 @@ def read_voltage(buffer: ProtocolResponse, offset: int = None) -> float:
868
887
 
869
888
  def encode_voltage(value: Any) -> bytes:
870
889
  """Encode voltage value to raw (2 unsigned bytes) payload"""
871
- return int.to_bytes(int(value * 10), length=2, byteorder="big", signed=False)
890
+ return int.to_bytes(int(float(value) * 10), length=2, byteorder="big", signed=False)
872
891
 
873
892
 
874
893
  def read_current(buffer: ProtocolResponse, offset: int = None) -> float:
@@ -889,12 +908,12 @@ def read_current_signed(buffer: ProtocolResponse, offset: int = None) -> float:
889
908
 
890
909
  def encode_current(value: Any) -> bytes:
891
910
  """Encode current value to raw (2 unsigned bytes) payload"""
892
- return int.to_bytes(int(value * 10), length=2, byteorder="big", signed=False)
911
+ return int.to_bytes(int(float(value) * 10), length=2, byteorder="big", signed=False)
893
912
 
894
913
 
895
914
  def encode_current_signed(value: Any) -> bytes:
896
915
  """Encode current value to raw (2 signed bytes) payload"""
897
- return int.to_bytes(int(value * 10), length=2, byteorder="big", signed=True)
916
+ return int.to_bytes(int(float(value) * 10), length=2, byteorder="big", signed=True)
898
917
 
899
918
 
900
919
  def read_freq(buffer: ProtocolResponse, offset: int = None) -> float:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: goodwe
3
- Version: 0.4.5
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)
@@ -101,7 +102,7 @@ class GW6000_DT_Test(DtMock):
101
102
  self.assertFalse(self.sensor_map, f"Some sensors were not tested {self.sensor_map}")
102
103
 
103
104
  def test_GW6000_DT_setting(self):
104
- self.assertEqual(4, len(self.settings()))
105
+ self.assertEqual(8, len(self.settings()))
105
106
  settings = {s.id_: s for s in self.settings()}
106
107
  self.assertEqual('Timestamp', type(settings.get("time")).__name__)
107
108
  self.assertEqual('Integer', type(settings.get("grid_export")).__name__)
@@ -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)
@@ -240,7 +243,7 @@ class GW10K_ET_Test(EtMock):
240
243
  self.assertFalse(self.sensor_map, f"Some sensors were not tested {self.sensor_map}")
241
244
 
242
245
  def test_GW10K_ET_setting(self):
243
- self.assertEqual(65, len(self.settings()))
246
+ self.assertEqual(66, len(self.settings()))
244
247
  settings = {s.id_: s for s in self.settings()}
245
248
  self.assertEqual('Timestamp', type(settings.get("time")).__name__)
246
249
  self.assertEqual('EcoModeV1', type(settings.get("eco_mode_1")).__name__)
@@ -338,7 +341,7 @@ class GW10K_ET_fw819_Test(EtMock):
338
341
  self.assertEqual('02041-19-S00', self.arm_firmware)
339
342
 
340
343
  def test_GW10K_ET_settings_fw819(self):
341
- self.assertEqual(72, len(self.settings()))
344
+ self.assertEqual(73, len(self.settings()))
342
345
  settings = {s.id_: s for s in self.settings()}
343
346
  self.assertEqual('EcoModeV2', type(settings.get("eco_mode_1")).__name__)
344
347
  self.assertEqual(None, settings.get("peak_shaving_mode"))
@@ -379,14 +382,14 @@ class GW10K_ET_fw1023_Test(EtMock):
379
382
  self.assertEqual('02041-23-S00', self.arm_firmware)
380
383
 
381
384
  def test_GW10K_ET_setting_fw1023(self):
382
- self.assertEqual(80, len(self.settings()))
385
+ self.assertEqual(81, len(self.settings()))
383
386
  settings = {s.id_: s for s in self.settings()}
384
387
  self.assertEqual('PeakShavingMode', type(settings.get("peak_shaving_mode")).__name__)
385
388
 
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)
@@ -30,6 +30,7 @@ class TestUtils(TestCase):
30
30
  self.assertEqual(32, testee.read(data))
31
31
 
32
32
  self.assertEqual("2039", testee.encode_value(32, bytes.fromhex("3039")).hex())
33
+ self.assertEqual("2039", testee.encode_value("32", bytes.fromhex("3039")).hex())
33
34
  self.assertEqual("ff39", testee.encode_value(-1, bytes.fromhex("3039")).hex())
34
35
  self.assertEqual("7f39", testee.encode_value(127, bytes.fromhex("3039")).hex())
35
36
  self.assertEqual("20ff", testee.encode_value(32, bytes.fromhex("ffff")).hex())
@@ -41,6 +42,7 @@ class TestUtils(TestCase):
41
42
  self.assertEqual(127, testee.read(data))
42
43
 
43
44
  self.assertEqual("3020", testee.encode_value(32, bytes.fromhex("3039")).hex())
45
+ self.assertEqual("3020", testee.encode_value("32", bytes.fromhex("3039")).hex())
44
46
  self.assertEqual("30ff", testee.encode_value(-1, bytes.fromhex("3039")).hex())
45
47
  self.assertEqual("307f", testee.encode_value(127, bytes.fromhex("3039")).hex())
46
48
  self.assertEqual("ff20", testee.encode_value(32, bytes.fromhex("ffff")).hex())
@@ -51,10 +53,12 @@ class TestUtils(TestCase):
51
53
  data = MockResponse("0031")
52
54
  self.assertEqual(49, testee.read(data))
53
55
  self.assertEqual("0031", testee.encode_value(49).hex())
56
+ self.assertEqual("0031", testee.encode_value("49").hex())
54
57
 
55
58
  data = MockResponse("ff9e")
56
59
  self.assertEqual(65438, testee.read(data))
57
60
  self.assertEqual("ff9e", testee.encode_value(65438).hex())
61
+ self.assertEqual("ff9e", testee.encode_value("65438").hex())
58
62
 
59
63
  def test_integer_signed(self):
60
64
  testee = IntegerS("", 0, "", "", None)
@@ -62,10 +66,12 @@ class TestUtils(TestCase):
62
66
  data = MockResponse("0031")
63
67
  self.assertEqual(49, testee.read(data))
64
68
  self.assertEqual("0031", testee.encode_value(49).hex())
69
+ self.assertEqual("0031", testee.encode_value("49").hex())
65
70
 
66
71
  data = MockResponse("ff9e")
67
72
  self.assertEqual(-98, testee.read(data))
68
73
  self.assertEqual("ff9e", testee.encode_value(-98).hex())
74
+ self.assertEqual("ff9e", testee.encode_value("-98").hex())
69
75
 
70
76
  def test_decimal(self):
71
77
  testee = Decimal("", 0, 10, "", "", None)
@@ -73,10 +79,12 @@ class TestUtils(TestCase):
73
79
  data = MockResponse("0031")
74
80
  self.assertEqual(4.9, testee.read(data))
75
81
  self.assertEqual("0031", testee.encode_value(4.9).hex())
82
+ self.assertEqual("0031", testee.encode_value("4.9").hex())
76
83
 
77
84
  data = MockResponse("ff9e")
78
85
  self.assertEqual(-9.8, testee.read(data))
79
86
  self.assertEqual("ff9e", testee.encode_value(-9.8).hex())
87
+ self.assertEqual("ff9e", testee.encode_value("-9.8").hex())
80
88
 
81
89
  def test_voltage(self):
82
90
  testee = Voltage("", 0, "", None)
@@ -84,10 +92,12 @@ class TestUtils(TestCase):
84
92
  data = MockResponse("0cfe")
85
93
  self.assertEqual(332.6, testee.read(data))
86
94
  self.assertEqual("0cfe", testee.encode_value(332.6).hex())
95
+ self.assertEqual("0cfe", testee.encode_value("332.6").hex())
87
96
 
88
97
  data = MockResponse("1f64")
89
98
  self.assertEqual(803.6, testee.read(data))
90
99
  self.assertEqual("1f64", testee.encode_value(803.6).hex())
100
+ self.assertEqual("1f64", testee.encode_value("803.6").hex())
91
101
 
92
102
  data = MockResponse("a000")
93
103
  self.assertEqual(4096.0, testee.read(data))
@@ -101,10 +111,12 @@ class TestUtils(TestCase):
101
111
  data = MockResponse("0031")
102
112
  self.assertEqual(4.9, testee.read(data))
103
113
  self.assertEqual("0031", testee.encode_value(4.9).hex())
114
+ self.assertEqual("0031", testee.encode_value("4.9").hex())
104
115
 
105
116
  data = MockResponse("ff9e")
106
117
  self.assertEqual(6543.8, testee.read(data))
107
118
  self.assertEqual("ff9e", testee.encode_value(6543.8).hex())
119
+ self.assertEqual("ff9e", testee.encode_value("6543.8").hex())
108
120
 
109
121
  data = MockResponse("ffff")
110
122
  self.assertEqual(0, testee.read(data))
@@ -115,10 +127,12 @@ class TestUtils(TestCase):
115
127
  data = MockResponse("0031")
116
128
  self.assertEqual(4.9, testee.read(data))
117
129
  self.assertEqual("0031", testee.encode_value(4.9).hex())
130
+ self.assertEqual("0031", testee.encode_value("4.9").hex())
118
131
 
119
132
  data = MockResponse("ff9e")
120
133
  self.assertEqual(-9.8, testee.read(data))
121
134
  self.assertEqual("ff9e", testee.encode_value(-9.8).hex())
135
+ self.assertEqual("ff9e", testee.encode_value("-9.8").hex())
122
136
 
123
137
  def test_power4(self):
124
138
  testee = Power4("", 0, "", None)
@@ -155,6 +169,16 @@ class TestUtils(TestCase):
155
169
  data = MockResponse("ffffffff")
156
170
  self.assertIsNone(testee.read(data))
157
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
+
158
182
  def test_temp(self):
159
183
  testee = Temp("", 0, "", None)
160
184
 
goodwe-0.4.5/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.4.5
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