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/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 .exceptions import InverterError, RequestRejectedException
7
- from .inverter import Inverter
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: Tuple[Sensor, ...] = (
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("ppv1",
26
- lambda data: round(read_voltage(data, 30103) * read_current(data, 30104)),
27
- "PV1 Power", "W", Kind.PV),
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("ppv2",
31
- lambda data: round(read_voltage(data, 30105) * read_current(data, 30106)),
32
- "PV2 Power", "W", Kind.PV),
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("ppv3",
36
- lambda data: round(read_voltage(data, 30107) * read_current(data, 30108)),
37
- "PV3 Power", "W", Kind.PV),
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("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),
44
- # Voltage("vpv4", 14, "PV4 Voltage", Kind.PV),
45
- # Current("ipv4", 16, "PV4 Current", Kind.PV),
46
- # Voltage("vpv5", 14, "PV5 Voltage", Kind.PV),
47
- # Current("ipv5", 16, "PV5 Current", Kind.PV),
48
- # Voltage("vpv6", 14, "PV6 Voltage", Kind.PV),
49
- # Current("ipv6", 16, "PV7 Current", Kind.PV),
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("pgrid1",
63
- lambda data: round(read_voltage(data, 30118) * read_current(data, 30121)),
64
- "On-grid L1 Power", "W", Kind.AC),
65
- Calculated("pgrid2",
66
- lambda data: round(read_voltage(data, 30119) * read_current(data, 30122)),
67
- "On-grid L2 Power", "W", Kind.AC),
68
- Calculated("pgrid3",
69
- lambda data: round(read_voltage(data, 30120) * read_current(data, 30123)),
70
- "On-grid L3 Power", "W", Kind.AC),
71
- # 30127 reserved
72
- PowerS("active_power", 30128, "Active Power", Kind.AC),
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
- # 30137 reserved
80
- # 30138 reserved
81
- # 30139 reserved
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
- # 30142 reserved
85
- # 30143 reserved
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("safety_country_label", 30149, SAFETY_COUNTRIES, "Safety Country", Kind.AC),
91
- # 30150 reserved
92
- # 30151 reserved
93
- # 30152 reserved
94
- # 30153 reserved
95
- # 30154 reserved
96
- # 30155 reserved
97
- # 30156 reserved
98
- # 30157 reserved
99
- # 30158 reserved
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 reserved
109
- # 30168 reserved
110
- # 30169 reserved
111
- # 30170 reserved
112
- # 30171 reserved
113
- # 30172 reserved
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: Tuple[Sensor, ...] = (
171
+ __all_settings: tuple[Sensor, ...] = (
118
172
  Timestamp("time", 40313, "Inverter time"),
119
-
120
- Integer("shadow_scan", 40326, "Shadow Scan", "", Kind.PV),
121
- Integer("grid_export", 40327, "Grid Export Enabled", "", Kind.GRID),
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("grid_export_hw", 40345, "Grid Export Enabled (HW)", "", Kind.GRID),
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: Tuple[Sensor, ...] = (
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: Tuple[Sensor, ...] = (
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__(self, host: str, port: int, comm_addr: int = 0, timeout: int = 1, retries: int = 3):
140
- super().__init__(host, port, comm_addr if comm_addr else 0x7f, timeout, retries)
141
- self._READ_DEVICE_VERSION_INFO: ProtocolCommand = self._read_command(0x7531, 0x0028)
142
- self._READ_DEVICE_RUNNING_DATA: ProtocolCommand = self._read_command(0x7594, 0x0049)
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('2') or s.id_.endswith('3')) and 'pv' not in s.id_)
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('pv3')
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
- print("No model name sent from the inverter.")
163
- self.serial_number = self._decode(response[6:22])
164
- self.dsp1_version = read_unsigned_int(response, 66)
165
- self.dsp2_version = read_unsigned_int(response, 68)
166
- self.arm_version = read_unsigned_int(response, 70)
167
- self.firmware = "{}.{}.{:02x}".format(self.dsp1_version, self.dsp2_version, self.arm_version)
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
- async def read_runtime_data(self) -> Dict[str, Any]:
185
- response = await self._read_from_socket(self._READ_DEVICE_RUNNING_DATA)
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._read_setting(setting)
193
- else:
194
- if setting_id.startswith("modbus"):
195
- response = await self._read_from_socket(self._read_command(int(setting_id[7:]), 1))
196
- return int.from_bytes(response.read(2), byteorder="big", signed=True)
197
- else:
198
- raise ValueError(f'Unknown setting "{setting_id}"')
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 _read_setting(self, setting: Sensor) -> Any:
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(self._read_command(setting.offset, count))
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(self._write_command(int(setting_id[7:]), int(value)))
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(self._read_command(setting.offset, 1))
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(self._write_multi_command(setting.offset, raw_value))
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) -> Dict[str, Any]:
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('grid_export_limit')
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('grid_export_limit', export_limit)
369
+ return await self.write_setting("grid_export_limit", export_limit)
248
370
 
249
- async def get_operation_modes(self, include_emulated: bool) -> Tuple[OperationMode, ...]:
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(self, operation_mode: OperationMode, eco_mode_power: int = 100,
256
- eco_mode_soc: int = 100) -> None:
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 sensors(self) -> Tuple[Sensor, ...]:
266
- return self._sensors
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) -> Tuple[Sensor, ...]:
412
+ def settings(self) -> tuple[Sensor, ...]:
269
413
  return tuple(self._settings.values())