Compare commits
No commits in common. "741d91e67df03afa091d072b0fe9803fe6e6e06e" and "3990228403da0652481079333780361be6e425a7" have entirely different histories.
741d91e67d
...
3990228403
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
name: Build prerelease .deb
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "*-pre"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Build:
|
|
||||||
uses: ./.gitea/workflows/build.yaml
|
|
707
Cargo.lock
generated
707
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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"
|
||||||
|
|
|
@ -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()
|
||||||
});
|
});
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue