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 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 or port == GOODWE_TCP_PORT:
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
- # Try the common AA55C07F0102000241 command first and detect inverter type from serial_number
69
- try:
70
- logger.debug("Probing inverter at %s:%s.", host, port)
71
- response = await DISCOVERY_COMMAND.execute(UdpInverterProtocol(host, port, timeout, retries))
72
- response = response.response_data()
73
- model_name = response[5:15].decode("ascii").rstrip()
74
- serial_number = response[31:47].decode("ascii")
75
-
76
- i: Inverter | None = None
77
- for model_tag in ET_MODEL_TAGS:
78
- if model_tag in serial_number:
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 DT/MS/D-NS/XS/GEP inverter %s, S/N:%s.", model_name, serial_number)
92
- i = DT(host, port, 0, timeout, retries)
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
- if i:
95
- await i.read_device_info()
96
- logger.debug("Connected to inverter %s, S/N:%s.", i.model_name, i.serial_number)
97
- return i
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
- except InverterError as ex:
100
- failures.append(ex)
97
+ except InverterError as ex:
98
+ failures.append(ex)
101
99
 
102
100
  # Probe inverter specific protocols
103
- for inv in _SUPPORTED_PROTOCOLS:
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
- finally:
134
- if not self.keep_alive:
135
- self._protocol.close_transport()
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
- expected_length = int.from_bytes(data[4:6], byteorder='big', signed=False) + 6
225
- if len(data) < expected_length:
226
- raise PartialResponseException(len(data), expected_length)
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), 14)
235
- raise PartialResponseException(len(data), expected_length)
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.close_transport()
62
+ self._close_transport()
61
63
  return self._lock
62
64
 
63
- def close_transport(self) -> None:
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.close_transport()
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("Received another response fragment: %s.", data.hex())
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
- return
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.close_transport()
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.close_transport()
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.close_transport()
193
+ self._close_transport()
192
194
 
193
- def close_transport(self) -> None:
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
- sock = self._transport.get_extra_info('socket')
231
- if sock is not None:
232
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
233
- sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 10)
234
- sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10)
235
- sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)
236
- if platform.system() == 'Windows':
237
- sock.ioctl(socket.SIO_KEEPALIVE_VALS, (1, 10000, 10000))
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.close_transport()
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.close_transport()
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("Received another response fragment: %s.", data.hex())
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
- if self._partial_data:
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.close_transport()
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
- return
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.close_transport()
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.close_transport()
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.close_transport()
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.close_transport()
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.close_transport()
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 close_transport(self) -> None:
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
- logger.debug("Response has unexpected length: %d, expected %d.", len(data), data[6] + 9)
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: goodwe
3
- Version: 0.4.2
3
+ Version: 0.4.4
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
@@ -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,,
@@ -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