goodwe 0.4.7__tar.gz → 0.4.9__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.7/goodwe.egg-info → goodwe-0.4.9}/PKG-INFO +4 -4
- goodwe-0.4.9/VERSION +1 -0
- {goodwe-0.4.7 → goodwe-0.4.9}/goodwe/__init__.py +49 -14
- {goodwe-0.4.7 → goodwe-0.4.9}/goodwe/const.py +23 -17
- goodwe-0.4.9/goodwe/dt.py +413 -0
- {goodwe-0.4.7 → goodwe-0.4.9}/goodwe/es.py +235 -106
- {goodwe-0.4.7 → goodwe-0.4.9}/goodwe/et.py +532 -201
- {goodwe-0.4.7 → goodwe-0.4.9}/goodwe/exceptions.py +6 -3
- {goodwe-0.4.7 → goodwe-0.4.9}/goodwe/inverter.py +209 -24
- {goodwe-0.4.7 → goodwe-0.4.9}/goodwe/modbus.py +1 -0
- goodwe-0.4.9/goodwe/model.py +138 -0
- {goodwe-0.4.7 → goodwe-0.4.9}/goodwe/protocol.py +72 -77
- {goodwe-0.4.7 → goodwe-0.4.9}/goodwe/sensor.py +83 -67
- {goodwe-0.4.7 → goodwe-0.4.9/goodwe.egg-info}/PKG-INFO +4 -4
- {goodwe-0.4.7 → goodwe-0.4.9}/setup.cfg +2 -2
- {goodwe-0.4.7 → goodwe-0.4.9}/tests/test_dt.py +86 -36
- goodwe-0.4.9/tests/test_et.py +1471 -0
- {goodwe-0.4.7 → goodwe-0.4.9}/tests/test_protocol.py +34 -34
- {goodwe-0.4.7 → goodwe-0.4.9}/tests/test_sensor.py +1 -0
- goodwe-0.4.7/VERSION +0 -1
- goodwe-0.4.7/goodwe/dt.py +0 -269
- goodwe-0.4.7/goodwe/model.py +0 -54
- goodwe-0.4.7/tests/test_et.py +0 -1314
- {goodwe-0.4.7 → goodwe-0.4.9}/LICENSE +0 -0
- {goodwe-0.4.7 → goodwe-0.4.9}/README.md +0 -0
- {goodwe-0.4.7 → goodwe-0.4.9}/goodwe.egg-info/SOURCES.txt +0 -0
- {goodwe-0.4.7 → goodwe-0.4.9}/goodwe.egg-info/dependency_links.txt +0 -0
- {goodwe-0.4.7 → goodwe-0.4.9}/goodwe.egg-info/top_level.txt +0 -0
- {goodwe-0.4.7 → goodwe-0.4.9}/pyproject.toml +0 -0
- {goodwe-0.4.7 → goodwe-0.4.9}/tests/test_es.py +0 -0
- {goodwe-0.4.7 → goodwe-0.4.9}/tests/test_modbus.py +0 -0
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: goodwe
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.9
|
|
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
|
|
7
|
-
Author-email:
|
|
7
|
+
Author-email: marcelblijleven@gmail.com
|
|
8
8
|
License: MIT
|
|
9
9
|
Keywords: GoodWe,Solar Panel,Inverter,Photovoltaics,PV
|
|
10
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
13
|
Classifier: Programming Language :: Python :: 3
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.8
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.9
|
|
@@ -21,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
21
20
|
Requires-Python: >=3.8
|
|
22
21
|
Description-Content-Type: text/markdown
|
|
23
22
|
License-File: LICENSE
|
|
23
|
+
Dynamic: license-file
|
|
24
24
|
|
|
25
25
|
# GoodWe
|
|
26
26
|
|
goodwe-0.4.9/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.4.9
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
"""Goodwe solar inverter communication library."""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
3
5
|
import asyncio
|
|
4
6
|
import logging
|
|
5
7
|
|
|
6
|
-
from .const import
|
|
8
|
+
from .const import GOODWE_UDP_PORT
|
|
7
9
|
from .dt import DT
|
|
8
10
|
from .es import ES
|
|
9
11
|
from .et import ET
|
|
@@ -23,8 +25,15 @@ DT_FAMILY = ["DT", "MS", "NS", "XS"]
|
|
|
23
25
|
DISCOVERY_COMMAND = Aa55ProtocolCommand("010200", "0182")
|
|
24
26
|
|
|
25
27
|
|
|
26
|
-
async def connect(
|
|
27
|
-
|
|
28
|
+
async def connect(
|
|
29
|
+
host: str,
|
|
30
|
+
port: int = GOODWE_UDP_PORT,
|
|
31
|
+
family: str = None,
|
|
32
|
+
comm_addr: int = 0,
|
|
33
|
+
timeout: int = 1,
|
|
34
|
+
retries: int = 3,
|
|
35
|
+
do_discover: bool = True,
|
|
36
|
+
) -> Inverter:
|
|
28
37
|
"""Contact the inverter at the specified host/port and answer appropriate Inverter instance.
|
|
29
38
|
|
|
30
39
|
The specific inverter family/type will be detected automatically, but it can be passed explicitly.
|
|
@@ -55,7 +64,9 @@ async def connect(host: str, port: int = GOODWE_UDP_PORT, family: str = None, co
|
|
|
55
64
|
return inv
|
|
56
65
|
|
|
57
66
|
|
|
58
|
-
async def discover(
|
|
67
|
+
async def discover(
|
|
68
|
+
host: str, port: int = GOODWE_UDP_PORT, timeout: int = 1, retries: int = 3
|
|
69
|
+
) -> Inverter:
|
|
59
70
|
"""Contact the inverter at the specified value and answer appropriate Inverter instance
|
|
60
71
|
|
|
61
72
|
Raise InverterError if unable to contact or recognise supported inverter
|
|
@@ -66,7 +77,9 @@ async def discover(host: str, port: int = GOODWE_UDP_PORT, timeout: int = 1, ret
|
|
|
66
77
|
# Try the common AA55C07F0102000241 command first and detect inverter type from serial_number
|
|
67
78
|
try:
|
|
68
79
|
logger.debug("Probing inverter at %s:%s.", host, port)
|
|
69
|
-
response = await DISCOVERY_COMMAND.execute(
|
|
80
|
+
response = await DISCOVERY_COMMAND.execute(
|
|
81
|
+
UdpInverterProtocol(host, port, timeout, retries)
|
|
82
|
+
)
|
|
70
83
|
response = response.response_data()
|
|
71
84
|
model_name = response[5:15].decode("ascii").rstrip()
|
|
72
85
|
serial_number = response[31:47].decode("ascii")
|
|
@@ -74,24 +87,38 @@ async def discover(host: str, port: int = GOODWE_UDP_PORT, timeout: int = 1, ret
|
|
|
74
87
|
i: Inverter | None = None
|
|
75
88
|
for model_tag in ET_MODEL_TAGS:
|
|
76
89
|
if model_tag in serial_number:
|
|
77
|
-
logger.debug(
|
|
90
|
+
logger.debug(
|
|
91
|
+
"Detected ET/EH/BT/BH/GEH inverter %s, S/N:%s.",
|
|
92
|
+
model_name,
|
|
93
|
+
serial_number,
|
|
94
|
+
)
|
|
78
95
|
i = ET(host, port, 0, timeout, retries)
|
|
79
96
|
break
|
|
80
97
|
if not i:
|
|
81
98
|
for model_tag in ES_MODEL_TAGS:
|
|
82
99
|
if model_tag in serial_number:
|
|
83
|
-
logger.debug(
|
|
100
|
+
logger.debug(
|
|
101
|
+
"Detected ES/EM/BP inverter %s, S/N:%s.",
|
|
102
|
+
model_name,
|
|
103
|
+
serial_number,
|
|
104
|
+
)
|
|
84
105
|
i = ES(host, port, 0, timeout, retries)
|
|
85
106
|
break
|
|
86
107
|
if not i:
|
|
87
108
|
for model_tag in DT_MODEL_TAGS:
|
|
88
109
|
if model_tag in serial_number:
|
|
89
|
-
logger.debug(
|
|
110
|
+
logger.debug(
|
|
111
|
+
"Detected DT/MS/D-NS/XS/GEP inverter %s, S/N:%s.",
|
|
112
|
+
model_name,
|
|
113
|
+
serial_number,
|
|
114
|
+
)
|
|
90
115
|
i = DT(host, port, 0, timeout, retries)
|
|
91
116
|
break
|
|
92
117
|
if i:
|
|
93
118
|
await i.read_device_info()
|
|
94
|
-
logger.debug(
|
|
119
|
+
logger.debug(
|
|
120
|
+
"Connected to inverter %s, S/N:%s.", i.model_name, i.serial_number
|
|
121
|
+
)
|
|
95
122
|
return i
|
|
96
123
|
|
|
97
124
|
except InverterError as ex:
|
|
@@ -104,7 +131,12 @@ async def discover(host: str, port: int = GOODWE_UDP_PORT, timeout: int = 1, ret
|
|
|
104
131
|
logger.debug("Probing %s inverter at %s.", inv.__name__, host)
|
|
105
132
|
await i.read_device_info()
|
|
106
133
|
await i.read_runtime_data()
|
|
107
|
-
logger.debug(
|
|
134
|
+
logger.debug(
|
|
135
|
+
"Detected %s family inverter %s, S/N:%s.",
|
|
136
|
+
inv.__name__,
|
|
137
|
+
i.model_name,
|
|
138
|
+
i.serial_number,
|
|
139
|
+
)
|
|
108
140
|
return i
|
|
109
141
|
except InverterError as ex:
|
|
110
142
|
failures.append(ex)
|
|
@@ -124,10 +156,13 @@ async def search_inverters() -> bytes:
|
|
|
124
156
|
logger.debug("Searching inverters by broadcast to port 48899")
|
|
125
157
|
command = ProtocolCommand("WIFIKIT-214028-READ".encode("utf-8"), lambda r: True)
|
|
126
158
|
try:
|
|
127
|
-
result = await command.execute(
|
|
159
|
+
result = await command.execute(
|
|
160
|
+
UdpInverterProtocol("255.255.255.255", 48899, 1, 0)
|
|
161
|
+
)
|
|
128
162
|
if result is not None:
|
|
129
163
|
return result.response_data()
|
|
130
|
-
|
|
131
|
-
raise InverterError("No response received to broadcast request.")
|
|
164
|
+
raise InverterError("No response received to broadcast request.")
|
|
132
165
|
except asyncio.CancelledError:
|
|
133
|
-
raise InverterError(
|
|
166
|
+
raise InverterError(
|
|
167
|
+
"No valid response received to broadcast request."
|
|
168
|
+
) from None
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"""Constants."""
|
|
3
2
|
GOODWE_TCP_PORT = 502
|
|
4
3
|
GOODWE_UDP_PORT = 8899
|
|
5
4
|
|
|
6
|
-
BATTERY_MODES:
|
|
5
|
+
BATTERY_MODES: dict[int, str] = {
|
|
7
6
|
0: "No battery",
|
|
8
7
|
1: "Standby",
|
|
9
8
|
2: "Discharge",
|
|
@@ -12,7 +11,7 @@ BATTERY_MODES: Dict[int, str] = {
|
|
|
12
11
|
5: "To be discharged",
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
ENERGY_MODES:
|
|
14
|
+
ENERGY_MODES: dict[int, str] = {
|
|
16
15
|
0: "Check Mode",
|
|
17
16
|
1: "Wait Mode",
|
|
18
17
|
2: "Normal (On-Grid)",
|
|
@@ -24,37 +23,37 @@ ENERGY_MODES: Dict[int, str] = {
|
|
|
24
23
|
128: "Battery Discharging",
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
GRID_MODES:
|
|
26
|
+
GRID_MODES: dict[int, str] = {
|
|
28
27
|
0: "Not connected to grid",
|
|
29
28
|
1: "Connected to grid",
|
|
30
29
|
2: "Fault",
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
GRID_IN_OUT_MODES:
|
|
32
|
+
GRID_IN_OUT_MODES: dict[int, str] = {
|
|
34
33
|
0: "Idle",
|
|
35
34
|
1: "Exporting",
|
|
36
35
|
2: "Importing",
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
LOAD_MODES:
|
|
38
|
+
LOAD_MODES: dict[int, str] = {
|
|
40
39
|
0: "Inverter and the load is disconnected",
|
|
41
40
|
1: "The inverter is connected to a load",
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
PV_MODES:
|
|
43
|
+
PV_MODES: dict[int, str] = {
|
|
45
44
|
0: "PV panels not connected",
|
|
46
45
|
1: "PV panels connected, no power",
|
|
47
46
|
2: "PV panels connected, producing power",
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
WORK_MODES:
|
|
49
|
+
WORK_MODES: dict[int, str] = {
|
|
51
50
|
0: "Wait Mode",
|
|
52
51
|
1: "Normal",
|
|
53
52
|
2: "Error",
|
|
54
53
|
4: "Check Mode",
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
WORK_MODES_ET:
|
|
56
|
+
WORK_MODES_ET: dict[int, str] = {
|
|
58
57
|
0: "Wait Mode",
|
|
59
58
|
1: "Normal (On-Grid)",
|
|
60
59
|
2: "Normal (Off-Grid)",
|
|
@@ -63,14 +62,14 @@ WORK_MODES_ET: Dict[int, str] = {
|
|
|
63
62
|
5: "Check Mode",
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
WORK_MODES_ES:
|
|
65
|
+
WORK_MODES_ES: dict[int, str] = {
|
|
67
66
|
0: "Inverter Off - Standby",
|
|
68
67
|
1: "Inverter On",
|
|
69
68
|
2: "Inverter Abnormal, stopping power",
|
|
70
69
|
3: "Inverter Severly Abnormal, 20 seconds to restart",
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
SAFETY_COUNTRIES:
|
|
72
|
+
SAFETY_COUNTRIES: dict[int, str] = {
|
|
74
73
|
0: "IT CEI 0-21",
|
|
75
74
|
1: "CZ-A1",
|
|
76
75
|
2: "DE LV with PV",
|
|
@@ -198,7 +197,7 @@ SAFETY_COUNTRIES: Dict[int, str] = {
|
|
|
198
197
|
149: "Brazil 254Vac",
|
|
199
198
|
}
|
|
200
199
|
|
|
201
|
-
ERROR_CODES:
|
|
200
|
+
ERROR_CODES: dict[int, str] = {
|
|
202
201
|
31: 'Internal Communication Failure',
|
|
203
202
|
30: 'EEPROM R/W Failure',
|
|
204
203
|
29: 'Fac Failure',
|
|
@@ -233,7 +232,7 @@ ERROR_CODES: Dict[int, str] = {
|
|
|
233
232
|
0: 'GFCI Device Check Failure',
|
|
234
233
|
}
|
|
235
234
|
|
|
236
|
-
DIAG_STATUS_CODES:
|
|
235
|
+
DIAG_STATUS_CODES: dict[int, str] = {
|
|
237
236
|
0: "Battery voltage low",
|
|
238
237
|
1: "Battery SOC low",
|
|
239
238
|
2: "Battery SOC in back",
|
|
@@ -263,9 +262,10 @@ DIAG_STATUS_CODES: Dict[int, str] = {
|
|
|
263
262
|
26: "Real power limit set",
|
|
264
263
|
27: "DC output on",
|
|
265
264
|
28: "SOC protect off",
|
|
265
|
+
30: "BMS: Emergency charging",
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
BMS_ALARM_CODES:
|
|
268
|
+
BMS_ALARM_CODES: dict[int, str] = {
|
|
269
269
|
15: 'Charging over-voltage 3',
|
|
270
270
|
14: 'Discharging under-voltage 3',
|
|
271
271
|
13: 'Cell temperature high 3',
|
|
@@ -284,7 +284,7 @@ BMS_ALARM_CODES: Dict[int, str] = {
|
|
|
284
284
|
0: 'Charging over-voltage 2',
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
-
BMS_WARNING_CODES:
|
|
287
|
+
BMS_WARNING_CODES: dict[int, str] = {
|
|
288
288
|
11: 'System temperature high',
|
|
289
289
|
10: 'System temperature low 2',
|
|
290
290
|
9: 'System temperature low 1',
|
|
@@ -299,7 +299,7 @@ BMS_WARNING_CODES: Dict[int, str] = {
|
|
|
299
299
|
0: 'Charging over-voltage 1',
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
-
DERATING_MODE_CODES:
|
|
302
|
+
DERATING_MODE_CODES: dict[int, str] = {
|
|
303
303
|
31: '',
|
|
304
304
|
30: '',
|
|
305
305
|
29: '',
|
|
@@ -333,3 +333,9 @@ DERATING_MODE_CODES: Dict[int, str] = {
|
|
|
333
333
|
1: 'Active power derating',
|
|
334
334
|
0: 'Overtemperature derating',
|
|
335
335
|
}
|
|
336
|
+
|
|
337
|
+
METER_COMMUNICATION_STATUS: dict[int, str] = {
|
|
338
|
+
0: '',
|
|
339
|
+
1: 'Normal',
|
|
340
|
+
2: 'Disconnected',
|
|
341
|
+
}
|