Compare commits

..

No commits in common. "741d91e67df03afa091d072b0fe9803fe6e6e06e" and "3990228403da0652481079333780361be6e425a7" have entirely different histories.

11 changed files with 565 additions and 293 deletions

View file

@ -1,25 +0,0 @@
name: Build .deb
on: workflow_call
jobs:
Build-Deb:
runs-on: aarch64
steps:
- name: Run sccache-cache
uses: https://github.com/mozilla-actions/sccache-action@v0.0.5
- name: Check out repository code
uses: actions/checkout@v3
with:
submodules: "recursive"
- name: Build
run: "cargo build --release --target=aarch64-unknown-linux-gnu"
- name: Build .deb
run: "cargo deb --target=aarch64-unknown-linux-gnu"
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: release
path: |
./target/aarch64-unknown-linux-gnu/debian/*.deb
./target/aarch64-unknown-linux-gnu/release/tesla-charge-controller

View file

@ -1,21 +1,25 @@
name: Build and release .deb name: Build .deb on release
run-name: Building .deb of latest release and adding to apt repo
on: on:
push: push:
tags: tags:
- "v[0-9]+.[0-9]+.[0-9]+" - "*"
jobs: jobs:
Build: Build-Deb:
uses: ./.gitea/workflows/build.yaml
Release:
runs-on: aarch64 runs-on: aarch64
steps: steps:
- name: Download artifacts - name: Check out repository code
uses: actions/download-artifact@v4 uses: actions/checkout@v3
with:
submodules: "recursive"
- name: Build
run: "cargo build --release --target=aarch64-unknown-linux-gnu"
- name: Build .deb
run: "cargo deb --target=aarch64-unknown-linux-gnu"
- name: Add .deb to apt repository - name: Add .deb to apt repository
run: "curl --user alex:${{ secrets.PACKAGING_TOKEN }} --upload-file $(ls -t ./release/*.deb | head -1) https://git.alexjanka.com/api/packages/alex/debian/pool/testing/main/upload" run: "curl --user alex:${{ secrets.PACKAGING_TOKEN }} --upload-file $(ls -t ./target/aarch64-unknown-linux-gnu/debian/*.deb | head -1) https://git.alexjanka.com/api/packages/alex/debian/pool/testing/main/upload"
- name: "Release package" - name: "Release package"
id: use-go-action id: use-go-action
uses: https://gitea.com/actions/release-action@main uses: https://gitea.com/actions/release-action@main

View file

@ -1,10 +0,0 @@
name: Build prerelease .deb
on:
push:
tags:
- "*-pre"
jobs:
Build:
uses: ./.gitea/workflows/build.yaml

707
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package] [package]
name = "tesla-charge-controller" name = "tesla-charge-controller"
version = "1.5.0-pre" version = "1.4.0"
edition = "2021" edition = "2021"
license = "MITNFA" license = "MITNFA"
description = "Controls Tesla charge rate based on solar charge data" description = "Controls Tesla charge rate based on solar charge data"
@ -28,8 +28,7 @@ prometheus = "0.13"
env_logger = "0.10" env_logger = "0.10"
log = "0.4" log = "0.4"
serialport = "4.3" serialport = "4.3"
tokio-modbus = "0.13.1" libmodbus-rs = "0.8.3"
tokio-serial = "5.4.4"
if_chain = "1.0.2" if_chain = "1.0.2"
notify-debouncer-mini = { version = "0.4.1", default-features = false } notify-debouncer-mini = { version = "0.4.1", default-features = false }
lazy_static = "1.4" lazy_static = "1.4"

View file

@ -470,9 +470,7 @@ impl TeslaInterface {
.map(|v| { .map(|v| {
if self.monitored_values.conn_charge_cable != v.conn_charge_cable { if self.monitored_values.conn_charge_cable != v.conn_charge_cable {
log::warn!("Current conn charge cable: \"{}\"", v.conn_charge_cable); log::warn!("Current conn charge cable: \"{}\"", v.conn_charge_cable);
self.monitored_values self.monitored_values.conn_charge_cable = v.conn_charge_cable.clone();
.conn_charge_cable
.clone_from(&v.conn_charge_cable);
} }
v.into() v.into()
}); });
@ -509,9 +507,7 @@ impl TeslaInterface {
.and_then(|v| { .and_then(|v| {
if self.monitored_values.climate_keeper != v.climate_keeper_mode { if self.monitored_values.climate_keeper != v.climate_keeper_mode {
log::warn!("Current climate keeper mode: \"{}\"", v.climate_keeper_mode); log::warn!("Current climate keeper mode: \"{}\"", v.climate_keeper_mode);
self.monitored_values self.monitored_values.climate_keeper = v.climate_keeper_mode.clone();
.climate_keeper
.clone_from(&v.climate_keeper_mode);
} }
v.try_into().ok() v.try_into().ok()
}); });

View file

@ -1,5 +1,5 @@
use libmodbus_rs::{Modbus, ModbusClient, ModbusRTU};
use prometheus::core::{AtomicI64, GenericGauge}; use prometheus::core::{AtomicI64, GenericGauge};
use tokio_modbus::client::Reader;
use crate::{ use crate::{
charge_controllers::gauges::*, charge_controllers::gauges::*,
@ -44,7 +44,7 @@ impl Scaling {
pub struct Tristar { pub struct Tristar {
state: TristarState, state: TristarState,
port_name: String, port_name: String,
modbus: tokio_modbus::client::Context, modbus: Modbus,
data_in: [u16; RAM_ARRAY_SIZE], data_in: [u16; RAM_ARRAY_SIZE],
charge_state_gauges: ChargeStateGauges, charge_state_gauges: ChargeStateGauges,
consecutive_errors: usize, consecutive_errors: usize,
@ -233,12 +233,16 @@ impl ChargeStateGauges {
} }
} }
const MAX_CONSECUTIVE_ERRORS: usize = 5;
impl Tristar { impl Tristar {
pub fn new(serial_port: String, baud_rate: u32) -> Result<Self, TristarError> { pub fn new(serial_port: String, baud_rate: i32) -> Result<Self, TristarError> {
let modbus_serial = let parity = 'N';
tokio_serial::SerialStream::open(&tokio_serial::new(&serial_port, baud_rate))?; let data_bit = 8;
let slave = tokio_modbus::Slave(DEVICE_ID); let stop_bit = 2;
let modbus = tokio_modbus::client::rtu::attach_slave(modbus_serial, slave); let mut modbus = Modbus::new_rtu(&serial_port, baud_rate, parity, data_bit, stop_bit)?;
modbus.set_slave(DEVICE_ID)?;
modbus.connect()?;
let charge_state_gauges = ChargeStateGauges::new(&serial_port); let charge_state_gauges = ChargeStateGauges::new(&serial_port);
Ok(Self { Ok(Self {
state: Default::default(), state: Default::default(),
@ -250,10 +254,9 @@ impl Tristar {
}) })
} }
pub async fn refresh(&mut self) { pub fn refresh(&mut self) {
if let Some(new_state) = self if let Some(new_state) = self
.get_data() .get_data()
.await
.some_or_print_with("reading tristar state") .some_or_print_with("reading tristar state")
.map(|scaling| TristarState::from_ram(scaling, &self.data_in)) .map(|scaling| TristarState::from_ram(scaling, &self.data_in))
{ {
@ -294,24 +297,21 @@ impl Tristar {
self.charge_state_gauges.set(new_state.charge_state); self.charge_state_gauges.set(new_state.charge_state);
self.state = new_state; self.state = new_state;
} else {
self.consecutive_errors += 1;
if self.consecutive_errors >= MAX_CONSECUTIVE_ERRORS {
self.modbus.close();
if let Err(e) = self.modbus.connect() {
log::error!("error reconnecting to modbus device: {e:?}");
}
}
} }
// else {
// self.consecutive_errors += 1;
// if self.consecutive_errors >= MAX_CONSECUTIVE_ERRORS {
// self.modbus.close();
// if let Err(e) = self.modbus.connect() {
// log::error!("error reconnecting to modbus device: {e:?}");
// }
// }
// }
} }
async fn get_data(&mut self) -> Result<Scaling, TristarError> { fn get_data(&mut self) -> Result<Scaling, TristarError> {
let data = self self.modbus
.modbus .read_registers(0x0000, RAM_DATA_SIZE + 1, &mut self.data_in)?;
.read_holding_registers(0x0000, RAM_DATA_SIZE + 1) let scaling = Scaling::from(&self.data_in);
.await??;
let scaling = Scaling::from(&data);
Ok(scaling) Ok(scaling)
} }
} }

View file

@ -157,7 +157,7 @@ pub enum ChargeControllerConfig {
}, },
Tristar { Tristar {
serial_port: String, serial_port: String,
baud_rate: u32, baud_rate: i32,
watch_interval_seconds: u64, watch_interval_seconds: u64,
}, },
} }

View file

@ -104,12 +104,14 @@ pub enum TeslaStateParseError {
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum TristarError { pub enum TristarError {
#[error(transparent)] #[error("modbus error")]
Modbus(#[from] tokio_modbus::Error), Modbus(libmodbus_rs::prelude::Error),
#[error(transparent)] }
ModbusException(#[from] tokio_modbus::Exception),
#[error(transparent)] impl From<libmodbus_rs::prelude::Error> for TristarError {
Serial(#[from] tokio_serial::Error), fn from(value: libmodbus_rs::prelude::Error) -> Self {
Self::Modbus(value)
}
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]

View file

@ -72,7 +72,8 @@ async fn main() {
let (api_requests, mut api_receiver) = tokio::sync::mpsc::unbounded_channel(); let (api_requests, mut api_receiver) = tokio::sync::mpsc::unbounded_channel();
// and to the pli thread // and to the pli thread
let (pli_requests, mut pli_receiver) = tokio::sync::mpsc::unbounded_channel(); let (pli_requests, mut pli_receiver) = tokio::sync::mpsc::unbounded_channel();
// and to the charge rate controller thread
let (tcrc_requests, mut tcrc_receiver) = tokio::sync::mpsc::unbounded_channel();
// try to spawn the pli loop // try to spawn the pli loop
let pli = { let pli = {
let config = access_config(); let config = access_config();
@ -145,7 +146,7 @@ async fn main() {
); );
loop { loop {
interval.tick().await; interval.tick().await;
tristar.refresh().await; tristar.refresh();
} }
}) })
}), }),
@ -172,7 +173,7 @@ async fn main() {
pl_state, pl_state,
api_requests, api_requests,
pli_requests, pli_requests,
// tcrc_requests, tcrc_requests,
)); ));
if let Some((mut interface, mut tesla_charge_rate_controller)) = interface_and_tcrc { if let Some((mut interface, mut tesla_charge_rate_controller)) = interface_and_tcrc {
@ -221,6 +222,10 @@ async fn main() {
api_message = api_receiver.recv() => match api_message { api_message = api_receiver.recv() => match api_message {
Some(message) => interface.process_request(message).await, Some(message) => interface.process_request(message).await,
None => panic!("Tesla send channel dropped") None => panic!("Tesla send channel dropped")
},
tcrc_message = tcrc_receiver.recv() => match tcrc_message {
Some(message) => tesla_charge_rate_controller.process_request(message),
None => panic!("TCRC send channel dropped")
} }
} }
} }

View file

@ -16,7 +16,7 @@ use crate::{
charge_controllers::pl::{PlState, PliRequest, RegulatorState}, charge_controllers::pl::{PlState, PliRequest, RegulatorState},
config::{access_config, write_to_config}, config::{access_config, write_to_config},
errors::{PrintErrors, ServerError}, errors::{PrintErrors, ServerError},
tesla_charge_rate::TcrcState, tesla_charge_rate::{TcrcRequest, TcrcState},
types::CarState, types::CarState,
}; };
@ -30,6 +30,7 @@ pub struct ServerState {
pub pl_state: Option<Arc<RwLock<PlState>>>, pub pl_state: Option<Arc<RwLock<PlState>>>,
pub api_requests: UnboundedSender<InterfaceRequest>, pub api_requests: UnboundedSender<InterfaceRequest>,
pub pli_requests: UnboundedSender<PliRequest>, pub pli_requests: UnboundedSender<PliRequest>,
pub tcrc_requests: UnboundedSender<TcrcRequest>,
waiting_for_auth: std::sync::Mutex<Option<(String, std::time::Instant)>>, waiting_for_auth: std::sync::Mutex<Option<(String, std::time::Instant)>>,
} }
@ -40,6 +41,7 @@ impl ServerState {
pl_state: Option<Arc<RwLock<PlState>>>, pl_state: Option<Arc<RwLock<PlState>>>,
api_requests: UnboundedSender<InterfaceRequest>, api_requests: UnboundedSender<InterfaceRequest>,
pli_requests: UnboundedSender<PliRequest>, pli_requests: UnboundedSender<PliRequest>,
tcrc_requests: UnboundedSender<TcrcRequest>,
) -> Self { ) -> Self {
Self { Self {
car_state, car_state,
@ -47,7 +49,7 @@ impl ServerState {
pl_state, pl_state,
api_requests, api_requests,
pli_requests, pli_requests,
// tcrc_requests, tcrc_requests,
waiting_for_auth: std::sync::Mutex::new(None), waiting_for_auth: std::sync::Mutex::new(None),
} }
} }