Can set forced state to relays

This commit is contained in:
2025-10-29 15:49:05 +01:00
parent 58eceeda2d
commit 3625188706
4 changed files with 136 additions and 14 deletions

View File

@@ -8,7 +8,7 @@ use crate::energy::consumption;
use crate::energy::consumption::EnergyConsumption;
use crate::energy::consumption_cache::ConsumptionCache;
use crate::energy::consumption_history_file::ConsumptionHistoryFile;
use crate::energy::engine::EnergyEngine;
use crate::energy::engine::{EnergyEngine, RelayForcedState};
use crate::utils::time_utils::time_secs;
use actix::prelude::*;
use openssl::x509::X509Req;
@@ -328,6 +328,19 @@ impl Handler<UpdateDeviceRelay> for EnergyActor {
}
}
#[derive(Message)]
#[rtype(result = "anyhow::Result<()>")]
pub struct SetRelayForcedState(pub DeviceRelayID, pub RelayForcedState);
impl Handler<SetRelayForcedState> for EnergyActor {
type Result = anyhow::Result<()>;
fn handle(&mut self, msg: SetRelayForcedState, _ctx: &mut Context<Self>) -> Self::Result {
self.engine.relay_state(msg.0).set_forced(msg.1);
Ok(())
}
}
/// Delete a device relay
#[derive(Message)]
#[rtype(result = "anyhow::Result<()>")]
@@ -408,6 +421,7 @@ pub struct ResRelayState {
pub id: DeviceRelayID,
pub on: bool,
pub r#for: usize,
pub forced_state: RelayForcedState,
}
/// Get the state of all relays
@@ -427,6 +441,7 @@ impl Handler<GetAllRelaysState> for EnergyActor {
id: d.id,
on: state.is_on(),
r#for: state.state_for(),
forced_state: state.actual_forced_state(),
})
}

View File

@@ -25,19 +25,56 @@ impl DeviceState {
}
}
#[derive(Default, Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type")]
pub enum RelayForcedState {
#[default]
None,
Off {
until: u64,
},
On {
until: u64,
},
}
#[derive(Default, Clone)]
pub struct RelayState {
on: bool,
since: usize,
forced_state: RelayForcedState,
}
impl RelayState {
/// Get actual forced state (returns None if state is expired)
pub fn actual_forced_state(&self) -> RelayForcedState {
match self.forced_state {
RelayForcedState::Off { until } if until > time_secs() => {
RelayForcedState::Off { until }
}
RelayForcedState::On { until } if until > time_secs() => RelayForcedState::On { until },
_ => RelayForcedState::None,
}
}
pub fn is_on(&self) -> bool {
self.on
let forced_state = self.actual_forced_state();
(self.on || matches!(forced_state, RelayForcedState::On { .. }))
&& !matches!(forced_state, RelayForcedState::Off { .. })
}
fn is_off(&self) -> bool {
!self.on
!self.is_on()
}
/// Check if relay state is enforced
pub fn is_forced(&self) -> bool {
self.actual_forced_state() != RelayForcedState::None
}
pub fn set_forced(&mut self, s: RelayForcedState) {
self.since = time_secs() as usize;
self.forced_state = s;
}
pub fn state_for(&self) -> usize {
@@ -146,7 +183,11 @@ impl EnergyEngine {
r.name,
r.consumption,
format!("{} / {}", r.minimal_downtime, r.minimal_uptime),
status.is_on().to_string(),
status.is_on().to_string()
+ match status.is_forced() {
true => " (Forced)",
false => "",
},
status.since,
match dev_online {
true => "Online",
@@ -192,19 +233,28 @@ impl EnergyEngine {
let mut new_relays_state = self.relays_state.clone();
// Forcefully turn off relays that belongs to offline devices
// Forcefully turn off disabled relays
for d in devices {
if !self.device_state(&d.id).is_online() {
for r in &d.relays {
if !r.enabled || !d.enabled {
new_relays_state.get_mut(&r.id).unwrap().on = false;
}
}
}
// Forcefully turn off disabled relays
// Apply forced relays state
for d in devices {
for r in &d.relays {
if !r.enabled || !d.enabled {
if self.relay_state(r.id).is_forced() {
new_relays_state.get_mut(&r.id).unwrap().on = self.relay_state(r.id).is_on();
}
}
}
// Forcefully turn off relays that belongs to offline devices
for d in devices {
if !self.device_state(&d.id).is_online() {
for r in &d.relays {
new_relays_state.get_mut(&r.id).unwrap().on = false;
}
}
@@ -216,7 +266,9 @@ impl EnergyEngine {
for d in devices {
for r in &d.relays {
if new_relays_state.get(&r.id).unwrap().is_off() {
if new_relays_state.get(&r.id).unwrap().is_off()
|| new_relays_state.get(&r.id).unwrap().is_forced()
{
continue;
}
@@ -240,7 +292,7 @@ impl EnergyEngine {
for d in devices {
for r in &d.relays {
let state = new_relays_state.get(&r.id).unwrap();
if state.is_off() {
if state.is_off() || state.is_forced() {
continue;
}
@@ -271,7 +323,9 @@ impl EnergyEngine {
continue;
}
if new_relays_state.get(&r.id).unwrap().is_on() {
if new_relays_state.get(&r.id).unwrap().is_on()
|| new_relays_state.get(&r.id).unwrap().is_forced()
{
continue;
}
@@ -298,7 +352,7 @@ impl EnergyEngine {
}
}
// Order relays
// Order relays to select the ones with the most elevated priorities
let mut ordered_relays = devices
.iter()
.filter(|d| self.device_state(&d.id).is_online() && d.enabled)
@@ -308,10 +362,13 @@ impl EnergyEngine {
ordered_relays.sort_by_key(|r| r.priority);
ordered_relays.reverse();
// Select relays to start, starting with those with highest priorities
loop {
let mut changed = false;
for relay in &ordered_relays {
if new_relays_state.get(&relay.id).unwrap().is_on() {
if new_relays_state.get(&relay.id).unwrap().is_on()
|| new_relays_state.get(&relay.id).unwrap().is_forced()
{
continue;
}

View File

@@ -231,6 +231,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
"/web_api/relay/{id}",
web::put().to(relays_controller::update),
)
.route(
"/web_api/relay/{id}/forced_state",
web::put().to(relays_controller::set_forced_state),
)
.route(
"/web_api/relay/{id}",
web::delete().to(relays_controller::delete),

View File

@@ -1,7 +1,9 @@
use crate::devices::device::{DeviceId, DeviceRelay, DeviceRelayID};
use crate::energy::energy_actor;
use crate::energy::engine::RelayForcedState;
use crate::server::WebEnergyActor;
use crate::server::custom_error::HttpResult;
use crate::utils::time_utils::time_secs;
use actix_web::{HttpResponse, web};
/// Get the full list of relays
@@ -85,6 +87,50 @@ pub async fn update(
Ok(HttpResponse::Accepted().finish())
}
#[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,
},
}
/// 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,
match &req.0 {
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,
},
},
))
.await??;
Ok(HttpResponse::Accepted().finish())
}
/// Delete an existing relay
pub async fn delete(actor: WebEnergyActor, path: web::Path<RelayIDInPath>) -> HttpResult {
actor