goodwe 0.4.6__tar.gz → 0.4.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {goodwe-0.4.6/goodwe.egg-info → goodwe-0.4.7}/PKG-INFO +1 -1
- goodwe-0.4.7/VERSION +1 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/dt.py +7 -1
- {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/et.py +35 -3
- {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/model.py +4 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/sensor.py +19 -0
- {goodwe-0.4.6 → goodwe-0.4.7/goodwe.egg-info}/PKG-INFO +1 -1
- {goodwe-0.4.6 → goodwe-0.4.7}/tests/test_dt.py +77 -16
- {goodwe-0.4.6 → goodwe-0.4.7}/tests/test_et.py +20 -37
- {goodwe-0.4.6 → goodwe-0.4.7}/tests/test_sensor.py +10 -0
- goodwe-0.4.6/VERSION +0 -1
- {goodwe-0.4.6 → goodwe-0.4.7}/LICENSE +0 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/README.md +0 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/__init__.py +0 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/const.py +0 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/es.py +0 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/exceptions.py +0 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/inverter.py +0 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/modbus.py +0 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/goodwe/protocol.py +0 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/goodwe.egg-info/SOURCES.txt +0 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/goodwe.egg-info/dependency_links.txt +0 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/goodwe.egg-info/top_level.txt +0 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/pyproject.toml +0 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/setup.cfg +0 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/tests/test_es.py +0 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/tests/test_modbus.py +0 -0
- {goodwe-0.4.6 → goodwe-0.4.7}/tests/test_protocol.py +0 -0
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
|
-
|
|
72
|
+
PowerS("active_power", 30128, "Active Power", Kind.AC),
|
|
67
73
|
Integer("work_mode", 30129, "Work Mode code"),
|
|
68
74
|
Enum2("work_mode_label", 30129, WORK_MODES, "Work Mode"),
|
|
69
75
|
Long("error_codes", 30130, "Error Codes"),
|
|
@@ -257,7 +257,8 @@ class ET(Inverter):
|
|
|
257
257
|
Apparent4("meter_apparent_power_total", 36041, "Meter Apparent Power Total", Kind.GRID),
|
|
258
258
|
Integer("meter_type", 36043, "Meter Type", "", Kind.GRID), # (0: Single phase, 1: 3P3W, 2: 3P4W, 3: HomeKit)
|
|
259
259
|
Integer("meter_sw_version", 36044, "Meter Software Version", "", Kind.GRID),
|
|
260
|
-
|
|
260
|
+
|
|
261
|
+
# Sensors added in some ARM fw update (or platform 745/753), read when flag _has_meter_extended is on
|
|
261
262
|
Power4S("meter2_active_power", 36045, "Meter 2 Active Power", Kind.GRID),
|
|
262
263
|
Float("meter2_e_total_exp", 36047, 1000, "Meter 2 Total Energy (export)", "kWh", Kind.GRID),
|
|
263
264
|
Float("meter2_e_total_imp", 36049, 1000, "Meter 2 Total Energy (import)", "kWh", Kind.GRID),
|
|
@@ -268,6 +269,15 @@ class ET(Inverter):
|
|
|
268
269
|
Current("meter_current1", 36055, "Meter L1 Current", Kind.GRID),
|
|
269
270
|
Current("meter_current2", 36056, "Meter L2 Current", Kind.GRID),
|
|
270
271
|
Current("meter_current3", 36057, "Meter L3 Current", Kind.GRID),
|
|
272
|
+
|
|
273
|
+
Energy8("meter_e_total_exp1", 36092, "Meter Total Energy (export) L1", Kind.GRID),
|
|
274
|
+
Energy8("meter_e_total_exp2", 36096, "Meter Total Energy (export) L2", Kind.GRID),
|
|
275
|
+
Energy8("meter_e_total_exp3", 36100, "Meter Total Energy (export) L3", Kind.GRID),
|
|
276
|
+
Energy8("meter_e_total_exp", 36104, "Meter Total Energy (export)", Kind.GRID),
|
|
277
|
+
Energy8("meter_e_total_imp1", 36108, "Meter Total Energy (import) L1", Kind.GRID),
|
|
278
|
+
Energy8("meter_e_total_imp2", 36112, "Meter Total Energy (import) L2", Kind.GRID),
|
|
279
|
+
Energy8("meter_e_total_imp3", 36116, "Meter Total Energy (import) L3", Kind.GRID),
|
|
280
|
+
Energy8("meter_e_total_imp", 36120, "Meter Total Energy (import)", Kind.GRID),
|
|
271
281
|
)
|
|
272
282
|
|
|
273
283
|
# Inverter's MPPT data
|
|
@@ -464,6 +474,7 @@ class ET(Inverter):
|
|
|
464
474
|
self._READ_RUNNING_DATA: ProtocolCommand = self._read_command(0x891c, 0x007d)
|
|
465
475
|
self._READ_METER_DATA: ProtocolCommand = self._read_command(0x8ca0, 0x2d)
|
|
466
476
|
self._READ_METER_DATA_EXTENDED: ProtocolCommand = self._read_command(0x8ca0, 0x3a)
|
|
477
|
+
self._READ_METER_DATA_EXTENDED2: ProtocolCommand = self._read_command(0x8ca0, 0x7d)
|
|
467
478
|
self._READ_BATTERY_INFO: ProtocolCommand = self._read_command(0x9088, 0x0018)
|
|
468
479
|
self._READ_BATTERY2_INFO: ProtocolCommand = self._read_command(0x9858, 0x0016)
|
|
469
480
|
self._READ_MPPT_DATA: ProtocolCommand = self._read_command(0x89e5, 0x3d)
|
|
@@ -472,6 +483,7 @@ class ET(Inverter):
|
|
|
472
483
|
self._has_battery: bool = True
|
|
473
484
|
self._has_battery2: bool = False
|
|
474
485
|
self._has_meter_extended: bool = False
|
|
486
|
+
self._has_meter_extended2: bool = False
|
|
475
487
|
self._has_mppt: bool = False
|
|
476
488
|
self._sensors = self.__all_sensors
|
|
477
489
|
self._sensors_battery = self.__all_sensors_battery
|
|
@@ -490,6 +502,11 @@ class ET(Inverter):
|
|
|
490
502
|
"""Filter to exclude extended meter sensors"""
|
|
491
503
|
return s.offset < 36045
|
|
492
504
|
|
|
505
|
+
@staticmethod
|
|
506
|
+
def _not_extended_meter2(s: Sensor) -> bool:
|
|
507
|
+
"""Filter to exclude extended meter sensors"""
|
|
508
|
+
return s.offset < 36058
|
|
509
|
+
|
|
493
510
|
async def read_device_info(self):
|
|
494
511
|
response = await self._read_from_socket(self._READ_DEVICE_VERSION_INFO)
|
|
495
512
|
response = response.response_data()
|
|
@@ -520,9 +537,10 @@ class ET(Inverter):
|
|
|
520
537
|
if is_2_battery(self) or self.rated_power >= 25000:
|
|
521
538
|
self._has_battery2 = True
|
|
522
539
|
|
|
523
|
-
if self.rated_power >= 15000:
|
|
540
|
+
if is_745_platform(self) or self.rated_power >= 15000:
|
|
524
541
|
self._has_mppt = True
|
|
525
542
|
self._has_meter_extended = True
|
|
543
|
+
self._has_meter_extended2 = True
|
|
526
544
|
else:
|
|
527
545
|
self._sensors_meter = tuple(filter(self._not_extended_meter, self._sensors_meter))
|
|
528
546
|
|
|
@@ -577,7 +595,21 @@ class ET(Inverter):
|
|
|
577
595
|
else:
|
|
578
596
|
raise ex
|
|
579
597
|
|
|
580
|
-
if self.
|
|
598
|
+
if self._has_meter_extended2:
|
|
599
|
+
try:
|
|
600
|
+
response = await self._read_from_socket(self._READ_METER_DATA_EXTENDED2)
|
|
601
|
+
data.update(self._map_response(response, self._sensors_meter))
|
|
602
|
+
except RequestRejectedException as ex:
|
|
603
|
+
if ex.message == ILLEGAL_DATA_ADDRESS:
|
|
604
|
+
logger.info("Extended meter values not supported, disabling further attempts.")
|
|
605
|
+
self._has_meter_extended2 = False
|
|
606
|
+
self._sensors_meter = tuple(filter(self._not_extended_meter2, self._sensors_meter))
|
|
607
|
+
response = await self._read_from_socket(self._READ_METER_DATA_EXTENDED)
|
|
608
|
+
data.update(
|
|
609
|
+
self._map_response(response, self._sensors_meter))
|
|
610
|
+
else:
|
|
611
|
+
raise ex
|
|
612
|
+
elif self._has_meter_extended:
|
|
581
613
|
try:
|
|
582
614
|
response = await self._read_from_socket(self._READ_METER_DATA_EXTENDED)
|
|
583
615
|
data.update(self._map_response(response, self._sensors_meter))
|
|
@@ -48,3 +48,7 @@ def is_2_battery(inverter: Inverter) -> bool:
|
|
|
48
48
|
def is_745_platform(inverter: Inverter) -> bool:
|
|
49
49
|
return any(model in inverter.serial_number for model in PLATFORM_745_LV_MODELS) or any(
|
|
50
50
|
model in inverter.serial_number for model in PLATFORM_745_HV_MODELS)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def is_753_platform(inverter: Inverter) -> bool:
|
|
54
|
+
return any(model in inverter.serial_number for model in PLATFORM_753_MODELS)
|
|
@@ -197,6 +197,17 @@ class Energy4(Sensor):
|
|
|
197
197
|
return float(value) / 10 if value is not None else None
|
|
198
198
|
|
|
199
199
|
|
|
200
|
+
class Energy8(Sensor):
|
|
201
|
+
"""Sensor representing energy [kWh] value encoded in 8 bytes"""
|
|
202
|
+
|
|
203
|
+
def __init__(self, id_: str, offset: int, name: str, kind: Optional[SensorKind]):
|
|
204
|
+
super().__init__(id_, offset, name, 8, "kWh", kind)
|
|
205
|
+
|
|
206
|
+
def read_value(self, data: ProtocolResponse):
|
|
207
|
+
value = read_bytes8(data)
|
|
208
|
+
return float(value) / 100 if value is not None else None
|
|
209
|
+
|
|
210
|
+
|
|
200
211
|
class Apparent(Sensor):
|
|
201
212
|
"""Sensor representing apparent power [VA] value encoded in 2 bytes"""
|
|
202
213
|
|
|
@@ -840,6 +851,14 @@ def read_bytes4_signed(buffer: ProtocolResponse, offset: int = None) -> int:
|
|
|
840
851
|
return int.from_bytes(buffer.read(4), byteorder="big", signed=True)
|
|
841
852
|
|
|
842
853
|
|
|
854
|
+
def read_bytes8(buffer: ProtocolResponse, offset: int = None, undef: int = None) -> int:
|
|
855
|
+
"""Retrieve 8 byte (unsigned int) value from buffer"""
|
|
856
|
+
if offset is not None:
|
|
857
|
+
buffer.seek(offset)
|
|
858
|
+
value = int.from_bytes(buffer.read(8), byteorder="big", signed=False)
|
|
859
|
+
return undef if value == 0xffffffffffffffff else value
|
|
860
|
+
|
|
861
|
+
|
|
843
862
|
def read_decimal2(buffer: ProtocolResponse, scale: int, offset: int = None) -> float:
|
|
844
863
|
"""Retrieve 2 byte (signed float) value from buffer"""
|
|
845
864
|
if offset is not None:
|
|
@@ -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",
|
|
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(
|
|
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('
|
|
83
|
+
self.assertSensor('active_power', 1835, 'W', data)
|
|
83
84
|
self.assertSensor('work_mode', 1, '', data)
|
|
84
85
|
self.assertSensor('work_mode_label', 'Normal', '', data)
|
|
85
86
|
self.assertSensor('error_codes', 0, '', data)
|
|
@@ -135,7 +136,7 @@ class GW8K_DT_Test(DtMock):
|
|
|
135
136
|
def test_GW8K_DT_runtime_data(self):
|
|
136
137
|
self.loop.run_until_complete(self.read_device_info())
|
|
137
138
|
data = self.loop.run_until_complete(self.read_runtime_data())
|
|
138
|
-
self.assertEqual(
|
|
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('
|
|
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(
|
|
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('
|
|
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(
|
|
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('
|
|
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(
|
|
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('
|
|
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(
|
|
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('
|
|
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(
|
|
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('
|
|
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
|
|
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,
|
|
45
|
-
self.assertEqual(expected_value, data.get(
|
|
46
|
-
|
|
47
|
-
self.
|
|
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
|
|
85
|
+
self.sensor_map = {s.id_: s for s in self.sensors()}
|
|
85
86
|
|
|
86
87
|
data = self.loop.run_until_complete(self.read_runtime_data())
|
|
87
88
|
self.assertEqual(145, len(data))
|
|
88
89
|
|
|
90
|
+
self.assertEqual(36015, self.sensor_map.get("meter_e_total_exp").offset)
|
|
91
|
+
|
|
89
92
|
# for sensor in self.sensors():
|
|
90
|
-
# print(f"self.assertSensor('{sensor.id_}', {data[sensor.id_]}, '{self.sensor_map.get(sensor.id_)}', data)")
|
|
93
|
+
# print(f"self.assertSensor('{sensor.id_}', {data[sensor.id_]}, '{self.sensor_map.get(sensor.id_).unit}', data)")
|
|
91
94
|
|
|
92
95
|
self.assertSensor('timestamp', datetime.strptime('2021-08-22 11:11:12', '%Y-%m-%d %H:%M:%S'), '', data)
|
|
93
96
|
self.assertSensor('vpv1', 332.6, 'V', data)
|
|
@@ -386,7 +389,7 @@ class GW10K_ET_fw1023_Test(EtMock):
|
|
|
386
389
|
def test_GW10K_ET_runtime_data_fw1023(self):
|
|
387
390
|
# Reset sensors
|
|
388
391
|
self.loop.run_until_complete(self.read_device_info())
|
|
389
|
-
self.sensor_map = {s.id_: s
|
|
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
|
|
602
|
+
self.sensor_map = {s.id_: s for s in self.sensors()}
|
|
600
603
|
|
|
601
604
|
data = self.loop.run_until_complete(self.read_runtime_data())
|
|
602
605
|
self.assertEqual(125, len(data))
|
|
@@ -760,6 +763,7 @@ class GW25K_ET_Test(EtMock):
|
|
|
760
763
|
EtMock.__init__(self, methodName)
|
|
761
764
|
self.mock_response(self._READ_DEVICE_VERSION_INFO, 'GW25K-ET_device_info.hex')
|
|
762
765
|
self.mock_response(self._READ_RUNNING_DATA, 'GW25K-ET_running_data.hex')
|
|
766
|
+
self.mock_response(self._READ_METER_DATA_EXTENDED2, ILLEGAL_DATA_ADDRESS)
|
|
763
767
|
self.mock_response(self._READ_METER_DATA_EXTENDED, 'GW25K-ET_meter_data.hex')
|
|
764
768
|
self.mock_response(self._READ_BATTERY_INFO, 'GW25K-ET_battery_info.hex')
|
|
765
769
|
self.mock_response(self._READ_MPPT_DATA, 'GW25K-ET_mppt_data.hex')
|
|
@@ -782,11 +786,14 @@ class GW25K_ET_Test(EtMock):
|
|
|
782
786
|
def test_GW25K_ET_runtime_data(self):
|
|
783
787
|
# Reset sensors
|
|
784
788
|
self.loop.run_until_complete(self.read_device_info())
|
|
785
|
-
self.sensor_map = {s.id_: s.unit for s in self.sensors()}
|
|
786
789
|
|
|
787
790
|
data = self.loop.run_until_complete(self.read_runtime_data())
|
|
788
791
|
self.assertEqual(237, len(data))
|
|
789
792
|
|
|
793
|
+
self.sensor_map = {s.id_: s for s in self.sensors()}
|
|
794
|
+
|
|
795
|
+
# self.assertEqual(36104, self.sensor_map.get("meter_e_total_exp").offset)
|
|
796
|
+
|
|
790
797
|
self.assertSensor('timestamp', datetime.strptime('2023-12-03 14:07:07', '%Y-%m-%d %H:%M:%S'), '', data)
|
|
791
798
|
self.assertSensor('vpv1', 737.9, 'V', data)
|
|
792
799
|
self.assertSensor('ipv1', 1.4, 'A', data)
|
|
@@ -1036,6 +1043,7 @@ class GW29K9_ET_Test(EtMock):
|
|
|
1036
1043
|
EtMock.__init__(self, methodName)
|
|
1037
1044
|
self.mock_response(self._READ_DEVICE_VERSION_INFO, 'GW29K9-ET_device_info.hex')
|
|
1038
1045
|
self.mock_response(self._READ_RUNNING_DATA, 'GW29K9-ET_running_data.hex')
|
|
1046
|
+
self.mock_response(self._READ_METER_DATA_EXTENDED2, ILLEGAL_DATA_ADDRESS)
|
|
1039
1047
|
self.mock_response(self._READ_METER_DATA_EXTENDED, 'GW29K9-ET_meter_data.hex')
|
|
1040
1048
|
self.mock_response(self._READ_BATTERY_INFO, 'GW29K9-ET_battery_info.hex')
|
|
1041
1049
|
self.mock_response(self._READ_BATTERY2_INFO, 'GW29K9-ET_battery2_info.hex')
|
|
@@ -1059,11 +1067,12 @@ class GW29K9_ET_Test(EtMock):
|
|
|
1059
1067
|
def test_GW29K9_ET_runtime_data(self):
|
|
1060
1068
|
# Reset sensors
|
|
1061
1069
|
self.loop.run_until_complete(self.read_device_info())
|
|
1062
|
-
self.sensor_map = {s.id_: s.unit for s in self.sensors()}
|
|
1063
1070
|
|
|
1064
1071
|
data = self.loop.run_until_complete(self.read_runtime_data())
|
|
1065
1072
|
self.assertEqual(211, len(data))
|
|
1066
1073
|
|
|
1074
|
+
self.sensor_map = {s.id_: s for s in self.sensors()}
|
|
1075
|
+
|
|
1067
1076
|
self.assertSensor('timestamp', datetime.strptime('2024-01-17 14:49:14', '%Y-%m-%d %H:%M:%S'), '', data)
|
|
1068
1077
|
self.assertSensor('vpv1', 682.9, 'V', data)
|
|
1069
1078
|
self.assertSensor('ipv1', 1.5, 'A', data)
|
|
@@ -1206,32 +1215,6 @@ class GW29K9_ET_Test(EtMock):
|
|
|
1206
1215
|
self.assertSensor('meter_current1', 4.6, 'A', data)
|
|
1207
1216
|
self.assertSensor('meter_current2', 6.0, 'A', data)
|
|
1208
1217
|
self.assertSensor('meter_current3', 13.6, 'A', data)
|
|
1209
|
-
self.assertSensor('battery_bms', None, '', data)
|
|
1210
|
-
self.assertSensor('battery_index', None, '', data)
|
|
1211
|
-
self.assertSensor('battery_status', None, '', data)
|
|
1212
|
-
self.assertSensor('battery_temperature', None, 'C', data)
|
|
1213
|
-
self.assertSensor('battery_charge_limit', None, 'A', data)
|
|
1214
|
-
self.assertSensor('battery_discharge_limit', None, 'A', data)
|
|
1215
|
-
self.assertSensor('battery_error_l', None, '', data)
|
|
1216
|
-
self.assertSensor('battery_soc', None, '%', data)
|
|
1217
|
-
self.assertSensor('battery_soh', None, '%', data)
|
|
1218
|
-
self.assertSensor('battery_modules', None, '', data)
|
|
1219
|
-
self.assertSensor('battery_warning_l', None, '', data)
|
|
1220
|
-
self.assertSensor('battery_protocol', None, '', data)
|
|
1221
|
-
self.assertSensor('battery_error_h', None, '', data)
|
|
1222
|
-
self.assertSensor('battery_error', None, '', data)
|
|
1223
|
-
self.assertSensor('battery_warning_h', None, '', data)
|
|
1224
|
-
self.assertSensor('battery_warning', None, '', data)
|
|
1225
|
-
self.assertSensor('battery_sw_version', None, '', data)
|
|
1226
|
-
self.assertSensor('battery_hw_version', None, '', data)
|
|
1227
|
-
self.assertSensor('battery_max_cell_temp_id', None, '', data)
|
|
1228
|
-
self.assertSensor('battery_min_cell_temp_id', None, '', data)
|
|
1229
|
-
self.assertSensor('battery_max_cell_voltage_id', None, '', data)
|
|
1230
|
-
self.assertSensor('battery_min_cell_voltage_id', None, '', data)
|
|
1231
|
-
self.assertSensor('battery_max_cell_temp', None, 'C', data)
|
|
1232
|
-
self.assertSensor('battery_min_cell_temp', None, 'C', data)
|
|
1233
|
-
self.assertSensor('battery_max_cell_voltage', None, 'V', data)
|
|
1234
|
-
self.assertSensor('battery_min_cell_voltage', None, 'V', data)
|
|
1235
1218
|
self.assertSensor('battery2_status', 0, '', data)
|
|
1236
1219
|
self.assertSensor('battery2_temperature', 0.0, 'C', data)
|
|
1237
1220
|
self.assertSensor('battery2_charge_limit', 0, 'A', data)
|
|
@@ -169,6 +169,16 @@ class TestUtils(TestCase):
|
|
|
169
169
|
data = MockResponse("ffffffff")
|
|
170
170
|
self.assertIsNone(testee.read(data))
|
|
171
171
|
|
|
172
|
+
def test_energy8(self):
|
|
173
|
+
testee = Energy8("", 0, "", None)
|
|
174
|
+
|
|
175
|
+
data = MockResponse("0000000000015b41")
|
|
176
|
+
self.assertEqual(888.97, testee.read(data))
|
|
177
|
+
data = MockResponse("0000000000038E6C")
|
|
178
|
+
self.assertEqual(2330.68, testee.read(data))
|
|
179
|
+
data = MockResponse("ffffffffffffffff")
|
|
180
|
+
self.assertIsNone(testee.read(data))
|
|
181
|
+
|
|
172
182
|
def test_temp(self):
|
|
173
183
|
testee = Temp("", 0, "", None)
|
|
174
184
|
|
goodwe-0.4.6/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.4.6
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|