Merge branch 'master' of ssh://gitea.communiquons.org:52001/pierre/SolarEnergy
This commit is contained in:
commit
b800b90337
26
central_backend/Cargo.lock
generated
26
central_backend/Cargo.lock
generated
@ -523,6 +523,25 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
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]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
@ -608,6 +627,7 @@ dependencies = [
|
|||||||
"actix-web",
|
"actix-web",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"asn1",
|
"asn1",
|
||||||
|
"bincode",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@ -2697,6 +2717,12 @@ version = "0.9.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "virtue"
|
||||||
|
version = "0.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
@ -37,4 +37,5 @@ rust-embed = "8.5.0"
|
|||||||
jsonwebtoken = { version = "9.3.0", features = ["use_pem"] }
|
jsonwebtoken = { version = "9.3.0", features = ["use_pem"] }
|
||||||
prettytable-rs = "0.10.0"
|
prettytable-rs = "0.10.0"
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
serde_yml = "0.0.12"
|
serde_yml = "0.0.12"
|
||||||
|
bincode = "=2.0.0-rc.3"
|
@ -2,6 +2,12 @@ use crate::devices::device::{DeviceId, DeviceRelayID};
|
|||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum ConsumptionHistoryType {
|
||||||
|
GridConsumption,
|
||||||
|
RelayConsumption,
|
||||||
|
}
|
||||||
|
|
||||||
/// Electrical consumption fetcher backend
|
/// Electrical consumption fetcher backend
|
||||||
#[derive(Subcommand, Debug, Clone)]
|
#[derive(Subcommand, Debug, Clone)]
|
||||||
pub enum ConsumptionBackend {
|
pub enum ConsumptionBackend {
|
||||||
@ -81,9 +87,13 @@ pub struct AppConfig {
|
|||||||
pub production_margin: i32,
|
pub production_margin: i32,
|
||||||
|
|
||||||
/// Energy refresh operations interval, in seconds
|
/// 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,
|
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
|
/// Consumption backend provider
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
pub consumption_backend: Option<ConsumptionBackend>,
|
pub consumption_backend: Option<ConsumptionBackend>,
|
||||||
@ -251,6 +261,28 @@ impl AppConfig {
|
|||||||
pub fn relay_runtime_day_file_path(&self, relay_id: DeviceRelayID, day: u64) -> PathBuf {
|
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())
|
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)]
|
#[cfg(test)]
|
||||||
|
@ -115,6 +115,11 @@ impl DevicesList {
|
|||||||
self.0.clone().into_values().collect()
|
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
|
/// Get the information about a single device
|
||||||
pub fn get_single(&self, id: &DeviceId) -> Option<Device> {
|
pub fn get_single(&self, id: &DeviceId) -> Option<Device> {
|
||||||
self.0.get(id).cloned()
|
self.0.get(id).cloned()
|
||||||
|
79
central_backend/src/energy/consumption_cache.rs
Normal file
79
central_backend/src/energy/consumption_cache.rs
Normal file
@ -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<EnergyConsumption>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
162
central_backend/src/energy/consumption_history_file.rs
Normal file
162
central_backend/src/energy/consumption_history_file.rs
Normal file
@ -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<EnergyConsumption>,
|
||||||
|
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<Self> {
|
||||||
|
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<usize> {
|
||||||
|
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<EnergyConsumption> {
|
||||||
|
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<Vec<EnergyConsumption>> {
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
use crate::app_config::AppConfig;
|
use crate::app_config::{AppConfig, ConsumptionHistoryType};
|
||||||
use crate::constants;
|
use crate::constants;
|
||||||
use crate::devices::device::{
|
use crate::devices::device::{
|
||||||
Device, DeviceGeneralInfo, DeviceId, DeviceInfo, DeviceRelay, DeviceRelayID,
|
Device, DeviceGeneralInfo, DeviceId, DeviceInfo, DeviceRelay, DeviceRelayID,
|
||||||
@ -6,6 +6,8 @@ use crate::devices::device::{
|
|||||||
use crate::devices::devices_list::DevicesList;
|
use crate::devices::devices_list::DevicesList;
|
||||||
use crate::energy::consumption;
|
use crate::energy::consumption;
|
||||||
use crate::energy::consumption::EnergyConsumption;
|
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::energy::engine::EnergyEngine;
|
||||||
use crate::utils::time_utils::time_secs;
|
use crate::utils::time_utils::time_secs;
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
@ -13,23 +15,35 @@ use openssl::x509::X509Req;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub struct EnergyActor {
|
pub struct EnergyActor {
|
||||||
curr_consumption: EnergyConsumption,
|
consumption_cache: ConsumptionCache,
|
||||||
devices: DevicesList,
|
devices: DevicesList,
|
||||||
engine: EnergyEngine,
|
engine: EnergyEngine,
|
||||||
|
last_engine_refresh: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EnergyActor {
|
impl EnergyActor {
|
||||||
pub async fn new() -> anyhow::Result<Self> {
|
pub async fn new() -> anyhow::Result<Self> {
|
||||||
|
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 {
|
Ok(Self {
|
||||||
curr_consumption: consumption::get_curr_consumption().await?,
|
consumption_cache,
|
||||||
devices: DevicesList::load()?,
|
devices: DevicesList::load()?,
|
||||||
engine: EnergyEngine::default(),
|
engine: EnergyEngine::default(),
|
||||||
|
last_engine_refresh: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn refresh(&mut self) -> anyhow::Result<()> {
|
async fn refresh(&mut self) -> anyhow::Result<()> {
|
||||||
// Refresh energy
|
// Refresh energy
|
||||||
self.curr_consumption = consumption::get_curr_consumption()
|
let latest_consumption = consumption::get_curr_consumption()
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
log::error!(
|
log::error!(
|
||||||
@ -37,10 +51,30 @@ impl EnergyActor {
|
|||||||
);
|
);
|
||||||
constants::FALLBACK_PRODUCTION_VALUE
|
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)?;
|
self.engine.persist_relays_state(&devices_list)?;
|
||||||
|
|
||||||
@ -55,7 +89,7 @@ impl Actor for EnergyActor {
|
|||||||
log::info!("Energy actor successfully started!");
|
log::info!("Energy actor successfully started!");
|
||||||
|
|
||||||
ctx.run_interval(
|
ctx.run_interval(
|
||||||
Duration::from_secs(AppConfig::get().refresh_interval),
|
Duration::from_secs(AppConfig::get().energy_fetch_interval),
|
||||||
|act, _ctx| {
|
|act, _ctx| {
|
||||||
log::info!("Performing energy refresh operation");
|
log::info!("Performing energy refresh operation");
|
||||||
if let Err(e) = futures::executor::block_on(act.refresh()) {
|
if let Err(e) = futures::executor::block_on(act.refresh()) {
|
||||||
@ -81,11 +115,25 @@ impl Handler<GetCurrConsumption> for EnergyActor {
|
|||||||
type Result = EnergyConsumption;
|
type Result = EnergyConsumption;
|
||||||
|
|
||||||
fn handle(&mut self, _msg: GetCurrConsumption, _ctx: &mut Context<Self>) -> Self::Result {
|
fn handle(&mut self, _msg: GetCurrConsumption, _ctx: &mut Context<Self>) -> 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<RelaysConsumption> for EnergyActor {
|
||||||
|
type Result = usize;
|
||||||
|
|
||||||
|
fn handle(&mut self, _msg: RelaysConsumption, _ctx: &mut Context<Self>) -> Self::Result {
|
||||||
|
self.engine
|
||||||
|
.sum_relays_consumption(&self.devices.full_list_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if device exists
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "bool")]
|
#[rtype(result = "bool")]
|
||||||
pub struct CheckDeviceExists(pub DeviceId);
|
pub struct CheckDeviceExists(pub DeviceId);
|
||||||
@ -326,3 +374,34 @@ impl Handler<GetDevicesState> for EnergyActor {
|
|||||||
.collect()
|
.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<ResRelayState>")]
|
||||||
|
pub struct GetAllRelaysState;
|
||||||
|
|
||||||
|
impl Handler<GetAllRelaysState> for EnergyActor {
|
||||||
|
type Result = Vec<ResRelayState>;
|
||||||
|
|
||||||
|
fn handle(&mut self, _msg: GetAllRelaysState, _ctx: &mut Context<Self>) -> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -39,6 +39,10 @@ impl RelayState {
|
|||||||
fn is_off(&self) -> bool {
|
fn is_off(&self) -> bool {
|
||||||
!self.on
|
!self.on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn state_for(&self) -> usize {
|
||||||
|
(time_secs() - self.since as u64) as usize
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RelaysState = HashMap<DeviceRelayID, RelayState>;
|
type RelaysState = HashMap<DeviceRelayID, RelayState>;
|
||||||
@ -51,7 +55,7 @@ pub struct EnergyEngine {
|
|||||||
|
|
||||||
impl DeviceRelay {
|
impl DeviceRelay {
|
||||||
// Note : this function is not recursive
|
// 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 d in devices {
|
||||||
for r in &d.relays {
|
for r in &d.relays {
|
||||||
if r.depends_on.contains(&self.id) && s.get(&r.id).unwrap().is_on() {
|
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())
|
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
|
if self
|
||||||
.conflicts_with
|
.conflicts_with
|
||||||
.iter()
|
.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;
|
let mut consumption = 0;
|
||||||
|
|
||||||
for d in devices {
|
for d in devices {
|
||||||
@ -115,7 +119,11 @@ impl EnergyEngine {
|
|||||||
self.relays_state.get_mut(&relay_id).unwrap()
|
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}");
|
log::info!("Current consumption: {curr_consumption}");
|
||||||
|
|
||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
@ -164,13 +172,13 @@ impl EnergyEngine {
|
|||||||
pub fn estimated_consumption_without_relays(
|
pub fn estimated_consumption_without_relays(
|
||||||
&self,
|
&self,
|
||||||
curr_consumption: EnergyConsumption,
|
curr_consumption: EnergyConsumption,
|
||||||
devices: &[Device],
|
devices: &[&Device],
|
||||||
) -> EnergyConsumption {
|
) -> 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 !
|
/// 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);
|
let base_production = self.estimated_consumption_without_relays(curr_consumption, devices);
|
||||||
log::info!("Estimated base production: {base_production}");
|
log::info!("Estimated base production: {base_production}");
|
||||||
|
|
||||||
@ -358,7 +366,7 @@ impl EnergyEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Save relays state to disk
|
/// 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
|
// Save all relays state
|
||||||
for d in devices {
|
for d in devices {
|
||||||
for r in &d.relays {
|
for r in &d.relays {
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
pub mod consumption;
|
pub mod consumption;
|
||||||
|
pub mod consumption_cache;
|
||||||
|
pub mod consumption_history_file;
|
||||||
pub mod energy_actor;
|
pub mod energy_actor;
|
||||||
pub mod engine;
|
pub mod engine;
|
||||||
pub mod relay_state_history;
|
pub mod relay_state_history;
|
||||||
|
@ -47,7 +47,7 @@ impl RelayStateHistory {
|
|||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
day,
|
day,
|
||||||
buff: vec![0; 3600 * 24 / TIME_INTERVAL],
|
buff: vec![0; (3600 * 24 / (TIME_INTERVAL * 8)) + 1],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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().pki_path()).unwrap();
|
||||||
create_directory_if_missing(AppConfig::get().devices_config_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().relays_runtime_stats_storage_path()).unwrap();
|
||||||
|
create_directory_if_missing(AppConfig::get().energy_consumption_history()).unwrap();
|
||||||
|
|
||||||
// Initialize PKI
|
// Initialize PKI
|
||||||
pki::initialize_root_ca().expect("Failed to initialize Root CA!");
|
pki::initialize_root_ca().expect("Failed to initialize Root CA!");
|
||||||
|
@ -131,10 +131,22 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
|
|||||||
"/web_api/energy/curr_consumption",
|
"/web_api/energy/curr_consumption",
|
||||||
web::get().to(energy_controller::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(
|
.route(
|
||||||
"/web_api/energy/cached_consumption",
|
"/web_api/energy/cached_consumption",
|
||||||
web::get().to(energy_controller::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
|
// Devices controller
|
||||||
.route(
|
.route(
|
||||||
"/web_api/devices/list_pending",
|
"/web_api/devices/list_pending",
|
||||||
@ -185,6 +197,14 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
|
|||||||
"/web_api/relay/{id}",
|
"/web_api/relay/{id}",
|
||||||
web::delete().to(relays_controller::delete),
|
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
|
// Devices API
|
||||||
.route(
|
.route(
|
||||||
"/devices_api/utils/time",
|
"/devices_api/utils/time",
|
||||||
|
@ -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::energy::{consumption, energy_actor};
|
||||||
use crate::server::custom_error::HttpResult;
|
use crate::server::custom_error::HttpResult;
|
||||||
use crate::server::WebEnergyActor;
|
use crate::server::WebEnergyActor;
|
||||||
|
use crate::utils::time_utils::time_secs;
|
||||||
use actix_web::HttpResponse;
|
use actix_web::HttpResponse;
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
@ -15,9 +19,38 @@ pub async fn curr_consumption() -> HttpResult {
|
|||||||
Ok(HttpResponse::Ok().json(Consumption { consumption }))
|
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
|
/// Get cached energy consumption
|
||||||
pub async fn cached_consumption(energy_actor: WebEnergyActor) -> HttpResult {
|
pub async fn cached_consumption(energy_actor: WebEnergyActor) -> HttpResult {
|
||||||
let consumption = energy_actor.send(energy_actor::GetCurrConsumption).await?;
|
let consumption = energy_actor.send(energy_actor::GetCurrConsumption).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(Consumption { consumption }))
|
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))
|
||||||
|
}
|
||||||
|
@ -93,3 +93,20 @@ pub async fn delete(actor: WebEnergyActor, path: web::Path<RelayIDInPath>) -> Ht
|
|||||||
|
|
||||||
Ok(HttpResponse::Accepted().finish())
|
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<RelayIDInPath>) -> 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))
|
||||||
|
}
|
||||||
|
8
central_backend/src/utils/math_utils.rs
Normal file
8
central_backend/src/utils/math_utils.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
use std::ops::Div;
|
||||||
|
|
||||||
|
pub fn median<E: Div + Copy + Ord>(numbers: &[E]) -> E {
|
||||||
|
let mut numbers = numbers.to_vec();
|
||||||
|
numbers.sort();
|
||||||
|
let mid = numbers.len() / 2;
|
||||||
|
numbers[mid]
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
pub mod files_utils;
|
pub mod files_utils;
|
||||||
|
pub mod math_utils;
|
||||||
pub mod time_utils;
|
pub mod time_utils;
|
||||||
|
@ -2,9 +2,9 @@ import { APIClient } from "./ApiClient";
|
|||||||
|
|
||||||
export class EnergyApi {
|
export class EnergyApi {
|
||||||
/**
|
/**
|
||||||
* Get current house consumption
|
* Get current grid consumption
|
||||||
*/
|
*/
|
||||||
static async CurrConsumption(): Promise<number> {
|
static async GridConsumption(): Promise<number> {
|
||||||
const data = await APIClient.exec({
|
const data = await APIClient.exec({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
uri: "/energy/curr_consumption",
|
uri: "/energy/curr_consumption",
|
||||||
@ -12,6 +12,18 @@ export class EnergyApi {
|
|||||||
return data.data.consumption;
|
return data.data.consumption;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get grid consumption history
|
||||||
|
*/
|
||||||
|
static async GridConsumptionHistory(): Promise<number[]> {
|
||||||
|
return (
|
||||||
|
await APIClient.exec({
|
||||||
|
method: "GET",
|
||||||
|
uri: "/energy/curr_consumption/history",
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get current cached consumption
|
* Get current cached consumption
|
||||||
*/
|
*/
|
||||||
@ -22,4 +34,28 @@ export class EnergyApi {
|
|||||||
});
|
});
|
||||||
return data.data.consumption;
|
return data.data.consumption;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get relays consumption
|
||||||
|
*/
|
||||||
|
static async RelaysConsumption(): Promise<number> {
|
||||||
|
return (
|
||||||
|
await APIClient.exec({
|
||||||
|
method: "GET",
|
||||||
|
uri: "/energy/relays_consumption",
|
||||||
|
})
|
||||||
|
).data.consumption;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get relays consumption history
|
||||||
|
*/
|
||||||
|
static async RelaysConsumptionHistory(): Promise<number[]> {
|
||||||
|
return (
|
||||||
|
await APIClient.exec({
|
||||||
|
method: "GET",
|
||||||
|
uri: "/energy/relays_consumption/history",
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
import { APIClient } from "./ApiClient";
|
import { APIClient } from "./ApiClient";
|
||||||
import { Device, DeviceRelay } from "./DeviceApi";
|
import { Device, DeviceRelay } from "./DeviceApi";
|
||||||
|
|
||||||
|
export interface RelayStatus {
|
||||||
|
id: string;
|
||||||
|
on: boolean;
|
||||||
|
for: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RelaysStatus = Map<string, RelayStatus>;
|
||||||
|
|
||||||
export class RelayApi {
|
export class RelayApi {
|
||||||
/**
|
/**
|
||||||
* Get the full list of relays
|
* Get the full list of relays
|
||||||
@ -49,4 +57,34 @@ export class RelayApi {
|
|||||||
uri: `/relay/${relay.id}`,
|
uri: `/relay/${relay.id}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the status of all relays
|
||||||
|
*/
|
||||||
|
static async GetRelaysStatus(): Promise<RelaysStatus> {
|
||||||
|
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<RelayStatus> {
|
||||||
|
return (
|
||||||
|
await APIClient.exec({
|
||||||
|
method: "GET",
|
||||||
|
uri: `/relay/${relay.id}/status`,
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { TableCell, TableRow } from "@mui/material";
|
|||||||
export function DeviceInfoProperty(p: {
|
export function DeviceInfoProperty(p: {
|
||||||
icon?: React.ReactElement;
|
icon?: React.ReactElement;
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string | React.ReactElement;
|
||||||
color?: string;
|
color?: string;
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
|
@ -14,9 +14,12 @@ import { EditDeviceRelaysDialog } from "../../dialogs/EditDeviceRelaysDialog";
|
|||||||
import { DeviceRouteCard } from "./DeviceRouteCard";
|
import { DeviceRouteCard } from "./DeviceRouteCard";
|
||||||
import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider";
|
import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider";
|
||||||
import { useLoadingMessage } from "../../hooks/context_providers/LoadingMessageProvider";
|
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 { useSnackbar } from "../../hooks/context_providers/SnackbarProvider";
|
||||||
import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
|
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: {
|
export function DeviceRelays(p: {
|
||||||
device: Device;
|
device: Device;
|
||||||
@ -115,10 +118,35 @@ export function DeviceRelays(p: {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ListItemText primary={r.name} secondary={"TODO: status"} />
|
<ListItemText
|
||||||
|
primary={r.name}
|
||||||
|
secondary={<RelayEntryStatus relay={r} />}
|
||||||
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
))}
|
))}
|
||||||
</DeviceRouteCard>
|
</DeviceRouteCard>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function RelayEntryStatus(p: { relay: DeviceRelay }): React.ReactElement {
|
||||||
|
const [state, setState] = React.useState<RelayStatus | undefined>();
|
||||||
|
|
||||||
|
const load = async () => {
|
||||||
|
setState(await RelayApi.SingleStatus(p.relay));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AsyncWidget
|
||||||
|
loadKey={p.relay.id}
|
||||||
|
load={load}
|
||||||
|
errMsg="Failed to load relay status!"
|
||||||
|
build={() => (
|
||||||
|
<>
|
||||||
|
<BoolText val={state!.on} positive="ON" negative="OFF" /> for{" "}
|
||||||
|
<TimeWidget diff time={state!.for} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
import { Table, TableBody } from "@mui/material";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Device, DeviceApi, DeviceState } from "../../api/DeviceApi";
|
import { Device, DeviceApi, DeviceState } from "../../api/DeviceApi";
|
||||||
import { AsyncWidget } from "../../widgets/AsyncWidget";
|
import { AsyncWidget } from "../../widgets/AsyncWidget";
|
||||||
import { DeviceRouteCard } from "./DeviceRouteCard";
|
import { BoolText } from "../../widgets/BoolText";
|
||||||
import { Table, TableBody } from "@mui/material";
|
|
||||||
import { DeviceInfoProperty } from "./DeviceInfoProperty";
|
|
||||||
import { timeDiff } from "../../widgets/TimeWidget";
|
import { timeDiff } from "../../widgets/TimeWidget";
|
||||||
|
import { DeviceInfoProperty } from "./DeviceInfoProperty";
|
||||||
|
import { DeviceRouteCard } from "./DeviceRouteCard";
|
||||||
|
|
||||||
export function DeviceStateBlock(p: { device: Device }): React.ReactElement {
|
export function DeviceStateBlock(p: { device: Device }): React.ReactElement {
|
||||||
const [state, setState] = React.useState<DeviceState>();
|
const [state, setState] = React.useState<DeviceState>();
|
||||||
@ -32,7 +33,13 @@ function DeviceStateInner(p: { state: DeviceState }): React.ReactElement {
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
<DeviceInfoProperty
|
<DeviceInfoProperty
|
||||||
label="Status"
|
label="Status"
|
||||||
value={p.state.online ? "Online" : "Offline"}
|
value={
|
||||||
|
<BoolText
|
||||||
|
val={p.state.online}
|
||||||
|
positive="ONLINE"
|
||||||
|
negative="Offline"
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<DeviceInfoProperty
|
<DeviceInfoProperty
|
||||||
label="Last ping"
|
label="Last ping"
|
||||||
|
@ -3,6 +3,7 @@ import { IconButton, Table, TableBody, Tooltip } from "@mui/material";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Device } from "../../api/DeviceApi";
|
import { Device } from "../../api/DeviceApi";
|
||||||
import { EditDeviceMetadataDialog } from "../../dialogs/EditDeviceMetadataDialog";
|
import { EditDeviceMetadataDialog } from "../../dialogs/EditDeviceMetadataDialog";
|
||||||
|
import { BoolText } from "../../widgets/BoolText";
|
||||||
import { formatDate } from "../../widgets/TimeWidget";
|
import { formatDate } from "../../widgets/TimeWidget";
|
||||||
import { DeviceInfoProperty } from "./DeviceInfoProperty";
|
import { DeviceInfoProperty } from "./DeviceInfoProperty";
|
||||||
import { DeviceRouteCard } from "./DeviceRouteCard";
|
import { DeviceRouteCard } from "./DeviceRouteCard";
|
||||||
@ -55,8 +56,9 @@ export function GeneralDeviceInfo(p: {
|
|||||||
/>
|
/>
|
||||||
<DeviceInfoProperty
|
<DeviceInfoProperty
|
||||||
label="Enabled"
|
label="Enabled"
|
||||||
value={p.device.enabled ? "YES" : "NO"}
|
value={
|
||||||
color={p.device.enabled ? "green" : "red"}
|
<BoolText val={p.device.enabled} positive="YES" negative="NO" />
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<DeviceInfoProperty
|
<DeviceInfoProperty
|
||||||
label="Maximum number of relays"
|
label="Maximum number of relays"
|
||||||
|
@ -15,10 +15,11 @@ import React from "react";
|
|||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { Device, DeviceApi, DevicesState, DeviceURL } from "../api/DeviceApi";
|
import { Device, DeviceApi, DevicesState, DeviceURL } from "../api/DeviceApi";
|
||||||
import { AsyncWidget } from "../widgets/AsyncWidget";
|
import { AsyncWidget } from "../widgets/AsyncWidget";
|
||||||
|
import { BoolText } from "../widgets/BoolText";
|
||||||
import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer";
|
import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer";
|
||||||
import { TimeWidget } from "../widgets/TimeWidget";
|
import { TimeWidget } from "../widgets/TimeWidget";
|
||||||
|
|
||||||
export function DevicesRoute(): React.ReactElement {
|
export function DevicesRoute(p: { homeWidget?: boolean }): React.ReactElement {
|
||||||
const loadKey = React.useRef(1);
|
const loadKey = React.useRef(1);
|
||||||
|
|
||||||
const [list, setList] = React.useState<Device[] | undefined>();
|
const [list, setList] = React.useState<Device[] | undefined>();
|
||||||
@ -37,6 +38,7 @@ export function DevicesRoute(): React.ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SolarEnergyRouteContainer
|
<SolarEnergyRouteContainer
|
||||||
|
homeWidget={p.homeWidget}
|
||||||
label="Devices"
|
label="Devices"
|
||||||
actions={
|
actions={
|
||||||
<Tooltip title="Refresh table">
|
<Tooltip title="Refresh table">
|
||||||
@ -80,12 +82,12 @@ function ValidatedDevicesList(p: {
|
|||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>#</TableCell>
|
<TableCell>#</TableCell>
|
||||||
<TableCell>Model</TableCell>
|
<TableCell align="center">Model</TableCell>
|
||||||
<TableCell>Version</TableCell>
|
<TableCell align="center">Version</TableCell>
|
||||||
<TableCell>Max number of relays</TableCell>
|
<TableCell align="center">Max relays</TableCell>
|
||||||
<TableCell>Created</TableCell>
|
<TableCell align="center">Created</TableCell>
|
||||||
<TableCell>Updated</TableCell>
|
<TableCell align="center">Updated</TableCell>
|
||||||
<TableCell>Status</TableCell>
|
<TableCell align="center">Status</TableCell>
|
||||||
<TableCell></TableCell>
|
<TableCell></TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
@ -99,21 +101,21 @@ function ValidatedDevicesList(p: {
|
|||||||
<TableCell component="th" scope="row">
|
<TableCell component="th" scope="row">
|
||||||
{dev.id}
|
{dev.id}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{dev.info.reference}</TableCell>
|
<TableCell align="center">{dev.info.reference}</TableCell>
|
||||||
<TableCell>{dev.info.version}</TableCell>
|
<TableCell align="center">{dev.info.version}</TableCell>
|
||||||
<TableCell>{dev.info.max_relays}</TableCell>
|
<TableCell align="center">{dev.info.max_relays}</TableCell>
|
||||||
<TableCell>
|
<TableCell align="center">
|
||||||
<TimeWidget time={dev.time_create} />
|
<TimeWidget time={dev.time_create} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell align="center">
|
||||||
<TimeWidget time={dev.time_update} />
|
<TimeWidget time={dev.time_update} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="center">
|
<TableCell align="center">
|
||||||
{p.states.get(dev.id)!.online ? (
|
<BoolText
|
||||||
<strong>Online</strong>
|
val={p.states.get(dev.id)!.online}
|
||||||
) : (
|
positive="Online"
|
||||||
<em>Offline</em>
|
negative="Offline"
|
||||||
)}
|
/>
|
||||||
<br />
|
<br />
|
||||||
<TimeWidget diff time={p.states.get(dev.id)!.last_ping} />
|
<TimeWidget diff time={p.states.get(dev.id)!.last_ping} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
@ -2,6 +2,9 @@ import { Typography } from "@mui/material";
|
|||||||
import { CurrConsumptionWidget } from "./HomeRoute/CurrConsumptionWidget";
|
import { CurrConsumptionWidget } from "./HomeRoute/CurrConsumptionWidget";
|
||||||
import Grid from "@mui/material/Grid2";
|
import Grid from "@mui/material/Grid2";
|
||||||
import { CachedConsumptionWidget } from "./HomeRoute/CachedConsumptionWidget";
|
import { CachedConsumptionWidget } from "./HomeRoute/CachedConsumptionWidget";
|
||||||
|
import { RelayConsumptionWidget } from "./HomeRoute/RelayConsumptionWidget";
|
||||||
|
import { RelaysListRoute } from "./RelaysListRoute";
|
||||||
|
import { DevicesRoute } from "./DevicesRoute";
|
||||||
|
|
||||||
export function HomeRoute(): React.ReactElement {
|
export function HomeRoute(): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
@ -18,9 +21,20 @@ export function HomeRoute(): React.ReactElement {
|
|||||||
<Grid size={{ xs: 12, sm: 6, lg: 3 }}>
|
<Grid size={{ xs: 12, sm: 6, lg: 3 }}>
|
||||||
<CurrConsumptionWidget />
|
<CurrConsumptionWidget />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, sm: 6, lg: 3 }}>
|
||||||
|
<RelayConsumptionWidget />
|
||||||
|
</Grid>
|
||||||
<Grid size={{ xs: 12, sm: 6, lg: 3 }}>
|
<Grid size={{ xs: 12, sm: 6, lg: 3 }}>
|
||||||
<CachedConsumptionWidget />
|
<CachedConsumptionWidget />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<Grid size={{ xs: 12, sm: 12, lg: 9 }}>
|
||||||
|
<DevicesRoute homeWidget />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid size={{ xs: 12, sm: 12, lg: 9 }}>
|
||||||
|
<RelaysListRoute homeWidget />
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -26,12 +26,6 @@ export function CachedConsumptionWidget(): React.ReactElement {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatCard
|
<StatCard title="Cached consumption" value={val?.toString() ?? "Loading"} />
|
||||||
title="Cached consumption"
|
|
||||||
data={[]}
|
|
||||||
interval="Current data"
|
|
||||||
trend="neutral"
|
|
||||||
value={val?.toString() ?? "Loading"}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,14 @@ export function CurrConsumptionWidget(): React.ReactElement {
|
|||||||
const snackbar = useSnackbar();
|
const snackbar = useSnackbar();
|
||||||
|
|
||||||
const [val, setVal] = React.useState<undefined | number>();
|
const [val, setVal] = React.useState<undefined | number>();
|
||||||
|
const [history, setHistory] = React.useState<number[] | undefined>();
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
try {
|
try {
|
||||||
const s = await EnergyApi.CurrConsumption();
|
const s = await EnergyApi.GridConsumption();
|
||||||
|
const history = await EnergyApi.GridConsumptionHistory();
|
||||||
setVal(s);
|
setVal(s);
|
||||||
|
setHistory(history);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
snackbar("Failed to refresh current consumption!");
|
snackbar("Failed to refresh current consumption!");
|
||||||
@ -19,7 +22,6 @@ export function CurrConsumptionWidget(): React.ReactElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
refresh();
|
|
||||||
const i = setInterval(() => refresh(), 3000);
|
const i = setInterval(() => refresh(), 3000);
|
||||||
|
|
||||||
return () => clearInterval(i);
|
return () => clearInterval(i);
|
||||||
@ -28,9 +30,8 @@ export function CurrConsumptionWidget(): React.ReactElement {
|
|||||||
return (
|
return (
|
||||||
<StatCard
|
<StatCard
|
||||||
title="Current consumption"
|
title="Current consumption"
|
||||||
data={[]}
|
data={history ?? []}
|
||||||
interval="Current data"
|
interval="Last day"
|
||||||
trend="neutral"
|
|
||||||
value={val?.toString() ?? "Loading"}
|
value={val?.toString() ?? "Loading"}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -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<undefined | number>();
|
||||||
|
const [history, setHistory] = React.useState<number[] | undefined>();
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<StatCard
|
||||||
|
title="Relays consumption"
|
||||||
|
data={history ?? []}
|
||||||
|
interval="Last day"
|
||||||
|
value={val?.toString() ?? "Loading"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -11,18 +11,28 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { DeviceRelay } from "../api/DeviceApi";
|
import { Device, DeviceApi, DeviceRelay, DeviceURL } from "../api/DeviceApi";
|
||||||
import { RelayApi } from "../api/RelayApi";
|
import { RelayApi, RelaysStatus } from "../api/RelayApi";
|
||||||
import { AsyncWidget } from "../widgets/AsyncWidget";
|
import { AsyncWidget } from "../widgets/AsyncWidget";
|
||||||
|
import { BoolText } from "../widgets/BoolText";
|
||||||
import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer";
|
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 loadKey = React.useRef(1);
|
||||||
|
|
||||||
const [list, setList] = React.useState<DeviceRelay[] | undefined>();
|
const [list, setList] = React.useState<DeviceRelay[] | undefined>();
|
||||||
|
const [devices, setDevices] = React.useState<Device[] | undefined>();
|
||||||
|
const [status, setStatus] = React.useState<RelaysStatus | undefined>();
|
||||||
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
setList(await RelayApi.GetList());
|
setList(await RelayApi.GetList());
|
||||||
|
setDevices(await DeviceApi.ValidatedList());
|
||||||
|
setStatus(await RelayApi.GetRelaysStatus());
|
||||||
|
|
||||||
list?.sort((a, b) => b.priority - a.priority);
|
list?.sort((a, b) => b.priority - a.priority);
|
||||||
};
|
};
|
||||||
@ -33,34 +43,53 @@ export function RelaysListRoute(): React.ReactElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SolarEnergyRouteContainer
|
<>
|
||||||
label="Relays list"
|
<SolarEnergyRouteContainer
|
||||||
actions={
|
label="Relays list"
|
||||||
<Tooltip title="Refresh list">
|
homeWidget={p.homeWidget}
|
||||||
<IconButton onClick={reload}>
|
actions={
|
||||||
<RefreshIcon />
|
<Tooltip title="Refresh list">
|
||||||
</IconButton>
|
<IconButton onClick={reload}>
|
||||||
</Tooltip>
|
<RefreshIcon />
|
||||||
}
|
</IconButton>
|
||||||
>
|
</Tooltip>
|
||||||
<AsyncWidget
|
}
|
||||||
loadKey={loadKey.current}
|
>
|
||||||
ready={!!list}
|
<AsyncWidget
|
||||||
errMsg="Failed to load the list of relays!"
|
loadKey={loadKey.current}
|
||||||
load={load}
|
ready={!!list}
|
||||||
build={() => <RelaysList onReload={reload} list={list!} />}
|
errMsg="Failed to load the list of relays!"
|
||||||
/>
|
load={load}
|
||||||
</SolarEnergyRouteContainer>
|
build={() => (
|
||||||
|
<RelaysList
|
||||||
|
onReload={reload}
|
||||||
|
list={list!}
|
||||||
|
devices={devices!}
|
||||||
|
status={status!}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SolarEnergyRouteContainer>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RelaysList(p: {
|
function RelaysList(p: {
|
||||||
list: DeviceRelay[];
|
list: DeviceRelay[];
|
||||||
|
devices: Device[];
|
||||||
|
status: RelaysStatus;
|
||||||
onReload: () => void;
|
onReload: () => void;
|
||||||
}): React.ReactElement {
|
}): 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 (
|
return (
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
<Table sx={{ minWidth: 650 }} aria-label="simple table">
|
<Table sx={{ minWidth: 650 }}>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Name</TableCell>
|
<TableCell>Name</TableCell>
|
||||||
@ -75,18 +104,23 @@ function RelaysList(p: {
|
|||||||
<TableRow
|
<TableRow
|
||||||
key={row.name}
|
key={row.name}
|
||||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||||
|
hover
|
||||||
|
onDoubleClick={() => openDevicePage(row)}
|
||||||
>
|
>
|
||||||
<TableCell>{row.name}</TableCell>
|
<TableCell>{row.name}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{row.enabled ? (
|
<BoolText val={row.enabled} positive="YES" negative="NO" />
|
||||||
<span style={{ color: "green" }}>YES</span>
|
|
||||||
) : (
|
|
||||||
<span style={{ color: "red" }}>NO</span>
|
|
||||||
)}
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{row.priority}</TableCell>
|
<TableCell>{row.priority}</TableCell>
|
||||||
<TableCell>{row.consumption}</TableCell>
|
<TableCell>{row.consumption}</TableCell>
|
||||||
<TableCell>TODO</TableCell>
|
<TableCell>
|
||||||
|
<BoolText
|
||||||
|
val={p.status.get(row.id)!.on}
|
||||||
|
positive="ON"
|
||||||
|
negative="OFF"
|
||||||
|
/>{" "}
|
||||||
|
for <TimeWidget diff time={p.status.get(row.id)!.for} />
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
11
central_frontend/src/widgets/BoolText.tsx
Normal file
11
central_frontend/src/widgets/BoolText.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export function BoolText(p: {
|
||||||
|
val: boolean;
|
||||||
|
positive: string;
|
||||||
|
negative: string;
|
||||||
|
}): React.ReactElement {
|
||||||
|
return p.val ? (
|
||||||
|
<span style={{ color: "green" }}>{p.positive}</span>
|
||||||
|
) : (
|
||||||
|
<span style={{ color: "red" }}>{p.negative}</span>
|
||||||
|
);
|
||||||
|
}
|
@ -4,11 +4,12 @@ import React, { PropsWithChildren } from "react";
|
|||||||
export function SolarEnergyRouteContainer(
|
export function SolarEnergyRouteContainer(
|
||||||
p: {
|
p: {
|
||||||
label: string;
|
label: string;
|
||||||
|
homeWidget?: boolean;
|
||||||
actions?: React.ReactElement;
|
actions?: React.ReactElement;
|
||||||
} & PropsWithChildren
|
} & PropsWithChildren
|
||||||
): React.ReactElement {
|
): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<div style={{ margin: "50px" }}>
|
<div style={{ margin: p.homeWidget ? "0px" : "50px" }}>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -17,7 +18,7 @@ export function SolarEnergyRouteContainer(
|
|||||||
marginBottom: "20px",
|
marginBottom: "20px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h4">{p.label}</Typography>
|
<Typography variant={p.homeWidget ? "h6" : "h4"}>{p.label}</Typography>
|
||||||
{p.actions ?? <></>}
|
{p.actions ?? <></>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -11,24 +11,25 @@ import { areaElementClasses } from "@mui/x-charts/LineChart";
|
|||||||
export type StatCardProps = {
|
export type StatCardProps = {
|
||||||
title: string;
|
title: string;
|
||||||
value: string;
|
value: string;
|
||||||
interval: string;
|
interval?: string;
|
||||||
trend: "up" | "down" | "neutral";
|
trend?: "up" | "down" | "neutral";
|
||||||
data: number[];
|
data?: number[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function getDaysInMonth(month: number, year: number) {
|
function last24Hours(): string[] {
|
||||||
const date = new Date(year, month, 0);
|
let res: Array<string> = [];
|
||||||
const monthName = date.toLocaleDateString("en-US", {
|
|
||||||
month: "short",
|
for (let index = 0; index < 3600 * 24; index += 60 * 10) {
|
||||||
});
|
const date = new Date();
|
||||||
const daysInMonth = date.getDate();
|
date.setTime(date.getTime() - index * 1000);
|
||||||
const days = [];
|
res.push(date.getHours() + "h" + date.getMinutes());
|
||||||
let i = 1;
|
|
||||||
while (days.length < daysInMonth) {
|
|
||||||
days.push(`${monthName} ${i}`);
|
|
||||||
i += 1;
|
|
||||||
}
|
}
|
||||||
return days;
|
|
||||||
|
res.reverse();
|
||||||
|
|
||||||
|
console.log(res);
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function AreaGradient({ color, id }: { color: string; id: string }) {
|
function AreaGradient({ color, id }: { color: string; id: string }) {
|
||||||
@ -50,7 +51,6 @@ export default function StatCard({
|
|||||||
data,
|
data,
|
||||||
}: StatCardProps) {
|
}: StatCardProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const daysInWeek = getDaysInMonth(4, 2024);
|
|
||||||
|
|
||||||
const trendColors = {
|
const trendColors = {
|
||||||
up:
|
up:
|
||||||
@ -73,8 +73,8 @@ export default function StatCard({
|
|||||||
neutral: "default" as const,
|
neutral: "default" as const,
|
||||||
};
|
};
|
||||||
|
|
||||||
const color = labelColors[trend];
|
const color = labelColors[trend ?? "neutral"];
|
||||||
const chartColor = trendColors[trend];
|
const chartColor = trendColors[trend ?? "neutral"];
|
||||||
const trendValues = { up: "+25%", down: "-25%", neutral: "+5%" };
|
const trendValues = { up: "+25%", down: "-25%", neutral: "+5%" };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -95,31 +95,38 @@ export default function StatCard({
|
|||||||
<Typography variant="h4" component="p">
|
<Typography variant="h4" component="p">
|
||||||
{value}
|
{value}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Chip size="small" color={color} label={trendValues[trend]} />
|
{trend && (
|
||||||
|
<Chip size="small" color={color} label={trendValues[trend]} />
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
<Typography variant="caption" sx={{ color: "text.secondary" }}>
|
<Typography variant="caption" sx={{ color: "text.secondary" }}>
|
||||||
{interval}
|
{interval}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Box sx={{ width: "100%", height: 50 }}>
|
<Box sx={{ width: "100%", height: 100 }}>
|
||||||
<SparkLineChart
|
{data && interval && (
|
||||||
colors={[chartColor]}
|
<SparkLineChart
|
||||||
data={data}
|
colors={[chartColor]}
|
||||||
area
|
data={data}
|
||||||
showHighlight
|
area
|
||||||
showTooltip
|
showHighlight
|
||||||
xAxis={{
|
showTooltip
|
||||||
scaleType: "band",
|
xAxis={{
|
||||||
data: daysInWeek, // Use the correct property 'data' for xAxis
|
scaleType: "band",
|
||||||
}}
|
data: last24Hours(),
|
||||||
sx={{
|
}}
|
||||||
[`& .${areaElementClasses.root}`]: {
|
sx={{
|
||||||
fill: `url(#area-gradient-${value})`,
|
[`& .${areaElementClasses.root}`]: {
|
||||||
},
|
fill: `url(#area-gradient-${value})`,
|
||||||
}}
|
},
|
||||||
>
|
}}
|
||||||
<AreaGradient color={chartColor} id={`area-gradient-${value}`} />
|
>
|
||||||
</SparkLineChart>
|
<AreaGradient
|
||||||
|
color={chartColor}
|
||||||
|
id={`area-gradient-${value}`}
|
||||||
|
/>
|
||||||
|
</SparkLineChart>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
@ -61,7 +61,10 @@ export function TimeWidget(p: {
|
|||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
if (!p.time) return <></>;
|
if (!p.time) return <></>;
|
||||||
return (
|
return (
|
||||||
<Tooltip title={formatDate(p.time)} arrow>
|
<Tooltip
|
||||||
|
title={formatDate(p.diff ? new Date().getTime() / 1000 - p.time : p.time)}
|
||||||
|
arrow
|
||||||
|
>
|
||||||
<span>{p.diff ? timeDiff(0, p.time) : timeDiffFromNow(p.time)}</span>
|
<span>{p.diff ? timeDiff(0, p.time) : timeDiffFromNow(p.time)}</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
167
custom_consumption/Cargo.lock
generated
167
custom_consumption/Cargo.lock
generated
@ -176,9 +176,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle"
|
name = "anstyle"
|
||||||
version = "1.0.7"
|
version = "1.0.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
|
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle-parse"
|
name = "anstyle-parse"
|
||||||
@ -619,6 +619,12 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder-lite"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@ -691,9 +697,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.8"
|
version = "4.5.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d"
|
checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@ -701,9 +707,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.8"
|
version = "4.5.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708"
|
checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@ -713,9 +719,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.8"
|
version = "4.5.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
|
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@ -738,36 +744,6 @@ dependencies = [
|
|||||||
"error-code",
|
"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]]
|
[[package]]
|
||||||
name = "codespan-reporting"
|
name = "codespan-reporting"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@ -778,12 +754,6 @@ dependencies = [
|
|||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "color_quant"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@ -985,21 +955,22 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ecolor"
|
name = "ecolor"
|
||||||
version = "0.27.2"
|
version = "0.28.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20930a432bbd57a6d55e07976089708d4893f3d556cf42a0d79e9e321fa73b10"
|
checksum = "2e6b451ff1143f6de0f33fc7f1b68fecfd2c7de06e104de96c4514de3f5396f8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
"emath",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "eframe"
|
name = "eframe"
|
||||||
version = "0.27.2"
|
version = "0.28.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "020e2ccef6bbcec71dbc542f7eed64a5846fc3076727f5746da8fd307c91bab2"
|
checksum = "6490ef800b2e41ee129b1f32f9ac15f713233fe3bc18e241a1afe1e4fb6811e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cocoa",
|
|
||||||
"document-features",
|
"document-features",
|
||||||
"egui",
|
"egui",
|
||||||
"egui-wgpu",
|
"egui-wgpu",
|
||||||
@ -1011,13 +982,14 @@ dependencies = [
|
|||||||
"image",
|
"image",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"objc",
|
"objc2 0.5.2",
|
||||||
|
"objc2-app-kit",
|
||||||
|
"objc2-foundation",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"raw-window-handle 0.5.2",
|
"raw-window-handle 0.5.2",
|
||||||
"raw-window-handle 0.6.2",
|
"raw-window-handle 0.6.2",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
"thiserror",
|
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
@ -1028,12 +1000,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui"
|
name = "egui"
|
||||||
version = "0.27.2"
|
version = "0.28.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "584c5d1bf9a67b25778a3323af222dbe1a1feb532190e103901187f92c7fe29a"
|
checksum = "20c97e70a2768de630f161bb5392cbd3874fcf72868f14df0e002e82e06cb798"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"ahash",
|
"ahash",
|
||||||
|
"emath",
|
||||||
"epaint",
|
"epaint",
|
||||||
"log",
|
"log",
|
||||||
"nohash-hasher",
|
"nohash-hasher",
|
||||||
@ -1041,10 +1014,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui-wgpu"
|
name = "egui-wgpu"
|
||||||
version = "0.27.2"
|
version = "0.28.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "469ff65843f88a702b731a1532b7d03b0e8e96d283e70f3a22b0e06c46cb9b37"
|
checksum = "47c7a7c707877c3362a321ebb4f32be811c0b91f7aebf345fb162405c0218b4c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"document-features",
|
"document-features",
|
||||||
"egui",
|
"egui",
|
||||||
@ -1059,11 +1033,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui-winit"
|
name = "egui-winit"
|
||||||
version = "0.27.2"
|
version = "0.28.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2e3da0cbe020f341450c599b35b92de4af7b00abde85624fd16f09c885573609"
|
checksum = "fac4e066af341bf92559f60dbdf2020b2a03c963415349af5f3f8d79ff7a4926"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit_winit",
|
"accesskit_winit",
|
||||||
|
"ahash",
|
||||||
"arboard",
|
"arboard",
|
||||||
"egui",
|
"egui",
|
||||||
"log",
|
"log",
|
||||||
@ -1076,10 +1051,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui_glow"
|
name = "egui_glow"
|
||||||
version = "0.27.2"
|
version = "0.28.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e0e5d975f3c86edc3d35b1db88bb27c15dde7c55d3b5af164968ab5ede3f44ca"
|
checksum = "4e2bdc8b38cfa17cc712c4ae079e30c71c00cd4c2763c9e16dc7860a02769103"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"egui",
|
"egui",
|
||||||
"glow",
|
"glow",
|
||||||
@ -1092,9 +1068,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "emath"
|
name = "emath"
|
||||||
version = "0.27.2"
|
version = "0.28.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e4c3a552cfca14630702449d35f41c84a0d15963273771c6059175a803620f3f"
|
checksum = "0a6a21708405ea88f63d8309650b4d77431f4bc28fb9d8e6f77d3963b51249e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
@ -1132,9 +1108,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.11.3"
|
version = "0.11.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
|
checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@ -1145,9 +1121,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "epaint"
|
name = "epaint"
|
||||||
version = "0.27.2"
|
version = "0.28.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b381f8b149657a4acf837095351839f32cd5c4aec1817fc4df84e18d76334176"
|
checksum = "3f0dcc0a0771e7500e94cd1cb797bd13c9f23b9409bdc3c824e2cbc562b7fa01"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ab_glyph",
|
"ab_glyph",
|
||||||
"ahash",
|
"ahash",
|
||||||
@ -1510,9 +1486,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpu-descriptor"
|
name = "gpu-descriptor"
|
||||||
version = "0.2.4"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c"
|
checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"gpu-descriptor-types",
|
"gpu-descriptor-types",
|
||||||
@ -1521,9 +1497,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpu-descriptor-types"
|
name = "gpu-descriptor-types"
|
||||||
version = "0.1.2"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c"
|
checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
]
|
]
|
||||||
@ -1621,13 +1597,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.24.9"
|
version = "0.25.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
|
checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder",
|
"byteorder-lite",
|
||||||
"color_quant",
|
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"png",
|
"png",
|
||||||
]
|
]
|
||||||
@ -1846,9 +1821,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "metal"
|
name = "metal"
|
||||||
version = "0.27.0"
|
version = "0.28.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25"
|
checksum = "5637e166ea14be6063a3f8ba5ccb9a4159df7d8f6d61c02fc3d480b1f90dcfcb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"block",
|
"block",
|
||||||
@ -1871,10 +1846,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "naga"
|
name = "naga"
|
||||||
version = "0.19.2"
|
version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843"
|
checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"codespan-reporting",
|
"codespan-reporting",
|
||||||
@ -1975,7 +1951,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
|
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"malloc_buf",
|
"malloc_buf",
|
||||||
"objc_exception",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2119,15 +2094,6 @@ dependencies = [
|
|||||||
"objc2-metal",
|
"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]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.19.0"
|
version = "1.19.0"
|
||||||
@ -3149,30 +3115,32 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webbrowser"
|
name = "webbrowser"
|
||||||
version = "0.8.15"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b"
|
checksum = "425ba64c1e13b1c6e8c5d2541c8fac10022ca584f33da781db01b5756aef1f4e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"block2 0.5.1",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"home",
|
"home",
|
||||||
"jni",
|
"jni",
|
||||||
"log",
|
"log",
|
||||||
"ndk-context",
|
"ndk-context",
|
||||||
"objc",
|
"objc2 0.5.2",
|
||||||
"raw-window-handle 0.5.2",
|
"objc2-foundation",
|
||||||
"url",
|
"url",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu"
|
name = "wgpu"
|
||||||
version = "0.19.4"
|
version = "0.20.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01"
|
checksum = "90e37c7b9921b75dfd26dd973fdcbce36f13dfa6e2dc82aece584e0ed48c355c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
|
"document-features",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
@ -3190,15 +3158,16 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-core"
|
name = "wgpu-core"
|
||||||
version = "0.19.4"
|
version = "0.21.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a"
|
checksum = "d50819ab545b867d8a454d1d756b90cd5f15da1f2943334ca314af10583c9d39"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"bit-vec",
|
"bit-vec",
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"codespan-reporting",
|
"codespan-reporting",
|
||||||
|
"document-features",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"log",
|
"log",
|
||||||
"naga",
|
"naga",
|
||||||
@ -3216,9 +3185,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-hal"
|
name = "wgpu-hal"
|
||||||
version = "0.19.4"
|
version = "0.21.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc1a4924366df7ab41a5d8546d6534f1f33231aa5b3f72b9930e300f254e39c3"
|
checksum = "172e490a87295564f3fcc0f165798d87386f6231b04d4548bca458cbbfd63222"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
@ -3257,9 +3226,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-types"
|
name = "wgpu-types"
|
||||||
version = "0.19.2"
|
version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805"
|
checksum = "1353d9a46bff7f955a680577f34c69122628cc2076e1d6f3a9be6ef00ae793ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
@ -4,9 +4,9 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
env_logger = "0.11.3"
|
env_logger = "0.11.5"
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
clap = { version = "4.5.8", features = ["derive", "env"] }
|
clap = { version = "4.5.18", features = ["derive", "env"] }
|
||||||
egui = "0.27.2"
|
egui = "0.28.1"
|
||||||
eframe = "0.27.2"
|
eframe = "0.28.1"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
|
@ -11,7 +11,7 @@ fn main() {
|
|||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
"Custom consumption",
|
"Custom consumption",
|
||||||
options,
|
options,
|
||||||
Box::new(|_cc| Box::<MyApp>::default()),
|
Box::new(|_cc| Ok(Box::<MyApp>::default())),
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user