2024-09-15 19:53:08 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2024-09-16 20:27:43 +00:00
|
|
|
use crate::app_config::AppConfig;
|
2024-09-15 19:53:08 +00:00
|
|
|
use prettytable::{row, Table};
|
|
|
|
|
2024-09-10 17:40:06 +00:00
|
|
|
use crate::constants;
|
2024-09-13 20:11:40 +00:00
|
|
|
use crate::devices::device::{Device, DeviceId, DeviceRelay, DeviceRelayID};
|
2024-09-10 17:40:06 +00:00
|
|
|
use crate::energy::consumption::EnergyConsumption;
|
|
|
|
use crate::utils::time_utils::time_secs;
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct DeviceState {
|
|
|
|
pub last_ping: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DeviceState {
|
|
|
|
pub fn record_ping(&mut self) {
|
|
|
|
self.last_ping = time_secs();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_online(&self) -> bool {
|
|
|
|
(time_secs() - self.last_ping) < constants::DEVICE_MAX_PING_TIME
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default, Clone)]
|
2024-09-10 17:55:51 +00:00
|
|
|
pub struct RelayState {
|
2024-09-10 17:40:06 +00:00
|
|
|
on: bool,
|
|
|
|
since: usize,
|
|
|
|
}
|
|
|
|
|
2024-09-15 19:53:08 +00:00
|
|
|
impl RelayState {
|
|
|
|
fn is_on(&self) -> bool {
|
|
|
|
self.on
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_off(&self) -> bool {
|
|
|
|
!self.on
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-13 20:11:40 +00:00
|
|
|
type RelaysState = HashMap<DeviceRelayID, RelayState>;
|
|
|
|
|
2024-09-10 17:40:06 +00:00
|
|
|
#[derive(Default)]
|
|
|
|
pub struct EnergyEngine {
|
|
|
|
devices_state: HashMap<DeviceId, DeviceState>,
|
2024-09-13 20:11:40 +00:00
|
|
|
relays_state: RelaysState,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DeviceRelay {
|
2024-09-15 19:53:08 +00:00
|
|
|
// Note : this function is not recursive
|
|
|
|
fn has_running_dependencies(&self, s: &RelaysState, devices: &[Device]) -> bool {
|
2024-09-13 20:11:40 +00:00
|
|
|
for d in devices {
|
|
|
|
for r in &d.relays {
|
2024-09-15 19:53:08 +00:00
|
|
|
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) {
|
2024-09-13 20:11:40 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
}
|
2024-09-10 17:40:06 +00:00
|
|
|
}
|
|
|
|
|
2024-09-16 20:27:43 +00:00
|
|
|
fn sum_relays_consumption(state: &RelaysState, devices: &[Device]) -> usize {
|
|
|
|
let mut consumption = 0;
|
|
|
|
|
|
|
|
for d in devices {
|
|
|
|
for r in &d.relays {
|
|
|
|
if matches!(state.get(&r.id).map(|r| r.on), Some(true)) {
|
|
|
|
consumption += r.consumption;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
consumption
|
|
|
|
}
|
|
|
|
|
2024-09-10 17:40:06 +00:00
|
|
|
impl EnergyEngine {
|
|
|
|
pub fn device_state(&mut self, dev_id: &DeviceId) -> &mut DeviceState {
|
2024-09-10 17:55:51 +00:00
|
|
|
self.devices_state.entry(dev_id.clone()).or_default();
|
2024-09-10 17:40:06 +00:00
|
|
|
self.devices_state.get_mut(dev_id).unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn relay_state(&mut self, relay_id: DeviceRelayID) -> &mut RelayState {
|
2024-09-10 17:55:51 +00:00
|
|
|
self.relays_state.entry(relay_id).or_default();
|
2024-09-10 17:40:06 +00:00
|
|
|
self.relays_state.get_mut(&relay_id).unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn print_summary(&mut self, curr_consumption: EnergyConsumption, devices: &[Device]) {
|
|
|
|
log::info!("Current consumption: {curr_consumption}");
|
|
|
|
|
|
|
|
let mut table = Table::new();
|
2024-09-16 20:27:43 +00:00
|
|
|
table.add_row(row![
|
|
|
|
"Device",
|
|
|
|
"Relay",
|
|
|
|
"Consumption",
|
|
|
|
"Min downtime / uptime",
|
|
|
|
"On",
|
|
|
|
"Since",
|
|
|
|
"Online",
|
|
|
|
"Enabled device / relay"
|
|
|
|
]);
|
2024-09-10 17:40:06 +00:00
|
|
|
for d in devices {
|
2024-09-16 20:27:43 +00:00
|
|
|
let dev_online = self.device_state(&d.id).is_online();
|
2024-09-10 17:40:06 +00:00
|
|
|
for r in &d.relays {
|
|
|
|
let status = self.relay_state(r.id);
|
2024-09-15 19:53:08 +00:00
|
|
|
table.add_row(row![
|
|
|
|
d.name,
|
|
|
|
r.name,
|
2024-09-16 20:27:43 +00:00
|
|
|
r.consumption,
|
|
|
|
format!("{} / {}", r.minimal_downtime, r.minimal_uptime),
|
2024-09-15 19:53:08 +00:00
|
|
|
status.is_on().to_string(),
|
2024-09-16 20:27:43 +00:00
|
|
|
status.since,
|
|
|
|
match dev_online {
|
|
|
|
true => "Online",
|
|
|
|
false => "Offline",
|
|
|
|
},
|
|
|
|
format!(
|
|
|
|
"{} / {}",
|
|
|
|
match d.enabled {
|
|
|
|
true => "Enabled",
|
|
|
|
false => "Disabled",
|
|
|
|
},
|
|
|
|
match r.enabled {
|
|
|
|
true => "Enabled",
|
|
|
|
false => "Disabled",
|
|
|
|
}
|
|
|
|
)
|
2024-09-15 19:53:08 +00:00
|
|
|
]);
|
2024-09-10 17:40:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
table.printstd();
|
|
|
|
}
|
|
|
|
|
2024-09-16 20:27:43 +00:00
|
|
|
pub fn estimated_consumption_without_relays(
|
|
|
|
&self,
|
|
|
|
curr_consumption: EnergyConsumption,
|
|
|
|
devices: &[Device],
|
|
|
|
) -> EnergyConsumption {
|
|
|
|
curr_consumption - sum_relays_consumption(&self.relays_state, devices) as i32
|
|
|
|
}
|
|
|
|
|
2024-09-10 17:40:06 +00:00
|
|
|
pub fn refresh(&mut self, curr_consumption: EnergyConsumption, devices: &[Device]) {
|
2024-09-16 20:27:43 +00:00
|
|
|
let base_production = self.estimated_consumption_without_relays(curr_consumption, devices);
|
|
|
|
log::info!("Estimated base production: {base_production}");
|
|
|
|
|
2024-09-12 19:45:58 +00:00
|
|
|
// Force creation of missing relays state
|
|
|
|
for d in devices {
|
|
|
|
for r in &d.relays {
|
|
|
|
// Requesting relay state is enough to trigger relay creation
|
|
|
|
self.relay_state(r.id);
|
|
|
|
}
|
|
|
|
}
|
2024-09-10 17:55:51 +00:00
|
|
|
|
2024-09-12 19:45:58 +00:00
|
|
|
let mut new_relays_state = self.relays_state.clone();
|
2024-09-10 17:40:06 +00:00
|
|
|
|
2024-09-12 19:45:58 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-15 19:53:08 +00:00
|
|
|
// Forcefully turn off disabled relays
|
|
|
|
for d in devices {
|
|
|
|
for r in &d.relays {
|
2024-09-15 20:01:06 +00:00
|
|
|
if !r.enabled || !d.enabled {
|
2024-09-15 19:53:08 +00:00
|
|
|
new_relays_state.get_mut(&r.id).unwrap().on = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-12 19:45:58 +00:00
|
|
|
// Forcefully turn off relays with missing dependency
|
|
|
|
loop {
|
|
|
|
let mut changed = false;
|
|
|
|
|
|
|
|
for d in devices {
|
2024-09-15 19:53:08 +00:00
|
|
|
for r in &d.relays {
|
|
|
|
if new_relays_state.get(&r.id).unwrap().is_off() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if any dependency of relay is off
|
|
|
|
if r.is_missing_dependencies(&new_relays_state) {
|
|
|
|
new_relays_state.get_mut(&r.id).unwrap().on = false;
|
|
|
|
changed = true;
|
2024-09-12 19:45:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-09-10 17:40:06 +00:00
|
|
|
|
2024-09-12 19:45:58 +00:00
|
|
|
if !changed {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2024-09-10 17:40:06 +00:00
|
|
|
|
2024-09-13 20:11:40 +00:00
|
|
|
// Virtually turn off all relays that can be stopped
|
|
|
|
loop {
|
|
|
|
let mut changed = false;
|
|
|
|
|
|
|
|
for d in devices {
|
|
|
|
for r in &d.relays {
|
|
|
|
let state = new_relays_state.get(&r.id).unwrap();
|
2024-09-15 19:53:08 +00:00
|
|
|
if state.is_off() {
|
2024-09-13 20:11:40 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if minimal runtime has not been reached
|
|
|
|
if (state.since + r.minimal_uptime) as i64 > time_secs() as i64 {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that no relay that depends on this relay are turned on
|
2024-09-15 19:53:08 +00:00
|
|
|
if r.has_running_dependencies(&new_relays_state, devices) {
|
2024-09-13 20:11:40 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
new_relays_state.get_mut(&r.id).unwrap().on = false;
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !changed {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2024-09-10 17:40:06 +00:00
|
|
|
|
2024-09-15 19:53:08 +00:00
|
|
|
// TODO Turn on relays with running constraints (only ENABLED)
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2024-09-10 17:40:06 +00:00
|
|
|
|
2024-09-15 19:53:08 +00:00
|
|
|
if relay.is_missing_dependencies(&new_relays_state) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if relay.is_having_conflict(&new_relays_state, devices) {
|
|
|
|
continue;
|
|
|
|
}
|
2024-09-10 17:55:51 +00:00
|
|
|
|
2024-09-16 20:27:43 +00:00
|
|
|
let new_consumption = base_production
|
|
|
|
+ sum_relays_consumption(&new_relays_state, devices) as EnergyConsumption;
|
|
|
|
|
|
|
|
if new_consumption + relay.consumption as i32 > AppConfig::get().production_margin {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-09-15 19:53:08 +00:00
|
|
|
log::info!("Turn on relay {}", relay.name);
|
|
|
|
new_relays_state.get_mut(&relay.id).unwrap().on = true;
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if !changed {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2024-09-10 17:55:51 +00:00
|
|
|
|
2024-09-15 19:53:08 +00:00
|
|
|
// Commit changes
|
2024-09-10 17:55:51 +00:00
|
|
|
for (id, new_state) in &new_relays_state {
|
|
|
|
let curr_state = self.relay_state(*id);
|
|
|
|
if curr_state.on != new_state.on {
|
|
|
|
curr_state.on = new_state.on;
|
|
|
|
curr_state.since = time_secs() as usize;
|
2024-09-15 19:53:08 +00:00
|
|
|
log::info!("Changing state of {id:?} to {}", new_state.on);
|
2024-09-10 17:55:51 +00:00
|
|
|
}
|
|
|
|
}
|
2024-09-10 17:40:06 +00:00
|
|
|
|
2024-09-10 17:55:51 +00:00
|
|
|
self.print_summary(curr_consumption, devices);
|
2024-09-10 17:40:06 +00:00
|
|
|
}
|
|
|
|
}
|