Compare commits
	
		
			129 Commits
		
	
	
		
			981e9c1065
			...
			renovate/l
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 439af298b7 | |||
| 4d46f9c52c | |||
| c90a05fcfd | |||
| abdca20a66 | |||
| 88a24565b4 | |||
| 3625188706 | |||
| 58eceeda2d | |||
| c44c6c3bf5 | |||
| 4ef714fdbd | |||
| bab4525908 | |||
| 2ed4299032 | |||
| bcc92da065 | |||
| af83130b63 | |||
| 950b576373 | |||
| 2459b0cf99 | |||
| 814204b4ce | |||
| de8fcfbcfb | |||
| 49a7011d84 | |||
| 39aff8054a | |||
| 850f828dca | |||
| 59383debd2 | |||
| 58cf0a9614 | |||
| f30aaf71dd | |||
| 27ce9e9a96 | |||
| 903dd104f3 | |||
| 205981fbc8 | |||
| 623b0a4671 | |||
| fd25e71cf8 | |||
| 842b48e782 | |||
| e8374a8ef7 | |||
| 347c247285 | |||
| c59aeed4ab | |||
| b7f1beb1b7 | |||
| 4bd700c2db | |||
| 3f1c5e4ac0 | |||
| f77506dc46 | |||
| 5b7554b6bf | |||
| 8fdbcc4f3a | |||
| 9f4c2b0e35 | |||
| 0a7138a82b | |||
| 2ce09a94b1 | |||
| 355b2a71ce | |||
| 6b04bf4261 | |||
| 99c6963210 | |||
| 89cfd3ce21 | |||
| 0c40ff2750 | |||
| 75be1ed1d2 | |||
| 6a2baca3f2 | |||
| 4d0d20b424 | |||
| 05cf488be7 | |||
| b8ecc83668 | |||
| c1912717e4 | |||
| a8780e60d3 | |||
| 5005cf84f9 | |||
| 561992010c | |||
| 4725e67ee1 | |||
| fbb55628be | |||
| 79e49ed5a9 | |||
| 138b9d2dbe | |||
| 3413a1ee21 | |||
| 64055568e0 | |||
| af71b574cc | |||
| 6088237b79 | |||
| 05a033b51c | |||
| 3d3ccf5242 | |||
| 68521c238c | |||
| f03278f8c5 | |||
| a63d1f17de | |||
| bb6d8a8be1 | |||
| 693414b1eb | |||
| f74e86a8db | |||
| 53d4cb1de7 | |||
| 498ca3925a | |||
| 155806df78 | |||
| 79f3668021 | |||
| 660e6e8a5b | |||
| 0e3182434f | |||
| cf2f034e6c | |||
| ddd27519a9 | |||
| e8d2e8b318 | |||
| 0dfc25a918 | |||
| 3078b3c645 | |||
| 9d3bed68af | |||
| 9fe00f149c | |||
| 65164055d6 | |||
| 08680122f2 | |||
| 642366540e | |||
| 482da63a3b | |||
| cade9dc02b | |||
| 9b87025b27 | |||
| 46d3f3580c | |||
| bda9f6a9c9 | |||
| 41380a103f | |||
| 2573778b82 | |||
| 527126d926 | |||
| 9a1104c57b | |||
| bf3e703bb1 | |||
| d7607bc483 | |||
| 306be8272a | |||
| 742bb8e2ed | |||
| e10d7ef478 | |||
| 18ba6c60d6 | |||
| d70562f97a | |||
| b0d192a9aa | |||
| 99f6b17f4e | |||
| 3fff4d624e | |||
| e2f6212a75 | |||
| bff1c88da8 | |||
| 7933c2dae3 | |||
| 15b8a69700 | |||
| 157c77142a | |||
| 25f6e808ed | |||
| 6f298d3238 | |||
| b75e868cc3 | |||
| 6a2b5e320d | |||
| 52637fc401 | |||
| 8ea9da9443 | |||
| b1b4eaa341 | |||
| 94652a82fe | |||
| 832ab86536 | |||
| 32dbbf4678 | |||
| 1f48203564 | |||
| 0b6526f901 | |||
| 741db9e13b | |||
| 061ae8c208 | |||
| 4a68800907 | |||
| 6738d47507 | |||
| acbce81b46 | |||
| 1615531d67 | 
							
								
								
									
										37
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								.drone.yml
									
									
									
									
									
								
							| @@ -46,6 +46,11 @@ steps: | ||||
|         path: /usr/local/cargo/registry | ||||
|       - name: web_app | ||||
|         path: /tmp/web_build | ||||
|       - name: releases | ||||
|         path: /tmp/releases | ||||
|     when: | ||||
|       event: | ||||
|         - tag | ||||
|     depends_on: | ||||
|       - backend_check | ||||
|       - web_build | ||||
| @@ -54,13 +59,41 @@ steps: | ||||
|       - mv /tmp/web_build/dist static | ||||
|       - cargo build --release | ||||
|       - ls -lah target/release/central_backend | ||||
|       - mv target/release/central_backend /tmp/releases/central_backend | ||||
|  | ||||
|   # Build ESP32 program | ||||
|   - name: esp32_compile | ||||
|     image: espressif/idf:v5.4.2 | ||||
|     image: espressif/idf:v5.5.1 | ||||
|     volumes: | ||||
|       - name: releases | ||||
|         path: /tmp/releases | ||||
|     commands: | ||||
|       - cd esp32_device | ||||
|       - /opt/esp/entrypoint.sh idf.py build | ||||
|       - ls -lah build/main.bin | ||||
|       - cp build/main.bin /tmp/releases/wt32-eth01.bin | ||||
|  | ||||
|   # Auto-release to Gitea | ||||
|   - name: gitea_release | ||||
|     image: plugins/gitea-release | ||||
|     depends_on: | ||||
|     - backend_compile | ||||
|     - esp32_compile | ||||
|     when: | ||||
|       event: | ||||
|         - tag | ||||
|     volumes: | ||||
|       - name: releases | ||||
|         path: /tmp/releases | ||||
|     environment: | ||||
|       PLUGIN_API_KEY: | ||||
|         from_secret: GITEA_API_KEY # needs permission write:repository | ||||
|     settings: | ||||
|       base_url: https://gitea.communiquons.org | ||||
|       files: | ||||
|         - /tmp/releases/central_backend | ||||
|         - /tmp/releases/wt32-eth01.bin | ||||
|       checksum: sha512 | ||||
|  | ||||
|  | ||||
| volumes: | ||||
| @@ -68,3 +101,5 @@ volumes: | ||||
|     temp: {} | ||||
|   - name: web_app | ||||
|     temp: {} | ||||
|   - name: releases | ||||
|     temp: {} | ||||
|   | ||||
							
								
								
									
										708
									
								
								central_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										708
									
								
								central_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,46 +1,46 @@ | ||||
| [package] | ||||
| name = "central_backend" | ||||
| version = "1.0.2" | ||||
| version = "1.0.3" | ||||
| edition = "2024" | ||||
|  | ||||
| [dependencies] | ||||
| log = "0.4.27" | ||||
| log = "0.4.28" | ||||
| env_logger = "0.11.8" | ||||
| lazy_static = "1.5.0" | ||||
| dotenvy = "0.15.7" | ||||
| clap = { version = "4.5.45", features = ["derive", "env"] } | ||||
| anyhow = "1.0.99" | ||||
| thiserror = "2.0.12" | ||||
| openssl = { version = "0.10.73" } | ||||
| openssl-sys = "0.9.109" | ||||
| libc = "0.2.174" | ||||
| clap = { version = "4.5.50", features = ["derive", "env"] } | ||||
| anyhow = "1.0.100" | ||||
| thiserror = "2.0.17" | ||||
| openssl = { version = "0.10.74" } | ||||
| openssl-sys = "0.9.110" | ||||
| libc = "0.2.177" | ||||
| foreign-types-shared = "0.1.1" | ||||
| asn1 = "0.22.0" | ||||
| asn1 = "0.23.0" | ||||
| actix-web = { version = "4.11.0", features = ["openssl"] } | ||||
| futures = "0.3.31" | ||||
| serde = { version = "1.0.219", features = ["derive"] } | ||||
| reqwest = { version = "0.12.22", features = ["json"] } | ||||
| serde_json = "1.0.142" | ||||
| rand = "0.9.2" | ||||
| serde = { version = "1.0.228", features = ["derive"] } | ||||
| reqwest = { version = "0.12.24", features = ["json"] } | ||||
| serde_json = "1.0.145" | ||||
| rand = "0.10.0-rc.0" | ||||
| actix = "0.13.5" | ||||
| actix-identity = "0.8.0" | ||||
| actix-session = { version = "0.10.1", features = ["cookie-session"] } | ||||
| actix-identity = "0.9.0" | ||||
| actix-session = { version = "0.11.0", features = ["cookie-session"] } | ||||
| actix-cors = "0.7.1" | ||||
| actix-multipart = { version = "0.7.2", features = ["derive"] } | ||||
| actix-remote-ip = "0.1.0" | ||||
| futures-util = "0.3.31" | ||||
| uuid = { version = "1.17.0", features = ["v4", "serde"] } | ||||
| semver = { version = "1.0.26", features = ["serde"] } | ||||
| lazy-regex = "3.4.1" | ||||
| tokio = { version = "1.47.1", features = ["full"] } | ||||
| uuid = { version = "1.18.1", features = ["v4", "serde"] } | ||||
| semver = { version = "1.0.27", features = ["serde"] } | ||||
| lazy-regex = "3.4.2" | ||||
| tokio = { version = "1.48.0", features = ["full"] } | ||||
| tokio_schedule = "0.3.2" | ||||
| mime_guess = "2.0.5" | ||||
| rust-embed = "8.7.2" | ||||
| jsonwebtoken = { version = "9.3.1", features = ["use_pem"] } | ||||
| rust-embed = "8.8.0" | ||||
| jsonwebtoken = { version = "10.1.0", features = ["use_pem", "rust_crypto"] } | ||||
| prettytable-rs = "0.10.0" | ||||
| chrono = "0.4.41" | ||||
| chrono = "0.4.42" | ||||
| serde_yml = "0.0.12" | ||||
| bincode = "2.0.1" | ||||
| fs4 = { version = "0.13.1", features = ["sync"] } | ||||
| zip = { version = "2.2.0", features = ["bzip2"] } | ||||
| zip = { version = "6.0.0", features = ["bzip2"] } | ||||
| walkdir = "2.5.0" | ||||
|   | ||||
							
								
								
									
										33
									
								
								central_backend/engine_test/test_turn_forced_off.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								central_backend/engine_test/test_turn_forced_off.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| devices: | ||||
|   - id: dev1 | ||||
|     info: | ||||
|       reference: A | ||||
|       version: 0.0.1 | ||||
|       max_relays: 1 | ||||
|     time_create: 1 | ||||
|     time_update: 1 | ||||
|     name: Dev1 | ||||
|     description: Day1 | ||||
|     validated: true | ||||
|     enabled: true | ||||
|     relays: | ||||
|       - id: dcb3fd91-bf9b-4de3-99e5-92c1c7dd72e9 | ||||
|         name: R1 | ||||
|         enabled: true | ||||
|         priority: 1 | ||||
|         consumption: 100 | ||||
|         minimal_uptime: 10 | ||||
|         minimal_downtime: 10 | ||||
|         depends_on: [] | ||||
|         conflicts_with: [] | ||||
|  | ||||
|         on: false | ||||
|         for: 5000 | ||||
|         forced_state: | ||||
|           type: Off | ||||
|           for_secs: 500 | ||||
|         should_be_on: false | ||||
|  | ||||
|     online: true | ||||
|  | ||||
| curr_consumption: -10000 | ||||
							
								
								
									
										49
									
								
								central_backend/engine_test/test_turn_forced_on.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								central_backend/engine_test/test_turn_forced_on.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| devices: | ||||
|   - id: dev1 | ||||
|     info: | ||||
|       reference: A | ||||
|       version: 0.0.1 | ||||
|       max_relays: 1 | ||||
|     time_create: 1 | ||||
|     time_update: 1 | ||||
|     name: Dev1 | ||||
|     description: Day1 | ||||
|     validated: true | ||||
|     enabled: true | ||||
|     relays: | ||||
|       - id: dcb3fd91-bf9b-4de3-99e5-92c1c7dd72e9 | ||||
|         name: R1 | ||||
|         enabled: true | ||||
|         priority: 1 | ||||
|         consumption: 100 | ||||
|         minimal_uptime: 10 | ||||
|         minimal_downtime: 10 | ||||
|         depends_on: [] | ||||
|         conflicts_with: [] | ||||
|  | ||||
|         on: false | ||||
|         for: 500 | ||||
|         forced_state: | ||||
|           type: On | ||||
|           for_secs: 500 | ||||
|         should_be_on: true | ||||
|  | ||||
|       - id: dcb3fd91-bf9b-4de3-99e5-92c1c7dd72f0 | ||||
|         name: R2 | ||||
|         enabled: true | ||||
|         priority: 1 | ||||
|         consumption: 100 | ||||
|         minimal_uptime: 10 | ||||
|         minimal_downtime: 10 | ||||
|         depends_on: [ ] | ||||
|         conflicts_with: [ ] | ||||
|  | ||||
|         on: false | ||||
|         for: 500 | ||||
|         forced_state: | ||||
|           type: None | ||||
|         should_be_on: false | ||||
|  | ||||
|     online: true | ||||
|  | ||||
| curr_consumption: 10000 | ||||
| @@ -8,7 +8,7 @@ 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::energy::engine::{EnergyEngine, RelayForcedState}; | ||||
| use crate::utils::time_utils::time_secs; | ||||
| use actix::prelude::*; | ||||
| use openssl::x509::X509Req; | ||||
| @@ -328,6 +328,19 @@ impl Handler<UpdateDeviceRelay> for EnergyActor { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<()>")] | ||||
| pub struct SetRelayForcedState(pub DeviceRelayID, pub RelayForcedState); | ||||
|  | ||||
| impl Handler<SetRelayForcedState> for EnergyActor { | ||||
|     type Result = anyhow::Result<()>; | ||||
|  | ||||
|     fn handle(&mut self, msg: SetRelayForcedState, _ctx: &mut Context<Self>) -> Self::Result { | ||||
|         self.engine.relay_state(msg.0).set_forced(msg.1); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Delete a device relay | ||||
| #[derive(Message)] | ||||
| #[rtype(result = "anyhow::Result<()>")] | ||||
| @@ -408,6 +421,7 @@ pub struct ResRelayState { | ||||
|     pub id: DeviceRelayID, | ||||
|     pub on: bool, | ||||
|     pub r#for: usize, | ||||
|     pub forced_state: RelayForcedState, | ||||
| } | ||||
|  | ||||
| /// Get the state of all relays | ||||
| @@ -427,6 +441,7 @@ impl Handler<GetAllRelaysState> for EnergyActor { | ||||
|                 id: d.id, | ||||
|                 on: state.is_on(), | ||||
|                 r#for: state.state_for(), | ||||
|                 forced_state: state.actual_forced_state(), | ||||
|             }) | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -25,19 +25,83 @@ impl DeviceState { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] | ||||
| #[serde(tag = "type")] | ||||
| pub enum SetRelayForcedStateReq { | ||||
|     #[default] | ||||
|     None, | ||||
|     Off { | ||||
|         for_secs: u64, | ||||
|     }, | ||||
|     On { | ||||
|         for_secs: u64, | ||||
|     }, | ||||
| } | ||||
|  | ||||
| impl SetRelayForcedStateReq { | ||||
|     pub fn to_forced_state(&self) -> RelayForcedState { | ||||
|         match &self { | ||||
|             SetRelayForcedStateReq::None => RelayForcedState::None, | ||||
|             SetRelayForcedStateReq::Off { for_secs } => RelayForcedState::Off { | ||||
|                 until: time_secs() + for_secs, | ||||
|             }, | ||||
|             SetRelayForcedStateReq::On { for_secs } => RelayForcedState::On { | ||||
|                 until: time_secs() + for_secs, | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] | ||||
| #[serde(tag = "type")] | ||||
| pub enum RelayForcedState { | ||||
|     #[default] | ||||
|     None, | ||||
|     Off { | ||||
|         until: u64, | ||||
|     }, | ||||
|     On { | ||||
|         until: u64, | ||||
|     }, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Clone)] | ||||
| pub struct RelayState { | ||||
|     on: bool, | ||||
|     since: usize, | ||||
|     forced_state: RelayForcedState, | ||||
| } | ||||
|  | ||||
| impl RelayState { | ||||
|     /// Get actual forced state (returns None if state is expired) | ||||
|     pub fn actual_forced_state(&self) -> RelayForcedState { | ||||
|         match self.forced_state { | ||||
|             RelayForcedState::Off { until } if until > time_secs() => { | ||||
|                 RelayForcedState::Off { until } | ||||
|             } | ||||
|             RelayForcedState::On { until } if until > time_secs() => RelayForcedState::On { until }, | ||||
|             _ => RelayForcedState::None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn is_on(&self) -> bool { | ||||
|         self.on | ||||
|         let forced_state = self.actual_forced_state(); | ||||
|         (self.on || matches!(forced_state, RelayForcedState::On { .. })) | ||||
|             && !matches!(forced_state, RelayForcedState::Off { .. }) | ||||
|     } | ||||
|  | ||||
|     fn is_off(&self) -> bool { | ||||
|         !self.on | ||||
|         !self.is_on() | ||||
|     } | ||||
|  | ||||
|     /// Check if relay state is enforced | ||||
|     pub fn is_forced(&self) -> bool { | ||||
|         self.actual_forced_state() != RelayForcedState::None | ||||
|     } | ||||
|  | ||||
|     pub fn set_forced(&mut self, s: RelayForcedState) { | ||||
|         self.since = time_secs() as usize; | ||||
|         self.forced_state = s; | ||||
|     } | ||||
|  | ||||
|     pub fn state_for(&self) -> usize { | ||||
| @@ -146,7 +210,11 @@ impl EnergyEngine { | ||||
|                     r.name, | ||||
|                     r.consumption, | ||||
|                     format!("{} / {}", r.minimal_downtime, r.minimal_uptime), | ||||
|                     status.is_on().to_string(), | ||||
|                     status.is_on().to_string() | ||||
|                         + match status.is_forced() { | ||||
|                             true => " (Forced)", | ||||
|                             false => "", | ||||
|                         }, | ||||
|                     status.since, | ||||
|                     match dev_online { | ||||
|                         true => "Online", | ||||
| @@ -192,19 +260,28 @@ impl EnergyEngine { | ||||
|  | ||||
|         let mut new_relays_state = self.relays_state.clone(); | ||||
|  | ||||
|         // Forcefully turn off relays that belongs to offline devices | ||||
|         // Forcefully turn off disabled relays | ||||
|         for d in devices { | ||||
|             if !self.device_state(&d.id).is_online() { | ||||
|                 for r in &d.relays { | ||||
|             for r in &d.relays { | ||||
|                 if !r.enabled || !d.enabled { | ||||
|                     new_relays_state.get_mut(&r.id).unwrap().on = false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Forcefully turn off disabled relays | ||||
|         // Apply forced relays state | ||||
|         for d in devices { | ||||
|             for r in &d.relays { | ||||
|                 if !r.enabled || !d.enabled { | ||||
|                 if self.relay_state(r.id).is_forced() { | ||||
|                     new_relays_state.get_mut(&r.id).unwrap().on = self.relay_state(r.id).is_on(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Forcefully turn off relays that belongs to offline devices | ||||
|         for d in devices { | ||||
|             if !self.device_state(&d.id).is_online() { | ||||
|                 for r in &d.relays { | ||||
|                     new_relays_state.get_mut(&r.id).unwrap().on = false; | ||||
|                 } | ||||
|             } | ||||
| @@ -216,7 +293,9 @@ impl EnergyEngine { | ||||
|  | ||||
|             for d in devices { | ||||
|                 for r in &d.relays { | ||||
|                     if new_relays_state.get(&r.id).unwrap().is_off() { | ||||
|                     if new_relays_state.get(&r.id).unwrap().is_off() | ||||
|                         || new_relays_state.get(&r.id).unwrap().is_forced() | ||||
|                     { | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
| @@ -240,7 +319,7 @@ impl EnergyEngine { | ||||
|             for d in devices { | ||||
|                 for r in &d.relays { | ||||
|                     let state = new_relays_state.get(&r.id).unwrap(); | ||||
|                     if state.is_off() { | ||||
|                     if state.is_off() || state.is_forced() { | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
| @@ -271,7 +350,9 @@ impl EnergyEngine { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 if new_relays_state.get(&r.id).unwrap().is_on() { | ||||
|                 if new_relays_state.get(&r.id).unwrap().is_on() | ||||
|                     || new_relays_state.get(&r.id).unwrap().is_forced() | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
| @@ -298,7 +379,7 @@ impl EnergyEngine { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Order relays | ||||
|         // Order relays to select the ones with the most elevated priorities | ||||
|         let mut ordered_relays = devices | ||||
|             .iter() | ||||
|             .filter(|d| self.device_state(&d.id).is_online() && d.enabled) | ||||
| @@ -308,10 +389,13 @@ impl EnergyEngine { | ||||
|         ordered_relays.sort_by_key(|r| r.priority); | ||||
|         ordered_relays.reverse(); | ||||
|  | ||||
|         // Select relays to start, starting with those with highest priorities | ||||
|         loop { | ||||
|             let mut changed = false; | ||||
|             for relay in &ordered_relays { | ||||
|                 if new_relays_state.get(&relay.id).unwrap().is_on() { | ||||
|                 if new_relays_state.get(&relay.id).unwrap().is_on() | ||||
|                     || new_relays_state.get(&relay.id).unwrap().is_forced() | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
| @@ -383,7 +467,7 @@ impl EnergyEngine { | ||||
| mod test { | ||||
|     use crate::devices::device::{Device, DeviceId, DeviceRelayID}; | ||||
|     use crate::energy::consumption::EnergyConsumption; | ||||
|     use crate::energy::engine::EnergyEngine; | ||||
|     use crate::energy::engine::{EnergyEngine, SetRelayForcedStateReq}; | ||||
|     use crate::utils::time_utils::time_secs; | ||||
|     use rust_embed::Embed; | ||||
|  | ||||
| @@ -392,6 +476,8 @@ mod test { | ||||
|         id: DeviceRelayID, | ||||
|         on: bool, | ||||
|         r#for: usize, | ||||
|         #[serde(default)] | ||||
|         forced_state: SetRelayForcedStateReq, | ||||
|         should_be_on: bool, | ||||
|     } | ||||
|  | ||||
| @@ -439,6 +525,7 @@ mod test { | ||||
|                 let s = engine.relay_state(r.id); | ||||
|                 s.on = r.on; | ||||
|                 s.since = time_secs() as usize - r.r#for; | ||||
|                 s.forced_state = r.forced_state.to_forced_state() | ||||
|             } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,7 @@ use crate::server::custom_error::HttpResult; | ||||
| use crate::server::devices_api::jwt_parser::JWTRequest; | ||||
| use actix_web::{HttpResponse, web}; | ||||
|  | ||||
| #[derive(Debug, serde::Deserialize)] | ||||
| #[derive(Debug, serde::Deserialize, Clone)] | ||||
| pub struct LogRequest { | ||||
|     severity: LogSeverity, | ||||
|     message: String, | ||||
|   | ||||
| @@ -30,7 +30,7 @@ pub struct JWTRequest { | ||||
| } | ||||
|  | ||||
| impl JWTRequest { | ||||
|     pub async fn parse_jwt<E: DeserializeOwned>( | ||||
|     pub async fn parse_jwt<E: DeserializeOwned + std::clone::Clone>( | ||||
|         &self, | ||||
|         actor: WebEnergyActor, | ||||
|     ) -> anyhow::Result<(Device, E)> { | ||||
|   | ||||
| @@ -130,7 +130,7 @@ pub async fn get_certificate(query: web::Query<ReqWithDevID>, actor: WebEnergyAc | ||||
|         .body(cert)) | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize, serde::Deserialize)] | ||||
| #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] | ||||
| struct Claims { | ||||
|     info: DeviceInfo, | ||||
| } | ||||
|   | ||||
| @@ -11,7 +11,7 @@ use crate::server::web_api::*; | ||||
| use crate::server::web_app_controller; | ||||
| use actix_cors::Cors; | ||||
| use actix_identity::IdentityMiddleware; | ||||
| use actix_identity::config::LogoutBehaviour; | ||||
| use actix_identity::config::LogoutBehavior; | ||||
| use actix_remote_ip::RemoteIPConfig; | ||||
| use actix_session::SessionMiddleware; | ||||
| use actix_session::storage::CookieSessionStore; | ||||
| @@ -84,7 +84,7 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()> | ||||
|         .build(); | ||||
|  | ||||
|         let identity_middleware = IdentityMiddleware::builder() | ||||
|             .logout_behaviour(LogoutBehaviour::PurgeSession) | ||||
|             .logout_behavior(LogoutBehavior::PurgeSession) | ||||
|             .visit_deadline(Some(Duration::from_secs( | ||||
|                 constants::MAX_INACTIVITY_DURATION, | ||||
|             ))) | ||||
| @@ -231,6 +231,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()> | ||||
|                 "/web_api/relay/{id}", | ||||
|                 web::put().to(relays_controller::update), | ||||
|             ) | ||||
|             .route( | ||||
|                 "/web_api/relay/{id}/forced_state", | ||||
|                 web::put().to(relays_controller::set_forced_state), | ||||
|             ) | ||||
|             .route( | ||||
|                 "/web_api/relay/{id}", | ||||
|                 web::delete().to(relays_controller::delete), | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| use crate::devices::device::{DeviceId, DeviceRelay, DeviceRelayID}; | ||||
| use crate::energy::energy_actor; | ||||
| use crate::energy::engine::SetRelayForcedStateReq; | ||||
| use crate::server::WebEnergyActor; | ||||
| use crate::server::custom_error::HttpResult; | ||||
| use actix_web::{HttpResponse, web}; | ||||
| @@ -85,6 +86,29 @@ pub async fn update( | ||||
|     Ok(HttpResponse::Accepted().finish()) | ||||
| } | ||||
|  | ||||
| /// Set relay forced status | ||||
| pub async fn set_forced_state( | ||||
|     actor: WebEnergyActor, | ||||
|     req: web::Json<SetRelayForcedStateReq>, | ||||
|     path: web::Path<RelayIDInPath>, | ||||
| ) -> HttpResult { | ||||
|     // Check if relay exists first | ||||
|     let list = actor.send(energy_actor::GetAllRelaysState).await?; | ||||
|     if !list.into_iter().any(|r| r.id == path.id) { | ||||
|         return Ok(HttpResponse::NotFound().json("Relay not found!")); | ||||
|     }; | ||||
|  | ||||
|     // Update relay forced state | ||||
|     actor | ||||
|         .send(energy_actor::SetRelayForcedState( | ||||
|             path.id, | ||||
|             req.to_forced_state(), | ||||
|         )) | ||||
|         .await??; | ||||
|  | ||||
|     Ok(HttpResponse::Accepted().finish()) | ||||
| } | ||||
|  | ||||
| /// Delete an existing relay | ||||
| pub async fn delete(actor: WebEnergyActor, path: web::Path<RelayIDInPath>) -> HttpResult { | ||||
|     actor | ||||
|   | ||||
							
								
								
									
										1726
									
								
								central_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1726
									
								
								central_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -12,34 +12,34 @@ | ||||
|   "dependencies": { | ||||
|     "@emotion/react": "^11.14.0", | ||||
|     "@emotion/styled": "^11.14.1", | ||||
|     "@fontsource/roboto": "^5.2.6", | ||||
|     "@fontsource/roboto": "^5.2.8", | ||||
|     "@mdi/js": "^7.4.47", | ||||
|     "@mdi/react": "^1.6.1", | ||||
|     "@mui/icons-material": "^7.0.2", | ||||
|     "@mui/material": "^7.0.2", | ||||
|     "@mui/x-charts": "^7.29.1", | ||||
|     "@mui/x-date-pickers": "^7.29.4", | ||||
|     "date-and-time": "^3.6.0", | ||||
|     "dayjs": "^1.11.13", | ||||
|     "filesize": "^10.1.6", | ||||
|     "react": "^19.1.1", | ||||
|     "react-dom": "^19.1.1", | ||||
|     "react-router-dom": "^7.7.1", | ||||
|     "semver": "^7.7.2" | ||||
|     "@mui/icons-material": "^7.3.4", | ||||
|     "@mui/material": "^7.3.4", | ||||
|     "@mui/x-charts": "^8.15.0", | ||||
|     "@mui/x-date-pickers": "^8.15.0", | ||||
|     "date-and-time": "^4.1.0", | ||||
|     "dayjs": "^1.11.18", | ||||
|     "filesize": "^11.0.13", | ||||
|     "react": "^19.2.0", | ||||
|     "react-dom": "^19.2.0", | ||||
|     "react-router-dom": "^7.9.4", | ||||
|     "semver": "^7.7.3" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@types/react": "^19.1.12", | ||||
|     "@types/react-dom": "^19.1.9", | ||||
|     "@types/semver": "^7.7.0", | ||||
|     "@typescript-eslint/eslint-plugin": "^8.39.1", | ||||
|     "@typescript-eslint/parser": "^8.41.0", | ||||
|     "@vitejs/plugin-react": "^4.7.0", | ||||
|     "eslint": "^9.33.0", | ||||
|     "eslint-plugin-react-hooks": "^5.2.0", | ||||
|     "eslint-plugin-react-refresh": "^0.4.20", | ||||
|     "globals": "^16.3.0", | ||||
|     "typescript": "^5.8.3", | ||||
|     "typescript-eslint": "^8.24.1", | ||||
|     "vite": "^6.3.5" | ||||
|     "@types/react": "^19.2.2", | ||||
|     "@types/react-dom": "^19.2.2", | ||||
|     "@types/semver": "^7.7.1", | ||||
|     "@typescript-eslint/eslint-plugin": "^8.46.2", | ||||
|     "@typescript-eslint/parser": "^8.46.2", | ||||
|     "@vitejs/plugin-react": "^5.1.0", | ||||
|     "eslint": "^9.38.0", | ||||
|     "eslint-plugin-react-hooks": "^7.0.1", | ||||
|     "eslint-plugin-react-refresh": "^0.4.24", | ||||
|     "globals": "^16.4.0", | ||||
|     "typescript": "^5.9.3", | ||||
|     "typescript-eslint": "^8.46.2", | ||||
|     "vite": "^7.1.12" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,10 +1,19 @@ | ||||
| import { APIClient } from "./ApiClient"; | ||||
| import { Device, DeviceRelay } from "./DeviceApi"; | ||||
|  | ||||
| export type RelayForcedState = | ||||
|   | { type: "None" } | ||||
|   | { type: "Off" | "On"; until: number }; | ||||
|  | ||||
| export type SetRelayForcedState = | ||||
|   | { type: "None" } | ||||
|   | { type: "Off" | "On"; for_secs: number }; | ||||
|  | ||||
| export interface RelayStatus { | ||||
|   id: string; | ||||
|   on: boolean; | ||||
|   for: number; | ||||
|   forced_state: RelayForcedState; | ||||
| } | ||||
|  | ||||
| export type RelaysStatus = Map<string, RelayStatus>; | ||||
| @@ -48,6 +57,20 @@ export class RelayApi { | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Set relay forced state | ||||
|    */ | ||||
|   static async SetForcedState( | ||||
|     relay: DeviceRelay, | ||||
|     forced: SetRelayForcedState | ||||
|   ): Promise<void> { | ||||
|     await APIClient.exec({ | ||||
|       method: "PUT", | ||||
|       uri: `/relay/${relay.id}/forced_state`, | ||||
|       jsonData: forced, | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Delete a relay configuration | ||||
|    */ | ||||
|   | ||||
| @@ -84,7 +84,7 @@ export function DeployOTAUpdateDialogProvider(p: { | ||||
|             </DialogContentText> | ||||
|  | ||||
|             <FormControl> | ||||
|               <FormLabel>Gender</FormLabel> | ||||
|               <FormLabel>Deployment target</FormLabel> | ||||
|               <RadioGroup | ||||
|                 name="radio-buttons-group" | ||||
|                 value={allDevices} | ||||
|   | ||||
| @@ -0,0 +1,53 @@ | ||||
| import { | ||||
|   Button, | ||||
|   Dialog, | ||||
|   DialogActions, | ||||
|   DialogContent, | ||||
|   DialogContentText, | ||||
|   DialogTitle, | ||||
|   TextField, | ||||
| } from "@mui/material"; | ||||
| import { DeviceRelay } from "../api/DeviceApi"; | ||||
| import React from "react"; | ||||
|  | ||||
| export function SelectForcedStateDurationDialog(p: { | ||||
|   relay: DeviceRelay; | ||||
|   forcedState: string; | ||||
|   onCancel: () => void; | ||||
|   onSubmit: (duration: number) => void; | ||||
| }): React.ReactElement { | ||||
|   const [duration, setDuration] = React.useState(60); | ||||
|  | ||||
|   return ( | ||||
|     <Dialog open onClose={p.onCancel}> | ||||
|       <DialogTitle>Set forced relay state</DialogTitle> | ||||
|       <DialogContent> | ||||
|         <DialogContentText> | ||||
|           Please specify the number of minutes the relay <i>{p.relay.name}</i>{" "} | ||||
|           will remain in forced state <i>{p.forcedState}</i>: | ||||
|         </DialogContentText> | ||||
|  | ||||
|         <TextField | ||||
|           label="Duration (min)" | ||||
|           variant="standard" | ||||
|           value={Math.floor(duration / 60)} | ||||
|           onChange={(e) => { | ||||
|             const val = Number.parseInt(e.target.value); | ||||
|             setDuration((Number.isNaN(val) ? 1 : val) * 60); | ||||
|           }} | ||||
|           fullWidth | ||||
|           style={{ marginTop: "5px" }} | ||||
|         /> | ||||
|  | ||||
|         <p>Equivalent in seconds: {duration} secs</p> | ||||
|         <p>Equivalent in hours: {duration / 3600} hours</p> | ||||
|       </DialogContent> | ||||
|       <DialogActions> | ||||
|         <Button onClick={p.onCancel}>Cancel</Button> | ||||
|         <Button onClick={() => p.onSubmit(duration)} autoFocus> | ||||
|           Start timer | ||||
|         </Button> | ||||
|       </DialogActions> | ||||
|     </Dialog> | ||||
|   ); | ||||
| } | ||||
| @@ -10,16 +10,16 @@ import { | ||||
| } from "@mui/material"; | ||||
| import React from "react"; | ||||
| import { Device, DeviceRelay } from "../../api/DeviceApi"; | ||||
| import { RelayApi, RelayStatus } from "../../api/RelayApi"; | ||||
| import { EditDeviceRelaysDialog } from "../../dialogs/EditDeviceRelaysDialog"; | ||||
| import { DeviceRouteCard } from "./DeviceRouteCard"; | ||||
| import { useAlert } from "../../hooks/context_providers/AlertDialogProvider"; | ||||
| import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider"; | ||||
| import { useLoadingMessage } from "../../hooks/context_providers/LoadingMessageProvider"; | ||||
| 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"; | ||||
| import { TimeWidget } from "../../widgets/TimeWidget"; | ||||
| import { DeviceRouteCard } from "./DeviceRouteCard"; | ||||
|  | ||||
| export function DeviceRelays(p: { | ||||
|   device: Device; | ||||
| @@ -145,7 +145,8 @@ function RelayEntryStatus( | ||||
|       errMsg="Failed to load relay status!" | ||||
|       build={() => ( | ||||
|         <> | ||||
|           <BoolText val={state!.on} positive="ON" negative="OFF" /> for{" "} | ||||
|           <BoolText val={state!.on} positive="ON" negative="OFF" />{" "} | ||||
|           {state?.forced_state.type !== "None" && <b>Forced</b>} for{" "} | ||||
|           <TimeWidget diff time={state!.for} /> | ||||
|         </> | ||||
|       )} | ||||
|   | ||||
| @@ -16,12 +16,13 @@ import React from "react"; | ||||
| import { useNavigate } from "react-router-dom"; | ||||
| import { Device, DeviceApi, DeviceRelay, DeviceURL } from "../api/DeviceApi"; | ||||
| import { RelayApi, RelaysStatus } from "../api/RelayApi"; | ||||
| import { ServerApi } from "../api/ServerApi"; | ||||
| import { AsyncWidget } from "../widgets/AsyncWidget"; | ||||
| import { BoolText } from "../widgets/BoolText"; | ||||
| import { CopyToClipboard } from "../widgets/CopyToClipboard"; | ||||
| import { RelayForcedState } from "../widgets/RelayForcedState"; | ||||
| import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer"; | ||||
| import { TimeWidget } from "../widgets/TimeWidget"; | ||||
| import { CopyToClipboard } from "../widgets/CopyToClipboard"; | ||||
| import { ServerApi } from "../api/ServerApi"; | ||||
|  | ||||
| export function RelaysListRoute(p: { | ||||
|   homeWidget?: boolean; | ||||
| @@ -104,6 +105,7 @@ function RelaysList(p: { | ||||
|             <TableCell>Priority</TableCell> | ||||
|             <TableCell>Consumption</TableCell> | ||||
|             <TableCell>Status</TableCell> | ||||
|             <TableCell>Forced state</TableCell> | ||||
|             <TableCell></TableCell> | ||||
|           </TableRow> | ||||
|         </TableHead> | ||||
| @@ -129,6 +131,13 @@ function RelaysList(p: { | ||||
|                 />{" "} | ||||
|                 for <TimeWidget diff time={p.status.get(row.id)!.for} /> | ||||
|               </TableCell> | ||||
|               <TableCell> | ||||
|                 <RelayForcedState | ||||
|                   relay={row} | ||||
|                   state={p.status.get(row.id)!} | ||||
|                   onUpdated={p.onReload} | ||||
|                 /> | ||||
|               </TableCell> | ||||
|               <TableCell> | ||||
|                 <Tooltip title="Copy legacy api status"> | ||||
|                   <CopyToClipboard | ||||
|   | ||||
							
								
								
									
										79
									
								
								central_frontend/src/widgets/RelayForcedState.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								central_frontend/src/widgets/RelayForcedState.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| import { MenuItem, Select, SelectChangeEvent } from "@mui/material"; | ||||
| import { DeviceRelay } from "../api/DeviceApi"; | ||||
| import { RelayApi, RelayStatus, SetRelayForcedState } from "../api/RelayApi"; | ||||
| import { TimeWidget } from "./TimeWidget"; | ||||
| import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider"; | ||||
| import { useAlert } from "../hooks/context_providers/AlertDialogProvider"; | ||||
| import { useSnackbar } from "../hooks/context_providers/SnackbarProvider"; | ||||
| import React from "react"; | ||||
| import { SelectForcedStateDurationDialog } from "../dialogs/SelectForcedStateDurationDialog"; | ||||
|  | ||||
| export function RelayForcedState(p: { | ||||
|   relay: DeviceRelay; | ||||
|   state: RelayStatus; | ||||
|   onUpdated: () => void; | ||||
| }): React.ReactElement { | ||||
|   const loadingMessage = useLoadingMessage(); | ||||
|   const alert = useAlert(); | ||||
|   const snackbar = useSnackbar(); | ||||
|  | ||||
|   const [futureStateType, setFutureStateType] = React.useState< | ||||
|     string | undefined | ||||
|   >(); | ||||
|  | ||||
|   const handleChange = (event: SelectChangeEvent) => { | ||||
|     if (event.target.value == "None") { | ||||
|       submitChange({ type: "None" }); | ||||
|     } else { | ||||
|       setFutureStateType(event.target.value); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const submitChange = async (state: SetRelayForcedState) => { | ||||
|     try { | ||||
|       loadingMessage.show("Setting forced state..."); | ||||
|       await RelayApi.SetForcedState(p.relay, state); | ||||
|       p.onUpdated(); | ||||
|       snackbar("Forced state successfully updated!"); | ||||
|     } catch (e) { | ||||
|       console.error(`Failed to set relay forced state! ${e}`); | ||||
|       alert(`Failed to set loading state for relay! ${e}`); | ||||
|     } finally { | ||||
|       loadingMessage.hide(); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <Select | ||||
|         value={p.state.forced_state.type} | ||||
|         onChange={handleChange} | ||||
|         size="small" | ||||
|         variant="standard" | ||||
|       > | ||||
|         <MenuItem value={"None"}>None</MenuItem> | ||||
|         <MenuItem value={"Off"}>Off</MenuItem> | ||||
|         <MenuItem value={"On"}>On</MenuItem> | ||||
|       </Select> | ||||
|       {p.state.forced_state.type !== "None" && ( | ||||
|         <> | ||||
|           <TimeWidget future time={p.state.forced_state.until} /> left | ||||
|         </> | ||||
|       )} | ||||
|  | ||||
|       {futureStateType !== undefined && ( | ||||
|         <SelectForcedStateDurationDialog | ||||
|           {...p} | ||||
|           forcedState={futureStateType} | ||||
|           onCancel={() => setFutureStateType(undefined)} | ||||
|           onSubmit={(d) => | ||||
|             submitChange({ | ||||
|               type: futureStateType as any, | ||||
|               for_secs: d, | ||||
|             }) | ||||
|           } | ||||
|         /> | ||||
|       )} | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
| @@ -106,7 +106,7 @@ export default function StatCard({ | ||||
|           <Box sx={{ width: "100%", height: 100 }}> | ||||
|             {data && interval && ( | ||||
|               <SparkLineChart | ||||
|                 colors={[chartColor]} | ||||
|                 color={chartColor} | ||||
|                 data={data} | ||||
|                 area | ||||
|                 showHighlight | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| import { Tooltip } from "@mui/material"; | ||||
| import date from "date-and-time"; | ||||
| import { format } from "date-and-time"; | ||||
| import { time } from "../utils/DateUtils"; | ||||
|  | ||||
| export function formatDate(time: number): string { | ||||
|   const t = new Date(); | ||||
|   t.setTime(1000 * time); | ||||
|   return date.format(t, "DD/MM/YYYY HH:mm:ss"); | ||||
|   return format(t, "DD/MM/YYYY HH:mm:ss"); | ||||
| } | ||||
|  | ||||
| export function timeDiff(a: number, b: number): string { | ||||
| @@ -51,13 +51,14 @@ export function timeDiff(a: number, b: number): string { | ||||
|   return `${diffYears} years`; | ||||
| } | ||||
|  | ||||
| export function timeDiffFromNow(t: number): string { | ||||
|   return timeDiff(t, time()); | ||||
| export function timeDiffFromNow(t: number, future?: boolean): string { | ||||
|   return future ? timeDiff(time(), t) : timeDiff(t, time()); | ||||
| } | ||||
|  | ||||
| export function TimeWidget(p: { | ||||
|   time?: number; | ||||
|   diff?: boolean; | ||||
|   future?: boolean; | ||||
| }): React.ReactElement { | ||||
|   if (!p.time) return <></>; | ||||
|   return ( | ||||
| @@ -65,7 +66,9 @@ export function TimeWidget(p: { | ||||
|       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, p.future)} | ||||
|       </span> | ||||
|     </Tooltip> | ||||
|   ); | ||||
| } | ||||
|   | ||||
							
								
								
									
										54
									
								
								custom_consumption/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										54
									
								
								custom_consumption/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -623,9 +623,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "clap" | ||||
| version = "4.5.45" | ||||
| version = "4.5.50" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" | ||||
| checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623" | ||||
| dependencies = [ | ||||
|  "clap_builder", | ||||
|  "clap_derive", | ||||
| @@ -633,9 +633,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "clap_builder" | ||||
| version = "4.5.44" | ||||
| version = "4.5.50" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" | ||||
| checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0" | ||||
| dependencies = [ | ||||
|  "anstream", | ||||
|  "anstyle", | ||||
| @@ -645,9 +645,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "clap_derive" | ||||
| version = "4.5.45" | ||||
| version = "4.5.49" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" | ||||
| checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" | ||||
| dependencies = [ | ||||
|  "heck", | ||||
|  "proc-macro2", | ||||
| @@ -844,9 +844,9 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" | ||||
|  | ||||
| [[package]] | ||||
| name = "ecolor" | ||||
| version = "0.32.1" | ||||
| version = "0.32.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b6a7fc3172c2ef56966b2ce4f84177e159804c40b9a84de8861558ce4a59f422" | ||||
| checksum = "94bdf37f8d5bd9aa7f753573fdda9cf7343afa73dd28d7bfe9593bd9798fc07e" | ||||
| dependencies = [ | ||||
|  "bytemuck", | ||||
|  "emath", | ||||
| @@ -854,9 +854,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "eframe" | ||||
| version = "0.32.1" | ||||
| version = "0.32.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "34037a80dc03a4147e1684bff4e4fdab2b1408d715d7b78470cd3179258964b9" | ||||
| checksum = "14d1c15e7bd136b309bd3487e6ffe5f668b354cd9768636a836dd738ac90eb0b" | ||||
| dependencies = [ | ||||
|  "ahash", | ||||
|  "bytemuck", | ||||
| @@ -890,9 +890,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "egui" | ||||
| version = "0.32.1" | ||||
| version = "0.32.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "49e2be082f77715496b4a39fdc6f5dc7491fefe2833111781b8697ea6ee919a7" | ||||
| checksum = "5d5d0306cd61ca75e29682926d71f2390160247f135965242e904a636f51c0dc" | ||||
| dependencies = [ | ||||
|  "accesskit", | ||||
|  "ahash", | ||||
| @@ -908,9 +908,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "egui-wgpu" | ||||
| version = "0.32.1" | ||||
| version = "0.32.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "64c7277a171ec1b711080ddb3b0bfa1b3aa9358834d5386d39e83fbc16d61212" | ||||
| checksum = "c12eca13293f8eba27a32aaaa1c765bfbf31acd43e8d30d5881dcbe5e99ca0c7" | ||||
| dependencies = [ | ||||
|  "ahash", | ||||
|  "bytemuck", | ||||
| @@ -928,9 +928,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "egui-winit" | ||||
| version = "0.32.1" | ||||
| version = "0.32.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "fe6d8b0f8d6de4d43e794e343f03bacc3908aada931f0ed6fd7041871388a590" | ||||
| checksum = "f95d0a91f9cb0dc2e732d49c2d521ac8948e1f0b758f306fb7b14d6f5db3927f" | ||||
| dependencies = [ | ||||
|  "accesskit_winit", | ||||
|  "ahash", | ||||
| @@ -948,9 +948,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "egui_glow" | ||||
| version = "0.32.1" | ||||
| version = "0.32.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0ab645760288e42eab70283a5cccf44509a6f43b554351855d3c73594bfe3c23" | ||||
| checksum = "cc7037813341727937f9e22f78d912f3e29bc3c46e2f40a9e82bb51cbf5e4cfb" | ||||
| dependencies = [ | ||||
|  "ahash", | ||||
|  "bytemuck", | ||||
| @@ -966,9 +966,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "emath" | ||||
| version = "0.32.1" | ||||
| version = "0.32.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "935df67dc48fdeef132f2f7ada156ddc79e021344dd42c17f066b956bb88dde3" | ||||
| checksum = "45fd7bc25f769a3c198fe1cf183124bf4de3bd62ef7b4f1eaf6b08711a3af8db" | ||||
| dependencies = [ | ||||
|  "bytemuck", | ||||
| ] | ||||
| @@ -1025,9 +1025,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "epaint" | ||||
| version = "0.32.1" | ||||
| version = "0.32.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b66fc0a5a9d322917de9bd3ac7d426ca8aa3127fbf1e76fae5b6b25e051e06a3" | ||||
| checksum = "63adcea970b7a13094fe97a36ab9307c35a750f9e24bf00bb7ef3de573e0fddb" | ||||
| dependencies = [ | ||||
|  "ab_glyph", | ||||
|  "ahash", | ||||
| @@ -1043,9 +1043,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "epaint_default_fonts" | ||||
| version = "0.32.1" | ||||
| version = "0.32.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "4f6cf8ce0fb817000aa24f5e630bda904a353536bd430b83ebc1dceee95b4a3a" | ||||
| checksum = "1537accc50c9cab5a272c39300bdd0dd5dca210f6e5e8d70be048df9596e7ca2" | ||||
|  | ||||
| [[package]] | ||||
| name = "equivalent" | ||||
| @@ -1714,7 +1714,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
|  "windows-targets 0.48.5", | ||||
|  "windows-targets 0.52.6", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -1770,9 +1770,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "log" | ||||
| version = "0.4.27" | ||||
| version = "0.4.28" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" | ||||
| checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" | ||||
|  | ||||
| [[package]] | ||||
| name = "malloc_buf" | ||||
|   | ||||
| @@ -5,8 +5,8 @@ edition = "2024" | ||||
|  | ||||
| [dependencies] | ||||
| env_logger = "0.11.8" | ||||
| log = "0.4.27" | ||||
| clap = { version = "4.5.45", features = ["derive", "env"] } | ||||
| egui = "0.32.1" | ||||
| eframe = "0.32.1" | ||||
| log = "0.4.28" | ||||
| clap = { version = "4.5.50", features = ["derive", "env"] } | ||||
| egui = "0.32.3" | ||||
| eframe = "0.32.3" | ||||
| lazy_static = "1.5.0" | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| #define CONFIG_ETH_USE_ESP32_EMAC | ||||
|  | ||||
| #include "esp_eth.h" | ||||
| #include "esp_eth_mac.h" | ||||
| #include "esp_eth_com.h" | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| 1.0.2 | ||||
| 1.0.3 | ||||
		Reference in New Issue
	
	Block a user