Compare commits
	
		
			1 Commits
		
	
	
		
			renovate/l
			...
			b119f25dec
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b119f25dec | 
							
								
								
									
										39
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								.drone.yml
									
									
									
									
									
								
							| @@ -5,7 +5,7 @@ name: default | |||||||
|  |  | ||||||
| steps: | steps: | ||||||
|   - name: web_build |   - name: web_build | ||||||
|     image: node:23 |     image: node:21 | ||||||
|     volumes: |     volumes: | ||||||
|       - name: web_app |       - name: web_app | ||||||
|         path: /tmp/web_build |         path: /tmp/web_build | ||||||
| @@ -46,11 +46,6 @@ steps: | |||||||
|         path: /usr/local/cargo/registry |         path: /usr/local/cargo/registry | ||||||
|       - name: web_app |       - name: web_app | ||||||
|         path: /tmp/web_build |         path: /tmp/web_build | ||||||
|       - name: releases |  | ||||||
|         path: /tmp/releases |  | ||||||
|     when: |  | ||||||
|       event: |  | ||||||
|         - tag |  | ||||||
|     depends_on: |     depends_on: | ||||||
|       - backend_check |       - backend_check | ||||||
|       - web_build |       - web_build | ||||||
| @@ -59,41 +54,13 @@ steps: | |||||||
|       - mv /tmp/web_build/dist static |       - mv /tmp/web_build/dist static | ||||||
|       - cargo build --release |       - cargo build --release | ||||||
|       - ls -lah target/release/central_backend |       - ls -lah target/release/central_backend | ||||||
|       - mv target/release/central_backend /tmp/releases/central_backend |  | ||||||
|  |  | ||||||
|   # Build ESP32 program |  | ||||||
|   - name: esp32_compile |   - name: esp32_compile | ||||||
|     image: espressif/idf:v5.5.1 |     image: espressif/idf:v5.3.1 | ||||||
|     volumes: |  | ||||||
|       - name: releases |  | ||||||
|         path: /tmp/releases |  | ||||||
|     commands: |     commands: | ||||||
|       - cd esp32_device |       - cd esp32_device | ||||||
|       - /opt/esp/entrypoint.sh idf.py build |       - /opt/esp/entrypoint.sh idf.py build | ||||||
|       - ls -lah build/main.bin |       - 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: | volumes: | ||||||
| @@ -101,5 +68,3 @@ volumes: | |||||||
|     temp: {} |     temp: {} | ||||||
|   - name: web_app |   - name: web_app | ||||||
|     temp: {} |     temp: {} | ||||||
|   - name: releases |  | ||||||
|     temp: {} |  | ||||||
|   | |||||||
							
								
								
									
										1736
									
								
								central_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1736
									
								
								central_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,46 +1,46 @@ | |||||||
| [package] | [package] | ||||||
| name = "central_backend" | name = "central_backend" | ||||||
| version = "1.0.3" | version = "1.0.2" | ||||||
| edition = "2024" | edition = "2021" | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| log = "0.4.28" | log = "0.4.22" | ||||||
| env_logger = "0.11.8" | env_logger = "0.11.5" | ||||||
| lazy_static = "1.5.0" | lazy_static = "1.5.0" | ||||||
| dotenvy = "0.15.7" | dotenvy = "0.15.7" | ||||||
| clap = { version = "4.5.50", features = ["derive", "env"] } | clap = { version = "4.5.20", features = ["derive", "env"] } | ||||||
| anyhow = "1.0.100" | anyhow = "1.0.89" | ||||||
| thiserror = "2.0.17" | thiserror = "2.0.3" | ||||||
| openssl = { version = "0.10.74" } | openssl = { version = "0.10.66" } | ||||||
| openssl-sys = "0.9.110" | openssl-sys = "0.9.102" | ||||||
| libc = "0.2.177" | libc = "0.2.159" | ||||||
| foreign-types-shared = "0.1.1" | foreign-types-shared = "0.1.1" | ||||||
| asn1 = "0.23.0" | asn1 = "0.19" | ||||||
| actix-web = { version = "4.11.0", features = ["openssl"] } | actix-web = { version = "4", features = ["openssl"] } | ||||||
| futures = "0.3.31" | futures = "0.3.31" | ||||||
| serde = { version = "1.0.228", features = ["derive"] } | serde = { version = "1.0.215", features = ["derive"] } | ||||||
| reqwest = { version = "0.12.24", features = ["json"] } | reqwest = { version = "0.12.7", features = ["json"] } | ||||||
| serde_json = "1.0.145" | serde_json = "1.0.131" | ||||||
| rand = "0.10.0-rc.0" | rand = "0.8.5" | ||||||
| actix = "0.13.5" | actix = "0.13.5" | ||||||
| actix-identity = "0.9.0" | actix-identity = "0.8.0" | ||||||
| actix-session = { version = "0.11.0", features = ["cookie-session"] } | actix-session = { version = "0.10.1", features = ["cookie-session"] } | ||||||
| actix-cors = "0.7.1" | actix-cors = "0.7.0" | ||||||
| actix-multipart = { version = "0.7.2", features = ["derive"] } | actix-multipart = { version = "0.7.2", features = ["derive"] } | ||||||
| actix-remote-ip = "0.1.0" | actix-remote-ip = "0.1.0" | ||||||
| futures-util = "0.3.31" | futures-util = "0.3.31" | ||||||
| uuid = { version = "1.18.1", features = ["v4", "serde"] } | uuid = { version = "1.11.0", features = ["v4", "serde"] } | ||||||
| semver = { version = "1.0.27", features = ["serde"] } | semver = { version = "1.0.23", features = ["serde"] } | ||||||
| lazy-regex = "3.4.2" | lazy-regex = "3.3.0" | ||||||
| tokio = { version = "1.48.0", features = ["full"] } | tokio = { version = "1.40.0", features = ["full"] } | ||||||
| tokio_schedule = "0.3.2" | tokio_schedule = "0.3.2" | ||||||
| mime_guess = "2.0.5" | mime_guess = "2.0.5" | ||||||
| rust-embed = "8.8.0" | rust-embed = "8.5.0" | ||||||
| jsonwebtoken = { version = "10.1.0", features = ["use_pem", "rust_crypto"] } | jsonwebtoken = { version = "9.3.0", features = ["use_pem"] } | ||||||
| prettytable-rs = "0.10.0" | prettytable-rs = "0.10.0" | ||||||
| chrono = "0.4.42" | chrono = "0.4.38" | ||||||
| serde_yml = "0.0.12" | serde_yml = "0.0.12" | ||||||
| bincode = "2.0.1" | bincode = "=2.0.0-rc.3" | ||||||
| fs4 = { version = "0.13.1", features = ["sync"] } | fs4 = { version = "0.11.0", features = ["sync"] } | ||||||
| zip = { version = "6.0.0", features = ["bzip2"] } | zip = { version = "2.2.0", features = ["bzip2"] } | ||||||
| walkdir = "2.5.0" | walkdir = "2.5.0" | ||||||
| @@ -1,33 +0,0 @@ | |||||||
| 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 |  | ||||||
| @@ -1,49 +0,0 @@ | |||||||
| 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 |  | ||||||
| @@ -16,13 +16,11 @@ impl CRLDistributionPointExt { | |||||||
|         let crl_bytes = asn1::write(|w| { |         let crl_bytes = asn1::write(|w| { | ||||||
|             w.write_element(&asn1::SequenceWriter::new(&|w| { |             w.write_element(&asn1::SequenceWriter::new(&|w| { | ||||||
|                 w.write_element(&asn1::SequenceWriter::new(&|w| { |                 w.write_element(&asn1::SequenceWriter::new(&|w| { | ||||||
|                     w.write_tlv(tag_a0, None, |w: &mut asn1::WriteBuf| { |                     w.write_tlv(tag_a0, |w| { | ||||||
|                         w.push_slice(&asn1::write(|w| { |                         w.push_slice(&asn1::write(|w| { | ||||||
|                             w.write_tlv(tag_a0, None, |w: &mut asn1::WriteBuf| { |                             w.write_tlv(tag_a0, |w| { | ||||||
|                                 w.push_slice(&asn1::write(|w| { |                                 w.push_slice(&asn1::write(|w| { | ||||||
|                                     w.write_tlv(tag_86, None, |b| { |                                     w.write_tlv(tag_86, |b| b.push_slice(self.url.as_bytes()))?; | ||||||
|                                         b.push_slice(self.url.as_bytes()) |  | ||||||
|                                     })?; |  | ||||||
|                                     Ok(()) |                                     Ok(()) | ||||||
|                                 })?) |                                 })?) | ||||||
|                             })?; |                             })?; | ||||||
|   | |||||||
| @@ -13,10 +13,10 @@ use openssl::pkey::{PKey, Private}; | |||||||
| use openssl::x509::extension::{ | use openssl::x509::extension::{ | ||||||
|     BasicConstraints, ExtendedKeyUsage, KeyUsage, SubjectAlternativeName, SubjectKeyIdentifier, |     BasicConstraints, ExtendedKeyUsage, KeyUsage, SubjectAlternativeName, SubjectKeyIdentifier, | ||||||
| }; | }; | ||||||
| use openssl::x509::{CrlStatus, X509, X509Crl, X509Name, X509NameBuilder, X509Req}; | use openssl::x509::{CrlStatus, X509Crl, X509Name, X509NameBuilder, X509Req, X509}; | ||||||
| use openssl_sys::{ | use openssl_sys::{ | ||||||
|     X509_CRL_add0_revoked, X509_CRL_new, X509_CRL_set_issuer_name, X509_CRL_set_version, |     X509_CRL_add0_revoked, X509_CRL_new, X509_CRL_set1_lastUpdate, X509_CRL_set1_nextUpdate, | ||||||
|     X509_CRL_set1_lastUpdate, X509_CRL_set1_nextUpdate, X509_CRL_sign, X509_REVOKED_dup, |     X509_CRL_set_issuer_name, X509_CRL_set_version, X509_CRL_sign, X509_REVOKED_dup, | ||||||
|     X509_REVOKED_new, X509_REVOKED_set_revocationDate, X509_REVOKED_set_serialNumber, |     X509_REVOKED_new, X509_REVOKED_set_revocationDate, X509_REVOKED_set_serialNumber, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -120,7 +120,7 @@ enum GenCertificatSubjectReq<'a> { | |||||||
|     CSR { csr: &'a X509Req }, |     CSR { csr: &'a X509Req }, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for GenCertificatSubjectReq<'_> { | impl<'a> Default for GenCertificatSubjectReq<'a> { | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Self::Subject { cn: "" } |         Self::Subject { cn: "" } | ||||||
|     } |     } | ||||||
| @@ -174,16 +174,17 @@ fn gen_certificate(req: GenCertificateReq) -> anyhow::Result<(Option<Vec<u8>>, V | |||||||
|     cert_builder.set_not_after(¬_after)?; |     cert_builder.set_not_after(¬_after)?; | ||||||
|  |  | ||||||
|     // Specify CRL URL |     // Specify CRL URL | ||||||
|     if let Some(issuer) = req.issuer |     if let Some(issuer) = req.issuer { | ||||||
|         && let Some(crl) = &issuer.crl |         if let Some(crl) = &issuer.crl { | ||||||
|     { |  | ||||||
|             let crl_url = format!( |             let crl_url = format!( | ||||||
|                 "{}/pki/{}", |                 "{}/pki/{}", | ||||||
|                 AppConfig::get().unsecure_origin(), |                 AppConfig::get().unsecure_origin(), | ||||||
|                 crl.file_name().unwrap().to_string_lossy() |                 crl.file_name().unwrap().to_string_lossy() | ||||||
|             ); |             ); | ||||||
|  |  | ||||||
|         cert_builder.append_extension(CRLDistributionPointExt { url: crl_url }.as_extension()?)?; |             cert_builder | ||||||
|  |                 .append_extension(CRLDistributionPointExt { url: crl_url }.as_extension()?)?; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // If cert is a CA or not |     // If cert is a CA or not | ||||||
| @@ -423,15 +424,15 @@ fn refresh_crl(d: &CertData, new_cert: Option<&X509>) -> anyhow::Result<()> { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Add old entries |         // Add old entries | ||||||
|         if let Some(old_crl) = old_crl |         if let Some(old_crl) = old_crl { | ||||||
|             && let Some(entries) = old_crl.get_revoked() |             if let Some(entries) = old_crl.get_revoked() { | ||||||
|         { |  | ||||||
|                 for entry in entries { |                 for entry in entries { | ||||||
|                     if X509_CRL_add0_revoked(crl, X509_REVOKED_dup(entry.as_ptr())) == 0 { |                     if X509_CRL_add0_revoked(crl, X509_REVOKED_dup(entry.as_ptr())) == 0 { | ||||||
|                         return Err(PKIError::GenCRLError("X509_CRL_add0_revoked").into()); |                         return Err(PKIError::GenCRLError("X509_CRL_add0_revoked").into()); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // If requested, add new entry |         // If requested, add new entry | ||||||
|         if let Some(new_cert) = new_cert { |         if let Some(new_cert) = new_cert { | ||||||
|   | |||||||
| @@ -325,11 +325,9 @@ mod tests { | |||||||
|             ..Default::default() |             ..Default::default() | ||||||
|         }; |         }; | ||||||
|         dep_cycle_1.depends_on = vec![dep_cycle_3.id]; |         dep_cycle_1.depends_on = vec![dep_cycle_3.id]; | ||||||
|         assert!( |         assert!(dep_cycle_1 | ||||||
|             dep_cycle_1 |  | ||||||
|             .error(&[dep_cycle_2.clone(), dep_cycle_3.clone()]) |             .error(&[dep_cycle_2.clone(), dep_cycle_3.clone()]) | ||||||
|                 .is_some() |             .is_some()); | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         dep_cycle_1.depends_on = vec![]; |         dep_cycle_1.depends_on = vec![]; | ||||||
|         assert!(dep_cycle_1.error(&[dep_cycle_2, dep_cycle_3]).is_none()); |         assert!(dep_cycle_1.error(&[dep_cycle_2, dep_cycle_3]).is_none()); | ||||||
| @@ -353,29 +351,21 @@ mod tests { | |||||||
|             ..Default::default() |             ..Default::default() | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         assert!( |         assert!(target_relay | ||||||
|             target_relay |  | ||||||
|             .error(&[other_dep.clone(), second_dep.clone()]) |             .error(&[other_dep.clone(), second_dep.clone()]) | ||||||
|                 .is_some() |             .is_some()); | ||||||
|         ); |         assert!(target_relay | ||||||
|         assert!( |  | ||||||
|             target_relay |  | ||||||
|             .error(&[other_dep.clone(), second_dep.clone(), target_relay.clone()]) |             .error(&[other_dep.clone(), second_dep.clone(), target_relay.clone()]) | ||||||
|                 .is_some() |             .is_some()); | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         second_dep.conflicts_with = vec![]; |         second_dep.conflicts_with = vec![]; | ||||||
|  |  | ||||||
|         assert!( |         assert!(target_relay | ||||||
|             target_relay |  | ||||||
|             .error(&[other_dep.clone(), second_dep.clone()]) |             .error(&[other_dep.clone(), second_dep.clone()]) | ||||||
|                 .is_none() |             .is_none()); | ||||||
|         ); |         assert!(target_relay | ||||||
|         assert!( |  | ||||||
|             target_relay |  | ||||||
|             .error(&[other_dep.clone(), second_dep.clone(), target_relay.clone()]) |             .error(&[other_dep.clone(), second_dep.clone(), target_relay.clone()]) | ||||||
|                 .is_none() |             .is_none()); | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         // self loop |         // self loop | ||||||
|         let mut self_loop = DeviceRelay { |         let mut self_loop = DeviceRelay { | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ use crate::devices::device::{ | |||||||
|     Device, DeviceGeneralInfo, DeviceId, DeviceInfo, DeviceRelay, DeviceRelayID, |     Device, DeviceGeneralInfo, DeviceId, DeviceInfo, DeviceRelay, DeviceRelayID, | ||||||
| }; | }; | ||||||
| use crate::utils::time_utils::time_secs; | use crate::utils::time_utils::time_secs; | ||||||
| use openssl::x509::{X509, X509Req}; | use openssl::x509::{X509Req, X509}; | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
|  |  | ||||||
| #[derive(thiserror::Error, Debug)] | #[derive(thiserror::Error, Debug)] | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| use crate::app_config::{AppConfig, ConsumptionBackend}; | use crate::app_config::{AppConfig, ConsumptionBackend}; | ||||||
| use rand::{Rng, rng}; | use rand::{thread_rng, Rng}; | ||||||
| use std::num::ParseIntError; | use std::num::ParseIntError; | ||||||
| use std::path::Path; | use std::path::Path; | ||||||
|  |  | ||||||
| @@ -49,7 +49,7 @@ pub async fn get_curr_consumption() -> anyhow::Result<EnergyConsumption> { | |||||||
|     match backend { |     match backend { | ||||||
|         ConsumptionBackend::Constant { value } => Ok(*value), |         ConsumptionBackend::Constant { value } => Ok(*value), | ||||||
|  |  | ||||||
|         ConsumptionBackend::Random { min, max } => Ok(rng().random_range(*min..*max)), |         ConsumptionBackend::Random { min, max } => Ok(thread_rng().gen_range(*min..*max)), | ||||||
|  |  | ||||||
|         ConsumptionBackend::File { path } => { |         ConsumptionBackend::File { path } => { | ||||||
|             let path = Path::new(path); |             let path = Path::new(path); | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ use crate::energy::consumption; | |||||||
| use crate::energy::consumption::EnergyConsumption; | use crate::energy::consumption::EnergyConsumption; | ||||||
| use crate::energy::consumption_cache::ConsumptionCache; | use crate::energy::consumption_cache::ConsumptionCache; | ||||||
| use crate::energy::consumption_history_file::ConsumptionHistoryFile; | use crate::energy::consumption_history_file::ConsumptionHistoryFile; | ||||||
| use crate::energy::engine::{EnergyEngine, RelayForcedState}; | 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::*; | ||||||
| use openssl::x509::X509Req; | use openssl::x509::X509Req; | ||||||
| @@ -328,19 +328,6 @@ 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 | /// Delete a device relay | ||||||
| #[derive(Message)] | #[derive(Message)] | ||||||
| #[rtype(result = "anyhow::Result<()>")] | #[rtype(result = "anyhow::Result<()>")] | ||||||
| @@ -421,7 +408,6 @@ pub struct ResRelayState { | |||||||
|     pub id: DeviceRelayID, |     pub id: DeviceRelayID, | ||||||
|     pub on: bool, |     pub on: bool, | ||||||
|     pub r#for: usize, |     pub r#for: usize, | ||||||
|     pub forced_state: RelayForcedState, |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Get the state of all relays | /// Get the state of all relays | ||||||
| @@ -441,7 +427,6 @@ impl Handler<GetAllRelaysState> for EnergyActor { | |||||||
|                 id: d.id, |                 id: d.id, | ||||||
|                 on: state.is_on(), |                 on: state.is_on(), | ||||||
|                 r#for: state.state_for(), |                 r#for: state.state_for(), | ||||||
|                 forced_state: state.actual_forced_state(), |  | ||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
|  |  | ||||||
| use crate::app_config::AppConfig; | use crate::app_config::AppConfig; | ||||||
| use prettytable::{Table, row}; | use prettytable::{row, Table}; | ||||||
|  |  | ||||||
| use crate::constants; | use crate::constants; | ||||||
| use crate::devices::device::{Device, DeviceId, DeviceRelay, DeviceRelayID}; | use crate::devices::device::{Device, DeviceId, DeviceRelay, DeviceRelayID}; | ||||||
| @@ -25,83 +25,19 @@ 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)] | #[derive(Default, Clone)] | ||||||
| pub struct RelayState { | pub struct RelayState { | ||||||
|     on: bool, |     on: bool, | ||||||
|     since: usize, |     since: usize, | ||||||
|     forced_state: RelayForcedState, |  | ||||||
| } | } | ||||||
|  |  | ||||||
| impl RelayState { | 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 { |     pub fn is_on(&self) -> bool { | ||||||
|         let forced_state = self.actual_forced_state(); |         self.on | ||||||
|         (self.on || matches!(forced_state, RelayForcedState::On { .. })) |  | ||||||
|             && !matches!(forced_state, RelayForcedState::Off { .. }) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn is_off(&self) -> bool { |     fn is_off(&self) -> bool { | ||||||
|         !self.is_on() |         !self.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 { |     pub fn state_for(&self) -> usize { | ||||||
| @@ -210,11 +146,7 @@ impl EnergyEngine { | |||||||
|                     r.name, |                     r.name, | ||||||
|                     r.consumption, |                     r.consumption, | ||||||
|                     format!("{} / {}", r.minimal_downtime, r.minimal_uptime), |                     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, |                     status.since, | ||||||
|                     match dev_online { |                     match dev_online { | ||||||
|                         true => "Online", |                         true => "Online", | ||||||
| @@ -260,28 +192,19 @@ impl EnergyEngine { | |||||||
|  |  | ||||||
|         let mut new_relays_state = self.relays_state.clone(); |         let mut new_relays_state = self.relays_state.clone(); | ||||||
|  |  | ||||||
|         // Forcefully turn off disabled relays |         // Forcefully turn off relays that belongs to offline devices | ||||||
|         for d in devices { |         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; |                     new_relays_state.get_mut(&r.id).unwrap().on = false; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Apply forced relays state |         // Forcefully turn off disabled relays | ||||||
|         for d in devices { |         for d in devices { | ||||||
|             for r in &d.relays { |             for r in &d.relays { | ||||||
|                 if self.relay_state(r.id).is_forced() { |                 if !r.enabled || !d.enabled { | ||||||
|                     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; |                     new_relays_state.get_mut(&r.id).unwrap().on = false; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -293,9 +216,7 @@ impl EnergyEngine { | |||||||
|  |  | ||||||
|             for d in devices { |             for d in devices { | ||||||
|                 for r in &d.relays { |                 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; |                         continue; | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
| @@ -319,7 +240,7 @@ impl EnergyEngine { | |||||||
|             for d in devices { |             for d in devices { | ||||||
|                 for r in &d.relays { |                 for r in &d.relays { | ||||||
|                     let state = new_relays_state.get(&r.id).unwrap(); |                     let state = new_relays_state.get(&r.id).unwrap(); | ||||||
|                     if state.is_off() || state.is_forced() { |                     if state.is_off() { | ||||||
|                         continue; |                         continue; | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
| @@ -350,9 +271,7 @@ impl EnergyEngine { | |||||||
|                     continue; |                     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; |                     continue; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
| @@ -370,16 +289,12 @@ impl EnergyEngine { | |||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 log::info!( |                 log::info!("Forcefully turn on relay {} to catch up running constraints (only {}s this day)", r.name, total_runtime); | ||||||
|                     "Forcefully turn on relay {} to catch up running constraints (only {}s this day)", |  | ||||||
|                     r.name, |  | ||||||
|                     total_runtime |  | ||||||
|                 ); |  | ||||||
|                 new_relays_state.get_mut(&r.id).unwrap().on = true; |                 new_relays_state.get_mut(&r.id).unwrap().on = true; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Order relays to select the ones with the most elevated priorities |         // Order relays | ||||||
|         let mut ordered_relays = devices |         let mut ordered_relays = devices | ||||||
|             .iter() |             .iter() | ||||||
|             .filter(|d| self.device_state(&d.id).is_online() && d.enabled) |             .filter(|d| self.device_state(&d.id).is_online() && d.enabled) | ||||||
| @@ -389,13 +304,10 @@ impl EnergyEngine { | |||||||
|         ordered_relays.sort_by_key(|r| r.priority); |         ordered_relays.sort_by_key(|r| r.priority); | ||||||
|         ordered_relays.reverse(); |         ordered_relays.reverse(); | ||||||
|  |  | ||||||
|         // Select relays to start, starting with those with highest priorities |  | ||||||
|         loop { |         loop { | ||||||
|             let mut changed = false; |             let mut changed = false; | ||||||
|             for relay in &ordered_relays { |             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; |                     continue; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
| @@ -467,7 +379,7 @@ impl EnergyEngine { | |||||||
| mod test { | mod test { | ||||||
|     use crate::devices::device::{Device, DeviceId, DeviceRelayID}; |     use crate::devices::device::{Device, DeviceId, DeviceRelayID}; | ||||||
|     use crate::energy::consumption::EnergyConsumption; |     use crate::energy::consumption::EnergyConsumption; | ||||||
|     use crate::energy::engine::{EnergyEngine, SetRelayForcedStateReq}; |     use crate::energy::engine::EnergyEngine; | ||||||
|     use crate::utils::time_utils::time_secs; |     use crate::utils::time_utils::time_secs; | ||||||
|     use rust_embed::Embed; |     use rust_embed::Embed; | ||||||
|  |  | ||||||
| @@ -476,8 +388,6 @@ mod test { | |||||||
|         id: DeviceRelayID, |         id: DeviceRelayID, | ||||||
|         on: bool, |         on: bool, | ||||||
|         r#for: usize, |         r#for: usize, | ||||||
|         #[serde(default)] |  | ||||||
|         forced_state: SetRelayForcedStateReq, |  | ||||||
|         should_be_on: bool, |         should_be_on: bool, | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -525,7 +435,6 @@ mod test { | |||||||
|                 let s = engine.relay_state(r.id); |                 let s = engine.relay_state(r.id); | ||||||
|                 s.on = r.on; |                 s.on = r.on; | ||||||
|                 s.since = time_secs() as usize - r.r#for; |                 s.since = time_secs() as usize - r.r#for; | ||||||
|                 s.forced_state = r.forced_state.to_forced_state() |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| use crate::app_config::AppConfig; | use crate::app_config::AppConfig; | ||||||
| use crate::devices::device::{DeviceRelay, DeviceRelayID}; | use crate::devices::device::{DeviceRelay, DeviceRelayID}; | ||||||
| use crate::utils::files_utils; | use crate::utils::files_utils; | ||||||
| use crate::utils::time_utils::{day_number, time_secs, time_start_of_day}; | use crate::utils::time_utils::{day_number, time_start_of_day}; | ||||||
|  |  | ||||||
| const TIME_INTERVAL: usize = 30; | const TIME_INTERVAL: usize = 30; | ||||||
|  |  | ||||||
| @@ -128,26 +128,15 @@ pub fn relay_total_runtime_adjusted(relay: &DeviceRelay) -> usize { | |||||||
|         .unwrap_or(0); |         .unwrap_or(0); | ||||||
|  |  | ||||||
|     let time_start_day = time_start_of_day().unwrap_or(1726696800); |     let time_start_day = time_start_of_day().unwrap_or(1726696800); | ||||||
|  |  | ||||||
|     // Check if we have reached reset_time today yet or not |  | ||||||
|     if time_start_day + reset_time as u64 <= time_secs() { |  | ||||||
|     let start_time = time_start_day + reset_time as u64; |     let start_time = time_start_day + reset_time as u64; | ||||||
|     let end_time = time_start_day + 3600 * 24 + reset_time as u64; |     let end_time = time_start_day + 3600 * 24 + reset_time as u64; | ||||||
|     relay_total_runtime(relay.id, start_time, end_time).unwrap_or(3600 * 24) |     relay_total_runtime(relay.id, start_time, end_time).unwrap_or(3600 * 24) | ||||||
| } | } | ||||||
|     // If we have not reached reset time yet, we need to focus on previous day |  | ||||||
|     else { |  | ||||||
|         let time_start_yesterday = time_start_day - 3600 * 24; |  | ||||||
|         let start_time = time_start_yesterday + reset_time as u64; |  | ||||||
|         let end_time = time_start_day + reset_time as u64; |  | ||||||
|         relay_total_runtime(relay.id, start_time, end_time).unwrap_or(3600 * 24) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use crate::devices::device::DeviceRelayID; |     use crate::devices::device::DeviceRelayID; | ||||||
|     use crate::energy::relay_state_history::{RelayStateHistory, relay_total_runtime}; |     use crate::energy::relay_state_history::{relay_total_runtime, RelayStateHistory}; | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_relay_state_history() { |     fn test_relay_state_history() { | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ pub fn save_log( | |||||||
|         .as_bytes(), |         .as_bytes(), | ||||||
|     )?; |     )?; | ||||||
|     file.flush()?; |     file.flush()?; | ||||||
|     fs4::fs_std::FileExt::unlock(&file)?; |     file.unlock()?; | ||||||
|  |  | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ use central_backend::energy::energy_actor::EnergyActor; | |||||||
| use central_backend::server::servers; | use central_backend::server::servers; | ||||||
| use central_backend::utils::files_utils::create_directory_if_missing; | use central_backend::utils::files_utils::create_directory_if_missing; | ||||||
| use futures::future; | use futures::future; | ||||||
| use tokio_schedule::{Job, every}; | use tokio_schedule::{every, Job}; | ||||||
|  |  | ||||||
| #[actix_web::main] | #[actix_web::main] | ||||||
| async fn main() -> std::io::Result<()> { | async fn main() -> std::io::Result<()> { | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| use actix_identity::Identity; | use actix_identity::Identity; | ||||||
| use std::future::{Ready, ready}; | use std::future::{ready, Ready}; | ||||||
| use std::rc::Rc; | use std::rc::Rc; | ||||||
|  |  | ||||||
| use crate::app_config::AppConfig; | use crate::app_config::AppConfig; | ||||||
| @@ -7,8 +7,8 @@ use crate::constants; | |||||||
| use actix_web::body::EitherBody; | use actix_web::body::EitherBody; | ||||||
| use actix_web::dev::Payload; | use actix_web::dev::Payload; | ||||||
| use actix_web::{ | use actix_web::{ | ||||||
|  |     dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, | ||||||
|     Error, FromRequest, HttpResponse, |     Error, FromRequest, HttpResponse, | ||||||
|     dev::{Service, ServiceRequest, ServiceResponse, Transform, forward_ready}, |  | ||||||
| }; | }; | ||||||
| use futures_util::future::LocalBoxFuture; | use futures_util::future::LocalBoxFuture; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,8 +1,9 @@ | |||||||
| use actix_web::HttpResponse; |  | ||||||
| use actix_web::body::BoxBody; | use actix_web::body::BoxBody; | ||||||
| use actix_web::http::StatusCode; | use actix_web::http::StatusCode; | ||||||
|  | use actix_web::HttpResponse; | ||||||
| use std::error::Error; | use std::error::Error; | ||||||
| use std::fmt::{Display, Formatter}; | use std::fmt::{Display, Formatter}; | ||||||
|  | use std::io::ErrorKind; | ||||||
| use zip::result::ZipError; | use zip::result::ZipError; | ||||||
|  |  | ||||||
| /// Custom error to ease controller writing | /// Custom error to ease controller writing | ||||||
| @@ -31,7 +32,7 @@ impl actix_web::error::ResponseError for HttpErr { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     fn error_response(&self) -> HttpResponse<BoxBody> { |     fn error_response(&self) -> HttpResponse<BoxBody> { | ||||||
|         log::error!("Error while processing request! {self}"); |         log::error!("Error while processing request! {}", self); | ||||||
|  |  | ||||||
|         HttpResponse::InternalServerError().body("Failed to execute request!") |         HttpResponse::InternalServerError().body("Failed to execute request!") | ||||||
|     } |     } | ||||||
| @@ -51,7 +52,7 @@ impl From<serde_json::Error> for HttpErr { | |||||||
|  |  | ||||||
| impl From<Box<dyn Error>> for HttpErr { | impl From<Box<dyn Error>> for HttpErr { | ||||||
|     fn from(value: Box<dyn Error>) -> Self { |     fn from(value: Box<dyn Error>) -> Self { | ||||||
|         HttpErr::Err(std::io::Error::other(value.to_string()).into()) |         HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -81,43 +82,43 @@ impl From<reqwest::header::ToStrError> for HttpErr { | |||||||
|  |  | ||||||
| impl From<actix_web::Error> for HttpErr { | impl From<actix_web::Error> for HttpErr { | ||||||
|     fn from(value: actix_web::Error) -> Self { |     fn from(value: actix_web::Error) -> Self { | ||||||
|         HttpErr::Err(std::io::Error::other(value.to_string()).into()) |         HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<actix::MailboxError> for HttpErr { | impl From<actix::MailboxError> for HttpErr { | ||||||
|     fn from(value: actix::MailboxError) -> Self { |     fn from(value: actix::MailboxError) -> Self { | ||||||
|         HttpErr::Err(std::io::Error::other(value.to_string()).into()) |         HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<actix_identity::error::GetIdentityError> for HttpErr { | impl From<actix_identity::error::GetIdentityError> for HttpErr { | ||||||
|     fn from(value: actix_identity::error::GetIdentityError) -> Self { |     fn from(value: actix_identity::error::GetIdentityError) -> Self { | ||||||
|         HttpErr::Err(std::io::Error::other(value.to_string()).into()) |         HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<actix_identity::error::LoginError> for HttpErr { | impl From<actix_identity::error::LoginError> for HttpErr { | ||||||
|     fn from(value: actix_identity::error::LoginError) -> Self { |     fn from(value: actix_identity::error::LoginError) -> Self { | ||||||
|         HttpErr::Err(std::io::Error::other(value.to_string()).into()) |         HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<openssl::error::ErrorStack> for HttpErr { | impl From<openssl::error::ErrorStack> for HttpErr { | ||||||
|     fn from(value: openssl::error::ErrorStack) -> Self { |     fn from(value: openssl::error::ErrorStack) -> Self { | ||||||
|         HttpErr::Err(std::io::Error::other(value.to_string()).into()) |         HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<ZipError> for HttpErr { | impl From<ZipError> for HttpErr { | ||||||
|     fn from(value: ZipError) -> Self { |     fn from(value: ZipError) -> Self { | ||||||
|         HttpErr::Err(std::io::Error::other(value.to_string()).into()) |         HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<walkdir::Error> for HttpErr { | impl From<walkdir::Error> for HttpErr { | ||||||
|     fn from(value: walkdir::Error) -> Self { |     fn from(value: walkdir::Error) -> Self { | ||||||
|         HttpErr::Err(std::io::Error::other(value.to_string()).into()) |         HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| use crate::logs::logs_manager; | use crate::logs::logs_manager; | ||||||
| use crate::logs::severity::LogSeverity; | use crate::logs::severity::LogSeverity; | ||||||
| use crate::server::WebEnergyActor; |  | ||||||
| use crate::server::custom_error::HttpResult; | use crate::server::custom_error::HttpResult; | ||||||
| use crate::server::devices_api::jwt_parser::JWTRequest; | use crate::server::devices_api::jwt_parser::JWTRequest; | ||||||
| use actix_web::{HttpResponse, web}; | use crate::server::WebEnergyActor; | ||||||
|  | use actix_web::{web, HttpResponse}; | ||||||
|  |  | ||||||
| #[derive(Debug, serde::Deserialize, Clone)] | #[derive(Debug, serde::Deserialize)] | ||||||
| pub struct LogRequest { | pub struct LogRequest { | ||||||
|     severity: LogSeverity, |     severity: LogSeverity, | ||||||
|     message: String, |     message: String, | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| use crate::ota::ota_manager; | use crate::ota::ota_manager; | ||||||
| use crate::ota::ota_update::OTAPlatform; | use crate::ota::ota_update::OTAPlatform; | ||||||
| use crate::server::custom_error::HttpResult; | use crate::server::custom_error::HttpResult; | ||||||
| use actix_web::{HttpResponse, web}; | use actix_web::{web, HttpResponse}; | ||||||
|  |  | ||||||
| #[derive(serde::Deserialize)] | #[derive(serde::Deserialize)] | ||||||
| pub struct FirmwarePath { | pub struct FirmwarePath { | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ pub struct JWTRequest { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl JWTRequest { | impl JWTRequest { | ||||||
|     pub async fn parse_jwt<E: DeserializeOwned + std::clone::Clone>( |     pub async fn parse_jwt<E: DeserializeOwned>( | ||||||
|         &self, |         &self, | ||||||
|         actor: WebEnergyActor, |         actor: WebEnergyActor, | ||||||
|     ) -> anyhow::Result<(Device, E)> { |     ) -> anyhow::Result<(Device, E)> { | ||||||
|   | |||||||
| @@ -4,10 +4,10 @@ use crate::energy::energy_actor; | |||||||
| use crate::energy::energy_actor::RelaySyncStatus; | use crate::energy::energy_actor::RelaySyncStatus; | ||||||
| use crate::ota::ota_manager; | use crate::ota::ota_manager; | ||||||
| use crate::ota::ota_update::OTAPlatform; | use crate::ota::ota_update::OTAPlatform; | ||||||
| use crate::server::WebEnergyActor; |  | ||||||
| use crate::server::custom_error::HttpResult; | use crate::server::custom_error::HttpResult; | ||||||
| use crate::server::devices_api::jwt_parser::JWTRequest; | use crate::server::devices_api::jwt_parser::JWTRequest; | ||||||
| use actix_web::{HttpResponse, web}; | use crate::server::WebEnergyActor; | ||||||
|  | use actix_web::{web, HttpResponse}; | ||||||
| use openssl::nid::Nid; | use openssl::nid::Nid; | ||||||
| use openssl::x509::X509Req; | use openssl::x509::X509Req; | ||||||
| use std::str::FromStr; | use std::str::FromStr; | ||||||
| @@ -130,7 +130,7 @@ pub async fn get_certificate(query: web::Query<ReqWithDevID>, actor: WebEnergyAc | |||||||
|         .body(cert)) |         .body(cert)) | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] | #[derive(Debug, serde::Serialize, serde::Deserialize)] | ||||||
| struct Claims { | struct Claims { | ||||||
|     info: DeviceInfo, |     info: DeviceInfo, | ||||||
| } | } | ||||||
| @@ -155,12 +155,13 @@ pub async fn sync_device(body: web::Json<JWTRequest>, actor: WebEnergyActor) -> | |||||||
|     let mut available_update = None; |     let mut available_update = None; | ||||||
|  |  | ||||||
|     // Check if the version is available |     // Check if the version is available | ||||||
|     if let Some(desired) = device.desired_version |     if let Some(desired) = device.desired_version { | ||||||
|         && claims.info.version < desired |         if claims.info.version < desired | ||||||
|             && ota_manager::update_exists(OTAPlatform::from_str(&claims.info.reference)?, &desired)? |             && ota_manager::update_exists(OTAPlatform::from_str(&claims.info.reference)?, &desired)? | ||||||
|         { |         { | ||||||
|             available_update = Some(desired); |             available_update = Some(desired); | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     Ok(HttpResponse::Ok().json(SyncResult { |     Ok(HttpResponse::Ok().json(SyncResult { | ||||||
|         relays, |         relays, | ||||||
|   | |||||||
| @@ -10,14 +10,14 @@ use crate::server::unsecure_server::*; | |||||||
| use crate::server::web_api::*; | use crate::server::web_api::*; | ||||||
| use crate::server::web_app_controller; | use crate::server::web_app_controller; | ||||||
| use actix_cors::Cors; | use actix_cors::Cors; | ||||||
|  | use actix_identity::config::LogoutBehaviour; | ||||||
| use actix_identity::IdentityMiddleware; | use actix_identity::IdentityMiddleware; | ||||||
| use actix_identity::config::LogoutBehavior; |  | ||||||
| use actix_remote_ip::RemoteIPConfig; | use actix_remote_ip::RemoteIPConfig; | ||||||
| use actix_session::SessionMiddleware; |  | ||||||
| use actix_session::storage::CookieSessionStore; | use actix_session::storage::CookieSessionStore; | ||||||
|  | use actix_session::SessionMiddleware; | ||||||
| use actix_web::cookie::{Key, SameSite}; | use actix_web::cookie::{Key, SameSite}; | ||||||
| use actix_web::middleware::Logger; | use actix_web::middleware::Logger; | ||||||
| use actix_web::{App, HttpServer, web}; | use actix_web::{web, App, HttpServer}; | ||||||
| use openssl::ssl::{SslAcceptor, SslMethod}; | use openssl::ssl::{SslAcceptor, SslMethod}; | ||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
|  |  | ||||||
| @@ -84,7 +84,7 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()> | |||||||
|         .build(); |         .build(); | ||||||
|  |  | ||||||
|         let identity_middleware = IdentityMiddleware::builder() |         let identity_middleware = IdentityMiddleware::builder() | ||||||
|             .logout_behavior(LogoutBehavior::PurgeSession) |             .logout_behaviour(LogoutBehaviour::PurgeSession) | ||||||
|             .visit_deadline(Some(Duration::from_secs( |             .visit_deadline(Some(Duration::from_secs( | ||||||
|                 constants::MAX_INACTIVITY_DURATION, |                 constants::MAX_INACTIVITY_DURATION, | ||||||
|             ))) |             ))) | ||||||
| @@ -231,10 +231,6 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()> | |||||||
|                 "/web_api/relay/{id}", |                 "/web_api/relay/{id}", | ||||||
|                 web::put().to(relays_controller::update), |                 web::put().to(relays_controller::update), | ||||||
|             ) |             ) | ||||||
|             .route( |  | ||||||
|                 "/web_api/relay/{id}/forced_state", |  | ||||||
|                 web::put().to(relays_controller::set_forced_state), |  | ||||||
|             ) |  | ||||||
|             .route( |             .route( | ||||||
|                 "/web_api/relay/{id}", |                 "/web_api/relay/{id}", | ||||||
|                 web::delete().to(relays_controller::delete), |                 web::delete().to(relays_controller::delete), | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| use crate::app_config::AppConfig; | use crate::app_config::AppConfig; | ||||||
| use crate::server::custom_error::HttpResult; | use crate::server::custom_error::HttpResult; | ||||||
| use actix_web::{HttpResponse, web}; | use actix_web::{web, HttpResponse}; | ||||||
|  |  | ||||||
| #[derive(serde::Deserialize)] | #[derive(serde::Deserialize)] | ||||||
| pub struct ServeCRLPath { | pub struct ServeCRLPath { | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| use crate::devices::device::DeviceRelayID; | use crate::devices::device::DeviceRelayID; | ||||||
| use crate::energy::{energy_actor, relay_state_history}; | use crate::energy::{energy_actor, relay_state_history}; | ||||||
| use crate::server::WebEnergyActor; |  | ||||||
| use crate::server::custom_error::HttpResult; | use crate::server::custom_error::HttpResult; | ||||||
| use actix_web::{HttpResponse, web}; | use crate::server::WebEnergyActor; | ||||||
|  | use actix_web::{web, HttpResponse}; | ||||||
|  |  | ||||||
| #[derive(serde::Deserialize)] | #[derive(serde::Deserialize)] | ||||||
| pub struct LegacyStateRelay { | pub struct LegacyStateRelay { | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ use crate::app_config::AppConfig; | |||||||
| use crate::server::custom_error::HttpResult; | use crate::server::custom_error::HttpResult; | ||||||
| use actix_identity::Identity; | use actix_identity::Identity; | ||||||
| use actix_remote_ip::RemoteIP; | use actix_remote_ip::RemoteIP; | ||||||
| use actix_web::{HttpMessage, HttpRequest, HttpResponse, web}; | use actix_web::{web, HttpMessage, HttpRequest, HttpResponse}; | ||||||
|  |  | ||||||
| #[derive(serde::Deserialize)] | #[derive(serde::Deserialize)] | ||||||
| pub struct AuthRequest { | pub struct AuthRequest { | ||||||
| @@ -17,11 +17,11 @@ pub async fn password_auth( | |||||||
|     remote_ip: RemoteIP, |     remote_ip: RemoteIP, | ||||||
| ) -> HttpResult { | ) -> HttpResult { | ||||||
|     if r.user != AppConfig::get().admin_username || r.password != AppConfig::get().admin_password { |     if r.user != AppConfig::get().admin_username || r.password != AppConfig::get().admin_password { | ||||||
|         log::error!("Failed login attempt from {}!", remote_ip.0); |         log::error!("Failed login attempt from {}!", remote_ip.0.to_string()); | ||||||
|         return Ok(HttpResponse::Unauthorized().json("Invalid credentials!")); |         return Ok(HttpResponse::Unauthorized().json("Invalid credentials!")); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     log::info!("Successful login attempt from {}!", remote_ip.0); |     log::info!("Successful login attempt from {}!", remote_ip.0.to_string()); | ||||||
|     Identity::login(&request.extensions(), r.user.to_string())?; |     Identity::login(&request.extensions(), r.user.to_string())?; | ||||||
|     Ok(HttpResponse::Ok().finish()) |     Ok(HttpResponse::Ok().finish()) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| use crate::devices::device::{DeviceGeneralInfo, DeviceId}; | use crate::devices::device::{DeviceGeneralInfo, DeviceId}; | ||||||
| use crate::energy::energy_actor; | use crate::energy::energy_actor; | ||||||
| use crate::server::WebEnergyActor; |  | ||||||
| use crate::server::custom_error::HttpResult; | use crate::server::custom_error::HttpResult; | ||||||
| use actix_web::{HttpResponse, web}; | use crate::server::WebEnergyActor; | ||||||
|  | use actix_web::{web, HttpResponse}; | ||||||
|  |  | ||||||
| /// Get the list of pending (not accepted yet) devices | /// Get the list of pending (not accepted yet) devices | ||||||
| pub async fn list_pending(actor: WebEnergyActor) -> HttpResult { | pub async fn list_pending(actor: WebEnergyActor) -> HttpResult { | ||||||
|   | |||||||
| @@ -2,8 +2,8 @@ use crate::app_config::ConsumptionHistoryType; | |||||||
| use crate::energy::consumption::EnergyConsumption; | use crate::energy::consumption::EnergyConsumption; | ||||||
| use crate::energy::consumption_history_file::ConsumptionHistoryFile; | use crate::energy::consumption_history_file::ConsumptionHistoryFile; | ||||||
| use crate::energy::{consumption, energy_actor}; | use crate::energy::{consumption, energy_actor}; | ||||||
| use crate::server::WebEnergyActor; |  | ||||||
| use crate::server::custom_error::HttpResult; | use crate::server::custom_error::HttpResult; | ||||||
|  | use crate::server::WebEnergyActor; | ||||||
| use crate::utils::time_utils::time_secs; | use crate::utils::time_utils::time_secs; | ||||||
| use actix_web::HttpResponse; | use actix_web::HttpResponse; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ use crate::logs::logs_manager; | |||||||
| use crate::logs::severity::LogSeverity; | use crate::logs::severity::LogSeverity; | ||||||
| use crate::server::custom_error::HttpResult; | use crate::server::custom_error::HttpResult; | ||||||
| use crate::utils::time_utils::curr_day_number; | use crate::utils::time_utils::curr_day_number; | ||||||
| use actix_web::{HttpResponse, web}; | use actix_web::{web, HttpResponse}; | ||||||
|  |  | ||||||
| #[derive(serde::Deserialize)] | #[derive(serde::Deserialize)] | ||||||
| pub struct LogRequest { | pub struct LogRequest { | ||||||
|   | |||||||
| @@ -3,11 +3,11 @@ use crate::devices::device::DeviceId; | |||||||
| use crate::energy::energy_actor; | use crate::energy::energy_actor; | ||||||
| use crate::ota::ota_manager; | use crate::ota::ota_manager; | ||||||
| use crate::ota::ota_update::OTAPlatform; | use crate::ota::ota_update::OTAPlatform; | ||||||
| use crate::server::WebEnergyActor; |  | ||||||
| use crate::server::custom_error::HttpResult; | use crate::server::custom_error::HttpResult; | ||||||
| use actix_multipart::form::MultipartForm; | use crate::server::WebEnergyActor; | ||||||
| use actix_multipart::form::tempfile::TempFile; | use actix_multipart::form::tempfile::TempFile; | ||||||
| use actix_web::{HttpResponse, web}; | use actix_multipart::form::MultipartForm; | ||||||
|  | use actix_web::{web, HttpResponse}; | ||||||
|  |  | ||||||
| pub async fn supported_platforms() -> HttpResult { | pub async fn supported_platforms() -> HttpResult { | ||||||
|     Ok(HttpResponse::Ok().json(OTAPlatform::supported_platforms())) |     Ok(HttpResponse::Ok().json(OTAPlatform::supported_platforms())) | ||||||
| @@ -123,18 +123,18 @@ pub async fn set_desired_version( | |||||||
|  |  | ||||||
|     for d in devices { |     for d in devices { | ||||||
|         // Filter per platform |         // Filter per platform | ||||||
|         if let Some(p) = body.platform |         if let Some(p) = body.platform { | ||||||
|             && d.info.reference != p.to_string() |             if d.info.reference != p.to_string() { | ||||||
|         { |  | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // Filter per device |         // Filter per device | ||||||
|         if let Some(ids) = &body.devices |         if let Some(ids) = &body.devices { | ||||||
|             && !ids.contains(&d.id) |             if !ids.contains(&d.id) { | ||||||
|         { |  | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         actor |         actor | ||||||
|             .send(energy_actor::SetDesiredVersion( |             .send(energy_actor::SetDesiredVersion( | ||||||
|   | |||||||
| @@ -1,9 +1,8 @@ | |||||||
| use crate::devices::device::{DeviceId, DeviceRelay, DeviceRelayID}; | use crate::devices::device::{DeviceId, DeviceRelay, DeviceRelayID}; | ||||||
| use crate::energy::energy_actor; | use crate::energy::energy_actor; | ||||||
| use crate::energy::engine::SetRelayForcedStateReq; |  | ||||||
| use crate::server::WebEnergyActor; |  | ||||||
| use crate::server::custom_error::HttpResult; | use crate::server::custom_error::HttpResult; | ||||||
| use actix_web::{HttpResponse, web}; | use crate::server::WebEnergyActor; | ||||||
|  | use actix_web::{web, HttpResponse}; | ||||||
|  |  | ||||||
| /// Get the full list of relays | /// Get the full list of relays | ||||||
| pub async fn get_list(actor: WebEnergyActor) -> HttpResult { | pub async fn get_list(actor: WebEnergyActor) -> HttpResult { | ||||||
| @@ -86,29 +85,6 @@ pub async fn update( | |||||||
|     Ok(HttpResponse::Accepted().finish()) |     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 | /// Delete an existing relay | ||||||
| pub async fn delete(actor: WebEnergyActor, path: web::Path<RelayIDInPath>) -> HttpResult { | pub async fn delete(actor: WebEnergyActor, path: web::Path<RelayIDInPath>) -> HttpResult { | ||||||
|     actor |     actor | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ mod serve_static_debug { | |||||||
|  |  | ||||||
| #[cfg(not(debug_assertions))] | #[cfg(not(debug_assertions))] | ||||||
| mod serve_static_release { | mod serve_static_release { | ||||||
|     use actix_web::{HttpResponse, Responder, web}; |     use actix_web::{web, HttpResponse, Responder}; | ||||||
|     use rust_embed::RustEmbed; |     use rust_embed::RustEmbed; | ||||||
|  |  | ||||||
|     #[derive(RustEmbed)] |     #[derive(RustEmbed)] | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| use chrono::prelude::*; | use chrono::prelude::*; | ||||||
| use std::time::{SystemTime, UNIX_EPOCH}; | use std::time::{SystemTime, UNIX_EPOCH}; | ||||||
|  |  | ||||||
| /// Get the current time since epoch, in seconds | /// Get the current time since epoch | ||||||
| pub fn time_secs() -> u64 { | pub fn time_secs() -> u64 { | ||||||
|     SystemTime::now() |     SystemTime::now() | ||||||
|         .duration_since(UNIX_EPOCH) |         .duration_since(UNIX_EPOCH) | ||||||
|   | |||||||
| @@ -1,28 +0,0 @@ | |||||||
| import js from '@eslint/js' |  | ||||||
| import globals from 'globals' |  | ||||||
| import reactHooks from 'eslint-plugin-react-hooks' |  | ||||||
| import reactRefresh from 'eslint-plugin-react-refresh' |  | ||||||
| import tseslint from 'typescript-eslint' |  | ||||||
|  |  | ||||||
| export default tseslint.config( |  | ||||||
|   { ignores: ['dist'] }, |  | ||||||
|   { |  | ||||||
|     extends: [js.configs.recommended, ...tseslint.configs.recommended], |  | ||||||
|     files: ['**/*.{ts,tsx}'], |  | ||||||
|     languageOptions: { |  | ||||||
|       ecmaVersion: 2020, |  | ||||||
|       globals: globals.browser, |  | ||||||
|     }, |  | ||||||
|     plugins: { |  | ||||||
|       'react-hooks': reactHooks, |  | ||||||
|       'react-refresh': reactRefresh, |  | ||||||
|     }, |  | ||||||
|     rules: { |  | ||||||
|       ...reactHooks.configs.recommended.rules, |  | ||||||
|       'react-refresh/only-export-components': [ |  | ||||||
|         'warn', |  | ||||||
|         { allowConstantExport: true }, |  | ||||||
|       ], |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
| ) |  | ||||||
							
								
								
									
										2476
									
								
								central_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2476
									
								
								central_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -6,40 +6,38 @@ | |||||||
|   "scripts": { |   "scripts": { | ||||||
|     "dev": "vite", |     "dev": "vite", | ||||||
|     "build": "tsc -b && vite build", |     "build": "tsc -b && vite build", | ||||||
|     "lint": "eslint .", |     "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", | ||||||
|     "preview": "vite preview" |     "preview": "vite preview" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@emotion/react": "^11.14.0", |     "@emotion/react": "^11.13.3", | ||||||
|     "@emotion/styled": "^11.14.1", |     "@emotion/styled": "^11.13.0", | ||||||
|     "@fontsource/roboto": "^5.2.8", |     "@fontsource/roboto": "^5.1.0", | ||||||
|     "@mdi/js": "^7.4.47", |     "@mdi/js": "^7.4.47", | ||||||
|     "@mdi/react": "^1.6.1", |     "@mdi/react": "^1.6.1", | ||||||
|     "@mui/icons-material": "^7.3.4", |     "@mui/icons-material": "^6.1.4", | ||||||
|     "@mui/material": "^7.3.4", |     "@mui/material": "^6.1.4", | ||||||
|     "@mui/x-charts": "^8.15.0", |     "@mui/x-charts": "^7.21.0", | ||||||
|     "@mui/x-date-pickers": "^8.15.0", |     "@mui/x-date-pickers": "^7.21.0", | ||||||
|     "date-and-time": "^4.1.0", |     "@types/semver": "^7.5.8", | ||||||
|     "dayjs": "^1.11.18", |     "date-and-time": "^3.6.0", | ||||||
|     "filesize": "^11.0.13", |     "dayjs": "^1.11.13", | ||||||
|     "react": "^19.2.0", |     "filesize": "^10.1.6", | ||||||
|     "react-dom": "^19.2.0", |     "react": "^18.3.1", | ||||||
|     "react-router-dom": "^7.9.5", |     "react-dom": "^18.3.1", | ||||||
|     "semver": "^7.7.3" |     "react-router-dom": "^6.27.0", | ||||||
|  |     "semver": "^7.6.3" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@types/react": "^19.2.2", |     "@types/react": "^18.3.11", | ||||||
|     "@types/react-dom": "^19.2.2", |     "@types/react-dom": "^18.3.1", | ||||||
|     "@types/semver": "^7.7.1", |     "@typescript-eslint/eslint-plugin": "^8.15.0", | ||||||
|     "@typescript-eslint/eslint-plugin": "^8.46.2", |     "@typescript-eslint/parser": "^8.10.0", | ||||||
|     "@typescript-eslint/parser": "^8.46.2", |     "@vitejs/plugin-react": "^4.3.2", | ||||||
|     "@vitejs/plugin-react": "^5.1.0", |     "eslint": "^9.0.0", | ||||||
|     "eslint": "^9.38.0", |     "eslint-plugin-react-hooks": "^5.0.0", | ||||||
|     "eslint-plugin-react-hooks": "^7.0.1", |     "eslint-plugin-react-refresh": "^0.4.12", | ||||||
|     "eslint-plugin-react-refresh": "^0.4.24", |     "typescript": "^5.6.3", | ||||||
|     "globals": "^16.4.0", |     "vite": "^5.4.9" | ||||||
|     "typescript": "^5.9.3", |  | ||||||
|     "typescript-eslint": "^8.46.2", |  | ||||||
|     "vite": "^7.1.12" |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,19 +1,10 @@ | |||||||
| import { APIClient } from "./ApiClient"; | import { APIClient } from "./ApiClient"; | ||||||
| import { Device, DeviceRelay } from "./DeviceApi"; | 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 { | export interface RelayStatus { | ||||||
|   id: string; |   id: string; | ||||||
|   on: boolean; |   on: boolean; | ||||||
|   for: number; |   for: number; | ||||||
|   forced_state: RelayForcedState; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| export type RelaysStatus = Map<string, RelayStatus>; | export type RelaysStatus = Map<string, RelayStatus>; | ||||||
| @@ -57,20 +48,6 @@ 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 |    * Delete a relay configuration | ||||||
|    */ |    */ | ||||||
|   | |||||||
| @@ -84,7 +84,7 @@ export function DeployOTAUpdateDialogProvider(p: { | |||||||
|             </DialogContentText> |             </DialogContentText> | ||||||
|  |  | ||||||
|             <FormControl> |             <FormControl> | ||||||
|               <FormLabel>Deployment target</FormLabel> |               <FormLabel>Gender</FormLabel> | ||||||
|               <RadioGroup |               <RadioGroup | ||||||
|                 name="radio-buttons-group" |                 name="radio-buttons-group" | ||||||
|                 value={allDevices} |                 value={allDevices} | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ import { | |||||||
|   DialogTitle, |   DialogTitle, | ||||||
|   Typography, |   Typography, | ||||||
| } from "@mui/material"; | } from "@mui/material"; | ||||||
| import Grid from "@mui/material/Grid"; | import Grid from "@mui/material/Grid2"; | ||||||
| import { TimePicker } from "@mui/x-date-pickers"; | import { TimePicker } from "@mui/x-date-pickers"; | ||||||
| import React from "react"; | import React from "react"; | ||||||
| import { Device, DeviceRelay } from "../api/DeviceApi"; | import { Device, DeviceRelay } from "../api/DeviceApi"; | ||||||
|   | |||||||
| @@ -1,53 +0,0 @@ | |||||||
| 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"; | } from "@mui/material"; | ||||||
| import React from "react"; | import React from "react"; | ||||||
| import { Device, DeviceRelay } from "../../api/DeviceApi"; | import { Device, DeviceRelay } from "../../api/DeviceApi"; | ||||||
| import { RelayApi, RelayStatus } from "../../api/RelayApi"; |  | ||||||
| import { EditDeviceRelaysDialog } from "../../dialogs/EditDeviceRelaysDialog"; | import { EditDeviceRelaysDialog } from "../../dialogs/EditDeviceRelaysDialog"; | ||||||
| import { useAlert } from "../../hooks/context_providers/AlertDialogProvider"; | 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, 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 { AsyncWidget } from "../../widgets/AsyncWidget"; | import { AsyncWidget } from "../../widgets/AsyncWidget"; | ||||||
| import { BoolText } from "../../widgets/BoolText"; |  | ||||||
| import { TimeWidget } from "../../widgets/TimeWidget"; | import { TimeWidget } from "../../widgets/TimeWidget"; | ||||||
| import { DeviceRouteCard } from "./DeviceRouteCard"; | import { BoolText } from "../../widgets/BoolText"; | ||||||
|  |  | ||||||
| export function DeviceRelays(p: { | export function DeviceRelays(p: { | ||||||
|   device: Device; |   device: Device; | ||||||
| @@ -145,8 +145,7 @@ function RelayEntryStatus( | |||||||
|       errMsg="Failed to load relay status!" |       errMsg="Failed to load relay status!" | ||||||
|       build={() => ( |       build={() => ( | ||||||
|         <> |         <> | ||||||
|           <BoolText val={state!.on} positive="ON" negative="OFF" />{" "} |           <BoolText val={state!.on} positive="ON" negative="OFF" /> for{" "} | ||||||
|           {state?.forced_state.type !== "None" && <b>Forced</b>} for{" "} |  | ||||||
|           <TimeWidget diff time={state!.for} /> |           <TimeWidget diff time={state!.for} /> | ||||||
|         </> |         </> | ||||||
|       )} |       )} | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import DeleteIcon from "@mui/icons-material/Delete"; | import DeleteIcon from "@mui/icons-material/Delete"; | ||||||
| import RefreshIcon from "@mui/icons-material/Refresh"; | import RefreshIcon from "@mui/icons-material/Refresh"; | ||||||
| import { IconButton, Tooltip } from "@mui/material"; | import { IconButton, Tooltip } from "@mui/material"; | ||||||
| import Grid from "@mui/material/Grid"; | import Grid from "@mui/material/Grid2"; | ||||||
| import React from "react"; | import React from "react"; | ||||||
| import { useNavigate, useParams } from "react-router-dom"; | import { useNavigate, useParams } from "react-router-dom"; | ||||||
| import { Device, DeviceApi } from "../../api/DeviceApi"; | import { Device, DeviceApi } from "../../api/DeviceApi"; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { Typography } from "@mui/material"; | import { Typography } from "@mui/material"; | ||||||
| import { CurrConsumptionWidget } from "./HomeRoute/CurrConsumptionWidget"; | import { CurrConsumptionWidget } from "./HomeRoute/CurrConsumptionWidget"; | ||||||
| import Grid from "@mui/material/Grid"; | import Grid from "@mui/material/Grid2"; | ||||||
| import { CachedConsumptionWidget } from "./HomeRoute/CachedConsumptionWidget"; | import { CachedConsumptionWidget } from "./HomeRoute/CachedConsumptionWidget"; | ||||||
| import { RelayConsumptionWidget } from "./HomeRoute/RelayConsumptionWidget"; | import { RelayConsumptionWidget } from "./HomeRoute/RelayConsumptionWidget"; | ||||||
| import { RelaysListRoute } from "./RelaysListRoute"; | import { RelaysListRoute } from "./RelaysListRoute"; | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ import Typography from "@mui/material/Typography"; | |||||||
| import * as React from "react"; | import * as React from "react"; | ||||||
| import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider"; | import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider"; | ||||||
| import { AuthApi } from "../api/AuthApi"; | import { AuthApi } from "../api/AuthApi"; | ||||||
| import Grid from "@mui/material/Grid"; | import Grid from "@mui/material/Grid2"; | ||||||
|  |  | ||||||
| function Copyright(props: any) { | function Copyright(props: any) { | ||||||
|   return ( |   return ( | ||||||
|   | |||||||
| @@ -16,13 +16,12 @@ import React from "react"; | |||||||
| import { useNavigate } from "react-router-dom"; | import { useNavigate } from "react-router-dom"; | ||||||
| import { Device, DeviceApi, DeviceRelay, DeviceURL } from "../api/DeviceApi"; | import { Device, DeviceApi, DeviceRelay, DeviceURL } from "../api/DeviceApi"; | ||||||
| import { RelayApi, RelaysStatus } from "../api/RelayApi"; | import { RelayApi, RelaysStatus } from "../api/RelayApi"; | ||||||
| import { ServerApi } from "../api/ServerApi"; |  | ||||||
| import { AsyncWidget } from "../widgets/AsyncWidget"; | import { AsyncWidget } from "../widgets/AsyncWidget"; | ||||||
| import { BoolText } from "../widgets/BoolText"; | import { BoolText } from "../widgets/BoolText"; | ||||||
| import { CopyToClipboard } from "../widgets/CopyToClipboard"; |  | ||||||
| import { RelayForcedState } from "../widgets/RelayForcedState"; |  | ||||||
| import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer"; | import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer"; | ||||||
| import { TimeWidget } from "../widgets/TimeWidget"; | import { TimeWidget } from "../widgets/TimeWidget"; | ||||||
|  | import { CopyToClipboard } from "../widgets/CopyToClipboard"; | ||||||
|  | import { ServerApi } from "../api/ServerApi"; | ||||||
|  |  | ||||||
| export function RelaysListRoute(p: { | export function RelaysListRoute(p: { | ||||||
|   homeWidget?: boolean; |   homeWidget?: boolean; | ||||||
| @@ -105,7 +104,6 @@ function RelaysList(p: { | |||||||
|             <TableCell>Priority</TableCell> |             <TableCell>Priority</TableCell> | ||||||
|             <TableCell>Consumption</TableCell> |             <TableCell>Consumption</TableCell> | ||||||
|             <TableCell>Status</TableCell> |             <TableCell>Status</TableCell> | ||||||
|             <TableCell>Forced state</TableCell> |  | ||||||
|             <TableCell></TableCell> |             <TableCell></TableCell> | ||||||
|           </TableRow> |           </TableRow> | ||||||
|         </TableHead> |         </TableHead> | ||||||
| @@ -131,13 +129,6 @@ function RelaysList(p: { | |||||||
|                 />{" "} |                 />{" "} | ||||||
|                 for <TimeWidget diff time={p.status.get(row.id)!.for} /> |                 for <TimeWidget diff time={p.status.get(row.id)!.for} /> | ||||||
|               </TableCell> |               </TableCell> | ||||||
|               <TableCell> |  | ||||||
|                 <RelayForcedState |  | ||||||
|                   relay={row} |  | ||||||
|                   state={p.status.get(row.id)!} |  | ||||||
|                   onUpdated={p.onReload} |  | ||||||
|                 /> |  | ||||||
|               </TableCell> |  | ||||||
|               <TableCell> |               <TableCell> | ||||||
|                 <Tooltip title="Copy legacy api status"> |                 <Tooltip title="Copy legacy api status"> | ||||||
|                   <CopyToClipboard |                   <CopyToClipboard | ||||||
|   | |||||||
| @@ -1,79 +0,0 @@ | |||||||
| 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 }}> |           <Box sx={{ width: "100%", height: 100 }}> | ||||||
|             {data && interval && ( |             {data && interval && ( | ||||||
|               <SparkLineChart |               <SparkLineChart | ||||||
|                 color={chartColor} |                 colors={[chartColor]} | ||||||
|                 data={data} |                 data={data} | ||||||
|                 area |                 area | ||||||
|                 showHighlight |                 showHighlight | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| import { Tooltip } from "@mui/material"; | import { Tooltip } from "@mui/material"; | ||||||
| import { format } from "date-and-time"; | import date from "date-and-time"; | ||||||
| import { time } from "../utils/DateUtils"; | import { time } from "../utils/DateUtils"; | ||||||
|  |  | ||||||
| export function formatDate(time: number): string { | export function formatDate(time: number): string { | ||||||
|   const t = new Date(); |   const t = new Date(); | ||||||
|   t.setTime(1000 * time); |   t.setTime(1000 * time); | ||||||
|   return format(t, "DD/MM/YYYY HH:mm:ss"); |   return date.format(t, "DD/MM/YYYY HH:mm:ss"); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function timeDiff(a: number, b: number): string { | export function timeDiff(a: number, b: number): string { | ||||||
| @@ -51,14 +51,13 @@ export function timeDiff(a: number, b: number): string { | |||||||
|   return `${diffYears} years`; |   return `${diffYears} years`; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function timeDiffFromNow(t: number, future?: boolean): string { | export function timeDiffFromNow(t: number): string { | ||||||
|   return future ? timeDiff(time(), t) : timeDiff(t, time()); |   return timeDiff(t, time()); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function TimeWidget(p: { | export function TimeWidget(p: { | ||||||
|   time?: number; |   time?: number; | ||||||
|   diff?: boolean; |   diff?: boolean; | ||||||
|   future?: boolean; |  | ||||||
| }): React.ReactElement { | }): React.ReactElement { | ||||||
|   if (!p.time) return <></>; |   if (!p.time) return <></>; | ||||||
|   return ( |   return ( | ||||||
| @@ -66,9 +65,7 @@ export function TimeWidget(p: { | |||||||
|       title={formatDate(p.diff ? new Date().getTime() / 1000 - p.time : p.time)} |       title={formatDate(p.diff ? new Date().getTime() / 1000 - p.time : p.time)} | ||||||
|       arrow |       arrow | ||||||
|     > |     > | ||||||
|       <span> |       <span>{p.diff ? timeDiff(0, p.time) : timeDiffFromNow(p.time)}</span> | ||||||
|         {p.diff ? timeDiff(0, p.time) : timeDiffFromNow(p.time, p.future)} |  | ||||||
|       </span> |  | ||||||
|     </Tooltip> |     </Tooltip> | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| { | { | ||||||
|   "compilerOptions": { |   "compilerOptions": { | ||||||
|  |     "composite": true, | ||||||
|     "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", |     "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", | ||||||
|     "target": "ES2020", |     "target": "ES2020", | ||||||
|     "useDefineForClassFields": true, |     "useDefineForClassFields": true, | ||||||
| @@ -10,6 +11,7 @@ | |||||||
|     /* Bundler mode */ |     /* Bundler mode */ | ||||||
|     "moduleResolution": "bundler", |     "moduleResolution": "bundler", | ||||||
|     "allowImportingTsExtensions": true, |     "allowImportingTsExtensions": true, | ||||||
|  |     "resolveJsonModule": true, | ||||||
|     "isolatedModules": true, |     "isolatedModules": true, | ||||||
|     "moduleDetection": "force", |     "moduleDetection": "force", | ||||||
|     "noEmit": true, |     "noEmit": true, | ||||||
| @@ -19,8 +21,7 @@ | |||||||
|     "strict": true, |     "strict": true, | ||||||
|     "noUnusedLocals": true, |     "noUnusedLocals": true, | ||||||
|     "noUnusedParameters": true, |     "noUnusedParameters": true, | ||||||
|     "noFallthroughCasesInSwitch": true, |     "noFallthroughCasesInSwitch": true | ||||||
|     "noUncheckedSideEffectImports": true |  | ||||||
|   }, |   }, | ||||||
|   "include": ["src"] |   "include": ["src"] | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,11 @@ | |||||||
| { | { | ||||||
|   "files": [], |   "files": [], | ||||||
|   "references": [ |   "references": [ | ||||||
|     { "path": "./tsconfig.app.json" }, |     { | ||||||
|     { "path": "./tsconfig.node.json" } |       "path": "./tsconfig.app.json" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "./tsconfig.node.json" | ||||||
|  |     } | ||||||
|   ] |   ] | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,24 +1,13 @@ | |||||||
| { | { | ||||||
|   "compilerOptions": { |   "compilerOptions": { | ||||||
|  |     "composite": true, | ||||||
|     "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", |     "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", | ||||||
|     "target": "ES2022", |  | ||||||
|     "lib": ["ES2023"], |  | ||||||
|     "module": "ESNext", |  | ||||||
|     "skipLibCheck": true, |     "skipLibCheck": true, | ||||||
|  |     "module": "ESNext", | ||||||
|     /* Bundler mode */ |  | ||||||
|     "moduleResolution": "bundler", |     "moduleResolution": "bundler", | ||||||
|     "allowImportingTsExtensions": true, |     "allowSyntheticDefaultImports": true, | ||||||
|     "isolatedModules": true, |  | ||||||
|     "moduleDetection": "force", |  | ||||||
|     "noEmit": true, |  | ||||||
|  |  | ||||||
|     /* Linting */ |  | ||||||
|     "strict": true, |     "strict": true, | ||||||
|     "noUnusedLocals": true, |     "noEmit": true | ||||||
|     "noUnusedParameters": true, |  | ||||||
|     "noFallthroughCasesInSwitch": true, |  | ||||||
|     "noUncheckedSideEffectImports": true |  | ||||||
|   }, |   }, | ||||||
|   "include": ["vite.config.ts"] |   "include": ["vite.config.ts"] | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { defineConfig } from 'vite' | import { defineConfig } from 'vite' | ||||||
| import react from '@vitejs/plugin-react' | import react from '@vitejs/plugin-react' | ||||||
|  |  | ||||||
| // https://vite.dev/config/ | // https://vitejs.dev/config/ | ||||||
| export default defineConfig({ | export default defineConfig({ | ||||||
|   plugins: [react()], |   plugins: [react()], | ||||||
| }) | }) | ||||||
|   | |||||||
							
								
								
									
										1861
									
								
								custom_consumption/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1861
									
								
								custom_consumption/Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,12 +1,12 @@ | |||||||
| [package] | [package] | ||||||
| name = "custom_consumption" | name = "custom_consumption" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| edition = "2024" | edition = "2021" | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| env_logger = "0.11.8" | env_logger = "0.11.5" | ||||||
| log = "0.4.28" | log = "0.4.22" | ||||||
| clap = { version = "4.5.50", features = ["derive", "env"] } | clap = { version = "4.5.18", features = ["derive", "env"] } | ||||||
| egui = "0.32.3" | egui = "0.29.1" | ||||||
| eframe = "0.32.3" | eframe = "0.29.1" | ||||||
| lazy_static = "1.5.0" | lazy_static = "1.5.0" | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| use std::time::{SystemTime, UNIX_EPOCH}; | use std::time::{SystemTime, UNIX_EPOCH}; | ||||||
|  |  | ||||||
| /// Get the current time since epoch | /// Get the current time since epoch | ||||||
|  |  | ||||||
| pub fn time_millis() -> u128 { | pub fn time_millis() -> u128 { | ||||||
|     SystemTime::now() |     SystemTime::now() | ||||||
|         .duration_since(UNIX_EPOCH) |         .duration_since(UNIX_EPOCH) | ||||||
|   | |||||||
| @@ -2,11 +2,6 @@ | |||||||
|  |  | ||||||
| ESP32 client device, using `W32-ETH01` device | ESP32 client device, using `W32-ETH01` device | ||||||
|  |  | ||||||
| ## Pins for relays |  | ||||||
| The pins are the following (in the order of definition): 4, 14, 15, 2 |  | ||||||
|  |  | ||||||
| **WARNING!** The Pin 2 MUST be disconnect to reflash the card! |  | ||||||
|  |  | ||||||
| ## Some commands | ## Some commands | ||||||
|  |  | ||||||
| Create a new firmware build: | Create a new firmware build: | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | #define CONFIG_ETH_USE_ESP32_EMAC | ||||||
|  |  | ||||||
| #include "esp_eth.h" | #include "esp_eth.h" | ||||||
| #include "esp_eth_mac.h" | #include "esp_eth_mac.h" | ||||||
| #include "esp_eth_com.h" | #include "esp_eth_com.h" | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ static const char *TAG = "relays"; | |||||||
| /** | /** | ||||||
|  * Device relays GPIO ids |  * Device relays GPIO ids | ||||||
|  */ |  */ | ||||||
| static int DEVICE_GPIO_IDS[4] = {4, 14, 15, 2}; | static int DEVICE_GPIO_IDS[3] = {4, 14, 15}; | ||||||
|  |  | ||||||
| int relays_count() | int relays_count() | ||||||
| { | { | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| 1.0.3 | 1.0.2-b1 | ||||||
| @@ -1,3 +1,9 @@ | |||||||
| { | { | ||||||
|   "extends": ["local>renovate/presets"] |   "$schema": "https://docs.renovatebot.com/renovate-schema.json", | ||||||
|  |   "packageRules": [ | ||||||
|  |     { | ||||||
|  |       "matchUpdateTypes": ["major", "minor", "patch"], | ||||||
|  |       "automerge": true | ||||||
|  |     } | ||||||
|  |   ] | ||||||
| } | } | ||||||
		Reference in New Issue
	
	Block a user