407 lines
14 KiB
Rust
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]
|
|
}
|