use modbus::{Client as _, tcp};
use crate::error::Error;
pub struct TcpClient {
client: tcp::Transport,
}
impl TcpClient {
pub fn new(address: &str, port: u16, device_id: u8) -> Result<Self, Error> {
Ok(TcpClient {
client: tcp::Transport::new_with_cfg(
address,
tcp::Config {
tcp_port: port,
modbus_uid: device_id,
..Default::default()
},
)?,
})
}
#[cfg(feature = "discover")]
pub fn new_from_host_info(host_info: &crate::SolaredgeHostInfo) -> Result<Self, Error> {
Self::new(&host_info.address(), host_info.port, host_info.modbus_id)
}
pub fn spec_id(&mut self) -> Result<String, Error> {
Ok(self.read_register(SunspecRegister::C_SunSpec_ID)?.string())
}
pub fn model_id(&mut self) -> Result<u16, Error> {
Ok(self.read_register(SunspecRegister::C_SunSpec_DID)?.u16())
}
pub fn manufacturer(&mut self) -> Result<String, Error> {
Ok(self.read_register(SunspecRegister::C_Manufacturer)?.string())
}
pub fn model(&mut self) -> Result<String, Error> {
Ok(self.read_register(SunspecRegister::C_Model)?.string())
}
pub fn version(&mut self) -> Result<String, Error> {
Ok(self.read_register(SunspecRegister::C_Version)?.string())
}
pub fn serial_number(&mut self) -> Result<String, Error> {
Ok(self.read_register(SunspecRegister::C_SerialNumber)?.string())
}
pub fn device_address(&mut self) -> Result<u16, Error> {
Ok(self.read_register(SunspecRegister::C_DeviceAddress)?.u16())
}
pub fn phase_count(&mut self) -> Result<PhaseCount, Error> {
let val = self.read_register(SunspecRegister::I_PhaseCount)?.u16();
PhaseCount::from_u16(val).ok_or(Error::InvalidPhaseCountValue(val))
}
pub fn ac_current(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_Current, SunspecRegister::I_AC_Current_SF)?;
Ok(val.u16().scaled(scale))
}
pub fn ac_current_a(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_CurrentA, SunspecRegister::I_AC_Current_SF)?;
Ok(val.u16().scaled(scale))
}
pub fn ac_current_b(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_CurrentB, SunspecRegister::I_AC_Current_SF)?;
Ok(val.u16().scaled(scale))
}
pub fn ac_current_c(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_CurrentC, SunspecRegister::I_AC_Current_SF)?;
Ok(val.u16().scaled(scale))
}
pub fn ac_voltage_ab(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_VoltageAB, SunspecRegister::I_AC_Voltage_SF)?;
Ok(val.u16().scaled(scale))
}
pub fn ac_voltage_bc(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_VoltageBC, SunspecRegister::I_AC_Voltage_SF)?;
Ok(val.u16().scaled(scale))
}
pub fn ac_voltage_ca(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_VoltageCA, SunspecRegister::I_AC_Voltage_SF)?;
Ok(val.u16().scaled(scale))
}
pub fn ac_voltage_an(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_VoltageAN, SunspecRegister::I_AC_Voltage_SF)?;
Ok(val.u16().scaled(scale))
}
pub fn ac_voltage_bn(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_VoltageBN, SunspecRegister::I_AC_Voltage_SF)?;
Ok(val.u16().scaled(scale))
}
pub fn ac_voltage_cn(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_VoltageCN, SunspecRegister::I_AC_Voltage_SF)?;
Ok(val.u16().scaled(scale))
}
pub fn ac_power(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_Power, SunspecRegister::I_AC_Power_SF)?;
Ok(val.i16().scaled(scale))
}
pub fn ac_frequency(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_Frequency, SunspecRegister::I_AC_Frequency_SF)?;
Ok(val.u16().scaled(scale))
}
pub fn ac_va(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_VA, SunspecRegister::I_AC_VA_SF)?;
Ok(val.i16().scaled(scale))
}
pub fn ac_var(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_VAR2, SunspecRegister::I_AC_VAR_SF2)?;
Ok(val.i16().scaled(scale))
}
pub fn power_factor(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_PF1, SunspecRegister::I_AC_PF_SF1)?;
Ok(val.i16().scaled(scale))
}
pub fn energy_wh(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_AC_Energy_WH, SunspecRegister::I_AC_Energy_WH_SF)?;
Ok(val.u32().scaled(scale))
}
pub fn dc_current(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_DC_Current, SunspecRegister::I_DC_Current_SF)?;
Ok(val.u16().scaled(scale))
}
pub fn dc_voltage(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_DC_Voltage, SunspecRegister::I_DC_Voltage_SF)?;
Ok(val.u16().scaled(scale))
}
pub fn dc_power(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_DC_Power, SunspecRegister::I_DC_Power_SF)?;
Ok(val.i16().scaled(scale))
}
pub fn temp_sink(&mut self) -> Result<f64, Error> {
let (val, scale) = self.read_register_with_scale(SunspecRegister::I_Temp_Sink, SunspecRegister::I_Temp_SF)?;
Ok(val.i16().scaled(scale))
}
pub fn status(&mut self) -> Result<InverterStatus, Error> {
let val = self.read_register(SunspecRegister::I_Status)?.u16();
Ok(InverterStatus::from_u16(val))
}
pub fn status_vendor(&mut self) -> Result<u16, Error> {
Ok(self.read_register(SunspecRegister::I_Status_Vendor)?.u16())
}
pub fn status_vendor4(&mut self) -> Result<u32, Error> {
Ok(self.read_register(SunspecRegister::I_Status_Vendor4)?.u32())
}
#[inline(always)]
fn read_register<R: Register>(&mut self, register: R) -> modbus::Result<Vec<u16>> {
self.client.read_holding_registers(register.address(), register.size())
}
#[inline(always)]
fn read_register_with_scale<R: Register, RS: Register>(
&mut self,
register: R,
scale_register: RS,
) -> modbus::Result<(Vec<u16>, i16)> {
if register.address() + register.size() == scale_register.address() {
let mut combined = self
.client
.read_holding_registers(register.address(), register.size() + scale_register.size())?;
let scale = combined.pop().unwrap();
Ok((combined, scale as i16))
} else if scale_register.address() + scale_register.size() == register.address() {
let mut combined = self
.client
.read_holding_registers(scale_register.address(), scale_register.size() + register.size())?;
let scale = combined.remove(0);
Ok((combined, scale as i16))
} else {
Ok((self.read_register(register)?, self.read_register(scale_register)?.i16()))
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum PhaseCount {
SinglePhase,
SplitPhase,
ThreePhase,
}
impl PhaseCount {
pub fn from_u16(value: u16) -> Option<Self> {
match value {
101 => Some(PhaseCount::SinglePhase),
102 => Some(PhaseCount::SplitPhase),
103 => Some(PhaseCount::ThreePhase),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum InverterStatus {
Off,
Sleeping,
Starting,
Mppt,
Throttled,
ShuttingDown,
Fault,
Standby,
Vendor(u16),
}
impl InverterStatus {
pub fn from_u16(value: u16) -> Self {
match value {
1 => InverterStatus::Off,
2 => InverterStatus::Sleeping,
3 => InverterStatus::Starting,
4 => InverterStatus::Mppt,
5 => InverterStatus::Throttled,
6 => InverterStatus::ShuttingDown,
7 => InverterStatus::Fault,
8 => InverterStatus::Standby,
v => InverterStatus::Vendor(v),
}
}
}
trait Register {
fn address(&self) -> u16;
fn size(&self) -> u16;
}
#[derive(Debug, Clone, Copy)]
#[allow(non_camel_case_types)]
enum SunspecRegister {
C_SunSpec_ID = 40000,
C_SunSpec_DID = 40002,
C_Manufacturer = 40004,
C_Model = 40020,
C_Version = 40044,
C_SerialNumber = 40052,
C_DeviceAddress = 40068,
I_PhaseCount = 40069,
I_AC_Current = 40071,
I_AC_CurrentA = 40072,
I_AC_CurrentB = 40073,
I_AC_CurrentC = 40074,
I_AC_Current_SF = 40075,
I_AC_VoltageAB = 40076,
I_AC_VoltageBC = 40077,
I_AC_VoltageCA = 40078,
I_AC_VoltageAN = 40079,
I_AC_VoltageBN = 40080,
I_AC_VoltageCN = 40081,
I_AC_Voltage_SF = 40082,
I_AC_Power = 40083,
I_AC_Power_SF = 40084,
I_AC_Frequency = 40085,
I_AC_Frequency_SF = 40086,
I_AC_VA = 40087,
I_AC_VA_SF = 40088,
I_AC_VAR2 = 40089,
I_AC_VAR_SF2 = 40090,
I_AC_PF1 = 40091,
I_AC_PF_SF1 = 40092,
I_AC_Energy_WH = 40093,
I_AC_Energy_WH_SF = 40095,
I_DC_Current = 40096,
I_DC_Current_SF = 40097,
I_DC_Voltage = 40098,
I_DC_Voltage_SF = 40099,
I_DC_Power = 40100,
I_DC_Power_SF = 40101,
I_Temp_Sink = 40103,
I_Temp_SF = 40106,
I_Status = 40107,
I_Status_Vendor = 40108,
I_Status_Vendor4 = 40119,
}
impl Register for SunspecRegister {
#[inline(always)]
fn address(&self) -> u16 {
*self as u16
}
#[inline(always)]
fn size(&self) -> u16 {
match self {
SunspecRegister::C_SunSpec_ID => 2,
SunspecRegister::C_SunSpec_DID => 1,
SunspecRegister::C_Manufacturer => 16,
SunspecRegister::C_Model => 16,
SunspecRegister::C_Version => 8,
SunspecRegister::C_SerialNumber => 16,
SunspecRegister::C_DeviceAddress => 1,
SunspecRegister::I_PhaseCount => 1,
SunspecRegister::I_AC_Current => 1,
SunspecRegister::I_AC_CurrentA => 1,
SunspecRegister::I_AC_CurrentB => 1,
SunspecRegister::I_AC_CurrentC => 1,
SunspecRegister::I_AC_Current_SF => 1,
SunspecRegister::I_AC_VoltageAB => 1,
SunspecRegister::I_AC_VoltageBC => 1,
SunspecRegister::I_AC_VoltageCA => 1,
SunspecRegister::I_AC_VoltageAN => 1,
SunspecRegister::I_AC_VoltageBN => 1,
SunspecRegister::I_AC_VoltageCN => 1,
SunspecRegister::I_AC_Voltage_SF => 1,
SunspecRegister::I_AC_Power => 1,
SunspecRegister::I_AC_Power_SF => 1,
SunspecRegister::I_AC_Frequency => 1,
SunspecRegister::I_AC_Frequency_SF => 1,
SunspecRegister::I_AC_VA => 1,
SunspecRegister::I_AC_VA_SF => 1,
SunspecRegister::I_AC_VAR2 => 1,
SunspecRegister::I_AC_VAR_SF2 => 1,
SunspecRegister::I_AC_PF1 => 1,
SunspecRegister::I_AC_PF_SF1 => 1,
SunspecRegister::I_AC_Energy_WH => 2,
SunspecRegister::I_AC_Energy_WH_SF => 1,
SunspecRegister::I_DC_Current => 1,
SunspecRegister::I_DC_Current_SF => 1,
SunspecRegister::I_DC_Voltage => 1,
SunspecRegister::I_DC_Voltage_SF => 1,
SunspecRegister::I_DC_Power => 1,
SunspecRegister::I_DC_Power_SF => 1,
SunspecRegister::I_Temp_Sink => 1,
SunspecRegister::I_Temp_SF => 1,
SunspecRegister::I_Status => 1,
SunspecRegister::I_Status_Vendor => 1,
SunspecRegister::I_Status_Vendor4 => 2,
}
}
}
trait RegisterResponse {
fn string(self) -> String;
fn u16(self) -> u16;
fn i16(self) -> i16;
fn u32(self) -> u32;
}
impl RegisterResponse for Vec<u16> {
fn string(self) -> String {
self
.into_iter()
.flat_map(|s| s.to_be_bytes())
.take_while(|&b| b != 0)
.map(char::from)
.collect()
}
fn u16(self) -> u16 {
self[0]
}
fn i16(self) -> i16 {
self[0] as i16
}
fn u32(self) -> u32 {
((self[0] as u32) << 16) | self[1] as u32
}
}
trait Scaled {
fn scaled(self, scale: i16) -> f64;
}
impl Scaled for i16 {
fn scaled(self, scale: i16) -> f64 {
let value = self as f64;
value * 10f64.powi(i32::from(scale))
}
}
impl Scaled for u16 {
fn scaled(self, scale: i16) -> f64 {
let value = self as f64;
value * 10f64.powi(i32::from(scale))
}
}
impl Scaled for u32 {
fn scaled(self, scale: i16) -> f64 {
let value = self as f64;
value * 10f64.powi(i32::from(scale))
}
}