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.
- {goodwe-0.4.0/goodwe.egg-info → goodwe-0.4.1}/PKG-INFO +1 -1
- goodwe-0.4.1/VERSION +1 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/et.py +85 -26
- {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/inverter.py +2 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/modbus.py +3 -1
- {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/protocol.py +4 -4
- {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/sensor.py +25 -1
- {goodwe-0.4.0 → goodwe-0.4.1/goodwe.egg-info}/PKG-INFO +1 -1
- {goodwe-0.4.0 → goodwe-0.4.1}/tests/test_et.py +34 -8
- {goodwe-0.4.0 → goodwe-0.4.1}/tests/test_sensor.py +3 -0
- goodwe-0.4.0/VERSION +0 -1
- {goodwe-0.4.0 → goodwe-0.4.1}/LICENSE +0 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/README.md +0 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/__init__.py +0 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/const.py +0 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/dt.py +0 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/es.py +0 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/exceptions.py +0 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/goodwe/model.py +0 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/goodwe.egg-info/SOURCES.txt +0 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/goodwe.egg-info/dependency_links.txt +0 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/goodwe.egg-info/top_level.txt +0 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/pyproject.toml +0 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/setup.cfg +0 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/tests/test_dt.py +0 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/tests/test_es.py +0 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/tests/test_modbus.py +0 -0
- {goodwe-0.4.0 → goodwe-0.4.1}/tests/test_protocol.py +0 -0
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
|
|
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 ==
|
|
489
|
-
logger.debug("
|
|
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 ==
|
|
498
|
-
logger.debug("
|
|
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 ==
|
|
512
|
-
logger.
|
|
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 ==
|
|
523
|
-
logger.
|
|
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 ==
|
|
534
|
-
logger.
|
|
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 ==
|
|
552
|
-
logger.
|
|
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
|
-
|
|
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:
|
|
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
|
|
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.
|
|
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)
|
|
@@ -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
|
|
30
|
-
raise RequestRejectedException(
|
|
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),
|
|
60
|
-
self.mock_response(ModbusRtuReadCommand(self.comm_addr, 47589, 6),
|
|
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(
|
|
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),
|
|
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(
|
|
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(
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|