1use modbus::{Client as _, tcp};
2
3use crate::error::Error;
4
5pub struct TcpClient {
6 client: tcp::Transport,
7}
8
9impl TcpClient {
10 pub fn new(address: &str, port: u16, device_id: u8) -> Result<Self, Error> {
11 Ok(TcpClient {
12 client: tcp::Transport::new_with_cfg(
13 address,
14 tcp::Config {
15 tcp_port: port,
16 modbus_uid: device_id,
17 ..Default::default()
18 },
19 )?,
20 })
21 }
22
23 #[cfg(feature = "discover")]
24 pub fn new_from_host_info(host_info: &crate::SolaredgeHostInfo) -> Result<Self, Error> {
25 Self::new(&host_info.address(), host_info.port, host_info.modbus_id)
26 }
27
28 pub fn spec_id(&mut self) -> Result<String, Error> {
30 Ok(self.read_register(SunspecRegister::C_SunSpec_ID)?.string())
31 }
32
33 pub fn model_id(&mut self) -> Result<u16, Error> {
35 Ok(self.read_register(SunspecRegister::C_SunSpec_DID)?.u16())
36 }
37
38 pub fn manufacturer(&mut self) -> Result<String, Error> {
40 Ok(self.read_register(SunspecRegister::C_Manufacturer)?.string())
41 }
42
43 pub fn model(&mut self) -> Result<String, Error> {
45 Ok(self.read_register(SunspecRegister::C_Model)?.string())
46 }
47
48 pub fn version(&mut self) -> Result<String, Error> {
50 Ok(self.read_register(SunspecRegister::C_Version)?.string())
51 }
52
53 pub fn serial_number(&mut self) -> Result<String, Error> {
55 Ok(self.read_register(SunspecRegister::C_SerialNumber)?.string())
56 }
57
58 pub fn device_address(&mut self) -> Result<u16, Error> {
60 Ok(self.read_register(SunspecRegister::C_DeviceAddress)?.u16())
61 }
62
63 pub fn phase_count(&mut self) -> Result<PhaseCount, Error> {
64 let val = self.read_register(SunspecRegister::I_PhaseCount)?.u16();
65 PhaseCount::from_u16(val).ok_or(Error::InvalidPhaseCountValue(val))
66 }
67
68 pub fn ac_current(&mut self) -> Result<f64, Error> {
70 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_Current, SunspecRegister::I_AC_Current_SF)?;
71 Ok(val.u16().scaled(scale))
72 }
73
74 pub fn ac_current_a(&mut self) -> Result<f64, Error> {
76 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_CurrentA, SunspecRegister::I_AC_Current_SF)?;
77 Ok(val.u16().scaled(scale))
78 }
79
80 pub fn ac_current_b(&mut self) -> Result<f64, Error> {
82 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_CurrentB, SunspecRegister::I_AC_Current_SF)?;
83 Ok(val.u16().scaled(scale))
84 }
85
86 pub fn ac_current_c(&mut self) -> Result<f64, Error> {
88 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_CurrentC, SunspecRegister::I_AC_Current_SF)?;
89 Ok(val.u16().scaled(scale))
90 }
91
92 pub fn ac_voltage_ab(&mut self) -> Result<f64, Error> {
94 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_VoltageAB, SunspecRegister::I_AC_Voltage_SF)?;
95 Ok(val.u16().scaled(scale))
96 }
97
98 pub fn ac_voltage_bc(&mut self) -> Result<f64, Error> {
100 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_VoltageBC, SunspecRegister::I_AC_Voltage_SF)?;
101 Ok(val.u16().scaled(scale))
102 }
103
104 pub fn ac_voltage_ca(&mut self) -> Result<f64, Error> {
106 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_VoltageCA, SunspecRegister::I_AC_Voltage_SF)?;
107 Ok(val.u16().scaled(scale))
108 }
109
110 pub fn ac_voltage_an(&mut self) -> Result<f64, Error> {
112 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_VoltageAN, SunspecRegister::I_AC_Voltage_SF)?;
113 Ok(val.u16().scaled(scale))
114 }
115
116 pub fn ac_voltage_bn(&mut self) -> Result<f64, Error> {
118 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_VoltageBN, SunspecRegister::I_AC_Voltage_SF)?;
119 Ok(val.u16().scaled(scale))
120 }
121
122 pub fn ac_voltage_cn(&mut self) -> Result<f64, Error> {
124 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_VoltageCN, SunspecRegister::I_AC_Voltage_SF)?;
125 Ok(val.u16().scaled(scale))
126 }
127
128 pub fn ac_power(&mut self) -> Result<f64, Error> {
130 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_Power, SunspecRegister::I_AC_Power_SF)?;
131 Ok(val.i16().scaled(scale))
132 }
133
134 pub fn ac_frequency(&mut self) -> Result<f64, Error> {
136 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_Frequency, SunspecRegister::I_AC_Frequency_SF)?;
137 Ok(val.u16().scaled(scale))
138 }
139
140 pub fn ac_va(&mut self) -> Result<f64, Error> {
142 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_VA, SunspecRegister::I_AC_VA_SF)?;
143 Ok(val.i16().scaled(scale))
144 }
145
146 pub fn ac_var(&mut self) -> Result<f64, Error> {
148 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_VAR2, SunspecRegister::I_AC_VAR_SF2)?;
149 Ok(val.i16().scaled(scale))
150 }
151
152 pub fn power_factor(&mut self) -> Result<f64, Error> {
154 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_PF1, SunspecRegister::I_AC_PF_SF1)?;
155 Ok(val.i16().scaled(scale))
156 }
157
158 pub fn energy_wh(&mut self) -> Result<f64, Error> {
160 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_Energy_WH, SunspecRegister::I_AC_Energy_WH_SF)?;
161 Ok(val.u32().scaled(scale))
162 }
163
164 pub fn dc_current(&mut self) -> Result<f64, Error> {
166 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_DC_Current, SunspecRegister::I_DC_Current_SF)?;
167 Ok(val.u16().scaled(scale))
168 }
169
170 pub fn dc_voltage(&mut self) -> Result<f64, Error> {
172 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_DC_Voltage, SunspecRegister::I_DC_Voltage_SF)?;
173 Ok(val.u16().scaled(scale))
174 }
175
176 pub fn dc_power(&mut self) -> Result<f64, Error> {
178 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_DC_Power, SunspecRegister::I_DC_Power_SF)?;
179 Ok(val.i16().scaled(scale))
180 }
181
182 pub fn temp_sink(&mut self) -> Result<f64, Error> {
184 let (val, scale) = self.read_register_with_scale(SunspecRegister::I_Temp_Sink, SunspecRegister::I_Temp_SF)?;
185 Ok(val.i16().scaled(scale))
186 }
187
188 pub fn status(&mut self) -> Result<InverterStatus, Error> {
190 let val = self.read_register(SunspecRegister::I_Status)?.u16();
191 Ok(InverterStatus::from_u16(val))
192 }
193
194 pub fn status_vendor(&mut self) -> Result<u16, Error> {
197 Ok(self.read_register(SunspecRegister::I_Status_Vendor)?.u16())
198 }
199
200 pub fn status_vendor4(&mut self) -> Result<u32, Error> {
203 Ok(self.read_register(SunspecRegister::I_Status_Vendor4)?.u32())
204 }
205
206 #[inline(always)]
207 fn read_register<R: Register>(&mut self, register: R) -> modbus::Result<Vec<u16>> {
208 self.client.read_holding_registers(register.address(), register.size())
209 }
210
211 #[inline(always)]
215 fn read_register_with_scale<R: Register, RS: Register>(
216 &mut self,
217 register: R,
218 scale_register: RS,
219 ) -> modbus::Result<(Vec<u16>, i16)> {
220 if register.address() + register.size() == scale_register.address() {
221 let mut combined = self
222 .client
223 .read_holding_registers(register.address(), register.size() + scale_register.size())?;
224 let scale = combined.pop().unwrap();
225 Ok((combined, scale as i16))
226 } else if scale_register.address() + scale_register.size() == register.address() {
227 let mut combined = self
228 .client
229 .read_holding_registers(scale_register.address(), scale_register.size() + register.size())?;
230 let scale = combined.remove(0);
231 Ok((combined, scale as i16))
232 } else {
233 Ok((self.read_register(register)?, self.read_register(scale_register)?.i16()))
234 }
235 }
236}
237
238#[derive(Debug, Clone, Copy)]
239pub enum PhaseCount {
240 SinglePhase,
241 SplitPhase,
242 ThreePhase,
243}
244
245impl PhaseCount {
246 pub fn from_u16(value: u16) -> Option<Self> {
247 match value {
248 101 => Some(PhaseCount::SinglePhase),
249 102 => Some(PhaseCount::SplitPhase),
250 103 => Some(PhaseCount::ThreePhase),
251 _ => None,
252 }
253 }
254}
255
256#[derive(Debug, Clone, Copy)]
257pub enum InverterStatus {
258 Off,
259 Sleeping,
261 Starting,
263 Mppt,
265 Throttled,
267 ShuttingDown,
268 Fault,
269 Standby,
271 Vendor(u16),
273}
274
275impl InverterStatus {
276 pub fn from_u16(value: u16) -> Self {
277 match value {
278 1 => InverterStatus::Off,
279 2 => InverterStatus::Sleeping,
280 3 => InverterStatus::Starting,
281 4 => InverterStatus::Mppt,
282 5 => InverterStatus::Throttled,
283 6 => InverterStatus::ShuttingDown,
284 7 => InverterStatus::Fault,
285 8 => InverterStatus::Standby,
286 v => InverterStatus::Vendor(v),
287 }
288 }
289}
290
291trait Register {
292 fn address(&self) -> u16;
293 fn size(&self) -> u16;
294}
295
296#[derive(Debug, Clone, Copy)]
297#[allow(non_camel_case_types)]
298enum SunspecRegister {
299 C_SunSpec_ID = 40000,
301 C_SunSpec_DID = 40002,
303 C_Manufacturer = 40004,
305 C_Model = 40020,
307 C_Version = 40044,
309 C_SerialNumber = 40052,
311 C_DeviceAddress = 40068,
313 I_PhaseCount = 40069,
317 I_AC_Current = 40071,
319 I_AC_CurrentA = 40072,
321 I_AC_CurrentB = 40073,
323 I_AC_CurrentC = 40074,
325 I_AC_Current_SF = 40075,
327 I_AC_VoltageAB = 40076,
329 I_AC_VoltageBC = 40077,
331 I_AC_VoltageCA = 40078,
333 I_AC_VoltageAN = 40079,
335 I_AC_VoltageBN = 40080,
337 I_AC_VoltageCN = 40081,
339 I_AC_Voltage_SF = 40082,
341 I_AC_Power = 40083,
343 I_AC_Power_SF = 40084,
345 I_AC_Frequency = 40085,
347 I_AC_Frequency_SF = 40086,
349 I_AC_VA = 40087,
351 I_AC_VA_SF = 40088,
353 I_AC_VAR2 = 40089,
355 I_AC_VAR_SF2 = 40090,
357 I_AC_PF1 = 40091,
359 I_AC_PF_SF1 = 40092,
361 I_AC_Energy_WH = 40093,
363 I_AC_Energy_WH_SF = 40095,
365 I_DC_Current = 40096,
367 I_DC_Current_SF = 40097,
369 I_DC_Voltage = 40098,
371 I_DC_Voltage_SF = 40099,
373 I_DC_Power = 40100,
375 I_DC_Power_SF = 40101,
377 I_Temp_Sink = 40103,
379 I_Temp_SF = 40106,
381 I_Status = 40107,
383 I_Status_Vendor = 40108,
386 I_Status_Vendor4 = 40119,
389}
390
391impl Register for SunspecRegister {
392 #[inline(always)]
393 fn address(&self) -> u16 {
394 *self as u16
395 }
396
397 #[inline(always)]
398 fn size(&self) -> u16 {
399 match self {
400 SunspecRegister::C_SunSpec_ID => 2,
401 SunspecRegister::C_SunSpec_DID => 1,
402 SunspecRegister::C_Manufacturer => 16,
403 SunspecRegister::C_Model => 16,
404 SunspecRegister::C_Version => 8,
405 SunspecRegister::C_SerialNumber => 16,
406 SunspecRegister::C_DeviceAddress => 1,
407 SunspecRegister::I_PhaseCount => 1,
408 SunspecRegister::I_AC_Current => 1,
409 SunspecRegister::I_AC_CurrentA => 1,
410 SunspecRegister::I_AC_CurrentB => 1,
411 SunspecRegister::I_AC_CurrentC => 1,
412 SunspecRegister::I_AC_Current_SF => 1,
413 SunspecRegister::I_AC_VoltageAB => 1,
414 SunspecRegister::I_AC_VoltageBC => 1,
415 SunspecRegister::I_AC_VoltageCA => 1,
416 SunspecRegister::I_AC_VoltageAN => 1,
417 SunspecRegister::I_AC_VoltageBN => 1,
418 SunspecRegister::I_AC_VoltageCN => 1,
419 SunspecRegister::I_AC_Voltage_SF => 1,
420 SunspecRegister::I_AC_Power => 1,
421 SunspecRegister::I_AC_Power_SF => 1,
422 SunspecRegister::I_AC_Frequency => 1,
423 SunspecRegister::I_AC_Frequency_SF => 1,
424 SunspecRegister::I_AC_VA => 1,
425 SunspecRegister::I_AC_VA_SF => 1,
426 SunspecRegister::I_AC_VAR2 => 1,
427 SunspecRegister::I_AC_VAR_SF2 => 1,
428 SunspecRegister::I_AC_PF1 => 1,
429 SunspecRegister::I_AC_PF_SF1 => 1,
430 SunspecRegister::I_AC_Energy_WH => 2,
431 SunspecRegister::I_AC_Energy_WH_SF => 1,
432 SunspecRegister::I_DC_Current => 1,
433 SunspecRegister::I_DC_Current_SF => 1,
434 SunspecRegister::I_DC_Voltage => 1,
435 SunspecRegister::I_DC_Voltage_SF => 1,
436 SunspecRegister::I_DC_Power => 1,
437 SunspecRegister::I_DC_Power_SF => 1,
438 SunspecRegister::I_Temp_Sink => 1,
439 SunspecRegister::I_Temp_SF => 1,
440 SunspecRegister::I_Status => 1,
441 SunspecRegister::I_Status_Vendor => 1,
442 SunspecRegister::I_Status_Vendor4 => 2,
443 }
444 }
445}
446
447trait RegisterResponse {
448 fn string(self) -> String;
449 fn u16(self) -> u16;
450 fn i16(self) -> i16;
451 fn u32(self) -> u32;
452}
453
454impl RegisterResponse for Vec<u16> {
455 fn string(self) -> String {
456 self
457 .into_iter()
458 .flat_map(|s| s.to_be_bytes())
459 .take_while(|&b| b != 0)
460 .map(char::from)
461 .collect()
462 }
463
464 fn u16(self) -> u16 {
465 self[0]
466 }
467
468 fn i16(self) -> i16 {
469 self[0] as i16
470 }
471
472 fn u32(self) -> u32 {
473 ((self[0] as u32) << 16) | self[1] as u32
474 }
475}
476
477trait Scaled {
478 fn scaled(self, scale: i16) -> f64;
479}
480
481impl Scaled for i16 {
482 fn scaled(self, scale: i16) -> f64 {
483 let value = self as f64;
484 value * 10f64.powi(i32::from(scale))
485 }
486}
487
488impl Scaled for u16 {
489 fn scaled(self, scale: i16) -> f64 {
490 let value = self as f64;
491 value * 10f64.powi(i32::from(scale))
492 }
493}
494
495impl Scaled for u32 {
496 fn scaled(self, scale: i16) -> f64 {
497 let value = self as f64;
498 value * 10f64.powi(i32::from(scale))
499 }
500}