goodwe 0.4.8__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 +230 -110
- 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 +26 -34
- goodwe/sensor.py +83 -67
- {goodwe-0.4.8.dist-info → goodwe-0.4.9.dist-info}/METADATA +4 -4
- goodwe-0.4.9.dist-info/RECORD +16 -0
- {goodwe-0.4.8.dist-info → goodwe-0.4.9.dist-info}/WHEEL +1 -1
- goodwe-0.4.8.dist-info/RECORD +0 -16
- {goodwe-0.4.8.dist-info → goodwe-0.4.9.dist-info/licenses}/LICENSE +0 -0
- {goodwe-0.4.8.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
|
|
|
7
|
+
from .const import *
|
|
6
8
|
from .exceptions import InverterError, RequestFailedException, RequestRejectedException
|
|
7
|
-
from .inverter import Inverter
|
|
8
|
-
from .inverter import OperationMode
|
|
9
|
-
from .inverter import SensorKind as Kind
|
|
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,116 +75,164 @@ 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
|
-
# 30138 reserved
|
|
106
|
+
PowerS("total_input_power", 30137, "Total Input Power", Kind.PV),
|
|
81
107
|
Decimal("power_factor", 30139, 1000, "Power Factor", "", Kind.GRID),
|
|
82
|
-
# 30140
|
|
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
|
|
114
142
|
)
|
|
115
143
|
|
|
116
144
|
# Inverter's meter data
|
|
117
|
-
# Modbus registers from offset
|
|
118
|
-
__all_sensors_meter:
|
|
119
|
-
|
|
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
|
|
120
168
|
)
|
|
121
169
|
|
|
122
170
|
# Modbus registers of inverter settings, offsets are modbus register addresses
|
|
123
|
-
__all_settings:
|
|
171
|
+
__all_settings: tuple[Sensor, ...] = (
|
|
124
172
|
Timestamp("time", 40313, "Inverter time"),
|
|
125
|
-
|
|
126
|
-
Integer("
|
|
127
|
-
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),
|
|
128
179
|
Integer("grid_export_limit", 40328, "Grid Export Limit", "%", Kind.GRID),
|
|
129
180
|
Integer("start", 40330, "Start / Power On", "", Kind.GRID),
|
|
130
181
|
Integer("stop", 40331, "Stop / Power Off", "", Kind.GRID),
|
|
131
182
|
Integer("restart", 40332, "Restart", "", Kind.GRID),
|
|
132
|
-
Integer(
|
|
183
|
+
Integer(
|
|
184
|
+
"grid_export_hw", 40345, "Grid Export Limit Enabled (HW)", "", Kind.GRID
|
|
185
|
+
),
|
|
133
186
|
)
|
|
134
187
|
|
|
135
188
|
# Settings for single phase inverters
|
|
136
|
-
__settings_single_phase:
|
|
189
|
+
__settings_single_phase: tuple[Sensor, ...] = (
|
|
137
190
|
Long("grid_export_limit", 40328, "Grid Export Limit", "W", Kind.GRID),
|
|
138
191
|
)
|
|
139
192
|
|
|
140
193
|
# Settings for three phase inverters
|
|
141
|
-
__settings_three_phase:
|
|
194
|
+
__settings_three_phase: tuple[Sensor, ...] = (
|
|
142
195
|
Integer("grid_export_limit", 40336, "Grid Export Limit", "%", Kind.GRID),
|
|
143
196
|
)
|
|
144
197
|
|
|
145
|
-
def __init__(
|
|
146
|
-
|
|
147
|
-
|
|
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)
|
|
148
214
|
self._READ_RUNNING_DATA: ProtocolCommand = self._read_command(0x7594, 0x0049)
|
|
149
|
-
self._READ_METER_DATA: ProtocolCommand = self._read_command(
|
|
215
|
+
self._READ_METER_DATA: ProtocolCommand = self._read_command(0x75F3, 0xF)
|
|
150
216
|
self._sensors = self.__all_sensors
|
|
151
217
|
self._sensors_meter = self.__all_sensors_meter
|
|
152
218
|
self._settings: dict[str, Sensor] = {s.id_: s for s in self.__all_settings}
|
|
219
|
+
self._sensors_map: dict[str, Sensor] | None = None
|
|
153
220
|
self._has_meter: bool = True
|
|
154
221
|
|
|
155
222
|
@staticmethod
|
|
156
223
|
def _single_phase_only(s: Sensor) -> bool:
|
|
157
224
|
"""Filter to exclude phase2/3 sensors on single phase inverters"""
|
|
158
|
-
return not ((s.id_.endswith(
|
|
225
|
+
return not ((s.id_.endswith("2") or s.id_.endswith("3")) and "pv" not in s.id_)
|
|
159
226
|
|
|
160
227
|
@staticmethod
|
|
161
228
|
def _pv1_pv2_only(s: Sensor) -> bool:
|
|
162
229
|
"""Filter to exclude sensors on < 3 PV inverters"""
|
|
163
|
-
return not s.id_.endswith(
|
|
230
|
+
return not s.id_.endswith("pv3")
|
|
164
231
|
|
|
165
232
|
async def read_device_info(self):
|
|
166
233
|
response = await self._read_from_socket(self._READ_DEVICE_VERSION_INFO)
|
|
167
234
|
response = response.response_data()
|
|
168
|
-
|
|
169
|
-
self.model_name = response[22:32].decode("ascii").rstrip()
|
|
170
|
-
except:
|
|
171
|
-
print("No model name sent from the inverter.")
|
|
235
|
+
|
|
172
236
|
# Modbus registers from 30001 - 30040
|
|
173
237
|
self.serial_number = self._decode(response[6:22]) # 30004 - 30012
|
|
174
238
|
self.dsp1_version = read_unsigned_int(response, 66) # 30034
|
|
@@ -176,24 +240,40 @@ class DT(Inverter):
|
|
|
176
240
|
self.arm_version = read_unsigned_int(response, 70) # 30036
|
|
177
241
|
self.dsp_svn_version = read_unsigned_int(response, 72) # 35037
|
|
178
242
|
self.arm_svn_version = read_unsigned_int(response, 74) # 35038
|
|
179
|
-
self.firmware =
|
|
243
|
+
self.firmware = (
|
|
244
|
+
f"{self.dsp1_version}.{self.dsp2_version}.{self.arm_version:02x}"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
self.model_name = response[22:32].decode("ascii").rstrip()
|
|
249
|
+
except:
|
|
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.")
|
|
180
256
|
|
|
181
257
|
if is_single_phase(self):
|
|
182
|
-
# this is single phase inverter, filter out all L2 and L3 sensors
|
|
183
258
|
self._sensors = tuple(filter(self._single_phase_only, self.__all_sensors))
|
|
184
259
|
self._settings.update({s.id_: s for s in self.__settings_single_phase})
|
|
185
260
|
else:
|
|
186
261
|
self._settings.update({s.id_: s for s in self.__settings_three_phase})
|
|
187
262
|
|
|
188
263
|
if is_3_mppt(self):
|
|
189
|
-
# this is 3 PV strings inverter, keep all sensors
|
|
190
264
|
pass
|
|
191
265
|
else:
|
|
192
|
-
# this is only 2 PV strings inverter
|
|
193
266
|
self._sensors = tuple(filter(self._pv1_pv2_only, self._sensors))
|
|
194
|
-
pass
|
|
195
267
|
|
|
196
|
-
|
|
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]:
|
|
197
277
|
response = await self._read_from_socket(self._READ_RUNNING_DATA)
|
|
198
278
|
data = self._map_response(response, self._sensors)
|
|
199
279
|
|
|
@@ -201,33 +281,48 @@ class DT(Inverter):
|
|
|
201
281
|
try:
|
|
202
282
|
response = await self._read_from_socket(self._READ_METER_DATA)
|
|
203
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))
|
|
204
286
|
except (RequestRejectedException, RequestFailedException):
|
|
205
287
|
logger.info("Meter values not supported, disabling further attempts.")
|
|
206
288
|
self._has_meter = False
|
|
207
289
|
|
|
208
290
|
return data
|
|
209
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
|
+
|
|
210
303
|
async def read_setting(self, setting_id: str) -> Any:
|
|
211
304
|
setting = self._settings.get(setting_id)
|
|
212
305
|
if setting:
|
|
213
|
-
return await self.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
async def
|
|
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}"')
|
|
313
|
+
|
|
314
|
+
async def _read_sensor(self, setting: Sensor) -> Any:
|
|
222
315
|
try:
|
|
223
316
|
count = (setting.size_ + (setting.size_ % 2)) // 2
|
|
224
|
-
response = await self._read_from_socket(
|
|
317
|
+
response = await self._read_from_socket(
|
|
318
|
+
self._read_command(setting.offset, count)
|
|
319
|
+
)
|
|
225
320
|
return setting.read_value(response)
|
|
226
321
|
except RequestRejectedException as ex:
|
|
227
322
|
if ex.message == ILLEGAL_DATA_ADDRESS:
|
|
228
|
-
logger.debug("Unsupported setting %s", setting.id_)
|
|
323
|
+
logger.debug("Unsupported sensor/setting %s", setting.id_)
|
|
229
324
|
self._settings.pop(setting.id_, None)
|
|
230
|
-
raise ValueError(f'Unknown setting "{setting.id_}"')
|
|
325
|
+
raise ValueError(f'Unknown sensor/setting "{setting.id_}"')
|
|
231
326
|
return None
|
|
232
327
|
|
|
233
328
|
async def write_setting(self, setting_id: str, value: Any):
|
|
@@ -236,14 +331,18 @@ class DT(Inverter):
|
|
|
236
331
|
await self._write_setting(setting, value)
|
|
237
332
|
else:
|
|
238
333
|
if setting_id.startswith("modbus"):
|
|
239
|
-
await self._read_from_socket(
|
|
334
|
+
await self._read_from_socket(
|
|
335
|
+
self._write_command(int(setting_id[7:]), int(value))
|
|
336
|
+
)
|
|
240
337
|
else:
|
|
241
338
|
raise ValueError(f'Unknown setting "{setting_id}"')
|
|
242
339
|
|
|
243
340
|
async def _write_setting(self, setting: Sensor, value: Any):
|
|
244
341
|
if setting.size_ == 1:
|
|
245
342
|
# modbus can address/store only 16 bit values, read the other 8 bytes
|
|
246
|
-
response = await self._read_from_socket(
|
|
343
|
+
response = await self._read_from_socket(
|
|
344
|
+
self._read_command(setting.offset, 1)
|
|
345
|
+
)
|
|
247
346
|
raw_value = setting.encode_value(value, response.response_data()[0:2])
|
|
248
347
|
else:
|
|
249
348
|
raw_value = setting.encode_value(value)
|
|
@@ -251,9 +350,11 @@ class DT(Inverter):
|
|
|
251
350
|
value = int.from_bytes(raw_value, byteorder="big", signed=True)
|
|
252
351
|
await self._read_from_socket(self._write_command(setting.offset, value))
|
|
253
352
|
else:
|
|
254
|
-
await self._read_from_socket(
|
|
353
|
+
await self._read_from_socket(
|
|
354
|
+
self._write_multi_command(setting.offset, raw_value)
|
|
355
|
+
)
|
|
255
356
|
|
|
256
|
-
async def read_settings_data(self) ->
|
|
357
|
+
async def read_settings_data(self) -> dict[str, Any]:
|
|
257
358
|
data = {}
|
|
258
359
|
for setting in self.settings():
|
|
259
360
|
value = await self.read_setting(setting.id_)
|
|
@@ -261,20 +362,34 @@ class DT(Inverter):
|
|
|
261
362
|
return data
|
|
262
363
|
|
|
263
364
|
async def get_grid_export_limit(self) -> int:
|
|
264
|
-
return await self.read_setting(
|
|
365
|
+
return await self.read_setting("grid_export_limit")
|
|
265
366
|
|
|
266
367
|
async def set_grid_export_limit(self, export_limit: int) -> None:
|
|
267
368
|
if export_limit >= 0:
|
|
268
|
-
return await self.write_setting(
|
|
369
|
+
return await self.write_setting("grid_export_limit", export_limit)
|
|
269
370
|
|
|
270
|
-
async def get_operation_modes(
|
|
371
|
+
async def get_operation_modes(
|
|
372
|
+
self, include_emulated: bool
|
|
373
|
+
) -> tuple[OperationMode, ...]:
|
|
271
374
|
return ()
|
|
272
375
|
|
|
273
376
|
async def get_operation_mode(self) -> OperationMode:
|
|
274
377
|
raise InverterError("Operation not supported.")
|
|
275
378
|
|
|
276
|
-
async def set_operation_mode(
|
|
277
|
-
|
|
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:
|
|
278
393
|
raise InverterError("Operation not supported.")
|
|
279
394
|
|
|
280
395
|
async def get_ongrid_battery_dod(self) -> int:
|
|
@@ -283,11 +398,16 @@ class DT(Inverter):
|
|
|
283
398
|
async def set_ongrid_battery_dod(self, dod: int) -> None:
|
|
284
399
|
raise InverterError("Operation not supported, inverter has no batteries.")
|
|
285
400
|
|
|
286
|
-
def
|
|
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, ...]:
|
|
287
407
|
result = self._sensors
|
|
288
408
|
if self._has_meter:
|
|
289
409
|
result = result + self._sensors_meter
|
|
290
410
|
return result
|
|
291
411
|
|
|
292
|
-
def settings(self) ->
|
|
412
|
+
def settings(self) -> tuple[Sensor, ...]:
|
|
293
413
|
return tuple(self._settings.values())
|