config validation and reloading
This commit is contained in:
parent
b0b8cac319
commit
2ae9403893
120
Cargo.lock
generated
120
Cargo.lock
generated
|
@ -485,6 +485,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.17"
|
||||
|
@ -676,6 +685,18 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
@ -706,6 +727,15 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
|
@ -1071,6 +1101,26 @@ version = "0.1.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-kit-sys"
|
||||
version = "0.4.0"
|
||||
|
@ -1129,6 +1179,26 @@ dependencies = [
|
|||
"winapi-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
|
||||
dependencies = [
|
||||
"kqueue-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue-sys"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "0.2.11"
|
||||
|
@ -1382,6 +1452,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log 0.4.20",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
@ -1444,6 +1515,35 @@ dependencies = [
|
|||
"memchr 1.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "6.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log 0.4.20",
|
||||
"mio",
|
||||
"walkdir",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-debouncer-mini"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43"
|
||||
dependencies = [
|
||||
"log 0.4.20",
|
||||
"notify",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
|
@ -2140,6 +2240,15 @@ version = "1.0.16"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.22"
|
||||
|
@ -2535,6 +2644,7 @@ dependencies = [
|
|||
"log 0.4.20",
|
||||
"metrics",
|
||||
"metrics-prometheus",
|
||||
"notify-debouncer-mini",
|
||||
"prometheus",
|
||||
"rocket",
|
||||
"ron",
|
||||
|
@ -3004,6 +3114,16 @@ version = "0.9.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
|
|
|
@ -31,3 +31,4 @@ log = "0.4"
|
|||
serialport = "4.3"
|
||||
libmodbus-rs = "0.8.3"
|
||||
if_chain = "1.0.2"
|
||||
notify-debouncer-mini = { version = "0.4.1", default-features = false }
|
||||
|
|
113
src/config.rs
113
src/config.rs
|
@ -1,24 +1,83 @@
|
|||
use std::{
|
||||
ops::DerefMut,
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
sync::{OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use notify_debouncer_mini::{new_debouncer, notify::RecommendedWatcher, Debouncer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use crate::{
|
||||
errors::{ConfigError, PrintErrors},
|
||||
types::Coords,
|
||||
};
|
||||
|
||||
pub(super) static CONFIG_PATH: OnceLock<PathBuf> = OnceLock::new();
|
||||
static CONFIG: OnceLock<RwLock<Config>> = OnceLock::new();
|
||||
static CONFIG_PATH: OnceLock<PathBuf> = OnceLock::new();
|
||||
pub(super) static CONFIG: OnceLock<RwLock<Config>> = OnceLock::new();
|
||||
|
||||
pub fn access_config<'a>() -> RwLockReadGuard<'a, Config> {
|
||||
CONFIG
|
||||
.get_or_init(|| RwLock::new(Config::load_and_save_defaults()))
|
||||
.read()
|
||||
.unwrap()
|
||||
CONFIG.get().unwrap().read().unwrap()
|
||||
}
|
||||
|
||||
pub(super) struct ConfigWatcher {
|
||||
_debouncer: Debouncer<RecommendedWatcher>,
|
||||
_handle: JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl ConfigWatcher {
|
||||
pub fn new(path: &Path) -> Option<Self> {
|
||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
let mut debouncer = new_debouncer(Duration::from_secs(1), move |v| {
|
||||
tx.send(v)
|
||||
.some_or_print_with("Failed to send event to queue");
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
debouncer
|
||||
.watcher()
|
||||
.watch(
|
||||
path,
|
||||
notify_debouncer_mini::notify::RecursiveMode::NonRecursive,
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
let config_path = PathBuf::from(path);
|
||||
|
||||
let handle = tokio::task::spawn(async move {
|
||||
log::warn!("from task");
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Some(Ok(_event)) => {
|
||||
log::warn!("Reloading config");
|
||||
let mut config = Config::load(&config_path);
|
||||
config.validate();
|
||||
if let Some(mut c) = CONFIG.get().and_then(|v| v.write().ok()) {
|
||||
*c = config;
|
||||
} else {
|
||||
log::error!("Reloading config: got notified, but failed to lock");
|
||||
}
|
||||
}
|
||||
Some(Err(e)) => log::error!("Error {e:?} from watcher"),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Some(Self {
|
||||
_debouncer: debouncer,
|
||||
_handle: handle,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_config(path: PathBuf) -> (Config, Option<ConfigWatcher>) {
|
||||
let config = Config::load_and_save_defaults(&path);
|
||||
let config_watcher = ConfigWatcher::new(&path);
|
||||
let _ = CONFIG_PATH.get_or_init(|| path);
|
||||
(config, config_watcher)
|
||||
}
|
||||
|
||||
pub struct ConfigHandle<'a> {
|
||||
|
@ -48,14 +107,11 @@ impl<'a> Drop for ConfigHandle<'a> {
|
|||
|
||||
pub fn write_to_config<'a>() -> ConfigHandle<'a> {
|
||||
ConfigHandle {
|
||||
handle: CONFIG
|
||||
.get_or_init(|| RwLock::new(Config::load_and_save_defaults()))
|
||||
.write()
|
||||
.unwrap(),
|
||||
handle: CONFIG.get().unwrap().write().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
pub tesla_update_interval_seconds: u64,
|
||||
|
@ -74,7 +130,7 @@ pub struct Config {
|
|||
pub pid_controls: PidControls,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
|
||||
pub struct PidControls {
|
||||
pub proportional_gain: f64,
|
||||
pub derivative_gain: f64,
|
||||
|
@ -91,7 +147,7 @@ impl Default for PidControls {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub enum ChargeControllerConfig {
|
||||
Pl {
|
||||
serial_port: String,
|
||||
|
@ -131,23 +187,38 @@ impl Default for Config {
|
|||
}
|
||||
|
||||
impl Config {
|
||||
fn load() -> Self {
|
||||
serde_json::from_str(&std::fs::read_to_string(CONFIG_PATH.get().unwrap()).unwrap()).unwrap()
|
||||
fn load(path: &Path) -> Self {
|
||||
serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap()
|
||||
}
|
||||
|
||||
fn save(&self) -> Result<(), ConfigError> {
|
||||
fn save_to(&self, path: &Path) -> Result<(), ConfigError> {
|
||||
Ok(serde_json::ser::to_writer_pretty(
|
||||
std::io::BufWriter::new(std::fs::File::create(CONFIG_PATH.get().unwrap())?),
|
||||
std::io::BufWriter::new(std::fs::File::create(path)?),
|
||||
self,
|
||||
)?)
|
||||
}
|
||||
|
||||
fn load_and_save_defaults() -> Self {
|
||||
let config = Self::load();
|
||||
let result = config.save();
|
||||
fn save(&self) -> Result<(), ConfigError> {
|
||||
self.save_to(CONFIG_PATH.get().unwrap())
|
||||
}
|
||||
|
||||
fn load_and_save_defaults(path: &Path) -> Self {
|
||||
let mut config = Self::load(path);
|
||||
config.validate();
|
||||
let result = config.save_to(path);
|
||||
if let Err(e) = result {
|
||||
log::error!("Failed to save config: {e:#?}",);
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
fn validate(&mut self) {
|
||||
self.shutoff_voltage = self.shutoff_voltage.clamp(40.0, 60.0);
|
||||
self.max_rate = self.max_rate.clamp(1, 30);
|
||||
self.min_rate = self.min_rate.clamp(1, self.max_rate);
|
||||
self.duty_cycle_too_high = self.duty_cycle_too_high.clamp(0.0, 1.0);
|
||||
self.duty_cycle_too_low = self.duty_cycle_too_low.clamp(0.0, 1.0);
|
||||
self.pid_controls.proportional_gain = self.pid_controls.proportional_gain.clamp(0.0, 50.0);
|
||||
self.pid_controls.derivative_gain = self.pid_controls.derivative_gain.clamp(0.0, 50.0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ extern crate rocket;
|
|||
use api_interface::TeslaInterface;
|
||||
use charge_controllers::{pl::Pli, tristar::Tristar};
|
||||
use clap::{Parser, Subcommand};
|
||||
use config::{access_config, ChargeControllerConfig, CONFIG_PATH};
|
||||
use config::{access_config, ChargeControllerConfig};
|
||||
use errors::PrintErrors;
|
||||
use std::path::PathBuf;
|
||||
use std::{path::PathBuf, sync::RwLock};
|
||||
use tesla_charge_rate::TeslaChargeRateController;
|
||||
use teslatte::vehicles::ChargingState;
|
||||
|
||||
|
@ -52,7 +52,9 @@ async fn main() {
|
|||
|
||||
let args = Args::parse();
|
||||
let auth_path = args.config_dir.join("auth");
|
||||
let _ = CONFIG_PATH.set(args.config_dir.join("config.json"));
|
||||
let config_path = args.config_dir.join("config.json");
|
||||
let (config, _config_watcher) = config::init_config(config_path);
|
||||
config::CONFIG.get_or_init(|| RwLock::new(config));
|
||||
let _recorder = metrics_prometheus::install();
|
||||
|
||||
match args.command {
|
||||
|
|
|
@ -144,7 +144,7 @@ impl TryFrom<teslatte::vehicles::DriveState> for FromDriveState {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Coords {
|
||||
pub latitude: f64,
|
||||
pub longitude: f64,
|
||||
|
|
Loading…
Reference in a new issue