3 Commits

Author SHA1 Message Date
79b2ad12d8 Add missing device information synchronization 2024-09-15 22:06:42 +02:00
9c45e541dd Better handle enabled / disabled relays 2024-09-15 22:01:06 +02:00
2262b98952 WIP engine 2024-09-15 21:53:08 +02:00
5 changed files with 144 additions and 35 deletions

View File

@@ -143,6 +143,23 @@ impl DevicesList {
Ok(()) Ok(())
} }
/// Update a device general information
pub fn synchronise_dev_info(
&mut self,
id: &DeviceId,
general_info: DeviceInfo,
) -> anyhow::Result<()> {
let dev = self
.0
.get_mut(id)
.ok_or(DevicesListError::UpdateDeviceFailedDeviceNotFound)?;
dev.info = general_info;
self.persist_dev_config(id)?;
Ok(())
}
/// Update a device general information /// Update a device general information
pub fn update_general_info( pub fn update_general_info(
&mut self, &mut self,

View File

@@ -260,6 +260,7 @@ impl Handler<SynchronizeDevice> for EnergyActor {
type Result = anyhow::Result<Vec<RelaySyncStatus>>; type Result = anyhow::Result<Vec<RelaySyncStatus>>;
fn handle(&mut self, msg: SynchronizeDevice, _ctx: &mut Context<Self>) -> Self::Result { fn handle(&mut self, msg: SynchronizeDevice, _ctx: &mut Context<Self>) -> Self::Result {
self.devices.synchronise_dev_info(&msg.0, msg.1.clone())?;
self.engine.device_state(&msg.0).record_ping(); self.engine.device_state(&msg.0).record_ping();
// TODO : implement real code // TODO : implement real code

View File

@@ -1,9 +1,11 @@
use std::collections::HashMap;
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};
use crate::energy::consumption::EnergyConsumption; use crate::energy::consumption::EnergyConsumption;
use crate::utils::time_utils::time_secs; use crate::utils::time_utils::time_secs;
use prettytable::{row, Table};
use std::collections::HashMap;
#[derive(Default)] #[derive(Default)]
pub struct DeviceState { pub struct DeviceState {
@@ -26,6 +28,16 @@ pub struct RelayState {
since: usize, since: usize,
} }
impl RelayState {
fn is_on(&self) -> bool {
self.on
}
fn is_off(&self) -> bool {
!self.on
}
}
type RelaysState = HashMap<DeviceRelayID, RelayState>; type RelaysState = HashMap<DeviceRelayID, RelayState>;
#[derive(Default)] #[derive(Default)]
@@ -35,10 +47,37 @@ pub struct EnergyEngine {
} }
impl DeviceRelay { impl DeviceRelay {
fn relay_has_running_dependencies(&self, s: &RelaysState, devices: &[Device]) -> bool { // Note : this function is not recursive
fn has_running_dependencies(&self, s: &RelaysState, devices: &[Device]) -> bool {
for d in devices { for d in devices {
for r in &d.relays { for r in &d.relays {
if r.depends_on.contains(&self.id) && s.get(&r.id).unwrap().on { if r.depends_on.contains(&self.id) && s.get(&r.id).unwrap().is_on() {
return true;
}
}
}
false
}
// Note : this function is not recursive
fn is_missing_dependencies(&self, s: &RelaysState) -> bool {
self.depends_on.iter().any(|id| s.get(id).unwrap().is_off())
}
fn is_having_conflict(&self, s: &RelaysState, devices: &[Device]) -> bool {
if self
.conflicts_with
.iter()
.any(|id| s.get(id).unwrap().is_on())
{
return true;
}
// Reverse search
for device in devices {
for r in &device.relays {
if s.get(&r.id).unwrap().is_on() && r.conflicts_with.contains(&self.id) {
return true; return true;
} }
} }
@@ -67,7 +106,12 @@ impl EnergyEngine {
for d in devices { for d in devices {
for r in &d.relays { for r in &d.relays {
let status = self.relay_state(r.id); let status = self.relay_state(r.id);
table.add_row(row![d.name, r.name, status.on.to_string(), status.since]); table.add_row(row![
d.name,
r.name,
status.is_on().to_string(),
status.since
]);
} }
} }
table.printstd(); table.printstd();
@@ -93,25 +137,29 @@ impl EnergyEngine {
} }
} }
// Forcefully turn off disabled relays
for d in devices {
for r in &d.relays {
if !r.enabled || !d.enabled {
new_relays_state.get_mut(&r.id).unwrap().on = false;
}
}
}
// Forcefully turn off relays with missing dependency // Forcefully turn off relays with missing dependency
loop { loop {
let mut changed = false; let mut changed = false;
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 new_relays_state.get(&r.id).unwrap().is_off() {
if !new_relays_state.get(&r.id).unwrap().on { continue;
continue; }
}
// Check if any dependency of relay is off // Check if any dependency of relay is off
if r.depends_on if r.is_missing_dependencies(&new_relays_state) {
.iter() new_relays_state.get_mut(&r.id).unwrap().on = false;
.any(|d| !new_relays_state.get(d).unwrap().on) changed = true;
{
new_relays_state.get_mut(&r.id).unwrap().on = false;
changed = true;
}
} }
} }
} }
@@ -128,7 +176,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.on { if state.is_off() {
continue; continue;
} }
@@ -138,7 +186,7 @@ impl EnergyEngine {
} }
// Check that no relay that depends on this relay are turned on // Check that no relay that depends on this relay are turned on
if r.relay_has_running_dependencies(&new_relays_state, devices) { if r.has_running_dependencies(&new_relays_state, devices) {
continue; continue;
} }
@@ -152,19 +200,63 @@ impl EnergyEngine {
} }
} }
// TODO Turn on relays based on priority / dependencies / enabled / min downtime // TODO Turn on relays with running constraints (only ENABLED)
// TODO Turn on relays with running constraints // Order relays
let mut ordered_relays = devices
.iter()
.filter(|d| self.device_state(&d.id).is_online() && d.enabled)
.flat_map(|d| &d.relays)
.filter(|r| r.enabled)
.collect::<Vec<_>>();
ordered_relays.sort_by_key(|r| r.priority);
ordered_relays.reverse();
// TODO Commit changes loop {
let mut changed = false;
for relay in &ordered_relays {
if new_relays_state.get(&relay.id).unwrap().is_on() {
continue;
}
if !relay.enabled {
continue;
}
let real_relay_state = self.relays_state.get(&relay.id).unwrap();
if real_relay_state.is_off()
&& (real_relay_state.since + relay.minimal_downtime) as u64 > time_secs()
{
continue;
}
if relay.is_missing_dependencies(&new_relays_state) {
continue;
}
if relay.is_having_conflict(&new_relays_state, devices) {
continue;
}
// TODO : check consumption
log::info!("Turn on relay {}", relay.name);
new_relays_state.get_mut(&relay.id).unwrap().on = true;
changed = true;
}
if !changed {
break;
}
}
// Commit changes
for (id, new_state) in &new_relays_state { for (id, new_state) in &new_relays_state {
let curr_state = self.relay_state(*id); let curr_state = self.relay_state(*id);
if curr_state.on != new_state.on { if curr_state.on != new_state.on {
curr_state.on = new_state.on; curr_state.on = new_state.on;
curr_state.since = time_secs() as usize; curr_state.since = time_secs() as usize;
log::info!("Changing state of {id:?} to {}", new_state.on);
} }
log::info!("Changing state of {id:?}");
} }
self.print_summary(curr_consumption, devices); self.print_summary(curr_consumption, devices);

View File

@@ -36,7 +36,7 @@ export function EditDeviceRelaysDialog(p: {
p.relay ?? { p.relay ?? {
id: "", id: "",
name: "relay", name: "relay",
enabled: false, enabled: true,
priority: 1, priority: 1,
consumption: 500, consumption: 500,
minimal_downtime: 60 * 5, minimal_downtime: 60 * 5,

View File

@@ -1,5 +1,3 @@
import CheckIcon from "@mui/icons-material/Check";
import DeleteIcon from "@mui/icons-material/Delete";
import RefreshIcon from "@mui/icons-material/Refresh"; import RefreshIcon from "@mui/icons-material/Refresh";
import { import {
IconButton, IconButton,
@@ -13,15 +11,10 @@ import {
Tooltip, Tooltip,
} from "@mui/material"; } from "@mui/material";
import React from "react"; import React from "react";
import { Device, DeviceApi, DeviceRelay } from "../api/DeviceApi"; import { DeviceRelay } from "../api/DeviceApi";
import { useAlert } from "../hooks/context_providers/AlertDialogProvider"; import { RelayApi } from "../api/RelayApi";
import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider";
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
import { AsyncWidget } from "../widgets/AsyncWidget"; import { AsyncWidget } from "../widgets/AsyncWidget";
import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer"; import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer";
import { TimeWidget } from "../widgets/TimeWidget";
import { RelayApi } from "../api/RelayApi";
export function RelaysListRoute(): React.ReactElement { export function RelaysListRoute(): React.ReactElement {
const loadKey = React.useRef(1); const loadKey = React.useRef(1);
@@ -84,7 +77,13 @@ function RelaysList(p: {
sx={{ "&:last-child td, &:last-child th": { border: 0 } }} sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
> >
<TableCell>{row.name}</TableCell> <TableCell>{row.name}</TableCell>
<TableCell>{row.enabled ? "YES" : "NO"}</TableCell> <TableCell>
{row.enabled ? (
<span style={{ color: "green" }}>YES</span>
) : (
<span style={{ color: "red" }}>NO</span>
)}
</TableCell>
<TableCell>{row.priority}</TableCell> <TableCell>{row.priority}</TableCell>
<TableCell>{row.consumption}</TableCell> <TableCell>{row.consumption}</TableCell>
<TableCell>TODO</TableCell> <TableCell>TODO</TableCell>