goodwe 0.4.0__tar.gz → 0.4.1__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.0/goodwe.egg-info → goodwe-0.4.1}/PKG-INFO +1 -1
  2. goodwe-0.4.1/VERSION +1 -0
  3. {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/et.py +85 -26
  4. {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/inverter.py +2 -0
  5. {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/modbus.py +3 -1
  6. {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/protocol.py +4 -4
  7. {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/sensor.py +25 -1
  8. {goodwe-0.4.0 → goodwe-0.4.1/goodwe.egg-info}/PKG-INFO +1 -1
  9. {goodwe-0.4.0 → goodwe-0.4.1}/tests/test_et.py +34 -8
  10. {goodwe-0.4.0 → goodwe-0.4.1}/tests/test_sensor.py +3 -0
  11. goodwe-0.4.0/VERSION +0 -1
  12. {goodwe-0.4.0 → goodwe-0.4.1}/LICENSE +0 -0
  13. {goodwe-0.4.0 → goodwe-0.4.1}/README.md +0 -0
  14. {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/__init__.py +0 -0
  15. {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/const.py +0 -0
  16. {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/dt.py +0 -0
  17. {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/es.py +0 -0
  18. {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/exceptions.py +0 -0
  19. {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/model.py +0 -0
  20. {goodwe-0.4.0 → goodwe-0.4.1}/goodwe.egg-info/SOURCES.txt +0 -0
  21. {goodwe-0.4.0 → goodwe-0.4.1}/goodwe.egg-info/dependency_links.txt +0 -0
  22. {goodwe-0.4.0 → goodwe-0.4.1}/goodwe.egg-info/top_level.txt +0 -0
  23. {goodwe-0.4.0 → goodwe-0.4.1}/pyproject.toml +0 -0
  24. {goodwe-0.4.0 → goodwe-0.4.1}/setup.cfg +0 -0
  25. {goodwe-0.4.0 → goodwe-0.4.1}/tests/test_dt.py +0 -0
  26. {goodwe-0.4.0 → goodwe-0.4.1}/tests/test_es.py +0 -0
  27. {goodwe-0.4.0 → goodwe-0.4.1}/tests/test_modbus.py +0 -0
  28. {goodwe-0.4.0 → goodwe-0.4.1}/tests/test_protocol.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: goodwe
3
- Version: 0.4.0
3
+ Version: 0.4.1
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.1/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.4.1
@@ -3,10 +3,11 @@ from __future__ import annotations
3
3
  import logging
4
4
  from typing import Tuple
5
5
 
6
- from .exceptions import RequestRejectedException
6
+ from .exceptions import RequestFailedException, RequestRejectedException
7
7
  from .inverter import Inverter
8
8
  from .inverter import OperationMode
9
9
  from .inverter import SensorKind as Kind
10
+ from .modbus import ILLEGAL_DATA_ADDRESS
10
11
  from .model import is_2_battery, is_4_mppt, is_745_platform, is_single_phase
11
12
  from .protocol import ProtocolCommand
12
13
  from .sensor import *
@@ -331,7 +332,7 @@ class ET(Inverter):
331
332
  # Modbus registers of inverter settings, offsets are modbus register addresses
332
333
  __all_settings: Tuple[Sensor, ...] = (
333
334
  Integer("comm_address", 45127, "Communication Address", ""),
334
-
335
+ Integer("modbus_baud_rate", 45132, "Modbus Baud rate", ""),
335
336
  Timestamp("time", 45200, "Inverter time"),
336
337
 
337
338
  Integer("sensitivity_check", 45246, "Sensitivity Check Mode", "", Kind.AC),
@@ -371,6 +372,51 @@ class ET(Inverter):
371
372
  ByteH("eco_mode_3_switch", 47526, "Eco Mode Group 3 Switch"),
372
373
  EcoModeV1("eco_mode_4", 47527, "Eco Mode Group 4"),
373
374
  ByteH("eco_mode_4_switch", 47530, "Eco Mode Group 4 Switch"),
375
+
376
+ # Direct BMS communication for EMS Control
377
+ Integer("bms_version", 47900, "BMS Version"),
378
+ Integer("bms_bat_modules", 47901, "BMS Battery Modules"),
379
+ # Real time read from BMS
380
+ Voltage("bms_bat_charge_v_max", 47902, "BMS Battery Charge Voltage (max)", Kind.BMS),
381
+ Current("bms_bat_charge_i_max", 47903, "BMS Battery Charge Current (max)", Kind.BMS),
382
+ Voltage("bms_bat_discharge_v_min", 47904, "BMS min. Battery Discharge Voltage (min)", Kind.BMS),
383
+ Current("bms_bat_discharge_i_max", 47905, "BMS max. Battery Discharge Current (max)", Kind.BMS),
384
+ Voltage("bms_bat_voltage", 47906, "BMS Battery Voltage", Kind.BMS),
385
+ Current("bms_bat_current", 47907, "BMS Battery Current", Kind.BMS),
386
+ #
387
+ Integer("bms_bat_soc", 47908, "BMS Battery State of Charge", "%", Kind.BMS),
388
+ Integer("bms_bat_soh", 47909, "BMS Battery State of Health", "%", Kind.BMS),
389
+ Temp("bms_bat_temperature", 47910, "BMS Battery Temperature", Kind.BMS),
390
+ Long("bms_bat_warning-code", 47911, "BMS Battery Warning Code"),
391
+ # Reserved
392
+ Long("bms_bat_alarm-code", 47913, "BMS Battery Alarm Code"),
393
+ Integer("bms_status", 47915, "BMS Status"),
394
+ Integer("bms_comm_loss_disable", 47916, "BMS Communication Loss Disable"),
395
+ # RW settings of BMS voltage rate
396
+ Integer("bms_battery_string_rate_v", 47917, "BMS Battery String Rate Voltage"),
397
+
398
+ # Direct BMS communication for EMS Control
399
+ Integer("bms2_version", 47918, "BMS2 Version"),
400
+ Integer("bms2_bat_modules", 47919, "BMS2 Battery Modules"),
401
+ # Real time read from BMS
402
+ Voltage("bms2_bat_charge_v_max", 47920, "BMS2 Battery Charge Voltage (max)", Kind.BMS),
403
+ Current("bms2_bat_charge_i_max", 47921, "BMS2 Battery Charge Current (max)", Kind.BMS),
404
+ Voltage("bms2_bat_discharge_v_min", 47922, "BMS2 min. Battery Discharge Voltage (min)", Kind.BMS),
405
+ Current("bms2_bat_discharge_i_max", 47923, "BMS2 max. Battery Discharge Current (max)", Kind.BMS),
406
+ Voltage("bms2_bat_voltage", 47924, "BMS2 Battery Voltage", Kind.BMS),
407
+ Current("bms2_bat_current", 47925, "BMS2 Battery Current", Kind.BMS),
408
+ #
409
+ Integer("bms2_bat_soc", 47926, "BMS2 Battery State of Charge", "%", Kind.BMS),
410
+ Integer("bms2_bat_soh", 47927, "BMS2 Battery State of Health", "%", Kind.BMS),
411
+ Temp("bms2_bat_temperature", 47928, "BMS2 Battery Temperature", Kind.BMS),
412
+ Long("bms2_bat_warning-code", 47929, "BMS2 Battery Warning Code"),
413
+ # Reserved
414
+ Long("bms2_bat_alarm-code", 47931, "BMS2 Battery Alarm Code"),
415
+ Integer("bms2_status", 47933, "BMS2 Status"),
416
+ Integer("bms2_comm_loss_disable", 47934, "BMS2 Communication Loss Disable"),
417
+ # RW settings of BMS voltage rate
418
+ Integer("bms2_battery_string_rate_v", 47935, "BMS2 Battery String Rate Voltage"),
419
+
374
420
  )
375
421
 
376
422
  # Settings added in ARM firmware 19
@@ -389,6 +435,7 @@ class ET(Inverter):
389
435
  Integer("load_control_mode", 47595, "Load Control Mode", "", Kind.AC),
390
436
  Integer("load_control_switch", 47596, "Load Control Switch", "", Kind.AC),
391
437
  Integer("load_control_soc", 47597, "Load Control SoC", "", Kind.AC),
438
+ Integer("hardware_feed_power", 47599, "Hardware Feed Power"),
392
439
 
393
440
  Integer("fast_charging_power", 47603, "Fast Charging Power", "%", Kind.BAT),
394
441
  )
@@ -447,19 +494,19 @@ class ET(Inverter):
447
494
  async def read_device_info(self):
448
495
  response = await self._read_from_socket(self._READ_DEVICE_VERSION_INFO)
449
496
  response = response.response_data()
450
- # Modbus registers from offset (35000)
497
+ # Modbus registers from 35000 - 35032
451
498
  self.modbus_version = read_unsigned_int(response, 0)
452
499
  self.rated_power = read_unsigned_int(response, 2)
453
500
  self.ac_output_type = read_unsigned_int(response, 4) # 0: 1-phase, 1: 3-phase (4 wire), 2: 3-phase (3 wire)
454
- self.serial_number = self._decode(response[6:22])
455
- self.model_name = self._decode(response[22:32])
456
- self.dsp1_version = read_unsigned_int(response, 32)
457
- self.dsp2_version = read_unsigned_int(response, 34)
458
- self.dsp_svn_version = read_unsigned_int(response, 36)
459
- self.arm_version = read_unsigned_int(response, 38)
460
- self.arm_svn_version = read_unsigned_int(response, 40)
461
- self.firmware = self._decode(response[42:54])
462
- self.arm_firmware = self._decode(response[54:66])
501
+ self.serial_number = self._decode(response[6:22]) # 35003 - 350010
502
+ self.model_name = self._decode(response[22:32]) # 35011 - 35015
503
+ self.dsp1_version = read_unsigned_int(response, 32) # 35016
504
+ self.dsp2_version = read_unsigned_int(response, 34) # 35017
505
+ self.dsp_svn_version = read_unsigned_int(response, 36) # 35018
506
+ self.arm_version = read_unsigned_int(response, 38) # 35019
507
+ self.arm_svn_version = read_unsigned_int(response, 40) # 35020
508
+ self.firmware = self._decode(response[42:54]) # 35021 - 35027
509
+ self.arm_firmware = self._decode(response[54:66]) # 35027 - 35032
463
510
 
464
511
  if not is_4_mppt(self) and self.rated_power < 15000:
465
512
  # This inverter does not have 4 MPPTs or PV strings
@@ -485,18 +532,24 @@ class ET(Inverter):
485
532
  await self._read_from_socket(self._read_command(47547, 6))
486
533
  self._settings.update({s.id_: s for s in self.__settings_arm_fw_19})
487
534
  except RequestRejectedException as ex:
488
- if ex.message == 'ILLEGAL DATA ADDRESS':
489
- logger.debug("Cannot read EcoModeV2 settings, using to EcoModeV1.")
535
+ if ex.message == ILLEGAL_DATA_ADDRESS:
536
+ logger.debug("EcoModeV2 settings not supported, switching to EcoModeV1.")
490
537
  self._has_eco_mode_v2 = False
538
+ except RequestFailedException:
539
+ logger.debug("Cannot read EcoModeV2 settings, switching to EcoModeV1.")
540
+ self._has_eco_mode_v2 = False
491
541
 
492
542
  # Check and add Peak Shaving settings added in (ETU fw 22)
493
543
  try:
494
544
  await self._read_from_socket(self._read_command(47589, 6))
495
545
  self._settings.update({s.id_: s for s in self.__settings_arm_fw_22})
496
546
  except RequestRejectedException as ex:
497
- if ex.message == 'ILLEGAL DATA ADDRESS':
498
- logger.debug("Cannot read PeakShaving setting, disabling it.")
547
+ if ex.message == ILLEGAL_DATA_ADDRESS:
548
+ logger.debug("PeakShaving setting not supported, disabling it.")
499
549
  self._has_peak_shaving = False
550
+ except RequestFailedException:
551
+ logger.debug("Cannot read _has_peak_shaving settings, disabling it.")
552
+ self._has_peak_shaving = False
500
553
 
501
554
  async def read_runtime_data(self) -> Dict[str, Any]:
502
555
  response = await self._read_from_socket(self._READ_RUNNING_DATA)
@@ -508,8 +561,8 @@ class ET(Inverter):
508
561
  response = await self._read_from_socket(self._READ_BATTERY_INFO)
509
562
  data.update(self._map_response(response, self._sensors_battery))
510
563
  except RequestRejectedException as ex:
511
- if ex.message == 'ILLEGAL DATA ADDRESS':
512
- logger.warning("Cannot read battery values, disabling further attempts.")
564
+ if ex.message == ILLEGAL_DATA_ADDRESS:
565
+ logger.info("Battery values not supported, disabling further attempts.")
513
566
  self._has_battery = False
514
567
  else:
515
568
  raise ex
@@ -519,8 +572,8 @@ class ET(Inverter):
519
572
  data.update(
520
573
  self._map_response(response, self._sensors_battery2))
521
574
  except RequestRejectedException as ex:
522
- if ex.message == 'ILLEGAL DATA ADDRESS':
523
- logger.warning("Cannot read battery 2 values, disabling further attempts.")
575
+ if ex.message == ILLEGAL_DATA_ADDRESS:
576
+ logger.info("Battery 2 values not supported, disabling further attempts.")
524
577
  self._has_battery2 = False
525
578
  else:
526
579
  raise ex
@@ -530,8 +583,8 @@ class ET(Inverter):
530
583
  response = await self._read_from_socket(self._READ_METER_DATA_EXTENDED)
531
584
  data.update(self._map_response(response, self._sensors_meter))
532
585
  except RequestRejectedException as ex:
533
- if ex.message == 'ILLEGAL DATA ADDRESS':
534
- logger.warning("Cannot read extended meter values, disabling further attempts.")
586
+ if ex.message == ILLEGAL_DATA_ADDRESS:
587
+ logger.info("Extended meter values not supported, disabling further attempts.")
535
588
  self._has_meter_extended = False
536
589
  self._sensors_meter = tuple(filter(self._not_extended_meter, self._sensors_meter))
537
590
  response = await self._read_from_socket(self._READ_METER_DATA)
@@ -548,8 +601,8 @@ class ET(Inverter):
548
601
  response = await self._read_from_socket(self._READ_MPPT_DATA)
549
602
  data.update(self._map_response(response, self._sensors_mppt))
550
603
  except RequestRejectedException as ex:
551
- if ex.message == 'ILLEGAL DATA ADDRESS':
552
- logger.warning("Cannot read MPPT values, disabling further attempts.")
604
+ if ex.message == ILLEGAL_DATA_ADDRESS:
605
+ logger.info("MPPT values not supported, disabling further attempts.")
553
606
  self._has_mppt = False
554
607
  else:
555
608
  raise ex
@@ -560,7 +613,13 @@ class ET(Inverter):
560
613
  setting = self._settings.get(setting_id)
561
614
  if not setting:
562
615
  raise ValueError(f'Unknown setting "{setting_id}"')
563
- return await self._read_setting(setting)
616
+ try:
617
+ return await self._read_setting(setting)
618
+ except RequestRejectedException as ex:
619
+ if ex.message == ILLEGAL_DATA_ADDRESS:
620
+ logger.debug("Unsupported setting %s", setting.id_)
621
+ self._settings.pop(setting_id, None)
622
+ return None
564
623
 
565
624
  async def _read_setting(self, setting: Sensor) -> Any:
566
625
  count = (setting.size_ + (setting.size_ % 2)) // 2
@@ -592,7 +651,7 @@ class ET(Inverter):
592
651
  try:
593
652
  value = await self.read_setting(setting.id_)
594
653
  data[setting.id_] = value
595
- except ValueError:
654
+ except (ValueError, RequestFailedException):
596
655
  logger.exception("Error reading setting %s.", setting.id_)
597
656
  data[setting.id_] = None
598
657
  return data
@@ -22,6 +22,7 @@ class SensorKind(Enum):
22
22
  UPS - inverter ups/eps/backup output (e.g. ac voltage of backup/off-grid connected output)
23
23
  BAT - battery (e.g. dc voltage of connected battery pack)
24
24
  GRID - power grid/smart meter (e.g. active power exported to grid)
25
+ BMS - BMS direct data (e.g. dc voltage of)
25
26
  """
26
27
 
27
28
  PV = 1
@@ -29,6 +30,7 @@ class SensorKind(Enum):
29
30
  UPS = 3
30
31
  BAT = 4
31
32
  GRID = 5
33
+ BMS = 6
32
34
 
33
35
 
34
36
  @dataclass
@@ -9,9 +9,11 @@ MODBUS_READ_CMD: int = 0x3
9
9
  MODBUS_WRITE_CMD: int = 0x6
10
10
  MODBUS_WRITE_MULTI_CMD: int = 0x10
11
11
 
12
+ ILLEGAL_DATA_ADDRESS = 'ILLEGAL DATA ADDRESS'
13
+
12
14
  FAILURE_CODES = {
13
15
  1: "ILLEGAL FUNCTION",
14
- 2: "ILLEGAL DATA ADDRESS",
16
+ 2: ILLEGAL_DATA_ADDRESS,
15
17
  3: "ILLEGAL DATA VALUE",
16
18
  4: "SLAVE DEVICE FAILURE",
17
19
  5: "ACKNOWLEDGE",
@@ -241,7 +241,7 @@ class TcpInverterProtocol(InverterProtocol, asyncio.Protocol):
241
241
  """Send message via transport"""
242
242
  await self._ensure_lock().acquire()
243
243
  try:
244
- await self._connect()
244
+ await asyncio.wait_for(self._connect(), timeout=5)
245
245
  response_future = asyncio.get_running_loop().create_future()
246
246
  self._send_request(command, response_future)
247
247
  await response_future
@@ -249,7 +249,7 @@ class TcpInverterProtocol(InverterProtocol, asyncio.Protocol):
249
249
  except asyncio.CancelledError:
250
250
  if self._retry < self.retries:
251
251
  if self._timer:
252
- logger.debug("Connection broken error")
252
+ logger.debug("Connection broken error.")
253
253
  self._retry += 1
254
254
  if self._lock and self._lock.locked():
255
255
  self._lock.release()
@@ -257,9 +257,9 @@ class TcpInverterProtocol(InverterProtocol, asyncio.Protocol):
257
257
  return await self.send_request(command)
258
258
  else:
259
259
  return self._max_retries_reached()
260
- except (ConnectionRefusedError, TimeoutError) as exc:
260
+ except (ConnectionRefusedError, TimeoutError, OSError, asyncio.TimeoutError) as exc:
261
261
  if self._retry < self.retries:
262
- logger.debug("Connection refused error: %s", exc)
262
+ logger.debug("Connection refused error.")
263
263
  self._retry += 1
264
264
  if self._lock and self._lock.locked():
265
265
  self._lock.release()
@@ -515,6 +515,14 @@ class EcoMode(ABC):
515
515
  def set_schedule_type(self, schedule_type: ScheduleType, is745: bool):
516
516
  """Set the schedule type"""
517
517
 
518
+ @abstractmethod
519
+ def get_power(self) -> int:
520
+ """Answer the power value"""
521
+
522
+ @abstractmethod
523
+ def get_power_unit(self) -> str:
524
+ """Answer the power unit"""
525
+
518
526
 
519
527
  class EcoModeV1(Sensor, EcoMode):
520
528
  """Sensor representing Eco Mode Battery Power Group encoded in 8 bytes"""
@@ -606,6 +614,14 @@ class EcoModeV1(Sensor, EcoMode):
606
614
  """Set the schedule type"""
607
615
  pass
608
616
 
617
+ def get_power(self) -> int:
618
+ """Answer the power value"""
619
+ return self.power
620
+
621
+ def get_power_unit(self) -> str:
622
+ """Answer the power unit"""
623
+ return "%"
624
+
609
625
  def as_eco_mode_v2(self) -> EcoModeV2:
610
626
  """Convert V1 to V2 EcoMode"""
611
627
  result = EcoModeV2(self.id_, self.offset, self.name)
@@ -642,7 +658,7 @@ class Schedule(Sensor, EcoMode):
642
658
  def __str__(self):
643
659
  return f"{self.start_h}:{self.start_m}-{self.end_h}:{self.end_m} {self.days} " \
644
660
  f"{self.months + ' ' if self.months else ''}" \
645
- f"{self.schedule_type.decode_power(self.power)}{self.schedule_type.power_unit()} (SoC {self.soc}%) " \
661
+ f"{self.get_power()}{self.get_power_unit()} (SoC {self.soc}%) " \
646
662
  f"{'On' if -10 < self.on_off < 0 else 'Off' if 10 > self.on_off >= 0 else 'Unset'}"
647
663
 
648
664
  def read_value(self, data: ProtocolResponse):
@@ -736,6 +752,14 @@ class Schedule(Sensor, EcoMode):
736
752
  else:
737
753
  self.schedule_type = schedule_type
738
754
 
755
+ def get_power(self) -> int:
756
+ """Answer the power value"""
757
+ return self.schedule_type.decode_power(self.power)
758
+
759
+ def get_power_unit(self) -> str:
760
+ """Answer the power unit"""
761
+ return self.schedule_type.power_unit()
762
+
739
763
  def as_eco_mode_v1(self) -> EcoModeV1:
740
764
  """Convert V2 to V1 EcoMode"""
741
765
  result = EcoModeV1(self.id_, self.offset, self.name)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: goodwe
3
- Version: 0.4.0
3
+ Version: 0.4.1
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
@@ -6,6 +6,7 @@ from unittest import TestCase
6
6
  from goodwe.et import ET
7
7
  from goodwe.exceptions import RequestRejectedException, RequestFailedException
8
8
  from goodwe.inverter import OperationMode
9
+ from goodwe.modbus import ILLEGAL_DATA_ADDRESS
9
10
  from goodwe.protocol import ModbusRtuReadCommand, ProtocolCommand, ProtocolResponse
10
11
 
11
12
 
@@ -26,8 +27,10 @@ class EtMock(TestCase, ET):
26
27
  root_dir = os.path.dirname(os.path.abspath(__file__))
27
28
  filename = self._mock_responses.get(command)
28
29
  if filename is not None:
29
- if 'ILLEGAL DATA ADDRESS' == filename:
30
- raise RequestRejectedException('ILLEGAL DATA ADDRESS')
30
+ if ILLEGAL_DATA_ADDRESS == filename:
31
+ raise RequestRejectedException(ILLEGAL_DATA_ADDRESS)
32
+ if 'NO RESPONSE' == filename:
33
+ raise RequestFailedException()
31
34
  with open(root_dir + '/sample/et/' + filename, 'r') as f:
32
35
  response = bytes.fromhex(f.read())
33
36
  if not command.validator(response):
@@ -56,8 +59,8 @@ class GW10K_ET_Test(EtMock):
56
59
  self.mock_response(self._READ_RUNNING_DATA, 'GW10K-ET_running_data.hex')
57
60
  self.mock_response(self._READ_METER_DATA, 'GW10K-ET_meter_data.hex')
58
61
  self.mock_response(self._READ_BATTERY_INFO, 'GW10K-ET_battery_info.hex')
59
- self.mock_response(ModbusRtuReadCommand(self.comm_addr, 47547, 6), 'ILLEGAL DATA ADDRESS')
60
- self.mock_response(ModbusRtuReadCommand(self.comm_addr, 47589, 6), 'ILLEGAL DATA ADDRESS')
62
+ self.mock_response(ModbusRtuReadCommand(self.comm_addr, 47547, 6), ILLEGAL_DATA_ADDRESS)
63
+ self.mock_response(ModbusRtuReadCommand(self.comm_addr, 47589, 6), ILLEGAL_DATA_ADDRESS)
61
64
  self.mock_response(ModbusRtuReadCommand(self.comm_addr, 47515, 4), 'eco_mode_v1.hex')
62
65
 
63
66
  def test_GW10K_ET_device_info(self):
@@ -237,7 +240,7 @@ class GW10K_ET_Test(EtMock):
237
240
  self.assertFalse(self.sensor_map, f"Some sensors were not tested {self.sensor_map}")
238
241
 
239
242
  def test_GW10K_ET_setting(self):
240
- self.assertEqual(32, len(self.settings()))
243
+ self.assertEqual(65, len(self.settings()))
241
244
  settings = {s.id_: s for s in self.settings()}
242
245
  self.assertEqual('Timestamp', type(settings.get("time")).__name__)
243
246
  self.assertEqual('EcoModeV1', type(settings.get("eco_mode_1")).__name__)
@@ -311,7 +314,7 @@ class GW10K_ET_fw819_Test(EtMock):
311
314
  EtMock.__init__(self, methodName)
312
315
  self.mock_response(self._READ_DEVICE_VERSION_INFO, 'GW10K-ET_device_info_fw819.hex')
313
316
  self.mock_response(ModbusRtuReadCommand(self.comm_addr, 47547, 6), 'eco_mode_v2.hex')
314
- self.mock_response(ModbusRtuReadCommand(self.comm_addr, 47589, 6), 'ILLEGAL DATA ADDRESS')
317
+ self.mock_response(ModbusRtuReadCommand(self.comm_addr, 47589, 6), ILLEGAL_DATA_ADDRESS)
315
318
  asyncio.get_event_loop().run_until_complete(self.read_device_info())
316
319
 
317
320
  def test_GW10K_ET_fw819_device_info(self):
@@ -329,7 +332,7 @@ class GW10K_ET_fw819_Test(EtMock):
329
332
  self.assertEqual('02041-19-S00', self.arm_firmware)
330
333
 
331
334
  def test_GW10K_ET_settings_fw819(self):
332
- self.assertEqual(38, len(self.settings()))
335
+ self.assertEqual(72, len(self.settings()))
333
336
  settings = {s.id_: s for s in self.settings()}
334
337
  self.assertEqual('EcoModeV2', type(settings.get("eco_mode_1")).__name__)
335
338
  self.assertEqual(None, settings.get("peak_shaving_mode"))
@@ -369,7 +372,7 @@ class GW10K_ET_fw1023_Test(EtMock):
369
372
  self.assertEqual('02041-23-S00', self.arm_firmware)
370
373
 
371
374
  def test_GW10K_ET_setting_fw1023(self):
372
- self.assertEqual(46, len(self.settings()))
375
+ self.assertEqual(80, len(self.settings()))
373
376
  settings = {s.id_: s for s in self.settings()}
374
377
  self.assertEqual('PeakShavingMode', type(settings.get("peak_shaving_mode")).__name__)
375
378
 
@@ -1196,3 +1199,26 @@ class GW29K9_ET_Test(EtMock):
1196
1199
  self.assertSensor('apparent_power3', 0, 'VA', data)
1197
1200
 
1198
1201
  self.assertFalse(self.sensor_map, f"Some sensors were not tested {self.sensor_map}")
1202
+
1203
+
1204
+ class GW5K_BT_Test(EtMock):
1205
+
1206
+ def __init__(self, methodName='runTest'):
1207
+ EtMock.__init__(self, methodName)
1208
+ self.mock_response(self._READ_DEVICE_VERSION_INFO, 'GW5K-BT_device_info.hex')
1209
+ self.mock_response(ModbusRtuReadCommand(self.comm_addr, 47547, 6), 'NO RESPONSE')
1210
+
1211
+ def test_GW5K_BT_device_info(self):
1212
+ self.loop.run_until_complete(self.read_device_info())
1213
+ self.assertEqual('GW5K-BT', self.model_name)
1214
+ self.assertEqual('95000BTU203W0000', self.serial_number)
1215
+ self.assertEqual(5000, self.rated_power)
1216
+ self.assertEqual(0, self.modbus_version)
1217
+ self.assertEqual(254, self.ac_output_type)
1218
+ self.assertEqual(3, self.dsp1_version)
1219
+ self.assertEqual(3, self.dsp2_version)
1220
+ self.assertEqual(124, self.dsp_svn_version)
1221
+ self.assertEqual(11, self.arm_version)
1222
+ self.assertEqual(147, self.arm_svn_version)
1223
+ self.assertEqual('04029-03-S10', self.firmware)
1224
+ self.assertEqual('02041-11-S00', self.arm_firmware)
@@ -252,6 +252,9 @@ class TestUtils(TestCase):
252
252
  self.assertFalse(testee.read(data).is_eco_charge_mode())
253
253
  self.assertFalse(testee.read(data).is_eco_discharge_mode())
254
254
  self.assertEqual(ScheduleType.ECO_MODE_745, testee.schedule_type)
255
+ self.assertEqual(1000, testee.power)
256
+ self.assertEqual(100, testee.get_power())
257
+ self.assertEqual("%", testee.get_power_unit())
255
258
 
256
259
  data = MockResponse("10001600f97f00c800000fff")
257
260
  self.assertEqual("16:0-22:0 Sun,Mon,Tue,Wed,Thu,Fri,Sat 20% (SoC 0%) On", testee.read(data).__str__())
goodwe-0.4.0/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.4.0
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