goodwe 0.4.7__py3-none-any.whl → 0.4.9__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 +49 -14
- goodwe/const.py +23 -17
- goodwe/dt.py +255 -111
- goodwe/es.py +235 -106
- goodwe/et.py +532 -201
- goodwe/exceptions.py +6 -3
- goodwe/inverter.py +209 -24
- goodwe/modbus.py +1 -0
- goodwe/model.py +106 -22
- goodwe/protocol.py +72 -77
- goodwe/sensor.py +83 -67
- {goodwe-0.4.7.dist-info → goodwe-0.4.9.dist-info}/METADATA +4 -4
- goodwe-0.4.9.dist-info/RECORD +16 -0
- {goodwe-0.4.7.dist-info → goodwe-0.4.9.dist-info}/WHEEL +1 -1
- goodwe-0.4.7.dist-info/RECORD +0 -16
- {goodwe-0.4.7.dist-info → goodwe-0.4.9.dist-info/licenses}/LICENSE +0 -0
- {goodwe-0.4.7.dist-info → goodwe-0.4.9.dist-info}/top_level.txt +0 -0
goodwe/dt.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
"""Grid-only inverter support - models DT/MS/D-NS/XS or GE's GEP(PSB/PSC)"""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
3
5
|
import logging
|
|
4
|
-
from typing import Tuple
|
|
5
6
|
|
|
6
|
-
from .
|
|
7
|
-
from .
|
|
8
|
-
from .inverter import OperationMode
|
|
9
|
-
from .inverter import SensorKind as Kind
|
|
7
|
+
from .const import *
|
|
8
|
+
from .exceptions import InverterError, RequestFailedException, RequestRejectedException
|
|
9
|
+
from .inverter import EMSMode, Inverter, OperationMode, SensorKind as Kind
|
|
10
10
|
from .modbus import ILLEGAL_DATA_ADDRESS
|
|
11
11
|
from .model import is_3_mppt, is_single_phase
|
|
12
12
|
from .protocol import ProtocolCommand
|
|
@@ -18,35 +18,51 @@ logger = logging.getLogger(__name__)
|
|
|
18
18
|
class DT(Inverter):
|
|
19
19
|
"""Class representing inverter of DT/MS/D-NS/XS or GE's GEP(PSB/PSC) families"""
|
|
20
20
|
|
|
21
|
-
__all_sensors:
|
|
21
|
+
__all_sensors: tuple[Sensor, ...] = (
|
|
22
22
|
Timestamp("timestamp", 30100, "Timestamp"),
|
|
23
23
|
Voltage("vpv1", 30103, "PV1 Voltage", Kind.PV),
|
|
24
24
|
Current("ipv1", 30104, "PV1 Current", Kind.PV),
|
|
25
|
-
Calculated(
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
Calculated(
|
|
26
|
+
"ppv1",
|
|
27
|
+
lambda data: round(read_voltage(data, 30103) * read_current(data, 30104)),
|
|
28
|
+
"PV1 Power",
|
|
29
|
+
"W",
|
|
30
|
+
Kind.PV,
|
|
31
|
+
),
|
|
28
32
|
Voltage("vpv2", 30105, "PV2 Voltage", Kind.PV),
|
|
29
33
|
Current("ipv2", 30106, "PV2 Current", Kind.PV),
|
|
30
|
-
Calculated(
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
Calculated(
|
|
35
|
+
"ppv2",
|
|
36
|
+
lambda data: round(read_voltage(data, 30105) * read_current(data, 30106)),
|
|
37
|
+
"PV2 Power",
|
|
38
|
+
"W",
|
|
39
|
+
Kind.PV,
|
|
40
|
+
),
|
|
33
41
|
Voltage("vpv3", 30107, "PV3 Voltage", Kind.PV),
|
|
34
42
|
Current("ipv3", 30108, "PV3 Current", Kind.PV),
|
|
35
|
-
Calculated(
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
Calculated(
|
|
44
|
+
"ppv3",
|
|
45
|
+
lambda data: round(read_voltage(data, 30107) * read_current(data, 30108)),
|
|
46
|
+
"PV3 Power",
|
|
47
|
+
"W",
|
|
48
|
+
Kind.PV,
|
|
49
|
+
),
|
|
38
50
|
# ppv1 + ppv2 + ppv3
|
|
39
|
-
Calculated(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# Voltage("
|
|
49
|
-
# Current("
|
|
51
|
+
Calculated(
|
|
52
|
+
"ppv",
|
|
53
|
+
lambda data: (round(read_voltage(data, 30103) * read_current(data, 30104)))
|
|
54
|
+
+ (round(read_voltage(data, 30105) * read_current(data, 30106)))
|
|
55
|
+
+ (round(read_voltage(data, 30107) * read_current(data, 30108))),
|
|
56
|
+
"PV Power",
|
|
57
|
+
"W",
|
|
58
|
+
Kind.PV,
|
|
59
|
+
),
|
|
60
|
+
# Voltage("vpv4", 30109, "PV4 Voltage", Kind.PV),
|
|
61
|
+
# Current("ipv4", 30110, "PV4 Current", Kind.PV),
|
|
62
|
+
# Voltage("vpv5", 30111, "PV5 Voltage", Kind.PV),
|
|
63
|
+
# Current("ipv5", 30112, "PV5 Current", Kind.PV),
|
|
64
|
+
# Voltage("vpv6", 30113, "PV6 Voltage", Kind.PV),
|
|
65
|
+
# Current("ipv6", 30114, "PV7 Current", Kind.PV),
|
|
50
66
|
Voltage("vline1", 30115, "On-grid L1-L2 Voltage", Kind.AC),
|
|
51
67
|
Voltage("vline2", 30116, "On-grid L2-L3 Voltage", Kind.AC),
|
|
52
68
|
Voltage("vline3", 30117, "On-grid L3-L1 Voltage", Kind.AC),
|
|
@@ -59,154 +75,254 @@ class DT(Inverter):
|
|
|
59
75
|
Frequency("fgrid1", 30124, "On-grid L1 Frequency", Kind.AC),
|
|
60
76
|
Frequency("fgrid2", 30125, "On-grid L2 Frequency", Kind.AC),
|
|
61
77
|
Frequency("fgrid3", 30126, "On-grid L3 Frequency", Kind.AC),
|
|
62
|
-
Calculated(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
78
|
+
Calculated(
|
|
79
|
+
"pgrid1",
|
|
80
|
+
lambda data: round(read_voltage(data, 30118) * read_current(data, 30121)),
|
|
81
|
+
"On-grid L1 Power",
|
|
82
|
+
"W",
|
|
83
|
+
Kind.AC,
|
|
84
|
+
),
|
|
85
|
+
Calculated(
|
|
86
|
+
"pgrid2",
|
|
87
|
+
lambda data: round(read_voltage(data, 30119) * read_current(data, 30122)),
|
|
88
|
+
"On-grid L2 Power",
|
|
89
|
+
"W",
|
|
90
|
+
Kind.AC,
|
|
91
|
+
),
|
|
92
|
+
Calculated(
|
|
93
|
+
"pgrid3",
|
|
94
|
+
lambda data: round(read_voltage(data, 30120) * read_current(data, 30123)),
|
|
95
|
+
"On-grid L3 Power",
|
|
96
|
+
"W",
|
|
97
|
+
Kind.AC,
|
|
98
|
+
),
|
|
99
|
+
Power4("total_inverter_power", 30127, "Total Power", Kind.AC),
|
|
73
100
|
Integer("work_mode", 30129, "Work Mode code"),
|
|
74
101
|
Enum2("work_mode_label", 30129, WORK_MODES, "Work Mode"),
|
|
75
102
|
Long("error_codes", 30130, "Error Codes"),
|
|
76
103
|
Integer("warning_code", 30132, "Warning code"),
|
|
77
104
|
Apparent4("apparent_power", 30133, "Apparent Power", Kind.AC),
|
|
78
105
|
Reactive4("reactive_power", 30135, "Reactive Power", Kind.AC),
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
#
|
|
82
|
-
# 30140 reserved
|
|
106
|
+
PowerS("total_input_power", 30137, "Total Input Power", Kind.PV),
|
|
107
|
+
Decimal("power_factor", 30139, 1000, "Power Factor", "", Kind.GRID),
|
|
108
|
+
# 30140 inverter efficiency
|
|
83
109
|
Temp("temperature", 30141, "Inverter Temperature", Kind.AC),
|
|
84
|
-
|
|
85
|
-
# 30143
|
|
110
|
+
Temp("temperature_heatsink", 30142, "Heatsink Temperature", Kind.AC),
|
|
111
|
+
# Temp("temperature_module", 30143, "Module Temperature", Kind.AC),
|
|
86
112
|
Energy("e_day", 30144, "Today's PV Generation", Kind.PV),
|
|
87
113
|
Energy4("e_total", 30145, "Total PV Generation", Kind.PV),
|
|
88
114
|
Long("h_total", 30147, "Hours Total", "h", Kind.PV),
|
|
89
115
|
Integer("safety_country", 30149, "Safety Country code", "", Kind.AC),
|
|
90
|
-
Enum2(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
# 30154
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
# 30159 reserved
|
|
101
|
-
# 30160 reserved
|
|
102
|
-
# 30161 reserved
|
|
103
|
-
Integer("funbit", 30162, "FunBit", "", Kind.PV),
|
|
116
|
+
Enum2(
|
|
117
|
+
"safety_country_label", 30149, SAFETY_COUNTRIES, "Safety Country", Kind.AC
|
|
118
|
+
),
|
|
119
|
+
# 30150 PV1 input power kW
|
|
120
|
+
# 30152 PV2 input power kW
|
|
121
|
+
# 30154 PV3 input power kW
|
|
122
|
+
# 30156 PV4 input power kW
|
|
123
|
+
# 30158 PV5 input power kW
|
|
124
|
+
# 30160 PV6 input power kW
|
|
125
|
+
Integer("funbit", 30162, "FunctionBit", "", Kind.PV),
|
|
104
126
|
Voltage("vbus", 30163, "Bus Voltage", Kind.PV),
|
|
105
127
|
Voltage("vnbus", 30164, "NBus Voltage", Kind.PV),
|
|
106
128
|
Long("derating_mode", 30165, "Derating Mode code"),
|
|
107
129
|
EnumBitmap4("derating_mode_label", 30165, DERATING_MODE_CODES, "Derating Mode"),
|
|
108
|
-
# 30167
|
|
109
|
-
# 30168
|
|
110
|
-
# 30169
|
|
111
|
-
# 30170
|
|
112
|
-
# 30171
|
|
113
|
-
|
|
130
|
+
# 30167 PV2 fault value
|
|
131
|
+
# 30168 Line2 fault value
|
|
132
|
+
# 30169 Line3 fault value
|
|
133
|
+
# 30170 Line3 fault value (duplicate commentary in modbus protocol document)
|
|
134
|
+
# 30171 Manufacture ID
|
|
135
|
+
Integer("rssi", 30172, "RSSI"),
|
|
136
|
+
# 30173 ISO test value
|
|
137
|
+
# 30174 PID and Wietap status
|
|
138
|
+
# 30175 String 1 Current
|
|
139
|
+
# 30176 String 2 Current
|
|
140
|
+
# 30177 String 3 Current
|
|
141
|
+
# 30178 - 30194 listed String 4 Current through to String 20 Current
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Inverter's meter data
|
|
145
|
+
# Modbus registers from offset 0x75f3 (30195)
|
|
146
|
+
__all_sensors_meter: tuple[Sensor, ...] = (
|
|
147
|
+
Power4S("meter_active_power", 30195, "Meter Active Power", Kind.GRID),
|
|
148
|
+
Energy4W("meter_e_total_exp", 30197, "Meter Total Energy (export)", Kind.GRID),
|
|
149
|
+
Energy4W("meter_e_total_imp", 30199, "Meter Total Energy (import)", Kind.GRID),
|
|
150
|
+
# 30201 GPRS Burn Mode
|
|
151
|
+
# 30202 Cabinet Humidity %
|
|
152
|
+
# 30203 ARM Error Message
|
|
153
|
+
# 30205 Warning Code 2
|
|
154
|
+
# 30207 AFCI Status
|
|
155
|
+
# 30208 Output control status - Japanese models only
|
|
156
|
+
Integer("meter_comm_status", 30209, "Meter Communication Status code"), # 1 Normal, 2 Disconnected
|
|
157
|
+
Enum2("meter_comm_label", 30209, METER_COMMUNICATION_STATUS, "Meter Communication Status"),
|
|
158
|
+
Calculated("house_consumption", lambda data: None, "House Consumption", "W", Kind.AC), # calculated and patched in read_runtime_data as we are unable to calculate it from seperate modbus offsets in all_sensors and all_sensors_meter
|
|
159
|
+
CurrentSmA("leakage_current", 30210, "Leakage Current", Kind.PV),
|
|
160
|
+
# 30211 repeat of 30197 in U64 instead of U32
|
|
161
|
+
# 30215 repeat of 30199 in U64 instead of U32
|
|
162
|
+
# 30219 Wireless Module AT Instruction Status Log
|
|
163
|
+
# 30220 Disable Inverter Flag - 0 for normal operation, 1 for inverter disabled until safety regulations changed
|
|
164
|
+
# 30221 ARM Internal Firmware Version
|
|
165
|
+
# 30227 G100 CLS State (UK anti-reflux status)
|
|
166
|
+
# 30228 Grid power monitored by DSPs CT (S32)
|
|
167
|
+
# 30230 String Current Detection Flag - 0 No string current detected, 1 String current detected
|
|
114
168
|
)
|
|
115
169
|
|
|
116
170
|
# Modbus registers of inverter settings, offsets are modbus register addresses
|
|
117
|
-
__all_settings:
|
|
171
|
+
__all_settings: tuple[Sensor, ...] = (
|
|
118
172
|
Timestamp("time", 40313, "Inverter time"),
|
|
119
|
-
|
|
120
|
-
Integer("
|
|
121
|
-
Integer("
|
|
173
|
+
Integer("shadow_scan_pv1", 40326, "Shadow Scan Status PV1", "", Kind.PV),
|
|
174
|
+
Integer("shadow_scan_pv2", 40352, "Shadow Scan Status PV2", "", Kind.PV),
|
|
175
|
+
Integer("shadow_scan_pv3", 40362, "Shadow Scan Status PV3", "", Kind.PV),
|
|
176
|
+
Integer("shadow_scan_time", 40347, "Shadow Scan Time", "", Kind.PV),
|
|
177
|
+
# Integer("shadow_scan_pv2_time", 40353, "Shadow Scan PV2 Time", "", Kind.PV), - documentation suggests a duplicate of 40347 as the value is global for all 3 strings
|
|
178
|
+
Integer("grid_export", 40327, "Grid Export Limit Enabled", "", Kind.GRID),
|
|
122
179
|
Integer("grid_export_limit", 40328, "Grid Export Limit", "%", Kind.GRID),
|
|
123
180
|
Integer("start", 40330, "Start / Power On", "", Kind.GRID),
|
|
124
181
|
Integer("stop", 40331, "Stop / Power Off", "", Kind.GRID),
|
|
125
182
|
Integer("restart", 40332, "Restart", "", Kind.GRID),
|
|
126
|
-
Integer(
|
|
183
|
+
Integer(
|
|
184
|
+
"grid_export_hw", 40345, "Grid Export Limit Enabled (HW)", "", Kind.GRID
|
|
185
|
+
),
|
|
127
186
|
)
|
|
128
187
|
|
|
129
188
|
# Settings for single phase inverters
|
|
130
|
-
__settings_single_phase:
|
|
189
|
+
__settings_single_phase: tuple[Sensor, ...] = (
|
|
131
190
|
Long("grid_export_limit", 40328, "Grid Export Limit", "W", Kind.GRID),
|
|
132
191
|
)
|
|
133
192
|
|
|
134
193
|
# Settings for three phase inverters
|
|
135
|
-
__settings_three_phase:
|
|
194
|
+
__settings_three_phase: tuple[Sensor, ...] = (
|
|
136
195
|
Integer("grid_export_limit", 40336, "Grid Export Limit", "%", Kind.GRID),
|
|
137
196
|
)
|
|
138
197
|
|
|
139
|
-
def __init__(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
198
|
+
def __init__(
|
|
199
|
+
self,
|
|
200
|
+
host: str,
|
|
201
|
+
port: int,
|
|
202
|
+
comm_addr: int = 0,
|
|
203
|
+
timeout: int = 1,
|
|
204
|
+
retries: int = 3,
|
|
205
|
+
):
|
|
206
|
+
super().__init__(host, port, comm_addr if comm_addr else 0x7F, timeout, retries)
|
|
207
|
+
self._READ_DEVICE_VERSION_INFO: ProtocolCommand = self._read_command(
|
|
208
|
+
0x7531, 0x0028
|
|
209
|
+
)
|
|
210
|
+
self._READ_METER_VERSION_INFO: ProtocolCommand = self._read_command(
|
|
211
|
+
0x756F, 0x0014
|
|
212
|
+
)
|
|
213
|
+
self._READ_DEVICE_MODEL: ProtocolCommand = self._read_command(0x9CED, 0x0008)
|
|
214
|
+
self._READ_RUNNING_DATA: ProtocolCommand = self._read_command(0x7594, 0x0049)
|
|
215
|
+
self._READ_METER_DATA: ProtocolCommand = self._read_command(0x75F3, 0xF)
|
|
143
216
|
self._sensors = self.__all_sensors
|
|
217
|
+
self._sensors_meter = self.__all_sensors_meter
|
|
144
218
|
self._settings: dict[str, Sensor] = {s.id_: s for s in self.__all_settings}
|
|
219
|
+
self._sensors_map: dict[str, Sensor] | None = None
|
|
220
|
+
self._has_meter: bool = True
|
|
145
221
|
|
|
146
222
|
@staticmethod
|
|
147
223
|
def _single_phase_only(s: Sensor) -> bool:
|
|
148
224
|
"""Filter to exclude phase2/3 sensors on single phase inverters"""
|
|
149
|
-
return not ((s.id_.endswith(
|
|
225
|
+
return not ((s.id_.endswith("2") or s.id_.endswith("3")) and "pv" not in s.id_)
|
|
150
226
|
|
|
151
227
|
@staticmethod
|
|
152
228
|
def _pv1_pv2_only(s: Sensor) -> bool:
|
|
153
229
|
"""Filter to exclude sensors on < 3 PV inverters"""
|
|
154
|
-
return not s.id_.endswith(
|
|
230
|
+
return not s.id_.endswith("pv3")
|
|
155
231
|
|
|
156
232
|
async def read_device_info(self):
|
|
157
233
|
response = await self._read_from_socket(self._READ_DEVICE_VERSION_INFO)
|
|
158
234
|
response = response.response_data()
|
|
235
|
+
|
|
236
|
+
# Modbus registers from 30001 - 30040
|
|
237
|
+
self.serial_number = self._decode(response[6:22]) # 30004 - 30012
|
|
238
|
+
self.dsp1_version = read_unsigned_int(response, 66) # 30034
|
|
239
|
+
self.dsp2_version = read_unsigned_int(response, 68) # 30035
|
|
240
|
+
self.arm_version = read_unsigned_int(response, 70) # 30036
|
|
241
|
+
self.dsp_svn_version = read_unsigned_int(response, 72) # 35037
|
|
242
|
+
self.arm_svn_version = read_unsigned_int(response, 74) # 35038
|
|
243
|
+
self.firmware = (
|
|
244
|
+
f"{self.dsp1_version}.{self.dsp2_version}.{self.arm_version:02x}"
|
|
245
|
+
)
|
|
246
|
+
|
|
159
247
|
try:
|
|
160
248
|
self.model_name = response[22:32].decode("ascii").rstrip()
|
|
161
249
|
except:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
250
|
+
try:
|
|
251
|
+
response = await self._read_from_socket(self._READ_DEVICE_MODEL)
|
|
252
|
+
response = response.response_data()
|
|
253
|
+
self.model_name = response[0:16].decode("ascii").rstrip("\x00").strip()
|
|
254
|
+
except InverterError as e:
|
|
255
|
+
logger.debug("No model name sent from the inverter.")
|
|
168
256
|
|
|
169
257
|
if is_single_phase(self):
|
|
170
|
-
# this is single phase inverter, filter out all L2 and L3 sensors
|
|
171
258
|
self._sensors = tuple(filter(self._single_phase_only, self.__all_sensors))
|
|
172
259
|
self._settings.update({s.id_: s for s in self.__settings_single_phase})
|
|
173
260
|
else:
|
|
174
261
|
self._settings.update({s.id_: s for s in self.__settings_three_phase})
|
|
175
262
|
|
|
176
263
|
if is_3_mppt(self):
|
|
177
|
-
# this is 3 PV strings inverter, keep all sensors
|
|
178
264
|
pass
|
|
179
265
|
else:
|
|
180
|
-
# this is only 2 PV strings inverter
|
|
181
266
|
self._sensors = tuple(filter(self._pv1_pv2_only, self._sensors))
|
|
182
|
-
pass
|
|
183
267
|
|
|
184
|
-
|
|
185
|
-
|
|
268
|
+
try:
|
|
269
|
+
response = await self._read_from_socket(self._READ_METER_VERSION_INFO)
|
|
270
|
+
response = response.response_data()
|
|
271
|
+
self.meter_software_version = read_unsigned_int(response, 0) # 30063
|
|
272
|
+
self.meter_serial_number = self._decode(response[24:38]) # 30075 - 30082
|
|
273
|
+
except InverterError as e:
|
|
274
|
+
logger.debug("Could not read meter version info.")
|
|
275
|
+
|
|
276
|
+
async def read_runtime_data(self) -> dict[str, Any]:
|
|
277
|
+
response = await self._read_from_socket(self._READ_RUNNING_DATA)
|
|
186
278
|
data = self._map_response(response, self._sensors)
|
|
279
|
+
|
|
280
|
+
if self._has_meter:
|
|
281
|
+
try:
|
|
282
|
+
response = await self._read_from_socket(self._READ_METER_DATA)
|
|
283
|
+
data.update(self._map_response(response, self._sensors_meter))
|
|
284
|
+
#patch house_consumption int from all_sensors_meter now that we have values available
|
|
285
|
+
data["house_consumption"] = abs(data.get("ppv", 0) - data.get("meter_active_power", 0))
|
|
286
|
+
except (RequestRejectedException, RequestFailedException):
|
|
287
|
+
logger.info("Meter values not supported, disabling further attempts.")
|
|
288
|
+
self._has_meter = False
|
|
289
|
+
|
|
187
290
|
return data
|
|
188
291
|
|
|
292
|
+
async def read_sensor(self, sensor_id: str) -> Any:
|
|
293
|
+
sensor: Sensor = self._get_sensor(sensor_id)
|
|
294
|
+
if sensor:
|
|
295
|
+
return await self._read_sensor(sensor)
|
|
296
|
+
if sensor_id.startswith("modbus"):
|
|
297
|
+
response = await self._read_from_socket(
|
|
298
|
+
self._read_command(int(sensor_id[7:]), 1)
|
|
299
|
+
)
|
|
300
|
+
return int.from_bytes(response.read(2), byteorder="big", signed=True)
|
|
301
|
+
raise ValueError(f'Unknown sensor "{sensor_id}"')
|
|
302
|
+
|
|
189
303
|
async def read_setting(self, setting_id: str) -> Any:
|
|
190
304
|
setting = self._settings.get(setting_id)
|
|
191
305
|
if setting:
|
|
192
|
-
return await self.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
306
|
+
return await self._read_sensor(setting)
|
|
307
|
+
if setting_id.startswith("modbus"):
|
|
308
|
+
response = await self._read_from_socket(
|
|
309
|
+
self._read_command(int(setting_id[7:]), 1)
|
|
310
|
+
)
|
|
311
|
+
return int.from_bytes(response.read(2), byteorder="big", signed=True)
|
|
312
|
+
raise ValueError(f'Unknown setting "{setting_id}"')
|
|
199
313
|
|
|
200
|
-
async def
|
|
314
|
+
async def _read_sensor(self, setting: Sensor) -> Any:
|
|
201
315
|
try:
|
|
202
316
|
count = (setting.size_ + (setting.size_ % 2)) // 2
|
|
203
|
-
response = await self._read_from_socket(
|
|
317
|
+
response = await self._read_from_socket(
|
|
318
|
+
self._read_command(setting.offset, count)
|
|
319
|
+
)
|
|
204
320
|
return setting.read_value(response)
|
|
205
321
|
except RequestRejectedException as ex:
|
|
206
322
|
if ex.message == ILLEGAL_DATA_ADDRESS:
|
|
207
|
-
logger.debug("Unsupported setting %s", setting.id_)
|
|
323
|
+
logger.debug("Unsupported sensor/setting %s", setting.id_)
|
|
208
324
|
self._settings.pop(setting.id_, None)
|
|
209
|
-
raise ValueError(f'Unknown setting "{setting.id_}"')
|
|
325
|
+
raise ValueError(f'Unknown sensor/setting "{setting.id_}"')
|
|
210
326
|
return None
|
|
211
327
|
|
|
212
328
|
async def write_setting(self, setting_id: str, value: Any):
|
|
@@ -215,14 +331,18 @@ class DT(Inverter):
|
|
|
215
331
|
await self._write_setting(setting, value)
|
|
216
332
|
else:
|
|
217
333
|
if setting_id.startswith("modbus"):
|
|
218
|
-
await self._read_from_socket(
|
|
334
|
+
await self._read_from_socket(
|
|
335
|
+
self._write_command(int(setting_id[7:]), int(value))
|
|
336
|
+
)
|
|
219
337
|
else:
|
|
220
338
|
raise ValueError(f'Unknown setting "{setting_id}"')
|
|
221
339
|
|
|
222
340
|
async def _write_setting(self, setting: Sensor, value: Any):
|
|
223
341
|
if setting.size_ == 1:
|
|
224
342
|
# modbus can address/store only 16 bit values, read the other 8 bytes
|
|
225
|
-
response = await self._read_from_socket(
|
|
343
|
+
response = await self._read_from_socket(
|
|
344
|
+
self._read_command(setting.offset, 1)
|
|
345
|
+
)
|
|
226
346
|
raw_value = setting.encode_value(value, response.response_data()[0:2])
|
|
227
347
|
else:
|
|
228
348
|
raw_value = setting.encode_value(value)
|
|
@@ -230,9 +350,11 @@ class DT(Inverter):
|
|
|
230
350
|
value = int.from_bytes(raw_value, byteorder="big", signed=True)
|
|
231
351
|
await self._read_from_socket(self._write_command(setting.offset, value))
|
|
232
352
|
else:
|
|
233
|
-
await self._read_from_socket(
|
|
353
|
+
await self._read_from_socket(
|
|
354
|
+
self._write_multi_command(setting.offset, raw_value)
|
|
355
|
+
)
|
|
234
356
|
|
|
235
|
-
async def read_settings_data(self) ->
|
|
357
|
+
async def read_settings_data(self) -> dict[str, Any]:
|
|
236
358
|
data = {}
|
|
237
359
|
for setting in self.settings():
|
|
238
360
|
value = await self.read_setting(setting.id_)
|
|
@@ -240,20 +362,34 @@ class DT(Inverter):
|
|
|
240
362
|
return data
|
|
241
363
|
|
|
242
364
|
async def get_grid_export_limit(self) -> int:
|
|
243
|
-
return await self.read_setting(
|
|
365
|
+
return await self.read_setting("grid_export_limit")
|
|
244
366
|
|
|
245
367
|
async def set_grid_export_limit(self, export_limit: int) -> None:
|
|
246
368
|
if export_limit >= 0:
|
|
247
|
-
return await self.write_setting(
|
|
369
|
+
return await self.write_setting("grid_export_limit", export_limit)
|
|
248
370
|
|
|
249
|
-
async def get_operation_modes(
|
|
371
|
+
async def get_operation_modes(
|
|
372
|
+
self, include_emulated: bool
|
|
373
|
+
) -> tuple[OperationMode, ...]:
|
|
250
374
|
return ()
|
|
251
375
|
|
|
252
376
|
async def get_operation_mode(self) -> OperationMode:
|
|
253
377
|
raise InverterError("Operation not supported.")
|
|
254
378
|
|
|
255
|
-
async def set_operation_mode(
|
|
256
|
-
|
|
379
|
+
async def set_operation_mode(
|
|
380
|
+
self,
|
|
381
|
+
operation_mode: OperationMode,
|
|
382
|
+
eco_mode_power: int = 100,
|
|
383
|
+
eco_mode_soc: int = 100,
|
|
384
|
+
) -> None:
|
|
385
|
+
raise InverterError("Operation not supported.")
|
|
386
|
+
|
|
387
|
+
async def get_ems_mode(self) -> EMSMode:
|
|
388
|
+
raise InverterError("Operation not supported.")
|
|
389
|
+
|
|
390
|
+
async def set_ems_mode(
|
|
391
|
+
self, ems_mode: EMSMode, ems_power_limit: int | None = None
|
|
392
|
+
) -> None:
|
|
257
393
|
raise InverterError("Operation not supported.")
|
|
258
394
|
|
|
259
395
|
async def get_ongrid_battery_dod(self) -> int:
|
|
@@ -262,8 +398,16 @@ class DT(Inverter):
|
|
|
262
398
|
async def set_ongrid_battery_dod(self, dod: int) -> None:
|
|
263
399
|
raise InverterError("Operation not supported, inverter has no batteries.")
|
|
264
400
|
|
|
265
|
-
def
|
|
266
|
-
|
|
401
|
+
def _get_sensor(self, sensor_id: str) -> Sensor | None:
|
|
402
|
+
if self._sensors_map is None:
|
|
403
|
+
self._sensors_map = {s.id_: s for s in self.sensors()}
|
|
404
|
+
return self._sensors_map.get(sensor_id)
|
|
405
|
+
|
|
406
|
+
def sensors(self) -> tuple[Sensor, ...]:
|
|
407
|
+
result = self._sensors
|
|
408
|
+
if self._has_meter:
|
|
409
|
+
result = result + self._sensors_meter
|
|
410
|
+
return result
|
|
267
411
|
|
|
268
|
-
def settings(self) ->
|
|
412
|
+
def settings(self) -> tuple[Sensor, ...]:
|
|
269
413
|
return tuple(self._settings.values())
|