tristar run 1
This commit is contained in:
parent
0247bcc607
commit
826918d67a
|
@ -1,5 +1,6 @@
|
||||||
pub const CHARGE_CONTROLLER_LABEL: &str = "charge_controller";
|
pub const CHARGE_CONTROLLER_LABEL: &str = "charge_controller";
|
||||||
pub const PL_LABEL: &str = "pl_device";
|
pub const PL_LABEL: &str = "pl_device";
|
||||||
|
pub const TRISTAR_LABEL: &str = "tristar_device";
|
||||||
|
|
||||||
pub const BATTERY_VOLTAGE: &str = "battery_voltage";
|
pub const BATTERY_VOLTAGE: &str = "battery_voltage";
|
||||||
pub const TARGET_VOLTAGE: &str = "target_voltage";
|
pub const TARGET_VOLTAGE: &str = "target_voltage";
|
||||||
|
@ -9,3 +10,11 @@ pub const BATTERY_TEMP: &str = "battery_temp";
|
||||||
|
|
||||||
pub const PL_DUTY_CYCLE: &str = "pl_duty_cycle";
|
pub const PL_DUTY_CYCLE: &str = "pl_duty_cycle";
|
||||||
pub const PL_LOAD_CURRENT: &str = "pl_internal_load_current";
|
pub const PL_LOAD_CURRENT: &str = "pl_internal_load_current";
|
||||||
|
|
||||||
|
pub const TRISTAR_INPUT_VOLTAGE: &str = "tristar_input_voltage";
|
||||||
|
pub const TRISTAR_CHARGE_CURRENT: &str = "tristar_charge_current";
|
||||||
|
pub const TRISTAR_POWER_OUT: &str = "tristar_power_out";
|
||||||
|
pub const TRISTAR_POWER_IN: &str = "tristar_power_in";
|
||||||
|
pub const TRISTAR_MAX_ARRAY_POWER: &str = "tristar_max_array_power";
|
||||||
|
pub const TRISTAR_MAX_ARRAY_VOLTAGE: &str = "tristar_max_array_voltage";
|
||||||
|
pub const TRISTAR_OPEN_CIRCUIT_VOLTAGE: &str = "tristar_open_circuit_voltage";
|
||||||
|
|
|
@ -1,18 +1,54 @@
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use metrics::{describe_gauge, Unit};
|
use metrics::{describe_gauge, Unit};
|
||||||
|
|
||||||
mod gauge_names;
|
mod gauge_names;
|
||||||
pub mod pl;
|
pub mod pl;
|
||||||
|
pub mod tristar;
|
||||||
|
|
||||||
pub fn register_metrics() {
|
static HAS_REGISTERED_SHARED: OnceLock<()> = OnceLock::new();
|
||||||
|
static HAS_REGISTERED_PL: OnceLock<()> = OnceLock::new();
|
||||||
|
static HAS_REGISTERED_TRISTAR: OnceLock<()> = OnceLock::new();
|
||||||
|
|
||||||
|
fn register_metrics() {
|
||||||
|
if HAS_REGISTERED_SHARED.get().is_none() {
|
||||||
describe_gauge!(gauge_names::BATTERY_VOLTAGE, "Battery voltage");
|
describe_gauge!(gauge_names::BATTERY_VOLTAGE, "Battery voltage");
|
||||||
describe_gauge!(gauge_names::TARGET_VOLTAGE, "Target voltage");
|
describe_gauge!(gauge_names::TARGET_VOLTAGE, "Target voltage");
|
||||||
describe_gauge!(gauge_names::INPUT_CURRENT, "Internal charge current");
|
describe_gauge!(gauge_names::INPUT_CURRENT, "Internal charge current");
|
||||||
describe_gauge!(gauge_names::CHARGE_STATE, "Regulator state");
|
describe_gauge!(gauge_names::CHARGE_STATE, "Regulator state");
|
||||||
describe_gauge!(gauge_names::BATTERY_TEMP, "Battery temperature");
|
describe_gauge!(gauge_names::BATTERY_TEMP, "Battery temperature");
|
||||||
register_pl_metrics();
|
|
||||||
|
HAS_REGISTERED_SHARED.get_or_init(|| ());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_pl_metrics() {
|
pub fn register_pl_metrics() {
|
||||||
|
register_metrics();
|
||||||
|
if HAS_REGISTERED_PL.get().is_none() {
|
||||||
describe_gauge!(gauge_names::PL_DUTY_CYCLE, Unit::Percent, "Duty cycle");
|
describe_gauge!(gauge_names::PL_DUTY_CYCLE, Unit::Percent, "Duty cycle");
|
||||||
describe_gauge!(gauge_names::PL_LOAD_CURRENT, "Internal load current");
|
describe_gauge!(gauge_names::PL_LOAD_CURRENT, "Internal load current");
|
||||||
|
|
||||||
|
HAS_REGISTERED_PL.get_or_init(|| ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_tristar_metrics() {
|
||||||
|
register_metrics();
|
||||||
|
if HAS_REGISTERED_TRISTAR.get().is_none() {
|
||||||
|
describe_gauge!(gauge_names::TRISTAR_INPUT_VOLTAGE, "Input voltage");
|
||||||
|
describe_gauge!(gauge_names::TRISTAR_CHARGE_CURRENT, "Charge current");
|
||||||
|
describe_gauge!(gauge_names::TRISTAR_POWER_OUT, "Power out");
|
||||||
|
describe_gauge!(gauge_names::TRISTAR_POWER_IN, "Power in");
|
||||||
|
describe_gauge!(gauge_names::TRISTAR_MAX_ARRAY_POWER, "Maximum array power");
|
||||||
|
describe_gauge!(
|
||||||
|
gauge_names::TRISTAR_MAX_ARRAY_VOLTAGE,
|
||||||
|
"Maximum array voltage"
|
||||||
|
);
|
||||||
|
describe_gauge!(
|
||||||
|
gauge_names::TRISTAR_OPEN_CIRCUIT_VOLTAGE,
|
||||||
|
"Open circuit voltage"
|
||||||
|
);
|
||||||
|
|
||||||
|
HAS_REGISTERED_TRISTAR.get_or_init(|| ());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ use serialport::SerialPort;
|
||||||
|
|
||||||
use crate::errors::{PliError, PrintErrors};
|
use crate::errors::{PliError, PrintErrors};
|
||||||
|
|
||||||
use super::gauge_names;
|
use super::{gauge_names, register_pl_metrics};
|
||||||
|
|
||||||
pub struct Pli {
|
pub struct Pli {
|
||||||
pub state: Arc<RwLock<PlState>>,
|
pub state: Arc<RwLock<PlState>>,
|
||||||
|
@ -152,6 +152,8 @@ impl Pli {
|
||||||
.timeout(Duration::from_millis(timeout))
|
.timeout(Duration::from_millis(timeout))
|
||||||
.open()?;
|
.open()?;
|
||||||
|
|
||||||
|
register_pl_metrics();
|
||||||
|
|
||||||
let device_labels = vec![
|
let device_labels = vec![
|
||||||
Label::new(gauge_names::CHARGE_CONTROLLER_LABEL, serial_port.clone()),
|
Label::new(gauge_names::CHARGE_CONTROLLER_LABEL, serial_port.clone()),
|
||||||
Label::new(gauge_names::PL_LABEL, serial_port),
|
Label::new(gauge_names::PL_LABEL, serial_port),
|
||||||
|
|
447
src/charge_controllers/tristar.rs
Normal file
447
src/charge_controllers/tristar.rs
Normal file
|
@ -0,0 +1,447 @@
|
||||||
|
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<u16> 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<Label>) -> Self {
|
||||||
|
let start = gauge!(
|
||||||
|
gauge_names::CHARGE_STATE,
|
||||||
|
[
|
||||||
|
labels.clone(),
|
||||||
|
vec![Label::new("state", String::from("start"))]
|
||||||
|
]
|
||||||
|
.concat()
|
||||||
|
);
|
||||||
|
let night_check = gauge!(
|
||||||
|
gauge_names::CHARGE_STATE,
|
||||||
|
[
|
||||||
|
labels.clone(),
|
||||||
|
vec![Label::new("state", String::from("night_check"))]
|
||||||
|
]
|
||||||
|
.concat()
|
||||||
|
);
|
||||||
|
let disconnect = gauge!(
|
||||||
|
gauge_names::CHARGE_STATE,
|
||||||
|
[
|
||||||
|
labels.clone(),
|
||||||
|
vec![Label::new("state", String::from("disconnect"))]
|
||||||
|
]
|
||||||
|
.concat()
|
||||||
|
);
|
||||||
|
let night = gauge!(
|
||||||
|
gauge_names::CHARGE_STATE,
|
||||||
|
[
|
||||||
|
labels.clone(),
|
||||||
|
vec![Label::new("state", String::from("night"))]
|
||||||
|
]
|
||||||
|
.concat()
|
||||||
|
);
|
||||||
|
let fault = gauge!(
|
||||||
|
gauge_names::CHARGE_STATE,
|
||||||
|
[
|
||||||
|
labels.clone(),
|
||||||
|
vec![Label::new("state", String::from("fault"))]
|
||||||
|
]
|
||||||
|
.concat()
|
||||||
|
);
|
||||||
|
let mppt = gauge!(
|
||||||
|
gauge_names::CHARGE_STATE,
|
||||||
|
[
|
||||||
|
labels.clone(),
|
||||||
|
vec![Label::new("state", String::from("mppt"))]
|
||||||
|
]
|
||||||
|
.concat()
|
||||||
|
);
|
||||||
|
let absorption = gauge!(
|
||||||
|
gauge_names::CHARGE_STATE,
|
||||||
|
[
|
||||||
|
labels.clone(),
|
||||||
|
vec![Label::new("state", String::from("absorption"))]
|
||||||
|
]
|
||||||
|
.concat()
|
||||||
|
);
|
||||||
|
let float = gauge!(
|
||||||
|
gauge_names::CHARGE_STATE,
|
||||||
|
[
|
||||||
|
labels.clone(),
|
||||||
|
vec![Label::new("state", String::from("float"))]
|
||||||
|
]
|
||||||
|
.concat()
|
||||||
|
);
|
||||||
|
let equalize = gauge!(
|
||||||
|
gauge_names::CHARGE_STATE,
|
||||||
|
[
|
||||||
|
labels.clone(),
|
||||||
|
vec![Label::new("state", String::from("equalize"))]
|
||||||
|
]
|
||||||
|
.concat()
|
||||||
|
);
|
||||||
|
let slave = gauge!(
|
||||||
|
gauge_names::CHARGE_STATE,
|
||||||
|
[
|
||||||
|
labels.clone(),
|
||||||
|
vec![Label::new("state", String::from("slave"))]
|
||||||
|
]
|
||||||
|
.concat()
|
||||||
|
);
|
||||||
|
let unknown = gauge!(
|
||||||
|
gauge_names::CHARGE_STATE,
|
||||||
|
[
|
||||||
|
labels.clone(),
|
||||||
|
vec![Label::new("state", String::from("unknown"))]
|
||||||
|
]
|
||||||
|
.concat()
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
start,
|
||||||
|
night_check,
|
||||||
|
disconnect,
|
||||||
|
night,
|
||||||
|
fault,
|
||||||
|
mppt,
|
||||||
|
absorption,
|
||||||
|
float,
|
||||||
|
equalize,
|
||||||
|
slave,
|
||||||
|
unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zero_all(&mut self) {
|
||||||
|
self.start.set(0.);
|
||||||
|
self.night_check.set(0.);
|
||||||
|
self.disconnect.set(0.);
|
||||||
|
self.night.set(0.);
|
||||||
|
self.fault.set(0.);
|
||||||
|
self.mppt.set(0.);
|
||||||
|
self.absorption.set(0.);
|
||||||
|
self.float.set(0.);
|
||||||
|
self.equalize.set(0.);
|
||||||
|
self.slave.set(0.);
|
||||||
|
self.unknown.set(0.);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(&mut self, state: ChargeState) {
|
||||||
|
match state {
|
||||||
|
ChargeState::Start => {
|
||||||
|
self.zero_all();
|
||||||
|
self.start.set(1.);
|
||||||
|
}
|
||||||
|
ChargeState::NightCheck => {
|
||||||
|
self.zero_all();
|
||||||
|
self.night_check.set(1.);
|
||||||
|
}
|
||||||
|
ChargeState::Disconnect => {
|
||||||
|
self.zero_all();
|
||||||
|
self.disconnect.set(1.);
|
||||||
|
}
|
||||||
|
ChargeState::Night => {
|
||||||
|
self.zero_all();
|
||||||
|
self.night.set(1.);
|
||||||
|
}
|
||||||
|
ChargeState::Fault => {
|
||||||
|
self.zero_all();
|
||||||
|
self.fault.set(1.);
|
||||||
|
}
|
||||||
|
ChargeState::Mppt => {
|
||||||
|
self.zero_all();
|
||||||
|
self.mppt.set(1.);
|
||||||
|
}
|
||||||
|
ChargeState::Absorption => {
|
||||||
|
self.zero_all();
|
||||||
|
self.absorption.set(1.);
|
||||||
|
}
|
||||||
|
ChargeState::Float => {
|
||||||
|
self.zero_all();
|
||||||
|
self.float.set(1.);
|
||||||
|
}
|
||||||
|
ChargeState::Equalize => {
|
||||||
|
self.zero_all();
|
||||||
|
self.equalize.set(1.);
|
||||||
|
}
|
||||||
|
ChargeState::Slave => {
|
||||||
|
self.zero_all();
|
||||||
|
self.slave.set(1.);
|
||||||
|
}
|
||||||
|
ChargeState::Unknown => {
|
||||||
|
self.zero_all();
|
||||||
|
self.unknown.set(1.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tristar {
|
||||||
|
pub fn new(serial_port: String, baud_rate: i32) -> Result<Self, TristarError> {
|
||||||
|
let parity = 'N';
|
||||||
|
let data_bit = 8;
|
||||||
|
let stop_bit = 2;
|
||||||
|
let mut modbus = Modbus::new_rtu(&serial_port, baud_rate, parity, data_bit, stop_bit)?;
|
||||||
|
modbus.set_slave(DEVICE_ID)?;
|
||||||
|
modbus.connect()?;
|
||||||
|
register_tristar_metrics();
|
||||||
|
Ok(Self {
|
||||||
|
state: Default::default(),
|
||||||
|
gauges: TristarGauges::new(serial_port.clone()),
|
||||||
|
modbus,
|
||||||
|
data_in: [0; RAM_ARRAY_SIZE],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh(&mut self) {
|
||||||
|
if let Ok(new_state) = self
|
||||||
|
.get_data()
|
||||||
|
.map(|scaling| TristarState::from_ram(scaling, &self.data_in))
|
||||||
|
{
|
||||||
|
self.gauges.battery_voltage.set(new_state.battery_voltage);
|
||||||
|
self.gauges.target_voltage.set(new_state.target_voltage);
|
||||||
|
self.gauges.input_current.set(new_state.input_current);
|
||||||
|
self.gauges.battery_temp.set(new_state.battery_temp as f64);
|
||||||
|
self.gauges.charge_state.set(new_state.charge_state);
|
||||||
|
self.gauges
|
||||||
|
.tristar_input_voltage
|
||||||
|
.set(new_state.tristar_input_voltage);
|
||||||
|
self.gauges
|
||||||
|
.tristar_charge_current
|
||||||
|
.set(new_state.tristar_charge_current);
|
||||||
|
self.gauges
|
||||||
|
.tristar_power_out
|
||||||
|
.set(new_state.tristar_power_out);
|
||||||
|
self.gauges.tristar_power_in.set(new_state.tristar_power_in);
|
||||||
|
self.gauges
|
||||||
|
.tristar_max_array_power
|
||||||
|
.set(new_state.tristar_max_array_power);
|
||||||
|
self.gauges
|
||||||
|
.tristar_max_array_voltage
|
||||||
|
.set(new_state.tristar_max_array_voltage);
|
||||||
|
self.gauges
|
||||||
|
.tristar_open_circuit_voltage
|
||||||
|
.set(new_state.tristar_open_circuit_voltage);
|
||||||
|
self.state = new_state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_data(&mut self) -> Result<Scaling, TristarError> {
|
||||||
|
self.modbus
|
||||||
|
.read_registers(0x0000, RAM_DATA_SIZE + 1, &mut self.data_in)?;
|
||||||
|
let scaling = Scaling::from(&self.data_in);
|
||||||
|
Ok(scaling)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TristarRamAddress {
|
||||||
|
AdcVbFMed = 25,
|
||||||
|
AdcVaF = 28,
|
||||||
|
Tbatt = 38,
|
||||||
|
AdcIbFShadow = 29,
|
||||||
|
AdcIaFShadow = 30,
|
||||||
|
ChargeState = 51,
|
||||||
|
VbRef = 52,
|
||||||
|
PowerOutShadow = 59,
|
||||||
|
PowerInShadow = 60,
|
||||||
|
SweepPinMax = 61,
|
||||||
|
SweepVmp = 62,
|
||||||
|
SweepVoc = 63,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Index<TristarRamAddress> for [u16] {
|
||||||
|
type Output = u16;
|
||||||
|
|
||||||
|
fn index(&self, index: TristarRamAddress) -> &Self::Output {
|
||||||
|
&self[index as usize]
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,22 @@ pub struct Config {
|
||||||
pub max_rate: i64,
|
pub max_rate: i64,
|
||||||
pub duty_cycle_too_high: f64,
|
pub duty_cycle_too_high: f64,
|
||||||
pub duty_cycle_too_low: f64,
|
pub duty_cycle_too_low: f64,
|
||||||
|
pub additional_charge_controllers: Vec<ChargeControllerConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub enum ChargeControllerConfig {
|
||||||
|
Pl {
|
||||||
|
serial_port: String,
|
||||||
|
baud_rate: u32,
|
||||||
|
timeout_milliseconds: u64,
|
||||||
|
watch_interval_seconds: u64,
|
||||||
|
},
|
||||||
|
Tristar {
|
||||||
|
serial_port: String,
|
||||||
|
baud_rate: i32,
|
||||||
|
watch_interval_seconds: u64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
@ -52,6 +68,7 @@ impl Default for Config {
|
||||||
max_rate: 10,
|
max_rate: 10,
|
||||||
duty_cycle_too_high: 0.9,
|
duty_cycle_too_high: 0.9,
|
||||||
duty_cycle_too_low: 0.7,
|
duty_cycle_too_low: 0.7,
|
||||||
|
additional_charge_controllers: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,3 +91,15 @@ pub enum TeslaStateParseError {
|
||||||
#[error("Invalid timestamp")]
|
#[error("Invalid timestamp")]
|
||||||
InvalidTimestamp,
|
InvalidTimestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum TristarError {
|
||||||
|
#[error("modbus error")]
|
||||||
|
Modbus(libmodbus_rs::prelude::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<libmodbus_rs::prelude::Error> for TristarError {
|
||||||
|
fn from(value: libmodbus_rs::prelude::Error) -> Self {
|
||||||
|
Self::Modbus(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
48
src/main.rs
48
src/main.rs
|
@ -2,9 +2,9 @@
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
use api_interface::TeslaInterface;
|
use api_interface::TeslaInterface;
|
||||||
use charge_controllers::pl::Pli;
|
use charge_controllers::{pl::Pli, tristar::Tristar};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use config::{access_config, CONFIG_PATH};
|
use config::{access_config, ChargeControllerConfig, CONFIG_PATH};
|
||||||
use errors::PrintErrors;
|
use errors::PrintErrors;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tesla_charge_rate::TeslaChargeRateController;
|
use tesla_charge_rate::TeslaChargeRateController;
|
||||||
|
@ -77,8 +77,6 @@ async fn main() {
|
||||||
// and to the charge rate controller thread
|
// and to the charge rate controller thread
|
||||||
let (tcrc_requests, tcrc_receiver) = async_channel::unbounded();
|
let (tcrc_requests, tcrc_receiver) = async_channel::unbounded();
|
||||||
|
|
||||||
charge_controllers::register_metrics();
|
|
||||||
|
|
||||||
// try to spawn the pli loop
|
// try to spawn the pli loop
|
||||||
let pl_state = match Pli::new(
|
let pl_state = match Pli::new(
|
||||||
config.serial_port.clone(),
|
config.serial_port.clone(),
|
||||||
|
@ -109,6 +107,48 @@ async fn main() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let _additional_controllers: Vec<_> = config
|
||||||
|
.additional_charge_controllers
|
||||||
|
.iter()
|
||||||
|
.filter_map(|v| match v {
|
||||||
|
ChargeControllerConfig::Pl {
|
||||||
|
serial_port,
|
||||||
|
baud_rate,
|
||||||
|
timeout_milliseconds,
|
||||||
|
watch_interval_seconds,
|
||||||
|
} => Pli::new(serial_port.clone(), *baud_rate, *timeout_milliseconds)
|
||||||
|
.some_or_print_with("Failed to connect to additional PLI")
|
||||||
|
.map(|mut pli| {
|
||||||
|
tokio::task::spawn(async move {
|
||||||
|
let mut interval = tokio::time::interval(
|
||||||
|
std::time::Duration::from_secs(*watch_interval_seconds),
|
||||||
|
);
|
||||||
|
loop {
|
||||||
|
interval.tick().await;
|
||||||
|
pli.refresh();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
ChargeControllerConfig::Tristar {
|
||||||
|
serial_port,
|
||||||
|
baud_rate,
|
||||||
|
watch_interval_seconds,
|
||||||
|
} => Tristar::new(serial_port.clone(), *baud_rate)
|
||||||
|
.some_or_print_with("Failed to connect to additional Tristar")
|
||||||
|
.map(|mut tristar| {
|
||||||
|
tokio::task::spawn_local(async move {
|
||||||
|
let mut interval = tokio::time::interval(
|
||||||
|
std::time::Duration::from_secs(*watch_interval_seconds),
|
||||||
|
);
|
||||||
|
loop {
|
||||||
|
interval.tick().await;
|
||||||
|
tristar.refresh();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let mut tesla_charge_rate_controller =
|
let mut tesla_charge_rate_controller =
|
||||||
TeslaChargeRateController::new(interface.state.clone(), pl_state.clone());
|
TeslaChargeRateController::new(interface.state.clone(), pl_state.clone());
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue