goodwe 0.4.6__py3-none-any.whl → 0.4.8__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/dt.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import logging
4
4
  from typing import Tuple
5
5
 
6
- from .exceptions import InverterError, RequestRejectedException
6
+ from .exceptions import InverterError, RequestFailedException, RequestRejectedException
7
7
  from .inverter import Inverter
8
8
  from .inverter import OperationMode
9
9
  from .inverter import SensorKind as Kind
@@ -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
- Power("ppv", 30128, "PV Power", Kind.PV),
72
+ PowerS("total_inverter_power", 30128, "Total 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"),
@@ -72,7 +78,7 @@ class DT(Inverter):
72
78
  Reactive4("reactive_power", 30135, "Reactive Power", Kind.AC),
73
79
  # 30137 reserved
74
80
  # 30138 reserved
75
- # 30139 reserved
81
+ Decimal("power_factor", 30139, 1000, "Power Factor", "", Kind.GRID),
76
82
  # 30140 reserved
77
83
  Temp("temperature", 30141, "Inverter Temperature", Kind.AC),
78
84
  # 30142 reserved
@@ -107,6 +113,12 @@ class DT(Inverter):
107
113
  # 30172 reserved
108
114
  )
109
115
 
116
+ # Inverter's meter data
117
+ # Modbus registers from offset 0x75f4 (30196)
118
+ __all_sensors_meter: Tuple[Sensor, ...] = (
119
+ PowerS("active_power", 30196, "Active Power", Kind.GRID),
120
+ )
121
+
110
122
  # Modbus registers of inverter settings, offsets are modbus register addresses
111
123
  __all_settings: Tuple[Sensor, ...] = (
112
124
  Timestamp("time", 40313, "Inverter time"),
@@ -133,9 +145,12 @@ class DT(Inverter):
133
145
  def __init__(self, host: str, port: int, comm_addr: int = 0, timeout: int = 1, retries: int = 3):
134
146
  super().__init__(host, port, comm_addr if comm_addr else 0x7f, timeout, retries)
135
147
  self._READ_DEVICE_VERSION_INFO: ProtocolCommand = self._read_command(0x7531, 0x0028)
136
- self._READ_DEVICE_RUNNING_DATA: ProtocolCommand = self._read_command(0x7594, 0x0049)
148
+ self._READ_RUNNING_DATA: ProtocolCommand = self._read_command(0x7594, 0x0049)
149
+ self._READ_METER_DATA: ProtocolCommand = self._read_command(0x75f4, 0x01)
137
150
  self._sensors = self.__all_sensors
151
+ self._sensors_meter = self.__all_sensors_meter
138
152
  self._settings: dict[str, Sensor] = {s.id_: s for s in self.__all_settings}
153
+ self._has_meter: bool = True
139
154
 
140
155
  @staticmethod
141
156
  def _single_phase_only(s: Sensor) -> bool:
@@ -154,10 +169,13 @@ class DT(Inverter):
154
169
  self.model_name = response[22:32].decode("ascii").rstrip()
155
170
  except:
156
171
  print("No model name sent from the inverter.")
157
- self.serial_number = self._decode(response[6:22])
158
- self.dsp1_version = read_unsigned_int(response, 66)
159
- self.dsp2_version = read_unsigned_int(response, 68)
160
- self.arm_version = read_unsigned_int(response, 70)
172
+ # Modbus registers from 30001 - 30040
173
+ self.serial_number = self._decode(response[6:22]) # 30004 - 30012
174
+ self.dsp1_version = read_unsigned_int(response, 66) # 30034
175
+ self.dsp2_version = read_unsigned_int(response, 68) # 30035
176
+ self.arm_version = read_unsigned_int(response, 70) # 30036
177
+ self.dsp_svn_version = read_unsigned_int(response, 72) # 35037
178
+ self.arm_svn_version = read_unsigned_int(response, 74) # 35038
161
179
  self.firmware = "{}.{}.{:02x}".format(self.dsp1_version, self.dsp2_version, self.arm_version)
162
180
 
163
181
  if is_single_phase(self):
@@ -176,8 +194,17 @@ class DT(Inverter):
176
194
  pass
177
195
 
178
196
  async def read_runtime_data(self) -> Dict[str, Any]:
179
- response = await self._read_from_socket(self._READ_DEVICE_RUNNING_DATA)
197
+ response = await self._read_from_socket(self._READ_RUNNING_DATA)
180
198
  data = self._map_response(response, self._sensors)
199
+
200
+ if self._has_meter:
201
+ try:
202
+ response = await self._read_from_socket(self._READ_METER_DATA)
203
+ data.update(self._map_response(response, self._sensors_meter))
204
+ except (RequestRejectedException, RequestFailedException):
205
+ logger.info("Meter values not supported, disabling further attempts.")
206
+ self._has_meter = False
207
+
181
208
  return data
182
209
 
183
210
  async def read_setting(self, setting_id: str) -> Any:
@@ -257,7 +284,10 @@ class DT(Inverter):
257
284
  raise InverterError("Operation not supported, inverter has no batteries.")
258
285
 
259
286
  def sensors(self) -> Tuple[Sensor, ...]:
260
- return self._sensors
287
+ result = self._sensors
288
+ if self._has_meter:
289
+ result = result + self._sensors_meter
290
+ return result
261
291
 
262
292
  def settings(self) -> Tuple[Sensor, ...]:
263
293
  return tuple(self._settings.values())
goodwe/et.py CHANGED
@@ -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
- # Sensors added in some ARM fw update, read when flag _has_meter_extended is on
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._has_meter_extended:
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))
goodwe/model.py CHANGED
@@ -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)
goodwe/protocol.py CHANGED
@@ -37,7 +37,7 @@ 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
+ self.keep_alive: bool = False
41
41
  self.protocol: asyncio.Protocol | None = None
42
42
  self.response_future: Future | None = None
43
43
  self.command: ProtocolCommand | None = None
@@ -62,6 +62,24 @@ class InverterProtocol:
62
62
  self._close_transport()
63
63
  return self._lock
64
64
 
65
+ def _max_retries_reached(self) -> Future:
66
+ logger.debug("Max number of retries (%d) reached, request %s failed.", self.retries, self.command)
67
+ self._close_transport()
68
+ self.response_future = asyncio.get_running_loop().create_future()
69
+ self.response_future.set_exception(MaxRetriesException)
70
+ return self.response_future
71
+
72
+ def _close_transport(self) -> None:
73
+ if self._transport:
74
+ try:
75
+ self._transport.close()
76
+ except RuntimeError:
77
+ logger.debug("Failed to close transport.")
78
+ self._transport = None
79
+ # Cancel Future on connection lost
80
+ if self.response_future and not self.response_future.done():
81
+ self.response_future.cancel()
82
+
65
83
  async def close(self) -> None:
66
84
  """Close the underlying transport/connection."""
67
85
  raise NotImplementedError()
@@ -133,15 +151,16 @@ class UdpInverterProtocol(InverterProtocol, asyncio.DatagramProtocol):
133
151
  self._partial_missing = 0
134
152
  if self.command.validator(data):
135
153
  logger.debug("Received: %s", data.hex())
154
+ self._retry = 0
136
155
  self.response_future.set_result(data)
137
156
  else:
138
157
  logger.debug("Received invalid response: %s", data.hex())
139
- asyncio.get_running_loop().call_soon(self._retry_mechanism)
158
+ asyncio.get_running_loop().call_soon(self._timeout_mechanism)
140
159
  except PartialResponseException as ex:
141
160
  logger.debug("Received response fragment (%d of %d): %s", ex.length, ex.expected, data.hex())
142
161
  self._partial_data = data
143
162
  self._partial_missing = ex.expected - ex.length
144
- self._timer = asyncio.get_running_loop().call_later(self.timeout, self._retry_mechanism)
163
+ self._timer = asyncio.get_running_loop().call_later(self.timeout, self._timeout_mechanism)
145
164
  except asyncio.InvalidStateError:
146
165
  logger.debug("Response already handled: %s", data.hex())
147
166
  except RequestRejectedException as ex:
@@ -158,13 +177,28 @@ class UdpInverterProtocol(InverterProtocol, asyncio.DatagramProtocol):
158
177
 
159
178
  async def send_request(self, command: ProtocolCommand) -> Future:
160
179
  """Send message via transport"""
161
- async with self._ensure_lock():
180
+ await self._ensure_lock().acquire()
181
+ try:
162
182
  await self._connect()
163
183
  response_future = asyncio.get_running_loop().create_future()
164
- self._retry = 0
165
184
  self._send_request(command, response_future)
166
185
  await response_future
167
186
  return response_future
187
+ except asyncio.CancelledError:
188
+ if self._retry < self.retries:
189
+ self._retry += 1
190
+ if self._lock and self._lock.locked():
191
+ self._lock.release()
192
+ if not self.keep_alive:
193
+ self._close_transport()
194
+ return await self.send_request(command)
195
+ else:
196
+ return self._max_retries_reached()
197
+ finally:
198
+ if self._lock and self._lock.locked():
199
+ self._lock.release()
200
+ if not self.keep_alive:
201
+ self._close_transport()
168
202
 
169
203
  def _send_request(self, command: ProtocolCommand, response_future: Future) -> None:
170
204
  """Send message via transport"""
@@ -178,32 +212,19 @@ class UdpInverterProtocol(InverterProtocol, asyncio.DatagramProtocol):
178
212
  else:
179
213
  logger.debug("Sending: %s", self.command)
180
214
  self._transport.sendto(payload)
181
- self._timer = asyncio.get_running_loop().call_later(self.timeout, self._retry_mechanism)
215
+ self._timer = asyncio.get_running_loop().call_later(self.timeout, self._timeout_mechanism)
182
216
 
183
- def _retry_mechanism(self) -> None:
184
- """Retry mechanism to prevent hanging transport"""
185
- if self.response_future.done():
217
+ def _timeout_mechanism(self) -> None:
218
+ """Timeout mechanism to prevent hanging transport"""
219
+ if self.response_future and self.response_future.done():
186
220
  logger.debug("Response already received.")
187
- elif self._retry < self.retries:
221
+ self._retry = 0
222
+ else:
188
223
  if self._timer:
189
224
  logger.debug("Failed to receive response to %s in time (%ds).", self.command, self.timeout)
190
- self._retry += 1
191
- self._send_request(self.command, self.response_future)
192
- else:
193
- logger.debug("Max number of retries (%d) reached, request %s failed.", self.retries, self.command)
194
- self.response_future.set_exception(MaxRetriesException)
195
- self._close_transport()
196
-
197
- def _close_transport(self) -> None:
198
- if self._transport:
199
- try:
200
- self._transport.close()
201
- except RuntimeError:
202
- logger.debug("Failed to close transport.")
203
- self._transport = None
204
- # Cancel Future on connection close
205
- if self.response_future and not self.response_future.done():
206
- self.response_future.cancel()
225
+ self._timer = None
226
+ if self.response_future and not self.response_future.done():
227
+ self.response_future.cancel()
207
228
 
208
229
  async def close(self):
209
230
  self._close_transport()
@@ -358,24 +379,6 @@ class TcpInverterProtocol(InverterProtocol, asyncio.Protocol):
358
379
  self._timer = None
359
380
  self._close_transport()
360
381
 
361
- def _max_retries_reached(self) -> Future:
362
- logger.debug("Max number of retries (%d) reached, request %s failed.", self.retries, self.command)
363
- self._close_transport()
364
- self.response_future = asyncio.get_running_loop().create_future()
365
- self.response_future.set_exception(MaxRetriesException)
366
- return self.response_future
367
-
368
- def _close_transport(self) -> None:
369
- if self._transport:
370
- try:
371
- self._transport.close()
372
- except RuntimeError:
373
- logger.debug("Failed to close transport.")
374
- self._transport = None
375
- # Cancel Future on connection lost
376
- if self.response_future and not self.response_future.done():
377
- self.response_future.cancel()
378
-
379
382
  async def close(self):
380
383
  await self._ensure_lock().acquire()
381
384
  try:
goodwe/sensor.py CHANGED
@@ -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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: goodwe
3
- Version: 0.4.6
3
+ Version: 0.4.8
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=IJxLDajuu2psYE5ZpwA2HFJDFdK5JIST6kUqWyRVBko,13663
4
+ goodwe/es.py,sha256=vvHmxcFykp8nhR1I8p7SF0YcYpvdCKBYacgcolbVHXI,23009
5
+ goodwe/et.py,sha256=Sdgqj13DXIg36NptkHMKxuP78oo4aUQ_6zlToyt78qI,46002
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=OAKfw6ggClgLR9JIdNd7tQ4pnh_7o_UqVdm1KOVsm-Y,2200
10
+ goodwe/protocol.py,sha256=2xRo1H53G6T0ANSuYKPK_KTNfCVTctIU2ZHVu-CvMPk,30163
11
+ goodwe/sensor.py,sha256=xeDZIwjJ_176ULrRXVCTYvVXx6o2_pWgS0KuR3PPQdg,38435
12
+ goodwe-0.4.8.dist-info/LICENSE,sha256=aZAhk3lRdYT1YZV-IKRHISEcc_KNUmgfuNO3QhRamNM,1073
13
+ goodwe-0.4.8.dist-info/METADATA,sha256=kla7IF_7dMZl-uEdQnGqMBXOkOyNlKt9Inwmi4BWCWQ,3376
14
+ goodwe-0.4.8.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
15
+ goodwe-0.4.8.dist-info/top_level.txt,sha256=kKoiqiVvAxDaDJYMZZQLgHQj9cuWT1MXLfXElTDuf8s,7
16
+ goodwe-0.4.8.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- goodwe/__init__.py,sha256=8fFGBBvBpCo6Ew4puTtW0kYo2hVPKUx6z5A-TA4Tbvc,5795
2
- goodwe/const.py,sha256=yhWk56YV7k7-MbgfmWEMYNlqeRNLOfOpfTqEfRj6Hp8,7934
3
- goodwe/dt.py,sha256=8lbc-G1vQUxtYZRDAHi6QW04bFJRW7l4OjBVDp0EjZc,12093
4
- goodwe/es.py,sha256=vvHmxcFykp8nhR1I8p7SF0YcYpvdCKBYacgcolbVHXI,23009
5
- goodwe/et.py,sha256=f4XWaV8Ltywlca1GYJCUM46ziQCtKleser34rO09WQg,44053
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=gnQ1vV4U_lPpaNq5-jmzJO6ngJEDFVo0jWXVujSyu_0,30083
11
- goodwe/sensor.py,sha256=gns0508zi3dCQ4C01RIUQ8aH-2b8TDpCjW71CFEVEiY,37707
12
- goodwe-0.4.6.dist-info/LICENSE,sha256=aZAhk3lRdYT1YZV-IKRHISEcc_KNUmgfuNO3QhRamNM,1073
13
- goodwe-0.4.6.dist-info/METADATA,sha256=jKbTn5rYaHEb6B8TpTYNqhE1F9dDApMFpBiwvJjMoPQ,3376
14
- goodwe-0.4.6.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
15
- goodwe-0.4.6.dist-info/top_level.txt,sha256=kKoiqiVvAxDaDJYMZZQLgHQj9cuWT1MXLfXElTDuf8s,7
16
- goodwe-0.4.6.dist-info/RECORD,,
File without changes