diff --git a/central_backend/Cargo.lock b/central_backend/Cargo.lock index 9c1271b..f1408bd 100644 --- a/central_backend/Cargo.lock +++ b/central_backend/Cargo.lock @@ -523,6 +523,25 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bincode" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95" +dependencies = [ + "bincode_derive", + "serde", +] + +[[package]] +name = "bincode_derive" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30759b3b99a1b802a7a3aa21c85c3ded5c28e1c83170d82d70f08bbf7f3e4c" +dependencies = [ + "virtue", +] + [[package]] name = "bitflags" version = "2.6.0" @@ -608,6 +627,7 @@ dependencies = [ "actix-web", "anyhow", "asn1", + "bincode", "chrono", "clap", "env_logger", @@ -2697,6 +2717,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "virtue" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314" + [[package]] name = "walkdir" version = "2.5.0" diff --git a/central_backend/Cargo.toml b/central_backend/Cargo.toml index a275bc5..44cd8da 100644 --- a/central_backend/Cargo.toml +++ b/central_backend/Cargo.toml @@ -37,4 +37,5 @@ rust-embed = "8.5.0" jsonwebtoken = { version = "9.3.0", features = ["use_pem"] } prettytable-rs = "0.10.0" chrono = "0.4.38" -serde_yml = "0.0.12" \ No newline at end of file +serde_yml = "0.0.12" +bincode = "=2.0.0-rc.3" \ No newline at end of file diff --git a/central_backend/src/app_config.rs b/central_backend/src/app_config.rs index 8f159c8..09a7130 100644 --- a/central_backend/src/app_config.rs +++ b/central_backend/src/app_config.rs @@ -2,6 +2,12 @@ use crate::devices::device::{DeviceId, DeviceRelayID}; use clap::{Parser, Subcommand}; use std::path::{Path, PathBuf}; +#[derive(Copy, Clone, Debug)] +pub enum ConsumptionHistoryType { + GridConsumption, + RelayConsumption, +} + /// Electrical consumption fetcher backend #[derive(Subcommand, Debug, Clone)] pub enum ConsumptionBackend { @@ -81,9 +87,13 @@ pub struct AppConfig { pub production_margin: i32, /// Energy refresh operations interval, in seconds - #[arg(short('i'), long, env, default_value_t = 20)] + #[arg(short('i'), long, env, default_value_t = 25)] pub refresh_interval: u64, + /// Energy refresh operations interval, in seconds + #[arg(short('f'), long, env, default_value_t = 5)] + pub energy_fetch_interval: u64, + /// Consumption backend provider #[clap(subcommand)] pub consumption_backend: Option, @@ -251,6 +261,28 @@ impl AppConfig { pub fn relay_runtime_day_file_path(&self, relay_id: DeviceRelayID, day: u64) -> PathBuf { self.relay_runtime_stats_dir(relay_id).join(day.to_string()) } + + /// Get energy consumption history path + pub fn energy_consumption_history(&self) -> PathBuf { + self.storage_path().join("consumption_history") + } + + /// Get energy consumption history file path for a given day + pub fn energy_consumption_history_day( + &self, + number: u64, + r#type: ConsumptionHistoryType, + ) -> PathBuf { + self.storage_path() + .join("consumption_history") + .join(format!( + "{number}-{}", + match r#type { + ConsumptionHistoryType::GridConsumption => "grid", + ConsumptionHistoryType::RelayConsumption => "relay-consumption", + } + )) + } } #[cfg(test)] diff --git a/central_backend/src/devices/devices_list.rs b/central_backend/src/devices/devices_list.rs index 0f5763e..9f196de 100644 --- a/central_backend/src/devices/devices_list.rs +++ b/central_backend/src/devices/devices_list.rs @@ -115,6 +115,11 @@ impl DevicesList { self.0.clone().into_values().collect() } + /// Get a reference on the full list of devices + pub fn full_list_ref(&self) -> Vec<&Device> { + self.0.values().collect() + } + /// Get the information about a single device pub fn get_single(&self, id: &DeviceId) -> Option { self.0.get(id).cloned() diff --git a/central_backend/src/energy/consumption_cache.rs b/central_backend/src/energy/consumption_cache.rs new file mode 100644 index 0000000..c702dc7 --- /dev/null +++ b/central_backend/src/energy/consumption_cache.rs @@ -0,0 +1,79 @@ +use crate::constants; +use crate::energy::consumption::EnergyConsumption; +use crate::utils::math_utils::median; + +pub struct ConsumptionCache { + nb_vals: usize, + values: Vec, +} + +impl ConsumptionCache { + pub fn new(nb_vals: usize) -> Self { + Self { + nb_vals, + values: vec![], + } + } + + pub fn add_value(&mut self, value: EnergyConsumption) { + if self.values.len() >= self.nb_vals { + self.values.remove(0); + } + + self.values.push(value); + } + + pub fn median_value(&self) -> EnergyConsumption { + if self.values.is_empty() { + return constants::FALLBACK_PRODUCTION_VALUE; + } + + median(&self.values) + } +} + +#[cfg(test)] +pub mod test { + use crate::constants; + use crate::energy::consumption_cache::ConsumptionCache; + + #[test] + fn empty_vec() { + let cache = ConsumptionCache::new(10); + assert_eq!(cache.median_value(), constants::FALLBACK_PRODUCTION_VALUE); + } + + #[test] + fn single_value() { + let mut cache = ConsumptionCache::new(10); + cache.add_value(-10); + assert_eq!(cache.median_value(), -10); + } + + #[test] + fn four_values() { + let mut cache = ConsumptionCache::new(10); + cache.add_value(50); + cache.add_value(-10); + cache.add_value(-10); + cache.add_value(-10000); + assert_eq!(cache.median_value(), -10); + } + + #[test] + fn many_values() { + let mut cache = ConsumptionCache::new(6); + + for i in 0..1000 { + cache.add_value(-i); + } + + cache.add_value(10); + cache.add_value(50); + cache.add_value(-10); + cache.add_value(-10); + cache.add_value(-30); + cache.add_value(-10000); + assert_eq!(cache.median_value(), -10); + } +} diff --git a/central_backend/src/energy/consumption_history_file.rs b/central_backend/src/energy/consumption_history_file.rs new file mode 100644 index 0000000..513c7e8 --- /dev/null +++ b/central_backend/src/energy/consumption_history_file.rs @@ -0,0 +1,162 @@ +use crate::app_config::{AppConfig, ConsumptionHistoryType}; +use crate::energy::consumption::EnergyConsumption; +use crate::utils::math_utils::median; +use crate::utils::time_utils::day_number; + +const TIME_INTERVAL: usize = 10; + +#[derive(thiserror::Error, Debug)] +pub enum ConsumptionHistoryError { + #[error("Given time is out of file bounds!")] + TimeOutOfFileBound, +} + +/// # ConsumptionHistoryFile +/// +/// Stores the history of house consumption +pub struct ConsumptionHistoryFile { + day: u64, + buff: Vec, + r#type: ConsumptionHistoryType, +} + +impl ConsumptionHistoryFile { + /// Open consumption history file, if it exists, or create an empty one + pub fn open(time: u64, r#type: ConsumptionHistoryType) -> anyhow::Result { + let day = day_number(time); + let path = AppConfig::get().energy_consumption_history_day(day, r#type); + + if path.exists() { + Ok(Self { + day, + buff: bincode::decode_from_slice( + &std::fs::read(path)?, + bincode::config::standard(), + )? + .0, + r#type, + }) + } else { + log::debug!( + "Energy consumption stats for day {day} does not exists yet, creating memory buffer" + ); + Ok(Self::new_memory(day, r#type)) + } + } + + /// Create a new in memory consumption history + fn new_memory(day: u64, r#type: ConsumptionHistoryType) -> Self { + Self { + day, + buff: vec![0; (3600 * 24 / TIME_INTERVAL) + 1], + r#type, + } + } + + /// Resolve time offset of a given time in buffer + fn resolve_offset(&self, time: u64) -> anyhow::Result { + let start_of_day = self.day * 3600 * 24; + + if time < start_of_day || time >= start_of_day + 3600 * 24 { + return Err(ConsumptionHistoryError::TimeOutOfFileBound.into()); + } + + let relative_time = (time - start_of_day) / TIME_INTERVAL as u64; + + Ok(relative_time as usize) + } + + /// Check if a time is contained in this history + pub fn contains_time(&self, time: u64) -> bool { + self.resolve_offset(time).is_ok() + } + + /// Set new state of relay + pub fn set_consumption( + &mut self, + time: u64, + consumption: EnergyConsumption, + ) -> anyhow::Result<()> { + let idx = self.resolve_offset(time)?; + self.buff[idx] = consumption; + Ok(()) + } + + /// Get the consumption recorded at a given time + pub fn get_consumption(&self, time: u64) -> anyhow::Result { + let idx = self.resolve_offset(time)?; + + Ok(self.buff[idx]) + } + + /// Persist device relay state history + pub fn save(&self) -> anyhow::Result<()> { + let path = AppConfig::get().energy_consumption_history_day(self.day, self.r#type); + std::fs::write( + path, + bincode::encode_to_vec(&self.buff, bincode::config::standard())?, + )?; + Ok(()) + } + + /// Get the total runtime of a relay during a given time window + pub fn get_history( + r#type: ConsumptionHistoryType, + from: u64, + to: u64, + interval: u64, + ) -> anyhow::Result> { + let mut res = Vec::with_capacity(((to - from) / interval) as usize); + let mut file = Self::open(from, r#type)?; + let mut curr_time = from; + + let mut intermediate_values = Vec::new(); + while curr_time < to { + if !file.contains_time(curr_time) { + file = Self::open(curr_time, r#type)?; + } + + intermediate_values.push(file.get_consumption(curr_time)?); + + if curr_time % interval == from % interval { + res.push(median(&intermediate_values)); + intermediate_values = Vec::new(); + } + + curr_time += TIME_INTERVAL as u64; + } + + Ok(res) + } +} + +#[cfg(test)] +mod tests { + use crate::energy::consumption::EnergyConsumption; + use crate::energy::consumption_history_file::{ConsumptionHistoryFile, TIME_INTERVAL}; + + #[test] + fn test_consumption_history() { + let mut history = ConsumptionHistoryFile::new_memory(0); + + for i in 0..50 { + assert_eq!( + history.get_consumption(i * TIME_INTERVAL as u64).unwrap(), + 0 + ); + } + + for i in 0..50 { + history + .set_consumption(i * TIME_INTERVAL as u64, i as EnergyConsumption * 2) + .unwrap(); + } + + for i in 0..50 { + assert_eq!( + history.get_consumption(i * TIME_INTERVAL as u64).unwrap(), + i as EnergyConsumption * 2 + ); + } + } +} diff --git a/central_backend/src/energy/energy_actor.rs b/central_backend/src/energy/energy_actor.rs index 766eca7..35426be 100644 --- a/central_backend/src/energy/energy_actor.rs +++ b/central_backend/src/energy/energy_actor.rs @@ -1,4 +1,4 @@ -use crate::app_config::AppConfig; +use crate::app_config::{AppConfig, ConsumptionHistoryType}; use crate::constants; use crate::devices::device::{ Device, DeviceGeneralInfo, DeviceId, DeviceInfo, DeviceRelay, DeviceRelayID, @@ -6,6 +6,8 @@ use crate::devices::device::{ use crate::devices::devices_list::DevicesList; use crate::energy::consumption; use crate::energy::consumption::EnergyConsumption; +use crate::energy::consumption_cache::ConsumptionCache; +use crate::energy::consumption_history_file::ConsumptionHistoryFile; use crate::energy::engine::EnergyEngine; use crate::utils::time_utils::time_secs; use actix::prelude::*; @@ -13,23 +15,35 @@ use openssl::x509::X509Req; use std::time::Duration; pub struct EnergyActor { - curr_consumption: EnergyConsumption, + consumption_cache: ConsumptionCache, devices: DevicesList, engine: EnergyEngine, + last_engine_refresh: u64, } impl EnergyActor { pub async fn new() -> anyhow::Result { + let consumption_cache_size = + AppConfig::get().refresh_interval / AppConfig::get().energy_fetch_interval; + let curr_consumption = consumption::get_curr_consumption().await?; + let mut consumption_cache = ConsumptionCache::new(consumption_cache_size as usize); + consumption_cache.add_value(curr_consumption); + + if consumption_cache_size < 1 { + panic!("Energy fetch interval must be equal or smaller than refresh interval!"); + } + Ok(Self { - curr_consumption: consumption::get_curr_consumption().await?, + consumption_cache, devices: DevicesList::load()?, engine: EnergyEngine::default(), + last_engine_refresh: 0, }) } async fn refresh(&mut self) -> anyhow::Result<()> { // Refresh energy - self.curr_consumption = consumption::get_curr_consumption() + let latest_consumption = consumption::get_curr_consumption() .await .unwrap_or_else(|e| { log::error!( @@ -37,10 +51,30 @@ impl EnergyActor { ); constants::FALLBACK_PRODUCTION_VALUE }); + self.consumption_cache.add_value(latest_consumption); - let devices_list = self.devices.full_list(); + let devices_list = self.devices.full_list_ref(); - self.engine.refresh(self.curr_consumption, &devices_list); + let mut history = + ConsumptionHistoryFile::open(time_secs(), ConsumptionHistoryType::GridConsumption)?; + history.set_consumption(time_secs(), latest_consumption)?; + history.save()?; + + let mut relays_consumption = + ConsumptionHistoryFile::open(time_secs(), ConsumptionHistoryType::RelayConsumption)?; + relays_consumption.set_consumption( + time_secs(), + self.engine.sum_relays_consumption(&devices_list) as EnergyConsumption, + )?; + relays_consumption.save()?; + + if self.last_engine_refresh + AppConfig::get().refresh_interval > time_secs() { + return Ok(()); + } + self.last_engine_refresh = time_secs(); + + self.engine + .refresh(self.consumption_cache.median_value(), &devices_list); self.engine.persist_relays_state(&devices_list)?; @@ -55,7 +89,7 @@ impl Actor for EnergyActor { log::info!("Energy actor successfully started!"); ctx.run_interval( - Duration::from_secs(AppConfig::get().refresh_interval), + Duration::from_secs(AppConfig::get().energy_fetch_interval), |act, _ctx| { log::info!("Performing energy refresh operation"); if let Err(e) = futures::executor::block_on(act.refresh()) { @@ -81,11 +115,25 @@ impl Handler for EnergyActor { type Result = EnergyConsumption; fn handle(&mut self, _msg: GetCurrConsumption, _ctx: &mut Context) -> Self::Result { - self.curr_consumption + self.consumption_cache.median_value() } } -/// Get current consumption +/// Get relays consumption +#[derive(Message)] +#[rtype(result = "usize")] +pub struct RelaysConsumption; + +impl Handler for EnergyActor { + type Result = usize; + + fn handle(&mut self, _msg: RelaysConsumption, _ctx: &mut Context) -> Self::Result { + self.engine + .sum_relays_consumption(&self.devices.full_list_ref()) + } +} + +/// Check if device exists #[derive(Message)] #[rtype(result = "bool")] pub struct CheckDeviceExists(pub DeviceId); @@ -326,3 +374,34 @@ impl Handler for EnergyActor { .collect() } } + +#[derive(serde::Serialize)] +pub struct ResRelayState { + pub id: DeviceRelayID, + on: bool, + r#for: usize, +} + +/// Get the state of all relays +#[derive(Message)] +#[rtype(result = "Vec")] +pub struct GetAllRelaysState; + +impl Handler for EnergyActor { + type Result = Vec; + + fn handle(&mut self, _msg: GetAllRelaysState, _ctx: &mut Context) -> Self::Result { + let mut list = vec![]; + + for d in &self.devices.relays_list() { + let state = self.engine.relay_state(d.id); + list.push(ResRelayState { + id: d.id, + on: state.is_on(), + r#for: state.state_for(), + }) + } + + list + } +} diff --git a/central_backend/src/energy/engine.rs b/central_backend/src/energy/engine.rs index 54d3190..1f8c236 100644 --- a/central_backend/src/energy/engine.rs +++ b/central_backend/src/energy/engine.rs @@ -39,6 +39,10 @@ impl RelayState { fn is_off(&self) -> bool { !self.on } + + pub fn state_for(&self) -> usize { + (time_secs() - self.since as u64) as usize + } } type RelaysState = HashMap; @@ -51,7 +55,7 @@ pub struct EnergyEngine { impl DeviceRelay { // Note : this function is not recursive - fn has_running_dependencies(&self, s: &RelaysState, devices: &[Device]) -> bool { + fn has_running_dependencies(&self, s: &RelaysState, devices: &[&Device]) -> bool { for d in devices { for r in &d.relays { if r.depends_on.contains(&self.id) && s.get(&r.id).unwrap().is_on() { @@ -68,7 +72,7 @@ impl DeviceRelay { self.depends_on.iter().any(|id| s.get(id).unwrap().is_off()) } - fn is_having_conflict(&self, s: &RelaysState, devices: &[Device]) -> bool { + fn is_having_conflict(&self, s: &RelaysState, devices: &[&Device]) -> bool { if self .conflicts_with .iter() @@ -90,7 +94,7 @@ impl DeviceRelay { } } -fn sum_relays_consumption(state: &RelaysState, devices: &[Device]) -> usize { +fn sum_relays_consumption(state: &RelaysState, devices: &[&Device]) -> usize { let mut consumption = 0; for d in devices { @@ -115,7 +119,11 @@ impl EnergyEngine { self.relays_state.get_mut(&relay_id).unwrap() } - fn print_summary(&mut self, curr_consumption: EnergyConsumption, devices: &[Device]) { + pub fn sum_relays_consumption(&self, devices: &[&Device]) -> usize { + sum_relays_consumption(&self.relays_state, devices) + } + + fn print_summary(&mut self, curr_consumption: EnergyConsumption, devices: &[&Device]) { log::info!("Current consumption: {curr_consumption}"); let mut table = Table::new(); @@ -164,13 +172,13 @@ impl EnergyEngine { pub fn estimated_consumption_without_relays( &self, curr_consumption: EnergyConsumption, - devices: &[Device], + devices: &[&Device], ) -> EnergyConsumption { - curr_consumption - sum_relays_consumption(&self.relays_state, devices) as i32 + curr_consumption - self.sum_relays_consumption(devices) as i32 } /// Refresh energy engine; this method shall never fail ! - pub fn refresh(&mut self, curr_consumption: EnergyConsumption, devices: &[Device]) { + pub fn refresh(&mut self, curr_consumption: EnergyConsumption, devices: &[&Device]) { let base_production = self.estimated_consumption_without_relays(curr_consumption, devices); log::info!("Estimated base production: {base_production}"); @@ -358,7 +366,7 @@ impl EnergyEngine { } /// Save relays state to disk - pub fn persist_relays_state(&mut self, devices: &[Device]) -> anyhow::Result<()> { + pub fn persist_relays_state(&mut self, devices: &[&Device]) -> anyhow::Result<()> { // Save all relays state for d in devices { for r in &d.relays { diff --git a/central_backend/src/energy/mod.rs b/central_backend/src/energy/mod.rs index 2922113..4c09ee7 100644 --- a/central_backend/src/energy/mod.rs +++ b/central_backend/src/energy/mod.rs @@ -1,4 +1,6 @@ pub mod consumption; +pub mod consumption_cache; +pub mod consumption_history_file; pub mod energy_actor; pub mod engine; pub mod relay_state_history; diff --git a/central_backend/src/energy/relay_state_history.rs b/central_backend/src/energy/relay_state_history.rs index 0eafaf4..c46a179 100644 --- a/central_backend/src/energy/relay_state_history.rs +++ b/central_backend/src/energy/relay_state_history.rs @@ -47,7 +47,7 @@ impl RelayStateHistory { Self { id, day, - buff: vec![0; 3600 * 24 / TIME_INTERVAL], + buff: vec![0; (3600 * 24 / (TIME_INTERVAL * 8)) + 1], } } diff --git a/central_backend/src/main.rs b/central_backend/src/main.rs index f997c65..f910784 100644 --- a/central_backend/src/main.rs +++ b/central_backend/src/main.rs @@ -18,6 +18,7 @@ async fn main() -> std::io::Result<()> { create_directory_if_missing(AppConfig::get().pki_path()).unwrap(); create_directory_if_missing(AppConfig::get().devices_config_path()).unwrap(); create_directory_if_missing(AppConfig::get().relays_runtime_stats_storage_path()).unwrap(); + create_directory_if_missing(AppConfig::get().energy_consumption_history()).unwrap(); // Initialize PKI pki::initialize_root_ca().expect("Failed to initialize Root CA!"); diff --git a/central_backend/src/server/servers.rs b/central_backend/src/server/servers.rs index 2e331f8..c0325f3 100644 --- a/central_backend/src/server/servers.rs +++ b/central_backend/src/server/servers.rs @@ -131,10 +131,22 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()> "/web_api/energy/curr_consumption", web::get().to(energy_controller::curr_consumption), ) + .route( + "/web_api/energy/curr_consumption/history", + web::get().to(energy_controller::curr_consumption_history), + ) .route( "/web_api/energy/cached_consumption", web::get().to(energy_controller::cached_consumption), ) + .route( + "/web_api/energy/relays_consumption", + web::get().to(energy_controller::relays_consumption), + ) + .route( + "/web_api/energy/relays_consumption/history", + web::get().to(energy_controller::relays_consumption_history), + ) // Devices controller .route( "/web_api/devices/list_pending", @@ -185,6 +197,14 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()> "/web_api/relay/{id}", web::delete().to(relays_controller::delete), ) + .route( + "/web_api/relays/status", + web::get().to(relays_controller::status_all), + ) + .route( + "/web_api/relay/{id}/status", + web::get().to(relays_controller::status_single), + ) // Devices API .route( "/devices_api/utils/time", diff --git a/central_backend/src/server/web_api/energy_controller.rs b/central_backend/src/server/web_api/energy_controller.rs index 2ab729e..86c19ed 100644 --- a/central_backend/src/server/web_api/energy_controller.rs +++ b/central_backend/src/server/web_api/energy_controller.rs @@ -1,6 +1,10 @@ +use crate::app_config::ConsumptionHistoryType; +use crate::energy::consumption::EnergyConsumption; +use crate::energy::consumption_history_file::ConsumptionHistoryFile; use crate::energy::{consumption, energy_actor}; use crate::server::custom_error::HttpResult; use crate::server::WebEnergyActor; +use crate::utils::time_utils::time_secs; use actix_web::HttpResponse; #[derive(serde::Serialize)] @@ -15,9 +19,38 @@ pub async fn curr_consumption() -> HttpResult { Ok(HttpResponse::Ok().json(Consumption { consumption })) } +/// Get curr consumption history +pub async fn curr_consumption_history() -> HttpResult { + let history = ConsumptionHistoryFile::get_history( + ConsumptionHistoryType::GridConsumption, + time_secs() - 3600 * 24, + time_secs(), + 60 * 10, + )?; + Ok(HttpResponse::Ok().json(history)) +} + /// Get cached energy consumption pub async fn cached_consumption(energy_actor: WebEnergyActor) -> HttpResult { let consumption = energy_actor.send(energy_actor::GetCurrConsumption).await?; Ok(HttpResponse::Ok().json(Consumption { consumption })) } + +/// Get current relays consumption +pub async fn relays_consumption(energy_actor: WebEnergyActor) -> HttpResult { + let consumption = + energy_actor.send(energy_actor::RelaysConsumption).await? as EnergyConsumption; + + Ok(HttpResponse::Ok().json(Consumption { consumption })) +} + +pub async fn relays_consumption_history() -> HttpResult { + let history = ConsumptionHistoryFile::get_history( + ConsumptionHistoryType::RelayConsumption, + time_secs() - 3600 * 24, + time_secs(), + 60 * 10, + )?; + Ok(HttpResponse::Ok().json(history)) +} diff --git a/central_backend/src/server/web_api/relays_controller.rs b/central_backend/src/server/web_api/relays_controller.rs index 3ff5dfc..6405aeb 100644 --- a/central_backend/src/server/web_api/relays_controller.rs +++ b/central_backend/src/server/web_api/relays_controller.rs @@ -93,3 +93,20 @@ pub async fn delete(actor: WebEnergyActor, path: web::Path) -> Ht Ok(HttpResponse::Accepted().finish()) } + +/// Get the status of all relays +pub async fn status_all(actor: WebEnergyActor) -> HttpResult { + let list = actor.send(energy_actor::GetAllRelaysState).await?; + + Ok(HttpResponse::Ok().json(list)) +} + +/// Get the state of a single relay +pub async fn status_single(actor: WebEnergyActor, path: web::Path) -> HttpResult { + let list = actor.send(energy_actor::GetAllRelaysState).await?; + let Some(state) = list.into_iter().find(|r| r.id == path.id) else { + return Ok(HttpResponse::NotFound().json("Relay not found!")); + }; + + Ok(HttpResponse::Ok().json(state)) +} diff --git a/central_backend/src/utils/math_utils.rs b/central_backend/src/utils/math_utils.rs new file mode 100644 index 0000000..4421ff8 --- /dev/null +++ b/central_backend/src/utils/math_utils.rs @@ -0,0 +1,8 @@ +use std::ops::Div; + +pub fn median(numbers: &[E]) -> E { + let mut numbers = numbers.to_vec(); + numbers.sort(); + let mid = numbers.len() / 2; + numbers[mid] +} diff --git a/central_backend/src/utils/mod.rs b/central_backend/src/utils/mod.rs index 25676ba..5ccc580 100644 --- a/central_backend/src/utils/mod.rs +++ b/central_backend/src/utils/mod.rs @@ -1,2 +1,3 @@ pub mod files_utils; + pub mod math_utils; pub mod time_utils; diff --git a/central_frontend/src/api/EnergyApi.ts b/central_frontend/src/api/EnergyApi.ts index a6d484d..bd5640c 100644 --- a/central_frontend/src/api/EnergyApi.ts +++ b/central_frontend/src/api/EnergyApi.ts @@ -2,9 +2,9 @@ import { APIClient } from "./ApiClient"; export class EnergyApi { /** - * Get current house consumption + * Get current grid consumption */ - static async CurrConsumption(): Promise { + static async GridConsumption(): Promise { const data = await APIClient.exec({ method: "GET", uri: "/energy/curr_consumption", @@ -12,6 +12,18 @@ export class EnergyApi { return data.data.consumption; } + /** + * Get grid consumption history + */ + static async GridConsumptionHistory(): Promise { + return ( + await APIClient.exec({ + method: "GET", + uri: "/energy/curr_consumption/history", + }) + ).data; + } + /** * Get current cached consumption */ @@ -22,4 +34,28 @@ export class EnergyApi { }); return data.data.consumption; } + + /** + * Get relays consumption + */ + static async RelaysConsumption(): Promise { + return ( + await APIClient.exec({ + method: "GET", + uri: "/energy/relays_consumption", + }) + ).data.consumption; + } + + /** + * Get relays consumption history + */ + static async RelaysConsumptionHistory(): Promise { + return ( + await APIClient.exec({ + method: "GET", + uri: "/energy/relays_consumption/history", + }) + ).data; + } } diff --git a/central_frontend/src/api/RelayApi.ts b/central_frontend/src/api/RelayApi.ts index 7d6a8db..0620541 100644 --- a/central_frontend/src/api/RelayApi.ts +++ b/central_frontend/src/api/RelayApi.ts @@ -1,6 +1,14 @@ import { APIClient } from "./ApiClient"; import { Device, DeviceRelay } from "./DeviceApi"; +export interface RelayStatus { + id: string; + on: boolean; + for: number; +} + +export type RelaysStatus = Map; + export class RelayApi { /** * Get the full list of relays @@ -49,4 +57,34 @@ export class RelayApi { uri: `/relay/${relay.id}`, }); } + + /** + * Get the status of all relays + */ + static async GetRelaysStatus(): Promise { + const data: any[] = ( + await APIClient.exec({ + method: "GET", + uri: `/relays/status`, + }) + ).data; + + const map = new Map(); + for (let r of data) { + map.set(r.id, r); + } + return map; + } + + /** + * Get the status of a single relay + */ + static async SingleStatus(relay: DeviceRelay): Promise { + return ( + await APIClient.exec({ + method: "GET", + uri: `/relay/${relay.id}/status`, + }) + ).data; + } } diff --git a/central_frontend/src/routes/DeviceRoute/DeviceInfoProperty.tsx b/central_frontend/src/routes/DeviceRoute/DeviceInfoProperty.tsx index cffd8a7..0714f1f 100644 --- a/central_frontend/src/routes/DeviceRoute/DeviceInfoProperty.tsx +++ b/central_frontend/src/routes/DeviceRoute/DeviceInfoProperty.tsx @@ -3,7 +3,7 @@ import { TableCell, TableRow } from "@mui/material"; export function DeviceInfoProperty(p: { icon?: React.ReactElement; label: string; - value: string; + value: string | React.ReactElement; color?: string; }): React.ReactElement { return ( diff --git a/central_frontend/src/routes/DeviceRoute/DeviceRelays.tsx b/central_frontend/src/routes/DeviceRoute/DeviceRelays.tsx index a0810ad..8ea8ff0 100644 --- a/central_frontend/src/routes/DeviceRoute/DeviceRelays.tsx +++ b/central_frontend/src/routes/DeviceRoute/DeviceRelays.tsx @@ -14,9 +14,12 @@ import { EditDeviceRelaysDialog } from "../../dialogs/EditDeviceRelaysDialog"; import { DeviceRouteCard } from "./DeviceRouteCard"; import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider"; import { useLoadingMessage } from "../../hooks/context_providers/LoadingMessageProvider"; -import { RelayApi } from "../../api/RelayApi"; +import { RelayApi, RelayStatus } from "../../api/RelayApi"; import { useSnackbar } from "../../hooks/context_providers/SnackbarProvider"; import { useAlert } from "../../hooks/context_providers/AlertDialogProvider"; +import { AsyncWidget } from "../../widgets/AsyncWidget"; +import { TimeWidget } from "../../widgets/TimeWidget"; +import { BoolText } from "../../widgets/BoolText"; export function DeviceRelays(p: { device: Device; @@ -115,10 +118,35 @@ export function DeviceRelays(p: { } > - + } + /> ))} ); } + +function RelayEntryStatus(p: { relay: DeviceRelay }): React.ReactElement { + const [state, setState] = React.useState(); + + const load = async () => { + setState(await RelayApi.SingleStatus(p.relay)); + }; + + return ( + ( + <> + for{" "} + + + )} + /> + ); +} diff --git a/central_frontend/src/routes/DeviceRoute/DeviceStateBlock.tsx b/central_frontend/src/routes/DeviceRoute/DeviceStateBlock.tsx index 726c0e3..2180558 100644 --- a/central_frontend/src/routes/DeviceRoute/DeviceStateBlock.tsx +++ b/central_frontend/src/routes/DeviceRoute/DeviceStateBlock.tsx @@ -1,10 +1,11 @@ +import { Table, TableBody } from "@mui/material"; import React from "react"; import { Device, DeviceApi, DeviceState } from "../../api/DeviceApi"; import { AsyncWidget } from "../../widgets/AsyncWidget"; -import { DeviceRouteCard } from "./DeviceRouteCard"; -import { Table, TableBody } from "@mui/material"; -import { DeviceInfoProperty } from "./DeviceInfoProperty"; +import { BoolText } from "../../widgets/BoolText"; import { timeDiff } from "../../widgets/TimeWidget"; +import { DeviceInfoProperty } from "./DeviceInfoProperty"; +import { DeviceRouteCard } from "./DeviceRouteCard"; export function DeviceStateBlock(p: { device: Device }): React.ReactElement { const [state, setState] = React.useState(); @@ -32,7 +33,13 @@ function DeviceStateInner(p: { state: DeviceState }): React.ReactElement { + } /> + } /> (); @@ -37,6 +38,7 @@ export function DevicesRoute(): React.ReactElement { return ( @@ -80,12 +82,12 @@ function ValidatedDevicesList(p: { # - Model - Version - Max number of relays - Created - Updated - Status + Model + Version + Max relays + Created + Updated + Status @@ -99,21 +101,21 @@ function ValidatedDevicesList(p: { {dev.id} - {dev.info.reference} - {dev.info.version} - {dev.info.max_relays} - + {dev.info.reference} + {dev.info.version} + {dev.info.max_relays} + - + - {p.states.get(dev.id)!.online ? ( - Online - ) : ( - Offline - )} +
diff --git a/central_frontend/src/routes/HomeRoute.tsx b/central_frontend/src/routes/HomeRoute.tsx index a01d284..6ec346e 100644 --- a/central_frontend/src/routes/HomeRoute.tsx +++ b/central_frontend/src/routes/HomeRoute.tsx @@ -2,6 +2,9 @@ import { Typography } from "@mui/material"; import { CurrConsumptionWidget } from "./HomeRoute/CurrConsumptionWidget"; import Grid from "@mui/material/Grid2"; import { CachedConsumptionWidget } from "./HomeRoute/CachedConsumptionWidget"; +import { RelayConsumptionWidget } from "./HomeRoute/RelayConsumptionWidget"; +import { RelaysListRoute } from "./RelaysListRoute"; +import { DevicesRoute } from "./DevicesRoute"; export function HomeRoute(): React.ReactElement { return ( @@ -18,9 +21,20 @@ export function HomeRoute(): React.ReactElement { + + + + + + + + + + + ); diff --git a/central_frontend/src/routes/HomeRoute/CachedConsumptionWidget.tsx b/central_frontend/src/routes/HomeRoute/CachedConsumptionWidget.tsx index 53c399b..bb5d97d 100644 --- a/central_frontend/src/routes/HomeRoute/CachedConsumptionWidget.tsx +++ b/central_frontend/src/routes/HomeRoute/CachedConsumptionWidget.tsx @@ -26,12 +26,6 @@ export function CachedConsumptionWidget(): React.ReactElement { }); return ( - + ); } diff --git a/central_frontend/src/routes/HomeRoute/CurrConsumptionWidget.tsx b/central_frontend/src/routes/HomeRoute/CurrConsumptionWidget.tsx index f521bca..474e269 100644 --- a/central_frontend/src/routes/HomeRoute/CurrConsumptionWidget.tsx +++ b/central_frontend/src/routes/HomeRoute/CurrConsumptionWidget.tsx @@ -7,11 +7,14 @@ export function CurrConsumptionWidget(): React.ReactElement { const snackbar = useSnackbar(); const [val, setVal] = React.useState(); + const [history, setHistory] = React.useState(); const refresh = async () => { try { - const s = await EnergyApi.CurrConsumption(); + const s = await EnergyApi.GridConsumption(); + const history = await EnergyApi.GridConsumptionHistory(); setVal(s); + setHistory(history); } catch (e) { console.error(e); snackbar("Failed to refresh current consumption!"); @@ -19,7 +22,6 @@ export function CurrConsumptionWidget(): React.ReactElement { }; React.useEffect(() => { - refresh(); const i = setInterval(() => refresh(), 3000); return () => clearInterval(i); @@ -28,9 +30,8 @@ export function CurrConsumptionWidget(): React.ReactElement { return ( ); diff --git a/central_frontend/src/routes/HomeRoute/RelayConsumptionWidget.tsx b/central_frontend/src/routes/HomeRoute/RelayConsumptionWidget.tsx new file mode 100644 index 0000000..8004fb1 --- /dev/null +++ b/central_frontend/src/routes/HomeRoute/RelayConsumptionWidget.tsx @@ -0,0 +1,38 @@ +import React from "react"; +import { EnergyApi } from "../../api/EnergyApi"; +import { useSnackbar } from "../../hooks/context_providers/SnackbarProvider"; +import StatCard from "../../widgets/StatCard"; + +export function RelayConsumptionWidget(): React.ReactElement { + const snackbar = useSnackbar(); + + const [val, setVal] = React.useState(); + const [history, setHistory] = React.useState(); + + const refresh = async () => { + try { + const s = await EnergyApi.RelaysConsumption(); + const history = await EnergyApi.RelaysConsumptionHistory(); + setVal(s); + setHistory(history); + } catch (e) { + console.error(e); + snackbar("Failed to refresh current relays consumption!"); + } + }; + + React.useEffect(() => { + const i = setInterval(() => refresh(), 3000); + + return () => clearInterval(i); + }); + + return ( + + ); +} diff --git a/central_frontend/src/routes/RelaysListRoute.tsx b/central_frontend/src/routes/RelaysListRoute.tsx index 1c297bf..8a04555 100644 --- a/central_frontend/src/routes/RelaysListRoute.tsx +++ b/central_frontend/src/routes/RelaysListRoute.tsx @@ -11,18 +11,28 @@ import { Tooltip, } from "@mui/material"; import React from "react"; -import { DeviceRelay } from "../api/DeviceApi"; -import { RelayApi } from "../api/RelayApi"; +import { Device, DeviceApi, DeviceRelay, DeviceURL } from "../api/DeviceApi"; +import { RelayApi, RelaysStatus } from "../api/RelayApi"; import { AsyncWidget } from "../widgets/AsyncWidget"; +import { BoolText } from "../widgets/BoolText"; import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer"; +import { TimeWidget } from "../widgets/TimeWidget"; +import { EditDeviceRelaysDialog } from "../dialogs/EditDeviceRelaysDialog"; +import { useNavigate } from "react-router-dom"; -export function RelaysListRoute(): React.ReactElement { +export function RelaysListRoute(p: { + homeWidget?: boolean; +}): React.ReactElement { const loadKey = React.useRef(1); const [list, setList] = React.useState(); + const [devices, setDevices] = React.useState(); + const [status, setStatus] = React.useState(); const load = async () => { setList(await RelayApi.GetList()); + setDevices(await DeviceApi.ValidatedList()); + setStatus(await RelayApi.GetRelaysStatus()); list?.sort((a, b) => b.priority - a.priority); }; @@ -33,34 +43,53 @@ export function RelaysListRoute(): React.ReactElement { }; return ( - - - - - - } - > - } - /> - + <> + + + + + + } + > + ( + + )} + /> + + ); } function RelaysList(p: { list: DeviceRelay[]; + devices: Device[]; + status: RelaysStatus; onReload: () => void; }): React.ReactElement { + const navigate = useNavigate(); + + const openDevicePage = (relay: DeviceRelay) => { + const dev = p.devices.find((d) => d.relays.find((r) => r.id === relay.id)); + navigate(DeviceURL(dev!)); + }; + return ( - +
Name @@ -75,18 +104,23 @@ function RelaysList(p: { openDevicePage(row)} > {row.name} - {row.enabled ? ( - YES - ) : ( - NO - )} + {row.priority} {row.consumption} - TODO + + {" "} + for + ))} diff --git a/central_frontend/src/widgets/BoolText.tsx b/central_frontend/src/widgets/BoolText.tsx new file mode 100644 index 0000000..52eabbd --- /dev/null +++ b/central_frontend/src/widgets/BoolText.tsx @@ -0,0 +1,11 @@ +export function BoolText(p: { + val: boolean; + positive: string; + negative: string; +}): React.ReactElement { + return p.val ? ( + {p.positive} + ) : ( + {p.negative} + ); +} diff --git a/central_frontend/src/widgets/SolarEnergyRouteContainer.tsx b/central_frontend/src/widgets/SolarEnergyRouteContainer.tsx index 9c7a706..5b87fee 100644 --- a/central_frontend/src/widgets/SolarEnergyRouteContainer.tsx +++ b/central_frontend/src/widgets/SolarEnergyRouteContainer.tsx @@ -4,11 +4,12 @@ import React, { PropsWithChildren } from "react"; export function SolarEnergyRouteContainer( p: { label: string; + homeWidget?: boolean; actions?: React.ReactElement; } & PropsWithChildren ): React.ReactElement { return ( -
+
- {p.label} + {p.label} {p.actions ?? <>}
diff --git a/central_frontend/src/widgets/StatCard.tsx b/central_frontend/src/widgets/StatCard.tsx index 54f6ed7..7a8d91b 100644 --- a/central_frontend/src/widgets/StatCard.tsx +++ b/central_frontend/src/widgets/StatCard.tsx @@ -11,24 +11,25 @@ import { areaElementClasses } from "@mui/x-charts/LineChart"; export type StatCardProps = { title: string; value: string; - interval: string; - trend: "up" | "down" | "neutral"; - data: number[]; + interval?: string; + trend?: "up" | "down" | "neutral"; + data?: number[]; }; -function getDaysInMonth(month: number, year: number) { - const date = new Date(year, month, 0); - const monthName = date.toLocaleDateString("en-US", { - month: "short", - }); - const daysInMonth = date.getDate(); - const days = []; - let i = 1; - while (days.length < daysInMonth) { - days.push(`${monthName} ${i}`); - i += 1; +function last24Hours(): string[] { + let res: Array = []; + + for (let index = 0; index < 3600 * 24; index += 60 * 10) { + const date = new Date(); + date.setTime(date.getTime() - index * 1000); + res.push(date.getHours() + "h" + date.getMinutes()); } - return days; + + res.reverse(); + + console.log(res); + + return res; } function AreaGradient({ color, id }: { color: string; id: string }) { @@ -50,7 +51,6 @@ export default function StatCard({ data, }: StatCardProps) { const theme = useTheme(); - const daysInWeek = getDaysInMonth(4, 2024); const trendColors = { up: @@ -73,8 +73,8 @@ export default function StatCard({ neutral: "default" as const, }; - const color = labelColors[trend]; - const chartColor = trendColors[trend]; + const color = labelColors[trend ?? "neutral"]; + const chartColor = trendColors[trend ?? "neutral"]; const trendValues = { up: "+25%", down: "-25%", neutral: "+5%" }; return ( @@ -95,31 +95,38 @@ export default function StatCard({ {value} - + {trend && ( + + )} {interval} - - - - + + {data && interval && ( + + + + )} diff --git a/central_frontend/src/widgets/TimeWidget.tsx b/central_frontend/src/widgets/TimeWidget.tsx index 9e0548c..f590a48 100644 --- a/central_frontend/src/widgets/TimeWidget.tsx +++ b/central_frontend/src/widgets/TimeWidget.tsx @@ -61,7 +61,10 @@ export function TimeWidget(p: { }): React.ReactElement { if (!p.time) return <>; return ( - + {p.diff ? timeDiff(0, p.time) : timeDiffFromNow(p.time)} ); diff --git a/custom_consumption/Cargo.lock b/custom_consumption/Cargo.lock index 266dcc9..cff5c7f 100644 --- a/custom_consumption/Cargo.lock +++ b/custom_consumption/Cargo.lock @@ -176,9 +176,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" @@ -619,6 +619,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.6.0" @@ -691,9 +697,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.8" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" +checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" dependencies = [ "clap_builder", "clap_derive", @@ -701,9 +707,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.8" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" +checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" dependencies = [ "anstream", "anstyle", @@ -713,9 +719,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.8" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", @@ -738,36 +744,6 @@ dependencies = [ "error-code", ] -[[package]] -name = "cocoa" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation", - "core-graphics-types", - "libc", - "objc", -] - [[package]] name = "codespan-reporting" version = "0.11.1" @@ -778,12 +754,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - [[package]] name = "colorchoice" version = "1.0.1" @@ -985,21 +955,22 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "ecolor" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20930a432bbd57a6d55e07976089708d4893f3d556cf42a0d79e9e321fa73b10" +checksum = "2e6b451ff1143f6de0f33fc7f1b68fecfd2c7de06e104de96c4514de3f5396f8" dependencies = [ "bytemuck", + "emath", ] [[package]] name = "eframe" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020e2ccef6bbcec71dbc542f7eed64a5846fc3076727f5746da8fd307c91bab2" +checksum = "6490ef800b2e41ee129b1f32f9ac15f713233fe3bc18e241a1afe1e4fb6811e0" dependencies = [ + "ahash", "bytemuck", - "cocoa", "document-features", "egui", "egui-wgpu", @@ -1011,13 +982,14 @@ dependencies = [ "image", "js-sys", "log", - "objc", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation", "parking_lot", "percent-encoding", "raw-window-handle 0.5.2", "raw-window-handle 0.6.2", "static_assertions", - "thiserror", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -1028,12 +1000,13 @@ dependencies = [ [[package]] name = "egui" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584c5d1bf9a67b25778a3323af222dbe1a1feb532190e103901187f92c7fe29a" +checksum = "20c97e70a2768de630f161bb5392cbd3874fcf72868f14df0e002e82e06cb798" dependencies = [ "accesskit", "ahash", + "emath", "epaint", "log", "nohash-hasher", @@ -1041,10 +1014,11 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469ff65843f88a702b731a1532b7d03b0e8e96d283e70f3a22b0e06c46cb9b37" +checksum = "47c7a7c707877c3362a321ebb4f32be811c0b91f7aebf345fb162405c0218b4c" dependencies = [ + "ahash", "bytemuck", "document-features", "egui", @@ -1059,11 +1033,12 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e3da0cbe020f341450c599b35b92de4af7b00abde85624fd16f09c885573609" +checksum = "fac4e066af341bf92559f60dbdf2020b2a03c963415349af5f3f8d79ff7a4926" dependencies = [ "accesskit_winit", + "ahash", "arboard", "egui", "log", @@ -1076,10 +1051,11 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0e5d975f3c86edc3d35b1db88bb27c15dde7c55d3b5af164968ab5ede3f44ca" +checksum = "4e2bdc8b38cfa17cc712c4ae079e30c71c00cd4c2763c9e16dc7860a02769103" dependencies = [ + "ahash", "bytemuck", "egui", "glow", @@ -1092,9 +1068,9 @@ dependencies = [ [[package]] name = "emath" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c3a552cfca14630702449d35f41c84a0d15963273771c6059175a803620f3f" +checksum = "0a6a21708405ea88f63d8309650b4d77431f4bc28fb9d8e6f77d3963b51249e6" dependencies = [ "bytemuck", ] @@ -1132,9 +1108,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", @@ -1145,9 +1121,9 @@ dependencies = [ [[package]] name = "epaint" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b381f8b149657a4acf837095351839f32cd5c4aec1817fc4df84e18d76334176" +checksum = "3f0dcc0a0771e7500e94cd1cb797bd13c9f23b9409bdc3c824e2cbc562b7fa01" dependencies = [ "ab_glyph", "ahash", @@ -1510,9 +1486,9 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" dependencies = [ "bitflags 2.6.0", "gpu-descriptor-types", @@ -1521,9 +1497,9 @@ dependencies = [ [[package]] name = "gpu-descriptor-types" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ "bitflags 2.6.0", ] @@ -1621,13 +1597,12 @@ dependencies = [ [[package]] name = "image" -version = "0.24.9" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" dependencies = [ "bytemuck", - "byteorder", - "color_quant", + "byteorder-lite", "num-traits", "png", ] @@ -1846,9 +1821,9 @@ dependencies = [ [[package]] name = "metal" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +checksum = "5637e166ea14be6063a3f8ba5ccb9a4159df7d8f6d61c02fc3d480b1f90dcfcb" dependencies = [ "bitflags 2.6.0", "block", @@ -1871,10 +1846,11 @@ dependencies = [ [[package]] name = "naga" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" +checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231" dependencies = [ + "arrayvec", "bit-set", "bitflags 2.6.0", "codespan-reporting", @@ -1975,7 +1951,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", - "objc_exception", ] [[package]] @@ -2119,15 +2094,6 @@ dependencies = [ "objc2-metal", ] -[[package]] -name = "objc_exception" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] - [[package]] name = "once_cell" version = "1.19.0" @@ -3149,30 +3115,32 @@ dependencies = [ [[package]] name = "webbrowser" -version = "0.8.15" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b" +checksum = "425ba64c1e13b1c6e8c5d2541c8fac10022ca584f33da781db01b5756aef1f4e" dependencies = [ + "block2 0.5.1", "core-foundation", "home", "jni", "log", "ndk-context", - "objc", - "raw-window-handle 0.5.2", + "objc2 0.5.2", + "objc2-foundation", "url", "web-sys", ] [[package]] name = "wgpu" -version = "0.19.4" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01" +checksum = "90e37c7b9921b75dfd26dd973fdcbce36f13dfa6e2dc82aece584e0ed48c355c" dependencies = [ "arrayvec", "cfg-if", "cfg_aliases", + "document-features", "js-sys", "log", "parking_lot", @@ -3190,15 +3158,16 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.19.4" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a" +checksum = "d50819ab545b867d8a454d1d756b90cd5f15da1f2943334ca314af10583c9d39" dependencies = [ "arrayvec", "bit-vec", "bitflags 2.6.0", "cfg_aliases", "codespan-reporting", + "document-features", "indexmap", "log", "naga", @@ -3216,9 +3185,9 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.19.4" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1a4924366df7ab41a5d8546d6534f1f33231aa5b3f72b9930e300f254e39c3" +checksum = "172e490a87295564f3fcc0f165798d87386f6231b04d4548bca458cbbfd63222" dependencies = [ "android_system_properties", "arrayvec", @@ -3257,9 +3226,9 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" +checksum = "1353d9a46bff7f955a680577f34c69122628cc2076e1d6f3a9be6ef00ae793ef" dependencies = [ "bitflags 2.6.0", "js-sys", diff --git a/custom_consumption/Cargo.toml b/custom_consumption/Cargo.toml index 3eaf2e8..b38451e 100644 --- a/custom_consumption/Cargo.toml +++ b/custom_consumption/Cargo.toml @@ -4,9 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] -env_logger = "0.11.3" +env_logger = "0.11.5" log = "0.4.22" -clap = { version = "4.5.8", features = ["derive", "env"] } -egui = "0.27.2" -eframe = "0.27.2" -lazy_static = "1.5.0" \ No newline at end of file +clap = { version = "4.5.18", features = ["derive", "env"] } +egui = "0.28.1" +eframe = "0.28.1" +lazy_static = "1.5.0" diff --git a/custom_consumption/src/main.rs b/custom_consumption/src/main.rs index 33d6471..972fd23 100644 --- a/custom_consumption/src/main.rs +++ b/custom_consumption/src/main.rs @@ -11,7 +11,7 @@ fn main() { eframe::run_native( "Custom consumption", options, - Box::new(|_cc| Box::::default()), + Box::new(|_cc| Ok(Box::::default())), ) .unwrap() }