tesla-charge-controller/src/pl_interface.rs

129 lines
4 KiB
Rust
Raw Normal View History

2024-01-11 08:57:41 +11:00
use std::{io::Write, time::Duration};
2024-01-10 15:22:28 +11:00
use anyhow::Context;
2024-01-10 15:22:28 +11:00
use metrics::{describe_gauge, gauge, Gauge};
use serialport::SerialPort;
2024-01-11 08:57:41 +11:00
use termcolor::WriteColor;
2024-01-10 15:22:28 +11:00
pub struct Pli {
port: Box<dyn SerialPort>,
voltage_gauge: Gauge,
duty_cycle: Gauge,
2024-01-10 16:27:02 +11:00
internal_charge_current: Gauge,
internal_load_current: Gauge,
2024-01-10 15:22:28 +11:00
}
2024-01-11 08:57:41 +11:00
#[derive(Debug, Clone, Copy)]
pub enum PliRequest {
ReadRam(u8),
}
2024-01-10 15:22:28 +11:00
impl Pli {
pub fn new(serial_port: String, baud_rate: u32) -> anyhow::Result<Self> {
let port = serialport::new(serial_port, baud_rate)
.timeout(Duration::from_millis(250))
.open()?;
describe_gauge!("pl_battery_voltage", "Battery voltage");
let voltage_gauge = gauge!("pl_battery_voltage");
describe_gauge!("pl_duty_cycle", "Duty cycle");
let duty_cycle = gauge!("pl_duty_cycle");
2024-01-10 16:27:02 +11:00
describe_gauge!("pl_internal_charge_current", "Internal charge current");
let internal_charge_current = gauge!("pl_internal_charge_current");
describe_gauge!("pl_internal_load_current", "Internal load current");
let internal_load_current = gauge!("pl_internal_load_current");
2024-01-10 15:22:28 +11:00
Ok(Self {
port,
voltage_gauge,
duty_cycle,
2024-01-10 16:27:02 +11:00
internal_charge_current,
internal_load_current,
2024-01-10 15:22:28 +11:00
})
}
pub fn refresh(&mut self) {
2024-01-10 16:27:02 +11:00
if let Ok(batv) = self.read_ram(PlRamAddress::Batv) {
2024-01-10 15:48:59 +11:00
self.voltage_gauge.set((batv as f64) * (4. / 10.));
}
2024-01-10 16:27:02 +11:00
if let Ok(duty_cycle) = self.read_ram(PlRamAddress::Dutycyc) {
self.duty_cycle.set((duty_cycle as f64) / 255.);
}
2024-01-10 16:27:02 +11:00
if let Ok(internal_charge_current) = self.read_ram(PlRamAddress::Cint) {
self.internal_charge_current
.set((internal_charge_current as f64) * (4. / 10.));
}
if let Ok(internal_load_current) = self.read_ram(PlRamAddress::Lint) {
self.internal_load_current
.set((internal_load_current as f64) * (4. / 10.));
}
2024-01-10 15:22:28 +11:00
}
2024-01-11 08:57:41 +11:00
pub fn process_request(&mut self, message: PliRequest) {
match message {
PliRequest::ReadRam(address) => match self.read_ram(address) {
Ok(data) => {
let mut stdout =
termcolor::StandardStream::stdout(termcolor::ColorChoice::Always);
let _ = stdout.set_color(
termcolor::ColorSpec::new().set_fg(Some(termcolor::Color::Green)),
);
if writeln!(&mut stdout, "Read RAM at {address}: data {data}").is_err() {
log::warn!(
"Failed to set stdout colour\nRead RAM at {address}: data {data}"
);
};
}
Err(e) => log::error!("RAM read error: {e:?}"),
},
}
}
2024-01-10 15:22:28 +11:00
fn send_command(&mut self, req: [u8; 4]) {
self.port
.write_all(&req)
.expect("failed to write to serial port");
}
fn receive<const LENGTH: usize>(&mut self) -> [u8; LENGTH] {
let mut buf = [0; LENGTH];
match self.port.read_exact(&mut buf) {
Ok(_) => {}
Err(e) => log::error!("PLI serial read error: {e:#?}"),
2024-01-10 15:22:28 +11:00
}
buf
}
2024-01-11 08:57:41 +11:00
fn read_ram<T>(&mut self, address: T) -> anyhow::Result<u8>
where
T: Into<u8>,
{
2024-01-10 16:27:02 +11:00
self.send_command(command(20, address.into(), 0));
let buf: [u8; 2] = self.receive();
if buf[0] == 200 { Some(buf[1]) } else { None }
.context(format!("Error from PLI: {}", buf[0]))
2024-01-10 15:22:28 +11:00
}
}
2024-01-10 16:27:02 +11:00
enum PlRamAddress {
Dutycyc,
Batv,
Cint,
Lint,
}
impl From<PlRamAddress> for u8 {
fn from(value: PlRamAddress) -> Self {
match value {
PlRamAddress::Dutycyc => 39,
PlRamAddress::Batv => 50,
PlRamAddress::Cint => 213,
PlRamAddress::Lint => 217,
}
}
}
2024-01-10 15:22:28 +11:00
fn command(operation: u8, address: u8, data: u8) -> [u8; 4] {
[operation, address, data, !operation]
}