goodwe 0.4.2__py3-none-any.whl → 0.4.4__py3-none-any.whl
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/__init__.py +33 -35
- goodwe/inverter.py +3 -4
- goodwe/modbus.py +10 -5
- goodwe/protocol.py +65 -46
- {goodwe-0.4.2.dist-info → goodwe-0.4.4.dist-info}/METADATA +1 -1
- goodwe-0.4.4.dist-info/RECORD +16 -0
- goodwe-0.4.2.dist-info/RECORD +0 -16
- {goodwe-0.4.2.dist-info → goodwe-0.4.4.dist-info}/LICENSE +0 -0
- {goodwe-0.4.2.dist-info → goodwe-0.4.4.dist-info}/WHEEL +0 -0
- {goodwe-0.4.2.dist-info → goodwe-0.4.4.dist-info}/top_level.txt +0 -0
goodwe/__init__.py
CHANGED
|
@@ -22,9 +22,6 @@ DT_FAMILY = ["DT", "MS", "NS", "XS"]
|
|
|
22
22
|
# Initial discovery command
|
|
23
23
|
DISCOVERY_COMMAND = Aa55ProtocolCommand("010200", "0182")
|
|
24
24
|
|
|
25
|
-
# supported inverter protocols
|
|
26
|
-
_SUPPORTED_PROTOCOLS = [ET, DT, ES]
|
|
27
|
-
|
|
28
25
|
|
|
29
26
|
async def connect(host: str, port: int = GOODWE_UDP_PORT, family: str = None, comm_addr: int = 0, timeout: int = 1,
|
|
30
27
|
retries: int = 3, do_discover: bool = True) -> Inverter:
|
|
@@ -41,7 +38,7 @@ async def connect(host: str, port: int = GOODWE_UDP_PORT, family: str = None, co
|
|
|
41
38
|
|
|
42
39
|
Raise InverterError if unable to contact or recognise supported inverter.
|
|
43
40
|
"""
|
|
44
|
-
if family in ET_FAMILY
|
|
41
|
+
if family in ET_FAMILY:
|
|
45
42
|
inv = ET(host, port, comm_addr, timeout, retries)
|
|
46
43
|
elif family in ES_FAMILY:
|
|
47
44
|
inv = ES(host, port, comm_addr, timeout, retries)
|
|
@@ -65,42 +62,43 @@ async def discover(host: str, port: int = GOODWE_UDP_PORT, timeout: int = 1, ret
|
|
|
65
62
|
"""
|
|
66
63
|
failures = []
|
|
67
64
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
logger.debug("Detected ET/EH/BT/BH/GEH inverter %s, S/N:%s.", model_name, serial_number)
|
|
80
|
-
i = ET(host, port, 0, timeout, retries)
|
|
81
|
-
break
|
|
82
|
-
if not i:
|
|
83
|
-
for model_tag in ES_MODEL_TAGS:
|
|
84
|
-
if model_tag in serial_number:
|
|
85
|
-
logger.debug("Detected ES/EM/BP inverter %s, S/N:%s.", model_name, serial_number)
|
|
86
|
-
i = ES(host, port, 0, timeout, retries)
|
|
87
|
-
break
|
|
88
|
-
if not i:
|
|
89
|
-
for model_tag in DT_MODEL_TAGS:
|
|
65
|
+
if port == GOODWE_UDP_PORT:
|
|
66
|
+
# Try the common AA55C07F0102000241 command first and detect inverter type from serial_number
|
|
67
|
+
try:
|
|
68
|
+
logger.debug("Probing inverter at %s:%s.", host, port)
|
|
69
|
+
response = await DISCOVERY_COMMAND.execute(UdpInverterProtocol(host, port, timeout, retries))
|
|
70
|
+
response = response.response_data()
|
|
71
|
+
model_name = response[5:15].decode("ascii").rstrip()
|
|
72
|
+
serial_number = response[31:47].decode("ascii")
|
|
73
|
+
|
|
74
|
+
i: Inverter | None = None
|
|
75
|
+
for model_tag in ET_MODEL_TAGS:
|
|
90
76
|
if model_tag in serial_number:
|
|
91
|
-
logger.debug("Detected
|
|
92
|
-
i =
|
|
77
|
+
logger.debug("Detected ET/EH/BT/BH/GEH inverter %s, S/N:%s.", model_name, serial_number)
|
|
78
|
+
i = ET(host, port, 0, timeout, retries)
|
|
93
79
|
break
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
80
|
+
if not i:
|
|
81
|
+
for model_tag in ES_MODEL_TAGS:
|
|
82
|
+
if model_tag in serial_number:
|
|
83
|
+
logger.debug("Detected ES/EM/BP inverter %s, S/N:%s.", model_name, serial_number)
|
|
84
|
+
i = ES(host, port, 0, timeout, retries)
|
|
85
|
+
break
|
|
86
|
+
if not i:
|
|
87
|
+
for model_tag in DT_MODEL_TAGS:
|
|
88
|
+
if model_tag in serial_number:
|
|
89
|
+
logger.debug("Detected DT/MS/D-NS/XS/GEP inverter %s, S/N:%s.", model_name, serial_number)
|
|
90
|
+
i = DT(host, port, 0, timeout, retries)
|
|
91
|
+
break
|
|
92
|
+
if i:
|
|
93
|
+
await i.read_device_info()
|
|
94
|
+
logger.debug("Connected to inverter %s, S/N:%s.", i.model_name, i.serial_number)
|
|
95
|
+
return i
|
|
98
96
|
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
except InverterError as ex:
|
|
98
|
+
failures.append(ex)
|
|
101
99
|
|
|
102
100
|
# Probe inverter specific protocols
|
|
103
|
-
for inv in
|
|
101
|
+
for inv in [ET, DT, ES]:
|
|
104
102
|
i = inv(host, port, 0, timeout, retries)
|
|
105
103
|
try:
|
|
106
104
|
logger.debug("Probing %s inverter at %s.", inv.__name__, host)
|
goodwe/inverter.py
CHANGED
|
@@ -91,7 +91,6 @@ class Inverter(ABC):
|
|
|
91
91
|
def __init__(self, host: str, port: int, comm_addr: int = 0, timeout: int = 1, retries: int = 3):
|
|
92
92
|
self._protocol: InverterProtocol = self._create_protocol(host, port, comm_addr, timeout, retries)
|
|
93
93
|
self._consecutive_failures_count: int = 0
|
|
94
|
-
self.keep_alive: bool = True
|
|
95
94
|
|
|
96
95
|
self.model_name: str | None = None
|
|
97
96
|
self.serial_number: str | None = None
|
|
@@ -130,9 +129,9 @@ class Inverter(ABC):
|
|
|
130
129
|
except RequestFailedException as ex:
|
|
131
130
|
self._consecutive_failures_count += 1
|
|
132
131
|
raise RequestFailedException(ex.message, self._consecutive_failures_count) from None
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
|
|
133
|
+
def set_keep_alive(self, keep_alive: bool) -> None:
|
|
134
|
+
self._protocol.keep_alive = keep_alive
|
|
136
135
|
|
|
137
136
|
@abstractmethod
|
|
138
137
|
async def read_device_info(self):
|
goodwe/modbus.py
CHANGED
|
@@ -221,18 +221,23 @@ def validate_modbus_tcp_response(data: bytes, cmd: int, offset: int, value: int)
|
|
|
221
221
|
if len(data) <= 8:
|
|
222
222
|
logger.debug("Response is too short.")
|
|
223
223
|
return False
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
224
|
+
|
|
225
|
+
# The Modbus/TCP message length check is completely ignore due to Goodwe bugs
|
|
226
|
+
# expected_length = int.from_bytes(data[4:6], byteorder='big', signed=False) + 6
|
|
227
|
+
# if len(data) < expected_length:
|
|
228
|
+
# raise PartialResponseException(len(data), expected_length)
|
|
227
229
|
|
|
228
230
|
if data[7] == MODBUS_READ_CMD:
|
|
231
|
+
expected_length = data[8] + 9
|
|
232
|
+
if len(data) < expected_length:
|
|
233
|
+
raise PartialResponseException(len(data), expected_length)
|
|
229
234
|
if data[8] != value * 2:
|
|
230
235
|
logger.debug("Response has unexpected length: %d, expected %d.", data[8], value * 2)
|
|
231
236
|
return False
|
|
232
237
|
elif data[7] in (MODBUS_WRITE_CMD, MODBUS_WRITE_MULTI_CMD):
|
|
233
238
|
if len(data) < 12:
|
|
234
|
-
logger.debug("Response has unexpected length: %d, expected %d.", len(data),
|
|
235
|
-
|
|
239
|
+
logger.debug("Response has unexpected length: %d, expected %d.", len(data), 12)
|
|
240
|
+
return False
|
|
236
241
|
response_offset = int.from_bytes(data[8:10], byteorder='big', signed=False)
|
|
237
242
|
if response_offset != offset:
|
|
238
243
|
logger.debug("Response has wrong offset: %X, expected %X.", response_offset, offset)
|
goodwe/protocol.py
CHANGED
|
@@ -37,10 +37,12 @@ class InverterProtocol:
|
|
|
37
37
|
self._timer: asyncio.TimerHandle | None = None
|
|
38
38
|
self.timeout: int = timeout
|
|
39
39
|
self.retries: int = retries
|
|
40
|
+
self.keep_alive: bool = True
|
|
40
41
|
self.protocol: asyncio.Protocol | None = None
|
|
41
42
|
self.response_future: Future | None = None
|
|
42
43
|
self.command: ProtocolCommand | None = None
|
|
43
44
|
self._partial_data: bytes | None = None
|
|
45
|
+
self._partial_missing: int = 0
|
|
44
46
|
|
|
45
47
|
def _ensure_lock(self) -> asyncio.Lock:
|
|
46
48
|
"""Validate (or create) asyncio Lock.
|
|
@@ -57,10 +59,10 @@ class InverterProtocol:
|
|
|
57
59
|
logger.debug("Creating lock instance for current event loop.")
|
|
58
60
|
self._lock = asyncio.Lock()
|
|
59
61
|
self._running_loop = asyncio.get_event_loop()
|
|
60
|
-
self.
|
|
62
|
+
self._close_transport()
|
|
61
63
|
return self._lock
|
|
62
64
|
|
|
63
|
-
def
|
|
65
|
+
async def close(self) -> None:
|
|
64
66
|
"""Close the underlying transport/connection."""
|
|
65
67
|
raise NotImplementedError()
|
|
66
68
|
|
|
@@ -116,7 +118,7 @@ class UdpInverterProtocol(InverterProtocol, asyncio.DatagramProtocol):
|
|
|
116
118
|
logger.debug("Socket closed with error: %s.", exc)
|
|
117
119
|
else:
|
|
118
120
|
logger.debug("Socket closed.")
|
|
119
|
-
self.
|
|
121
|
+
self._close_transport()
|
|
120
122
|
|
|
121
123
|
def datagram_received(self, data: bytes, addr: Tuple[str, int]) -> None:
|
|
122
124
|
"""On datagram received"""
|
|
@@ -124,35 +126,33 @@ class UdpInverterProtocol(InverterProtocol, asyncio.DatagramProtocol):
|
|
|
124
126
|
self._timer.cancel()
|
|
125
127
|
self._timer = None
|
|
126
128
|
try:
|
|
127
|
-
if self._partial_data:
|
|
128
|
-
logger.debug("
|
|
129
|
+
if self._partial_data and self._partial_missing == len(data):
|
|
130
|
+
logger.debug("Composed fragmented response: %s + %s", self._partial_data.hex(), data.hex())
|
|
129
131
|
data = self._partial_data + data
|
|
130
|
-
if self.command.validator(data):
|
|
131
|
-
if self._partial_data:
|
|
132
|
-
logger.debug("Composed fragmented response: %s", data.hex())
|
|
133
|
-
else:
|
|
134
|
-
logger.debug("Received: %s", data.hex())
|
|
135
132
|
self._partial_data = None
|
|
133
|
+
self._partial_missing = 0
|
|
134
|
+
if self.command.validator(data):
|
|
135
|
+
logger.debug("Received: %s", data.hex())
|
|
136
136
|
self.response_future.set_result(data)
|
|
137
137
|
else:
|
|
138
138
|
logger.debug("Received invalid response: %s", data.hex())
|
|
139
139
|
asyncio.get_running_loop().call_soon(self._retry_mechanism)
|
|
140
|
-
except PartialResponseException:
|
|
141
|
-
logger.debug("Received response fragment: %s", data.hex())
|
|
140
|
+
except PartialResponseException as ex:
|
|
141
|
+
logger.debug("Received response fragment (%d of %d): %s", ex.length, ex.expected, data.hex())
|
|
142
142
|
self._partial_data = data
|
|
143
|
-
|
|
143
|
+
self._partial_missing = ex.expected - ex.length
|
|
144
144
|
except asyncio.InvalidStateError:
|
|
145
145
|
logger.debug("Response already handled: %s", data.hex())
|
|
146
146
|
except RequestRejectedException as ex:
|
|
147
147
|
logger.debug("Received exception response: %s", data.hex())
|
|
148
148
|
self.response_future.set_exception(ex)
|
|
149
|
-
self.
|
|
149
|
+
self._close_transport()
|
|
150
150
|
|
|
151
151
|
def error_received(self, exc: Exception) -> None:
|
|
152
152
|
"""On error received"""
|
|
153
153
|
logger.debug("Received error: %s", exc)
|
|
154
154
|
self.response_future.set_exception(exc)
|
|
155
|
-
self.
|
|
155
|
+
self._close_transport()
|
|
156
156
|
|
|
157
157
|
async def send_request(self, command: ProtocolCommand) -> Future:
|
|
158
158
|
"""Send message via transport"""
|
|
@@ -168,6 +168,8 @@ class UdpInverterProtocol(InverterProtocol, asyncio.DatagramProtocol):
|
|
|
168
168
|
"""Send message via transport"""
|
|
169
169
|
self.command = command
|
|
170
170
|
self.response_future = response_future
|
|
171
|
+
self._partial_data = None
|
|
172
|
+
self._partial_missing = 0
|
|
171
173
|
payload = command.request_bytes()
|
|
172
174
|
if self._retry > 0:
|
|
173
175
|
logger.debug("Sending: %s - retry #%s/%s", self.command, self._retry, self.retries)
|
|
@@ -188,9 +190,9 @@ class UdpInverterProtocol(InverterProtocol, asyncio.DatagramProtocol):
|
|
|
188
190
|
else:
|
|
189
191
|
logger.debug("Max number of retries (%d) reached, request %s failed.", self.retries, self.command)
|
|
190
192
|
self.response_future.set_exception(MaxRetriesException)
|
|
191
|
-
self.
|
|
193
|
+
self._close_transport()
|
|
192
194
|
|
|
193
|
-
def
|
|
195
|
+
def _close_transport(self) -> None:
|
|
194
196
|
if self._transport:
|
|
195
197
|
try:
|
|
196
198
|
self._transport.close()
|
|
@@ -201,6 +203,9 @@ class UdpInverterProtocol(InverterProtocol, asyncio.DatagramProtocol):
|
|
|
201
203
|
if self.response_future and not self.response_future.done():
|
|
202
204
|
self.response_future.cancel()
|
|
203
205
|
|
|
206
|
+
async def close(self):
|
|
207
|
+
self._close_transport()
|
|
208
|
+
|
|
204
209
|
|
|
205
210
|
class TcpInverterProtocol(InverterProtocol, asyncio.Protocol):
|
|
206
211
|
def __init__(self, host: str, port: int, comm_addr: int, timeout: int = 1, retries: int = 0):
|
|
@@ -227,14 +232,18 @@ class TcpInverterProtocol(InverterProtocol, asyncio.Protocol):
|
|
|
227
232
|
lambda: self,
|
|
228
233
|
host=self._host, port=self._port,
|
|
229
234
|
)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
235
|
+
if self.keep_alive:
|
|
236
|
+
try:
|
|
237
|
+
sock = self._transport.get_extra_info('socket')
|
|
238
|
+
if sock is not None:
|
|
239
|
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
|
240
|
+
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 10)
|
|
241
|
+
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10)
|
|
242
|
+
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)
|
|
243
|
+
if platform.system() == 'Windows':
|
|
244
|
+
sock.ioctl(socket.SIO_KEEPALIVE_VALS, (1, 10000, 10000))
|
|
245
|
+
except AttributeError as ex:
|
|
246
|
+
logger.debug("Failed to apply KEEPALIVE: %s", ex)
|
|
238
247
|
|
|
239
248
|
def connection_made(self, transport: asyncio.DatagramTransport) -> None:
|
|
240
249
|
"""On connection made"""
|
|
@@ -243,7 +252,7 @@ class TcpInverterProtocol(InverterProtocol, asyncio.Protocol):
|
|
|
243
252
|
|
|
244
253
|
def eof_received(self) -> None:
|
|
245
254
|
logger.debug("EOF received.")
|
|
246
|
-
self.
|
|
255
|
+
self._close_transport()
|
|
247
256
|
|
|
248
257
|
def connection_lost(self, exc: Optional[Exception]) -> None:
|
|
249
258
|
"""On connection lost"""
|
|
@@ -251,44 +260,42 @@ class TcpInverterProtocol(InverterProtocol, asyncio.Protocol):
|
|
|
251
260
|
logger.debug("Connection closed with error: %s.", exc)
|
|
252
261
|
else:
|
|
253
262
|
logger.debug("Connection closed.")
|
|
254
|
-
self.
|
|
263
|
+
self._close_transport()
|
|
255
264
|
|
|
256
265
|
def data_received(self, data: bytes) -> None:
|
|
257
266
|
"""On data received"""
|
|
258
267
|
if self._timer:
|
|
259
268
|
self._timer.cancel()
|
|
260
269
|
try:
|
|
261
|
-
if self._partial_data:
|
|
262
|
-
logger.debug("
|
|
270
|
+
if self._partial_data and self._partial_missing == len(data):
|
|
271
|
+
logger.debug("Composed fragmented response: %s + %s", self._partial_data.hex(), data.hex())
|
|
263
272
|
data = self._partial_data + data
|
|
273
|
+
self._partial_data = None
|
|
274
|
+
self._partial_missing = 0
|
|
264
275
|
if self.command.validator(data):
|
|
265
|
-
|
|
266
|
-
logger.debug("Composed fragmented response: %s", data.hex())
|
|
267
|
-
else:
|
|
268
|
-
logger.debug("Received: %s", data.hex())
|
|
276
|
+
logger.debug("Received: %s", data.hex())
|
|
269
277
|
self._retry = 0
|
|
270
|
-
self._partial_data = None
|
|
271
278
|
self.response_future.set_result(data)
|
|
272
279
|
else:
|
|
273
280
|
logger.debug("Received invalid response: %s", data.hex())
|
|
274
281
|
self.response_future.set_exception(RequestRejectedException())
|
|
275
|
-
self.
|
|
276
|
-
except PartialResponseException:
|
|
277
|
-
logger.debug("Received response fragment: %s", data.hex())
|
|
282
|
+
self._close_transport()
|
|
283
|
+
except PartialResponseException as ex:
|
|
284
|
+
logger.debug("Received response fragment (%d of %d): %s", ex.length, ex.expected, data.hex())
|
|
278
285
|
self._partial_data = data
|
|
279
|
-
|
|
286
|
+
self._partial_missing = ex.expected - ex.length
|
|
280
287
|
except asyncio.InvalidStateError:
|
|
281
288
|
logger.debug("Response already handled: %s", data.hex())
|
|
282
289
|
except RequestRejectedException as ex:
|
|
283
290
|
logger.debug("Received exception response: %s", data.hex())
|
|
284
291
|
self.response_future.set_exception(ex)
|
|
285
|
-
# self.
|
|
292
|
+
# self._close_transport()
|
|
286
293
|
|
|
287
294
|
def error_received(self, exc: Exception) -> None:
|
|
288
295
|
"""On error received"""
|
|
289
296
|
logger.debug("Received error: %s", exc)
|
|
290
297
|
self.response_future.set_exception(exc)
|
|
291
|
-
self.
|
|
298
|
+
self._close_transport()
|
|
292
299
|
|
|
293
300
|
async def send_request(self, command: ProtocolCommand) -> Future:
|
|
294
301
|
"""Send message via transport"""
|
|
@@ -306,7 +313,7 @@ class TcpInverterProtocol(InverterProtocol, asyncio.Protocol):
|
|
|
306
313
|
self._retry += 1
|
|
307
314
|
if self._lock and self._lock.locked():
|
|
308
315
|
self._lock.release()
|
|
309
|
-
self.
|
|
316
|
+
self._close_transport()
|
|
310
317
|
return await self.send_request(command)
|
|
311
318
|
else:
|
|
312
319
|
return self._max_retries_reached()
|
|
@@ -327,6 +334,8 @@ class TcpInverterProtocol(InverterProtocol, asyncio.Protocol):
|
|
|
327
334
|
"""Send message via transport"""
|
|
328
335
|
self.command = command
|
|
329
336
|
self.response_future = response_future
|
|
337
|
+
self._partial_data = None
|
|
338
|
+
self._partial_missing = 0
|
|
330
339
|
payload = command.request_bytes()
|
|
331
340
|
if self._retry > 0:
|
|
332
341
|
logger.debug("Sending: %s - retry #%s/%s", self.command, self._retry, self.retries)
|
|
@@ -343,16 +352,16 @@ class TcpInverterProtocol(InverterProtocol, asyncio.Protocol):
|
|
|
343
352
|
if self._timer:
|
|
344
353
|
logger.debug("Failed to receive response to %s in time (%ds).", self.command, self.timeout)
|
|
345
354
|
self._timer = None
|
|
346
|
-
self.
|
|
355
|
+
self._close_transport()
|
|
347
356
|
|
|
348
357
|
def _max_retries_reached(self) -> Future:
|
|
349
358
|
logger.debug("Max number of retries (%d) reached, request %s failed.", self.retries, self.command)
|
|
350
|
-
self.
|
|
359
|
+
self._close_transport()
|
|
351
360
|
self.response_future = asyncio.get_running_loop().create_future()
|
|
352
361
|
self.response_future.set_exception(MaxRetriesException)
|
|
353
362
|
return self.response_future
|
|
354
363
|
|
|
355
|
-
def
|
|
364
|
+
def _close_transport(self) -> None:
|
|
356
365
|
if self._transport:
|
|
357
366
|
try:
|
|
358
367
|
self._transport.close()
|
|
@@ -363,6 +372,14 @@ class TcpInverterProtocol(InverterProtocol, asyncio.Protocol):
|
|
|
363
372
|
if self.response_future and not self.response_future.done():
|
|
364
373
|
self.response_future.cancel()
|
|
365
374
|
|
|
375
|
+
async def close(self):
|
|
376
|
+
await self._ensure_lock().acquire()
|
|
377
|
+
try:
|
|
378
|
+
self._close_transport()
|
|
379
|
+
finally:
|
|
380
|
+
if self._lock and self._lock.locked():
|
|
381
|
+
self._lock.release()
|
|
382
|
+
|
|
366
383
|
|
|
367
384
|
class ProtocolResponse:
|
|
368
385
|
"""Definition of response to protocol command"""
|
|
@@ -441,6 +458,9 @@ class ProtocolCommand:
|
|
|
441
458
|
raise RequestFailedException(
|
|
442
459
|
"No valid response received to '" + self.request.hex() + "' request."
|
|
443
460
|
) from None
|
|
461
|
+
finally:
|
|
462
|
+
if not protocol.keep_alive:
|
|
463
|
+
await protocol.close()
|
|
444
464
|
|
|
445
465
|
|
|
446
466
|
class Aa55ProtocolCommand(ProtocolCommand):
|
|
@@ -485,8 +505,7 @@ class Aa55ProtocolCommand(ProtocolCommand):
|
|
|
485
505
|
data[-2:] is checksum (plain sum of response data incl. header)
|
|
486
506
|
"""
|
|
487
507
|
if len(data) <= 8 or len(data) != data[6] + 9:
|
|
488
|
-
|
|
489
|
-
return False
|
|
508
|
+
raise PartialResponseException(len(data), data[6] + 9)
|
|
490
509
|
elif response_type:
|
|
491
510
|
data_rt_int = int.from_bytes(data[4:6], byteorder="big", signed=True)
|
|
492
511
|
if int(response_type, 16) != data_rt_int:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
goodwe/__init__.py,sha256=8fFGBBvBpCo6Ew4puTtW0kYo2hVPKUx6z5A-TA4Tbvc,5795
|
|
2
|
+
goodwe/const.py,sha256=yhWk56YV7k7-MbgfmWEMYNlqeRNLOfOpfTqEfRj6Hp8,7934
|
|
3
|
+
goodwe/dt.py,sha256=oGbkdVHP51KnlwQraKeebmiP6AtJ1S67aLB7euNRIoE,11743
|
|
4
|
+
goodwe/es.py,sha256=iVK8EMCaAJJFihZLntJZ_Eu4sQWoZTVtTROp9mHFG6o,22730
|
|
5
|
+
goodwe/et.py,sha256=CiX-PE7wouDnj1RnPnOyqiNE4FELhOGdyPUOm9VCzUw,43890
|
|
6
|
+
goodwe/exceptions.py,sha256=dKMLxotjoR1ic8OVlw1joIJ4mKWD6oFtUMZ86fNM5ZE,1403
|
|
7
|
+
goodwe/inverter.py,sha256=86aMJzJjNOr1I_tCF5H6mBwzDTjLbGDKUL2hbi0XSxg,10459
|
|
8
|
+
goodwe/modbus.py,sha256=Mg_s_v8kbZgqXZM6ZUUxkZx2boAG8LkuDG5OiFKK2X4,8402
|
|
9
|
+
goodwe/model.py,sha256=dWBjMFJMnhZoUdDd9fGT54DERDANz4TirK0Wy8kWMbk,2068
|
|
10
|
+
goodwe/protocol.py,sha256=bOieJx6jbgSPYvwk8tSGkmRE586HgeCUGKezAkzNSsA,28563
|
|
11
|
+
goodwe/sensor.py,sha256=buPG8BcgZmRDqaMrLQUACLHB85U134qG6qo_ggsu48A,37679
|
|
12
|
+
goodwe-0.4.4.dist-info/LICENSE,sha256=aZAhk3lRdYT1YZV-IKRHISEcc_KNUmgfuNO3QhRamNM,1073
|
|
13
|
+
goodwe-0.4.4.dist-info/METADATA,sha256=QQS2Kydn9eELSDLYpWLzibdXB1x9WON9wmLJXCT7KYw,3376
|
|
14
|
+
goodwe-0.4.4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
15
|
+
goodwe-0.4.4.dist-info/top_level.txt,sha256=kKoiqiVvAxDaDJYMZZQLgHQj9cuWT1MXLfXElTDuf8s,7
|
|
16
|
+
goodwe-0.4.4.dist-info/RECORD,,
|
goodwe-0.4.2.dist-info/RECORD
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
goodwe/__init__.py,sha256=0Zwuri1cbJ2Qe24R2rEjDMTZeVtsh21YIx3KlRaXgWg,5742
|
|
2
|
-
goodwe/const.py,sha256=yhWk56YV7k7-MbgfmWEMYNlqeRNLOfOpfTqEfRj6Hp8,7934
|
|
3
|
-
goodwe/dt.py,sha256=oGbkdVHP51KnlwQraKeebmiP6AtJ1S67aLB7euNRIoE,11743
|
|
4
|
-
goodwe/es.py,sha256=iVK8EMCaAJJFihZLntJZ_Eu4sQWoZTVtTROp9mHFG6o,22730
|
|
5
|
-
goodwe/et.py,sha256=CiX-PE7wouDnj1RnPnOyqiNE4FELhOGdyPUOm9VCzUw,43890
|
|
6
|
-
goodwe/exceptions.py,sha256=dKMLxotjoR1ic8OVlw1joIJ4mKWD6oFtUMZ86fNM5ZE,1403
|
|
7
|
-
goodwe/inverter.py,sha256=-eRq6ND-BpLmj6vgYW0K0Oq3WvNcjjScbkalAzPH5ew,10494
|
|
8
|
-
goodwe/modbus.py,sha256=zT3W9ByANPaZd7T0XTqYGBaVo9PEwyg8jus12mRxCPU,8211
|
|
9
|
-
goodwe/model.py,sha256=dWBjMFJMnhZoUdDd9fGT54DERDANz4TirK0Wy8kWMbk,2068
|
|
10
|
-
goodwe/protocol.py,sha256=m4n1VAonXLBswFEjUcvKXEPV2WcOv_-MDMAefpsQ_-g,27703
|
|
11
|
-
goodwe/sensor.py,sha256=buPG8BcgZmRDqaMrLQUACLHB85U134qG6qo_ggsu48A,37679
|
|
12
|
-
goodwe-0.4.2.dist-info/LICENSE,sha256=aZAhk3lRdYT1YZV-IKRHISEcc_KNUmgfuNO3QhRamNM,1073
|
|
13
|
-
goodwe-0.4.2.dist-info/METADATA,sha256=gOkkNodwpHtUf_743Nc7jCKpdxjwX_L5DSr2POJDjs8,3376
|
|
14
|
-
goodwe-0.4.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
15
|
-
goodwe-0.4.2.dist-info/top_level.txt,sha256=kKoiqiVvAxDaDJYMZZQLgHQj9cuWT1MXLfXElTDuf8s,7
|
|
16
|
-
goodwe-0.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|