tesla-charge-controller/charge-controller-supervisor/src/pl.rs
2025-01-09 09:51:30 +11:00

407 lines
14 KiB
Rust

use chrono::Timelike;
use serde::{Deserialize, Serialize};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio_serial::{SerialPort, SerialPortBuilderExt};
use crate::gauges::{
BATTERY_TEMP, BATTERY_VOLTAGE, CHARGE_STATE, INPUT_CURRENT, PL_DUTY_CYCLE, PL_LOAD_CURRENT,
TARGET_VOLTAGE,
};
pub struct Pli {
friendly_name: String,
port: tokio_serial::SerialStream,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
pub struct PlState {
pub battery_voltage: f64,
pub target_voltage: f64,
pub duty_cycle: f64,
pub internal_charge_current: f64,
pub internal_load_current: f64,
pub battery_temp: f64,
pub regulator_state: RegulatorState,
// pub internal_charge_ah_accumulator: u16,
// pub external_charge_ah_accumulator: u16,
// pub internal_load_ah_accumulator: u16,
// pub external_load_ah_accumulator: u16,
// pub internal_charge_ah: u16,
// pub external_charge_ah: u16,
// pub internal_load_ah: u16,
// pub external_load_ah: u16,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum RegulatorState {
Boost,
Equalise,
Absorption,
Float,
}
impl From<u8> for RegulatorState {
fn from(value: u8) -> Self {
match value & 0b11 {
0b00 => Self::Boost,
0b01 => Self::Equalise,
0b10 => Self::Absorption,
0b11 => Self::Float,
_ => unreachable!(),
}
}
}
impl From<RegulatorState> for u8 {
fn from(value: RegulatorState) -> Self {
match value {
RegulatorState::Boost => 0b00,
RegulatorState::Equalise => 0b01,
RegulatorState::Absorption => 0b10,
RegulatorState::Float => 0b11,
}
}
}
impl Default for RegulatorState {
fn default() -> Self {
Self::Absorption
}
}
fn set_regulator_gauges(state: RegulatorState, label: &str) {
let boost = CHARGE_STATE.with_label_values(&[label, "boost"]);
let equalise = CHARGE_STATE.with_label_values(&[label, "equalise"]);
let absorption = CHARGE_STATE.with_label_values(&[label, "absorption"]);
let float = CHARGE_STATE.with_label_values(&[label, "float"]);
match state {
RegulatorState::Boost => {
boost.set(1);
equalise.set(0);
absorption.set(0);
float.set(0);
}
RegulatorState::Equalise => {
boost.set(0);
equalise.set(1);
absorption.set(0);
float.set(0);
}
RegulatorState::Absorption => {
boost.set(0);
equalise.set(0);
absorption.set(1);
float.set(0);
}
RegulatorState::Float => {
boost.set(0);
equalise.set(0);
absorption.set(0);
float.set(1);
}
}
}
#[expect(dead_code, reason = "writing settings is not yet implemented")]
#[derive(Debug, Clone, Copy)]
pub enum PliRequest {
ReadRam(u8),
ReadEeprom(u8),
SyncTime,
SetRegulatorState(RegulatorState),
}
impl Pli {
pub fn new(
serial_port: &str,
friendly_name: &str,
baud_rate: u32,
timeout: u64,
) -> Result<Self, tokio_serial::Error> {
let port = tokio_serial::new(serial_port, baud_rate)
.timeout(std::time::Duration::from_millis(timeout))
.open_native_async()?;
Ok(Self {
friendly_name: friendly_name.to_owned(),
port,
})
}
pub async fn refresh(&mut self) -> eyre::Result<crate::controller::CommonData> {
let new_state = self.read_state().await?;
BATTERY_VOLTAGE
.with_label_values(&[&self.friendly_name])
.set(new_state.battery_voltage);
TARGET_VOLTAGE
.with_label_values(&[&self.friendly_name])
.set(new_state.target_voltage);
PL_DUTY_CYCLE
.with_label_values(&[&self.friendly_name])
.set(new_state.duty_cycle);
INPUT_CURRENT
.with_label_values(&[&self.friendly_name])
.set(new_state.internal_charge_current);
PL_LOAD_CURRENT
.with_label_values(&[&self.friendly_name])
.set(new_state.internal_load_current);
BATTERY_TEMP
.with_label_values(&[&self.friendly_name])
.set(new_state.battery_temp);
set_regulator_gauges(new_state.regulator_state, &self.friendly_name);
Ok(crate::controller::CommonData {
battery_voltage: new_state.battery_voltage,
target_voltage: new_state.target_voltage,
battery_temp: new_state.battery_temp,
})
}
#[expect(dead_code, reason = "writing settings is not yet implemented")]
pub async fn process_request(&mut self, message: PliRequest) -> eyre::Result<()> {
match message {
PliRequest::ReadRam(address) => {
let data = self.read_ram_with_retires(address).await?;
log::warn!("Read RAM at {address}: data {data}");
}
PliRequest::ReadEeprom(address) => {
let data = self.read_eeprom(address).await?;
log::warn!("Read EEPROM at {address}: data {data}");
}
PliRequest::SyncTime => {
let now = chrono::Local::now();
let timestamp = (((now.hour() * 10) + (now.minute() / 6)) & 0xFF) as u8;
let min = (now.minute() % 6) as u8;
let sec = (now.second() / 2).min(29) as u8;
self.write_ram(PlRamAddress::Hour, timestamp).await?;
self.write_ram(PlRamAddress::Min, min).await?;
self.write_ram(PlRamAddress::Sec, sec).await?;
log::warn!(
"Set time: {now} corresponds to {timestamp} + minutes {min} + seconds {sec}"
);
}
PliRequest::SetRegulatorState(state) => {
log::warn!("Setting regulator state to {state:?}");
self.write_ram(PlRamAddress::Rstate, state.into()).await?;
}
}
Ok(())
}
async fn read_state(&mut self) -> eyre::Result<PlState> {
// let int_charge_acc_low = self.read_ram(PlRamAddress::Ciacc1).await?;
// let int_charge_acc = self.read_ram(PlRamAddress::Ciacc2).await?;
// let int_charge_acc_high = self.read_ram(PlRamAddress::Ciacc3).await?;
// let mut internal_charge_ah_accumulator = u16::from(int_charge_acc_high) << 9;
// internal_charge_ah_accumulator |= u16::from(int_charge_acc) << 1;
// internal_charge_ah_accumulator |= u16::from(int_charge_acc_low & 0b1);
// let int_charge_low = self.read_ram(PlRamAddress::Ciahl).await?;
// let int_charge_high = self.read_ram(PlRamAddress::Ciahh).await?;
// let internal_charge_ah = u16::from_le_bytes([int_charge_low, int_charge_high]);
// let int_load_acc_low = self.read_ram(PlRamAddress::Liacc1).await?;
// let int_load_acc = self.read_ram(PlRamAddress::Liacc2).await?;
// let int_load_acc_high = self.read_ram(PlRamAddress::Liacc3).await?;
// let mut internal_load_ah_accumulator = u16::from(int_load_acc_high) << 9;
// internal_load_ah_accumulator |= u16::from(int_load_acc) << 1;
// internal_load_ah_accumulator |= u16::from(int_load_acc_low & 0b1);
// let int_load_low = self.read_ram(PlRamAddress::Liahl).await?;
// let int_load_high = self.read_ram(PlRamAddress::Liahh).await?;
// let internal_load_ah = u16::from_le_bytes([int_load_low, int_load_high]);
// let ext_charge_acc_low = self.read_ram(PlRamAddress::Ceacc1).await?;
// let ext_charge_acc = self.read_ram(PlRamAddress::Ceacc2).await?;
// let ext_charge_acc_high = self.read_ram(PlRamAddress::Ceacc3).await?;
// let mut external_charge_ah_accumulator = u16::from(ext_charge_acc_high) << 9;
// external_charge_ah_accumulator |= u16::from(ext_charge_acc) << 1;
// external_charge_ah_accumulator |= u16::from(ext_charge_acc_low & 0b1);
// let ext_charge_low = self.read_ram(PlRamAddress::Ceahl).await?;
// let ext_charge_high = self.read_ram(PlRamAddress::Ceahh).await?;
// let external_charge_ah = u16::from_le_bytes([ext_charge_low, ext_charge_high]);
// let ext_load_acc_low = self.read_ram(PlRamAddress::Leacc1).await?;
// let ext_load_acc = self.read_ram(PlRamAddress::Leacc2).await?;
// let ext_load_acc_high = self.read_ram(PlRamAddress::Leacc3).await?;
// let mut external_load_ah_accumulator = u16::from(ext_load_acc_high) << 9;
// external_load_ah_accumulator |= u16::from(ext_load_acc) << 1;
// external_load_ah_accumulator |= u16::from(ext_load_acc_low & 0b1);
// let ext_load_low = self.read_ram(PlRamAddress::Leahl).await?;
// let ext_load_high = self.read_ram(PlRamAddress::Leahh).await?;
// let external_load_ah = u16::from_le_bytes([ext_load_low, ext_load_high]);
Ok(PlState {
battery_voltage: f64::from(self.read_ram_with_retires(PlRamAddress::Batv).await?)
* (4. / 10.),
target_voltage: f64::from(self.read_ram_with_retires(PlRamAddress::Vreg).await?)
* (4. / 10.),
duty_cycle: f64::from(self.read_ram_with_retires(PlRamAddress::Dutycyc).await?) / 255.,
internal_charge_current: f64::from(
self.read_ram_with_retires(PlRamAddress::Cint).await?,
) * (4. / 10.),
internal_load_current: f64::from(self.read_ram_with_retires(PlRamAddress::Lint).await?)
* (2. / 10.),
battery_temp: f64::from(self.read_ram_with_retires(PlRamAddress::Battemp).await?),
regulator_state: self
.read_ram_with_retires(PlRamAddress::Rstate)
.await?
.into(),
// internal_charge_ah_accumulator,
// external_charge_ah_accumulator,
// internal_load_ah_accumulator,
// external_load_ah_accumulator,
// internal_charge_ah,
// external_charge_ah,
// internal_load_ah,
// external_load_ah,
})
}
async fn send_command(&mut self, req: [u8; 4]) -> eyre::Result<()> {
self.flush()?;
self.port.write_all(&req).await?;
Ok(())
}
fn flush(&mut self) -> eyre::Result<()> {
self.port.clear(tokio_serial::ClearBuffer::All)?;
Ok(())
}
async fn receive<const LENGTH: usize>(&mut self) -> eyre::Result<[u8; LENGTH]> {
let mut buf = [0; LENGTH];
self.port.read_exact(&mut buf).await?;
Ok(buf)
}
async fn read_ram_with_retires<T>(&mut self, address: T) -> eyre::Result<u8>
where
T: Into<u8>,
{
const READ_TRIES: usize = 3;
let address: u8 = address.into();
let mut last_err = None;
for _ in 0..READ_TRIES {
match self.read_ram_single(address).await {
Ok(v) => return Ok(v),
Err(e) => last_err = Some(e),
}
}
Err(last_err.unwrap_or(eyre::eyre!(
"no error was stored in read_ram_with_retries: this should be unreachable??"
)))
}
async fn read_ram_single<T>(&mut self, address: T) -> eyre::Result<u8>
where
T: Into<u8>,
{
self.send_command(command(20, address.into(), 0)).await?;
let buf = self.receive::<1>().await?;
if buf[0] == 200 {
Ok(self.receive::<1>().await?[0])
} else {
Err(eyre::eyre!("read error: result is {}", buf[0]))
}
}
async fn write_ram<T>(&mut self, address: T, data: u8) -> eyre::Result<()>
where
T: Into<u8>,
{
self.send_command(command(152, address.into(), data))
.await?;
Ok(())
}
async fn read_eeprom<T>(&mut self, address: T) -> eyre::Result<u8>
where
T: Into<u8>,
{
self.send_command(command(72, address.into(), 0)).await?;
let buf = self.receive::<1>().await?;
if buf[0] == 200 {
Ok(self.receive::<1>().await?[0])
} else {
Err(eyre::eyre!("read error: result is {}", buf[0]))
}
}
}
#[allow(dead_code)]
enum PlRamAddress {
Dutycyc,
Sec,
Min,
Hour,
Batv,
Battemp,
Rstate,
Vreg,
Cint,
Lint,
Ciacc1,
Ciacc2,
Ciacc3,
Ciahl,
Ciahh,
Ceacc1,
Ceacc2,
Ceacc3,
Ceahl,
Ceahh,
Liacc1,
Liacc2,
Liacc3,
Liahl,
Liahh,
Leacc1,
Leacc2,
Leacc3,
Leahl,
Leahh,
}
impl From<PlRamAddress> for u8 {
fn from(value: PlRamAddress) -> Self {
match value {
PlRamAddress::Dutycyc => 39,
PlRamAddress::Sec => 46,
PlRamAddress::Min => 47,
PlRamAddress::Hour => 48,
PlRamAddress::Batv => 50,
PlRamAddress::Battemp => 52,
PlRamAddress::Rstate => 101,
PlRamAddress::Vreg => 105,
PlRamAddress::Cint => 213,
PlRamAddress::Lint => 217,
PlRamAddress::Ciacc1 => 0xB9,
PlRamAddress::Ciacc2 => 0xBA,
PlRamAddress::Ciacc3 => 0xBB,
PlRamAddress::Ciahl => 0xBC,
PlRamAddress::Ciahh => 0xBD,
PlRamAddress::Ceacc1 => 0xBE,
PlRamAddress::Ceacc2 => 0xBF,
PlRamAddress::Ceacc3 => 0xC0,
PlRamAddress::Ceahl => 0xC1,
PlRamAddress::Ceahh => 0xC2,
PlRamAddress::Liacc1 => 0xC3,
PlRamAddress::Liacc2 => 0xC4,
PlRamAddress::Liacc3 => 0xC5,
PlRamAddress::Liahl => 0xC6,
PlRamAddress::Liahh => 0xC7,
PlRamAddress::Leacc1 => 0xC8,
PlRamAddress::Leacc2 => 0xC9,
PlRamAddress::Leacc3 => 0xCA,
PlRamAddress::Leahl => 0xCB,
PlRamAddress::Leahh => 0xCC,
}
}
}
fn command(operation: u8, address: u8, data: u8) -> [u8; 4] {
[operation, address, data, !operation]
}