goodwe 0.3.2__tar.gz → 0.3.3__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 (29) hide show
  1. {goodwe-0.3.2/goodwe.egg-info → goodwe-0.3.3}/PKG-INFO +1 -1
  2. goodwe-0.3.3/VERSION +1 -0
  3. {goodwe-0.3.2 → goodwe-0.3.3}/goodwe/es.py +1 -1
  4. {goodwe-0.3.2 → goodwe-0.3.3}/goodwe/et.py +18 -4
  5. {goodwe-0.3.2 → goodwe-0.3.3}/goodwe/inverter.py +2 -2
  6. goodwe-0.3.3/goodwe/model.py +50 -0
  7. {goodwe-0.3.2 → goodwe-0.3.3}/goodwe/sensor.py +72 -13
  8. {goodwe-0.3.2 → goodwe-0.3.3/goodwe.egg-info}/PKG-INFO +1 -1
  9. {goodwe-0.3.2 → goodwe-0.3.3}/tests/test_dt.py +4 -4
  10. {goodwe-0.3.2 → goodwe-0.3.3}/tests/test_et.py +4 -4
  11. {goodwe-0.3.2 → goodwe-0.3.3}/tests/test_sensor.py +13 -2
  12. goodwe-0.3.2/VERSION +0 -1
  13. goodwe-0.3.2/goodwe/model.py +0 -40
  14. {goodwe-0.3.2 → goodwe-0.3.3}/LICENSE +0 -0
  15. {goodwe-0.3.2 → goodwe-0.3.3}/README.md +0 -0
  16. {goodwe-0.3.2 → goodwe-0.3.3}/goodwe/__init__.py +0 -0
  17. {goodwe-0.3.2 → goodwe-0.3.3}/goodwe/const.py +0 -0
  18. {goodwe-0.3.2 → goodwe-0.3.3}/goodwe/dt.py +0 -0
  19. {goodwe-0.3.2 → goodwe-0.3.3}/goodwe/exceptions.py +0 -0
  20. {goodwe-0.3.2 → goodwe-0.3.3}/goodwe/modbus.py +0 -0
  21. {goodwe-0.3.2 → goodwe-0.3.3}/goodwe/protocol.py +0 -0
  22. {goodwe-0.3.2 → goodwe-0.3.3}/goodwe.egg-info/SOURCES.txt +0 -0
  23. {goodwe-0.3.2 → goodwe-0.3.3}/goodwe.egg-info/dependency_links.txt +0 -0
  24. {goodwe-0.3.2 → goodwe-0.3.3}/goodwe.egg-info/top_level.txt +0 -0
  25. {goodwe-0.3.2 → goodwe-0.3.3}/pyproject.toml +0 -0
  26. {goodwe-0.3.2 → goodwe-0.3.3}/setup.cfg +0 -0
  27. {goodwe-0.3.2 → goodwe-0.3.3}/tests/test_es.py +0 -0
  28. {goodwe-0.3.2 → goodwe-0.3.3}/tests/test_modbus.py +0 -0
  29. {goodwe-0.3.2 → goodwe-0.3.3}/tests/test_protocol.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: goodwe
3
- Version: 0.3.2
3
+ Version: 0.3.3
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.3/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.3
@@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
15
15
 
16
16
 
17
17
  class ES(Inverter):
18
- """Class representing inverter of ES/EM/BP family"""
18
+ """Class representing inverter of ES/EM/BP family AKA platform 105"""
19
19
 
20
20
  _READ_DEVICE_VERSION_INFO: ProtocolCommand = Aa55ProtocolCommand("010200", "0182")
21
21
  _READ_DEVICE_RUNNING_DATA: ProtocolCommand = Aa55ProtocolCommand("010600", "0186")
@@ -7,7 +7,7 @@ from .exceptions import RequestRejectedException
7
7
  from .inverter import Inverter
8
8
  from .inverter import OperationMode
9
9
  from .inverter import SensorKind as Kind
10
- from .model import is_2_battery, is_4_mppt, is_single_phase
10
+ from .model import is_2_battery, is_4_mppt, is_745_platform, is_single_phase
11
11
  from .protocol import ProtocolCommand, ModbusReadCommand, ModbusWriteCommand, ModbusWriteMultiCommand
12
12
  from .sensor import *
13
13
 
@@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
15
15
 
16
16
 
17
17
  class ET(Inverter):
18
- """Class representing inverter of ET/EH/BT/BH or GE's GEH families"""
18
+ """Class representing inverter of ET/EH/BT/BH or GE's GEH families AKA platform 205 or 745"""
19
19
 
20
20
  # Modbus registers from offset 0x891c (35100), count 0x7d (125)
21
21
  __all_sensors: Tuple[Sensor, ...] = (
@@ -384,19 +384,25 @@ class ET(Inverter):
384
384
 
385
385
  Integer("load_control_mode", 47595, "Load Control Mode", "", Kind.AC),
386
386
  Integer("load_control_switch", 47596, "Load Control Switch", "", Kind.AC),
387
- Integer("load_control_soc", 47596, "Load Control SoC", "", Kind.AC),
387
+ Integer("load_control_soc", 47597, "Load Control SoC", "", Kind.AC),
388
388
 
389
389
  Integer("fast_charging_power", 47603, "Fast Charging Power", "%", Kind.BAT),
390
390
  )
391
391
 
392
392
  # Settings added in ARM firmware 22
393
393
  __settings_arm_fw_22: Tuple[Sensor, ...] = (
394
+ Long("peak_shaving_power_limit", 47542, "Peak Shaving Power Limit"),
395
+ Integer("peak_shaving_soc", 47544, "Peak Shaving SoC"),
394
396
  # EcoModeV2("eco_modeV2_5", 47571, "Eco Mode Version 2 Power Group 5"),
395
397
  # EcoModeV2("eco_modeV2_6", 47577, "Eco Mode Version 2 Power Group 6"),
396
398
  # EcoModeV2("eco_modeV2_7", 47583, "Eco Mode Version 2 Power Group 7"),
397
399
  PeakShavingMode("peak_shaving_mode", 47589, "Peak Shaving Mode"),
398
400
 
399
401
  Integer("dod_holding", 47602, "DoD Holding", "", Kind.BAT),
402
+ Integer("backup_mode_enable", 47605, "Backup Mode Switch"),
403
+ Integer("max_charge_power", 47606, "Max Charge Power"),
404
+ Integer("smart_charging_enable", 47609, "Smart Charging Mode Switch"),
405
+ Integer("eco_mode_enable", 47612, "Eco Mode Switch"),
400
406
  )
401
407
 
402
408
  def __init__(self, host: str, comm_addr: int = 0, timeout: int = 1, retries: int = 3):
@@ -626,6 +632,7 @@ class ET(Inverter):
626
632
  await self._set_offline(True)
627
633
  await self.write_setting('backup_supply', 1)
628
634
  await self.write_setting('cold_start', 4)
635
+ await self._clear_battery_mode_param()
629
636
  elif operation_mode == OperationMode.BACKUP:
630
637
  await self.write_setting('work_mode', 2)
631
638
  await self._set_offline(False)
@@ -636,13 +643,20 @@ class ET(Inverter):
636
643
  elif operation_mode == OperationMode.PEAK_SHAVING:
637
644
  await self.write_setting('work_mode', 4)
638
645
  await self._set_offline(False)
646
+ await self._clear_battery_mode_param()
639
647
  elif operation_mode in (OperationMode.ECO_CHARGE, OperationMode.ECO_DISCHARGE):
640
648
  if eco_mode_power < 0 or eco_mode_power > 100:
641
649
  raise ValueError()
642
650
  if eco_mode_soc < 0 or eco_mode_soc > 100:
643
651
  raise ValueError()
652
+
644
653
  eco_mode: EcoMode | Sensor = self._settings.get('eco_mode_1')
645
- await self._read_setting(eco_mode)
654
+ # Load the current values to try to detect schedule type
655
+ try:
656
+ await self._read_setting(eco_mode)
657
+ except ValueError:
658
+ pass
659
+ eco_mode.set_schedule_type(ScheduleType.ECO_MODE, is_745_platform(self))
646
660
  if operation_mode == OperationMode.ECO_CHARGE:
647
661
  await self.write_setting('eco_mode_1', eco_mode.encode_charge(eco_mode_power, eco_mode_soc))
648
662
  else:
@@ -76,8 +76,8 @@ class OperationMode(IntEnum):
76
76
  BACKUP = 2
77
77
  ECO = 3
78
78
  PEAK_SHAVING = 4
79
- ECO_CHARGE = 5
80
- ECO_DISCHARGE = 6
79
+ ECO_CHARGE = 10
80
+ ECO_DISCHARGE = 11
81
81
 
82
82
 
83
83
  class Inverter(ABC):
@@ -0,0 +1,50 @@
1
+ # Serial number tags to identify inverter type
2
+ from .inverter import Inverter
3
+
4
+ PLATFORM_105_MODELS = ("ESU", "EMU", "ESA", "BPS", "BPU", "EMJ", "IJL")
5
+ PLATFORM_205_MODELS = ("ETU", "ETL", "ETR", "BHN", "EHU", "BHU", "EHR", "BTU")
6
+ PLATFORM_745_LV_MODELS = ("ESN", "EBN", "EMN", "SPN", "ERN", "ESC", "HLB", "HMB", "HBB", "EOA")
7
+ PLATFORM_745_HV_MODELS = ("ETT", "HTA", "HUB", "AEB", "SPB", "CUB", "EUB", "HEB", "ERB", "BTT", "ETF", "ARB", "URB",
8
+ "EBR")
9
+ PLATFORM_753_MODELS = ("AES", "HHI", "ABP", "EHB", "HSB", "HUA", "CUA")
10
+
11
+ ET_MODEL_TAGS = PLATFORM_205_MODELS + PLATFORM_745_LV_MODELS + PLATFORM_745_HV_MODELS + PLATFORM_753_MODELS + (
12
+ "ETC", "BTC", "BTN") # Qianhai
13
+ ES_MODEL_TAGS = PLATFORM_105_MODELS
14
+ DT_MODEL_TAGS = ("DTU", "DTS",
15
+ "MSU", "MST", "MSC", "DSN", "DTN", "DST", "NSU", "SSN", "SST", "SSX", "SSY",
16
+ "PSB", "PSC")
17
+
18
+ SINGLE_PHASE_MODELS = ("DSN", "DST", "NSU", "SSN", "SST", "SSX", "SSY", # DT
19
+ "MSU", "MST", "PSB", "PSC",
20
+ "MSC", # Found on third gen MS
21
+ "EHU", "EHR", "HSB", # ET
22
+ "ESN", "EMN", "ERN", "EBN", "HLB", "HMB", "HBB", "SPN") # ES Gen 2
23
+
24
+ MPPT3_MODELS = ("MSU", "MST", "PSC", "MSC",
25
+ "25KET", "29K9ET")
26
+
27
+ MPPT4_MODELS = ("HSB",)
28
+
29
+ BAT_2_MODELS = ("25KET", "29K9ET")
30
+
31
+
32
+ def is_single_phase(inverter: Inverter) -> bool:
33
+ return any(model in inverter.serial_number for model in SINGLE_PHASE_MODELS)
34
+
35
+
36
+ def is_3_mppt(inverter: Inverter) -> bool:
37
+ return any(model in inverter.serial_number for model in MPPT3_MODELS)
38
+
39
+
40
+ def is_4_mppt(inverter: Inverter) -> bool:
41
+ return any(model in inverter.serial_number for model in MPPT4_MODELS)
42
+
43
+
44
+ def is_2_battery(inverter: Inverter) -> bool:
45
+ return any(model in inverter.serial_number for model in BAT_2_MODELS)
46
+
47
+
48
+ def is_745_platform(inverter: Inverter) -> bool:
49
+ return any(model in inverter.serial_number for model in PLATFORM_745_LV_MODELS) or any(
50
+ model in inverter.serial_number for model in PLATFORM_745_HV_MODELS)
@@ -21,12 +21,13 @@ class ScheduleType(IntEnum):
21
21
  PEAK_SHAVING = 3,
22
22
  BACKUP_MODE = 4,
23
23
  SMART_CHARGE_MODE = 5,
24
- ECO_MODE_745 = 6
24
+ ECO_MODE_745 = 6,
25
+ NOT_SET = 85
25
26
 
26
27
  @classmethod
27
28
  def detect_schedule_type(cls, value: int) -> ScheduleType:
28
29
  """Detect schedule type from its on/off value"""
29
- if value in (0, -1, 85):
30
+ if value in (0, -1):
30
31
  return ScheduleType.ECO_MODE
31
32
  elif value in (1, -2):
32
33
  return ScheduleType.DRY_CONTACT_LOAD
@@ -40,6 +41,8 @@ class ScheduleType(IntEnum):
40
41
  return ScheduleType.SMART_CHARGE_MODE
41
42
  elif value in (6, -7):
42
43
  return ScheduleType.ECO_MODE_745
44
+ elif value == 85:
45
+ return ScheduleType.NOT_SET
43
46
  else:
44
47
  raise ValueError(f"{value}: on_off value {value} out of range.")
45
48
 
@@ -52,12 +55,13 @@ class ScheduleType(IntEnum):
52
55
 
53
56
  def decode_power(self, value: int) -> int:
54
57
  """Decode human readable value of power parameter"""
55
- if self == ScheduleType.ECO_MODE:
56
- return value
57
- elif self == ScheduleType.PEAK_SHAVING:
58
+ if self == ScheduleType.PEAK_SHAVING:
58
59
  return value * 10
59
- if self == ScheduleType.ECO_MODE_745:
60
+ elif self == ScheduleType.ECO_MODE_745:
60
61
  return int(value / 10)
62
+ elif self == ScheduleType.NOT_SET:
63
+ # Prevent out of range values when changing mode
64
+ return value if -100 <= value <= 100 else int(value / 10)
61
65
  else:
62
66
  return value
63
67
 
@@ -67,7 +71,7 @@ class ScheduleType(IntEnum):
67
71
  return value
68
72
  elif self == ScheduleType.PEAK_SHAVING:
69
73
  return int(value / 10)
70
- if self == ScheduleType.ECO_MODE_745:
74
+ elif self == ScheduleType.ECO_MODE_745:
71
75
  return value * 10
72
76
  else:
73
77
  return value
@@ -76,7 +80,7 @@ class ScheduleType(IntEnum):
76
80
  """Check if the value fits in allowed values range"""
77
81
  if self == ScheduleType.ECO_MODE:
78
82
  return -100 <= value <= 100
79
- if self == ScheduleType.ECO_MODE_745:
83
+ elif self == ScheduleType.ECO_MODE_745:
80
84
  return -1000 <= value <= 1000
81
85
  else:
82
86
  return True
@@ -298,6 +302,19 @@ class ByteL(Byte):
298
302
 
299
303
 
300
304
  class Integer(Sensor):
305
+ """Sensor representing unsigned int value encoded in 2 bytes"""
306
+
307
+ def __init__(self, id_: str, offset: int, name: str, unit: str = "", kind: Optional[SensorKind] = None):
308
+ super().__init__(id_, offset, name, 2, unit, kind)
309
+
310
+ def read_value(self, data: ProtocolResponse):
311
+ return read_bytes2(data)
312
+
313
+ def encode_value(self, value: Any, register_value: bytes = None) -> bytes:
314
+ return int.to_bytes(int(value), length=2, byteorder="big", signed=False)
315
+
316
+
317
+ class IntegerS(Sensor):
301
318
  """Sensor representing signed int value encoded in 2 bytes"""
302
319
 
303
320
  def __init__(self, id_: str, offset: int, name: str, unit: str = "", kind: Optional[SensorKind] = None):
@@ -311,6 +328,19 @@ class Integer(Sensor):
311
328
 
312
329
 
313
330
  class Long(Sensor):
331
+ """Sensor representing unsigned int value encoded in 4 bytes"""
332
+
333
+ def __init__(self, id_: str, offset: int, name: str, unit: str = "", kind: Optional[SensorKind] = None):
334
+ super().__init__(id_, offset, name, 4, unit, kind)
335
+
336
+ def read_value(self, data: ProtocolResponse):
337
+ return read_bytes4(data)
338
+
339
+ def encode_value(self, value: Any, register_value: bytes = None) -> bytes:
340
+ return int.to_bytes(int(value), length=4, byteorder="big", signed=False)
341
+
342
+
343
+ class LongS(Sensor):
314
344
  """Sensor representing signed int value encoded in 4 bytes"""
315
345
 
316
346
  def __init__(self, id_: str, offset: int, name: str, unit: str = "", kind: Optional[SensorKind] = None):
@@ -476,6 +506,14 @@ class EcoMode(ABC):
476
506
  def is_eco_discharge_mode(self) -> bool:
477
507
  """Answer if it represents the emulated 24/7 fulltime discharge mode"""
478
508
 
509
+ @abstractmethod
510
+ def get_schedule_type(self) -> ScheduleType:
511
+ """Answer the schedule type"""
512
+
513
+ @abstractmethod
514
+ def set_schedule_type(self, schedule_type: ScheduleType, is745: bool):
515
+ """Set the schedule type"""
516
+
479
517
 
480
518
  class EcoModeV1(Sensor, EcoMode):
481
519
  """Sensor representing Eco Mode Battery Power Group encoded in 8 bytes"""
@@ -518,8 +556,6 @@ class EcoModeV1(Sensor, EcoMode):
518
556
  raise ValueError(f"{self.id_}: on_off value {self.on_off} out of range.")
519
557
  self.day_bits = read_byte(data)
520
558
  self.days = decode_day_of_week(self.day_bits)
521
- if self.day_bits < 0:
522
- raise ValueError(f"{self.id_}: day_bits value {self.day_bits} out of range.")
523
559
  return self
524
560
 
525
561
  def encode_value(self, value: Any, register_value: bytes = None) -> bytes:
@@ -561,6 +597,14 @@ class EcoModeV1(Sensor, EcoMode):
561
597
  and self.day_bits == 127 \
562
598
  and self.power > 0
563
599
 
600
+ def get_schedule_type(self) -> ScheduleType:
601
+ """Answer the schedule type"""
602
+ return ScheduleType.ECO_MODE
603
+
604
+ def set_schedule_type(self, schedule_type: ScheduleType, is745: bool):
605
+ """Set the schedule type"""
606
+ pass
607
+
564
608
  def as_eco_mode_v2(self) -> EcoModeV2:
565
609
  """Convert V1 to V2 EcoMode"""
566
610
  result = EcoModeV2(self.id_, self.offset, self.name)
@@ -617,8 +661,6 @@ class Schedule(Sensor, EcoMode):
617
661
  self.schedule_type = ScheduleType.detect_schedule_type(self.on_off)
618
662
  self.day_bits = read_byte(data)
619
663
  self.days = decode_day_of_week(self.day_bits)
620
- if self.day_bits < 0:
621
- raise ValueError(f"{self.id_}: day_bits value {self.day_bits} out of range.")
622
664
  self.power = read_bytes2_signed(data) # negative=charge, positive=discharge
623
665
  if not self.schedule_type.is_in_range(self.power):
624
666
  raise ValueError(f"{self.id_}: power value {self.power} out of range.")
@@ -680,6 +722,19 @@ class Schedule(Sensor, EcoMode):
680
722
  and self.power > 0 \
681
723
  and (self.month_bits == 0 or self.month_bits == 0x0fff)
682
724
 
725
+ def get_schedule_type(self) -> ScheduleType:
726
+ """Answer the schedule type"""
727
+ return self.schedule_type
728
+
729
+ def set_schedule_type(self, schedule_type: ScheduleType, is745: bool):
730
+ """Set the schedule type"""
731
+ if schedule_type == ScheduleType.ECO_MODE:
732
+ # try to keep-reuse the type, use is745 only when necessary
733
+ if self.schedule_type not in (ScheduleType.ECO_MODE, ScheduleType.ECO_MODE_745):
734
+ self.schedule_type = ScheduleType.ECO_MODE_745 if is745 else ScheduleType.ECO_MODE
735
+ else:
736
+ self.schedule_type = schedule_type
737
+
683
738
  def as_eco_mode_v1(self) -> EcoModeV1:
684
739
  """Convert V2 to V1 EcoMode"""
685
740
  result = EcoModeV1(self.id_, self.offset, self.name)
@@ -891,6 +946,10 @@ def decode_bitmap(value: int, bitmap: Dict[int, str]) -> str:
891
946
 
892
947
 
893
948
  def decode_day_of_week(data: int) -> str:
949
+ if data == -1:
950
+ return "Mon-Sun"
951
+ elif data == 0:
952
+ return ""
894
953
  bits = bin(data)[2:]
895
954
  daynames = list(DAY_NAMES)
896
955
  days = ""
@@ -904,7 +963,7 @@ def decode_day_of_week(data: int) -> str:
904
963
 
905
964
 
906
965
  def decode_months(data: int) -> str | None:
907
- if data == 0 or data == 0x0fff:
966
+ if data <= 0 or data == 0x0fff:
908
967
  return None
909
968
  bits = bin(data)[2:]
910
969
  monthnames = list(MONTH_NAMES)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: goodwe
3
- Version: 0.3.2
3
+ Version: 0.3.3
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
@@ -95,7 +95,7 @@ class GW6000_DT_Test(DtMock):
95
95
  self.assertSensor('funbit', 0, '', data)
96
96
  self.assertSensor('vbus', 601.2, 'V', data)
97
97
  self.assertSensor('vnbus', 305.4, 'V', data)
98
- self.assertSensor('derating_mode', -1, '', data)
98
+ self.assertSensor('derating_mode', 0, '', data)
99
99
  self.assertSensor('derating_mode_label', '', '', data)
100
100
 
101
101
  self.assertFalse(self.sensor_map, f"Some sensors were not tested {self.sensor_map}")
@@ -169,7 +169,7 @@ class GW8K_DT_Test(DtMock):
169
169
  self.assertSensor('temperature', 45.3, 'C', data)
170
170
  self.assertSensor('e_day', 0.0, 'kWh', data)
171
171
  self.assertSensor('e_total', 0.0, 'kWh', data)
172
- self.assertSensor('h_total', -1, 'h', data)
172
+ self.assertSensor('h_total', 0, 'h', data)
173
173
  self.assertSensor('safety_country', 32, '', data)
174
174
  self.assertSensor('safety_country_label', '50Hz 230Vac Default', '', data)
175
175
  self.assertSensor('funbit', 512, '', data)
@@ -229,7 +229,7 @@ class GW5000D_NS_Test(DtMock):
229
229
  self.assertSensor('funbit', 2400, '', data)
230
230
  self.assertSensor('vbus', 291.7, 'V', data)
231
231
  self.assertSensor('vnbus', 0, 'V', data)
232
- self.assertSensor('derating_mode', -1, '', data)
232
+ self.assertSensor('derating_mode', 0, '', data)
233
233
  self.assertSensor('derating_mode_label', '', '', data)
234
234
 
235
235
  def test_get_grid_export_limit(self):
@@ -295,7 +295,7 @@ class GW5000_MS_Test(DtMock):
295
295
  self.assertSensor('funbit', 2384, '', data)
296
296
  self.assertSensor('vbus', 393.9, 'V', data)
297
297
  self.assertSensor('vnbus', 0, 'V', data)
298
- self.assertSensor('derating_mode', -1, '', data)
298
+ self.assertSensor('derating_mode', 0, '', data)
299
299
  self.assertSensor('derating_mode_label', '', '', data)
300
300
 
301
301
 
@@ -369,7 +369,7 @@ class GW10K_ET_fw1023_Test(EtMock):
369
369
  self.assertEqual('02041-23-S00', self.arm_firmware)
370
370
 
371
371
  def test_GW10K_ET_setting_fw1023(self):
372
- self.assertEqual(40, len(self.settings()))
372
+ self.assertEqual(46, len(self.settings()))
373
373
  settings = {s.id_: s for s in self.settings()}
374
374
  self.assertEqual('PeakShavingMode', type(settings.get("peak_shaving_mode")).__name__)
375
375
 
@@ -449,7 +449,7 @@ class GW6000_EH_Test(EtMock):
449
449
  self.assertSensor('safety_country_label', 'ES-A', '', data)
450
450
  self.assertSensor('work_mode', 1, '', data)
451
451
  self.assertSensor('work_mode_label', 'Normal (On-Grid)', '', data)
452
- self.assertSensor('operation_mode', -1, '', data)
452
+ self.assertSensor('operation_mode', 0, '', data)
453
453
  self.assertSensor('error_codes', 0, '', data)
454
454
  self.assertSensor('errors', '', '', data)
455
455
  self.assertSensor("e_total", 59.4, 'kWh', data)
@@ -550,7 +550,7 @@ class GEH10_1U_10_Test(EtMock):
550
550
  self.assertSensor('safety_country_label', 'Australia A', '', data)
551
551
  self.assertSensor('work_mode', 1, '', data)
552
552
  self.assertSensor('work_mode_label', 'Normal (On-Grid)', '', data)
553
- self.assertSensor('operation_mode', -1, '', data)
553
+ self.assertSensor('operation_mode', 0, '', data)
554
554
  self.assertSensor('error_codes', 0, '', data)
555
555
  self.assertSensor('errors', '', '', data)
556
556
  self.assertSensor('e_total', 10225.8, 'kWh', data)
@@ -1085,7 +1085,7 @@ class GW29K9_ET_Test(EtMock):
1085
1085
  self.assertSensor('meter_apparent_power3', -3175, 'VA', data)
1086
1086
  self.assertSensor('meter_apparent_power_total', -5667, 'VA', data)
1087
1087
  self.assertSensor('meter_type', 2, '', data)
1088
- self.assertSensor('meter_sw_version', -1, '', data)
1088
+ self.assertSensor('meter_sw_version', 0, '', data)
1089
1089
  self.assertSensor('meter2_active_power', 0, 'W', data)
1090
1090
  self.assertSensor('meter2_e_total_exp', 0.0, 'kWh', data)
1091
1091
  self.assertSensor('meter2_e_total_imp', 0.0, 'kWh', data)
@@ -52,6 +52,17 @@ class TestUtils(TestCase):
52
52
  self.assertEqual(49, testee.read(data))
53
53
  self.assertEqual("0031", testee.encode_value(49).hex())
54
54
 
55
+ data = MockResponse("ff9e")
56
+ self.assertEqual(65438, testee.read(data))
57
+ self.assertEqual("ff9e", testee.encode_value(65438).hex())
58
+
59
+ def test_integer_signed(self):
60
+ testee = IntegerS("", 0, "", "", None)
61
+
62
+ data = MockResponse("0031")
63
+ self.assertEqual(49, testee.read(data))
64
+ self.assertEqual("0031", testee.encode_value(49).hex())
65
+
55
66
  data = MockResponse("ff9e")
56
67
  self.assertEqual(-98, testee.read(data))
57
68
  self.assertEqual("ff9e", testee.encode_value(-98).hex())
@@ -158,7 +169,6 @@ class TestUtils(TestCase):
158
169
  data = MockResponse("0d1e0e28ffc4ff1a")
159
170
  self.assertEqual("13:30-14:40 Mon,Wed,Thu -60% On", testee.read(data).__str__())
160
171
  self.assertEqual(bytes.fromhex("0d1e0e28ffc4ff1a"), testee.encode_value(bytes.fromhex("0d1e0e28ffc4ff1a")))
161
- self.assertRaises(ValueError, lambda: testee.encode_value(bytes.fromhex("0d1e0e28ffc4ffff")))
162
172
  self.assertRaises(ValueError, lambda: testee.encode_value("some string"))
163
173
  self.assertFalse(testee.read(data).is_eco_charge_mode())
164
174
  self.assertFalse(testee.read(data).is_eco_discharge_mode())
@@ -188,7 +198,6 @@ class TestUtils(TestCase):
188
198
  self.assertEqual(ScheduleType.ECO_MODE, testee.schedule_type)
189
199
  self.assertEqual(bytes.fromhex("0d1e0e28ff1affc4005a0000"),
190
200
  testee.encode_value(bytes.fromhex("0d1e0e28ff1affc4005a0000")))
191
- self.assertRaises(ValueError, lambda: testee.encode_value(bytes.fromhex("0d1e0e28ffffffc4005a0000")))
192
201
  self.assertRaises(ValueError, lambda: testee.encode_value("some string"))
193
202
  self.assertFalse(testee.read(data).is_eco_charge_mode())
194
203
  self.assertFalse(testee.read(data).is_eco_discharge_mode())
@@ -208,6 +217,8 @@ class TestUtils(TestCase):
208
217
  self.assertFalse(testee.read(data).is_eco_charge_mode())
209
218
  self.assertFalse(testee.read(data).is_eco_discharge_mode())
210
219
 
220
+ data = MockResponse("0300080006fefd12005fcfff")
221
+ self.assertEqual("3:0-8:0 Mon -75% (SoC 95%) Off", testee.read(data).__str__())
211
222
  data = MockResponse("0000173b5500001400640000")
212
223
  self.assertEqual("0:0-23:59 20% (SoC 100%) Unset", testee.read(data).__str__())
213
224
  data = MockResponse("ffffffff557f000000010001")
goodwe-0.3.2/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.3.2
@@ -1,40 +0,0 @@
1
- from .inverter import Inverter
2
-
3
- # Serial number tags to identify inverter type
4
- ET_MODEL_TAGS = ["ETU", "ETL", "ETR", "ETC", "EHU", "EHR", "EHB", "BTU", "BTN", "BTC", "BHU", "AES", "ABP", "HHI",
5
- "HSB", "HUA", "CUA",
6
- "ESN", "EMN", "ERN", "EBN", # ES Gen 2
7
- "HLB", "HMB", "HBB", "SPN"] # Gen 2
8
- ES_MODEL_TAGS = ["ESU", "EMU", "ESA", "BPS", "BPU", "EMJ", "IJL"]
9
- DT_MODEL_TAGS = ["DTU", "DTS",
10
- "MSU", "MST", "MSC", "DSN", "DTN", "DST", "NSU", "SSN", "SST", "SSX", "SSY",
11
- "PSB", "PSC"]
12
-
13
- SINGLE_PHASE_MODELS = ["DSN", "DST", "NSU", "SSN", "SST", "SSX", "SSY", # DT
14
- "MSU", "MST", "PSB", "PSC",
15
- "MSC", # Found on third gen MS
16
- "EHU", "EHR", "HSB", # ET
17
- "ESN", "EMN", "ERN", "EBN", "HLB", "HMB", "HBB", "SPN"] # ES Gen 2
18
-
19
- MPPT3_MODELS = ["MSU", "MST", "PSC", "MSC",
20
- "25KET", "29K9ET"]
21
-
22
- MPPT4_MODELS = ["HSB"]
23
-
24
- BAT_2_MODELS = ["25KET", "29K9ET"]
25
-
26
-
27
- def is_single_phase(inverter: Inverter) -> bool:
28
- return any(model in inverter.serial_number for model in SINGLE_PHASE_MODELS)
29
-
30
-
31
- def is_3_mppt(inverter: Inverter) -> bool:
32
- return any(model in inverter.serial_number for model in MPPT3_MODELS)
33
-
34
-
35
- def is_4_mppt(inverter: Inverter) -> bool:
36
- return any(model in inverter.serial_number for model in MPPT4_MODELS)
37
-
38
-
39
- def is_2_battery(inverter: Inverter) -> bool:
40
- return any(model in inverter.serial_number for model in BAT_2_MODELS)
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