use libmodbus_rs::{Modbus, ModbusClient, ModbusRTU}; use metrics::{gauge, Gauge, Label}; use crate::errors::TristarError; use super::{gauge_names, register_tristar_metrics}; const DEVICE_ID: u8 = 0x01; const RAM_DATA_SIZE: u16 = 0x005B; const RAM_ARRAY_SIZE: usize = RAM_DATA_SIZE as usize + 1; #[derive(Debug, Clone)] pub struct Scaling { pub v_scale: f64, pub i_scale: f64, } impl Scaling { fn from(data: &[u16]) -> Self { Self::from_internal(data[0], data[1], data[2], data[3]) } fn from_internal(v_pu_hi: u16, v_pu_lo: u16, i_pu_hi: u16, i_pu_lo: u16) -> Self { Self { v_scale: v_pu_hi as f64 + (v_pu_lo as f64 / f64::powf(2., 16.)), i_scale: i_pu_hi as f64 + (i_pu_lo as f64 / f64::powf(2., 16.)), } } fn get_voltage(&self, data: u16) -> f64 { data as f64 * self.v_scale * f64::powf(2., -15.) } fn get_current(&self, data: u16) -> f64 { data as f64 * self.i_scale * f64::powf(2., -15.) } fn get_power(&self, data: u16) -> f64 { data as f64 * self.v_scale * self.i_scale * f64::powf(2., -17.) } } pub struct Tristar { state: TristarState, gauges: TristarGauges, modbus: Modbus, data_in: [u16; RAM_ARRAY_SIZE], } pub struct TristarGauges { battery_voltage: Gauge, target_voltage: Gauge, input_current: Gauge, battery_temp: Gauge, charge_state: ChargeStateGauges, tristar_input_voltage: Gauge, tristar_charge_current: Gauge, tristar_power_out: Gauge, tristar_power_in: Gauge, tristar_max_array_power: Gauge, tristar_max_array_voltage: Gauge, tristar_open_circuit_voltage: Gauge, } impl TristarGauges { fn new(serial_port: String) -> Self { let device_labels = vec![ Label::new(gauge_names::CHARGE_CONTROLLER_LABEL, serial_port.clone()), Label::new(gauge_names::TRISTAR_LABEL, serial_port), ]; let battery_voltage = gauge!(gauge_names::BATTERY_VOLTAGE, device_labels.clone()); let target_voltage = gauge!(gauge_names::TARGET_VOLTAGE, device_labels.clone()); let input_current = gauge!(gauge_names::INPUT_CURRENT, device_labels.clone()); let battery_temp = gauge!(gauge_names::BATTERY_TEMP, device_labels.clone()); let charge_state = ChargeStateGauges::new(device_labels.clone()); let tristar_input_voltage = gauge!(gauge_names::TRISTAR_INPUT_VOLTAGE, device_labels.clone()); let tristar_charge_current = gauge!(gauge_names::TRISTAR_CHARGE_CURRENT, device_labels.clone()); let tristar_power_out = gauge!(gauge_names::TRISTAR_POWER_OUT, device_labels.clone()); let tristar_power_in = gauge!(gauge_names::TRISTAR_POWER_IN, device_labels.clone()); let tristar_max_array_power = gauge!(gauge_names::TRISTAR_MAX_ARRAY_POWER, device_labels.clone()); let tristar_max_array_voltage = gauge!( gauge_names::TRISTAR_MAX_ARRAY_VOLTAGE, device_labels.clone() ); let tristar_open_circuit_voltage = gauge!( gauge_names::TRISTAR_OPEN_CIRCUIT_VOLTAGE, device_labels.clone() ); Self { battery_voltage, target_voltage, input_current, battery_temp, charge_state, tristar_input_voltage, tristar_charge_current, tristar_power_out, tristar_power_in, tristar_max_array_power, tristar_max_array_voltage, tristar_open_circuit_voltage, } } } #[derive(Default, Debug, Clone, Copy)] pub struct TristarState { battery_voltage: f64, target_voltage: f64, input_current: f64, battery_temp: u16, charge_state: ChargeState, tristar_input_voltage: f64, tristar_charge_current: f64, tristar_power_out: f64, tristar_power_in: f64, tristar_max_array_power: f64, tristar_max_array_voltage: f64, tristar_open_circuit_voltage: f64, } impl TristarState { fn from_ram(scaling: Scaling, ram: &[u16]) -> Self { Self { battery_voltage: scaling.get_voltage(ram[TristarRamAddress::AdcVbFMed]), target_voltage: scaling.get_voltage(ram[TristarRamAddress::VbRef]), input_current: scaling.get_current(ram[TristarRamAddress::AdcIaFShadow]), battery_temp: ram[TristarRamAddress::Tbatt], charge_state: ChargeState::from(ram[TristarRamAddress::ChargeState]), tristar_input_voltage: scaling.get_voltage(ram[TristarRamAddress::AdcVaF]), tristar_charge_current: scaling.get_current(ram[TristarRamAddress::AdcIbFShadow]), tristar_power_out: scaling.get_power(ram[TristarRamAddress::PowerOutShadow]), tristar_power_in: scaling.get_power(ram[TristarRamAddress::PowerInShadow]), tristar_max_array_power: scaling.get_power(ram[TristarRamAddress::SweepPinMax]), tristar_max_array_voltage: scaling.get_voltage(ram[TristarRamAddress::SweepVmp]), tristar_open_circuit_voltage: scaling.get_voltage(ram[TristarRamAddress::SweepVoc]), } } } #[derive(Debug, Clone, Copy)] enum ChargeState { Start, NightCheck, Disconnect, Night, Fault, Mppt, Absorption, Float, Equalize, Slave, Unknown, } impl Default for ChargeState { fn default() -> Self { Self::Unknown } } impl From for ChargeState { fn from(value: u16) -> Self { match value { 0 => Self::Start, 1 => Self::NightCheck, 2 => Self::Disconnect, 3 => Self::Night, 4 => Self::Fault, 5 => Self::Mppt, 6 => Self::Absorption, 7 => Self::Float, 8 => Self::Equalize, 9 => Self::Slave, _ => Self::Unknown, } } } struct ChargeStateGauges { start: Gauge, night_check: Gauge, disconnect: Gauge, night: Gauge, fault: Gauge, mppt: Gauge, absorption: Gauge, float: Gauge, equalize: Gauge, slave: Gauge, unknown: Gauge, } impl ChargeStateGauges { fn new(labels: Vec