Add support for legacy relays API
This commit is contained in:
		@@ -132,12 +132,14 @@ impl ConsumptionHistoryFile {
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use crate::app_config::ConsumptionHistoryType;
 | 
			
		||||
    use crate::energy::consumption::EnergyConsumption;
 | 
			
		||||
    use crate::energy::consumption_history_file::{ConsumptionHistoryFile, TIME_INTERVAL};
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_consumption_history() {
 | 
			
		||||
        let mut history = ConsumptionHistoryFile::new_memory(0);
 | 
			
		||||
        let mut history =
 | 
			
		||||
            ConsumptionHistoryFile::new_memory(0, ConsumptionHistoryType::GridConsumption);
 | 
			
		||||
 | 
			
		||||
        for i in 0..50 {
 | 
			
		||||
            assert_eq!(
 | 
			
		||||
 
 | 
			
		||||
@@ -399,8 +399,8 @@ impl Handler<GetDevicesState> for EnergyActor {
 | 
			
		||||
#[derive(serde::Serialize)]
 | 
			
		||||
pub struct ResRelayState {
 | 
			
		||||
    pub id: DeviceRelayID,
 | 
			
		||||
    on: bool,
 | 
			
		||||
    r#for: usize,
 | 
			
		||||
    pub on: bool,
 | 
			
		||||
    pub r#for: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get the state of all relays
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ use crate::devices::device::{Device, DeviceId, DeviceRelay, DeviceRelayID};
 | 
			
		||||
use crate::energy::consumption::EnergyConsumption;
 | 
			
		||||
use crate::energy::relay_state_history;
 | 
			
		||||
use crate::energy::relay_state_history::RelayStateHistory;
 | 
			
		||||
use crate::utils::time_utils::{curr_hour, time_secs, time_start_of_day};
 | 
			
		||||
use crate::utils::time_utils::{curr_hour, time_secs};
 | 
			
		||||
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub struct DeviceState {
 | 
			
		||||
@@ -283,12 +283,7 @@ impl EnergyEngine {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let time_start_day = time_start_of_day().unwrap_or(1726696800);
 | 
			
		||||
                let start_time = time_start_day + constraints.reset_time as u64;
 | 
			
		||||
                let end_time = time_start_day + 3600 * 24 + constraints.reset_time as u64;
 | 
			
		||||
                let total_runtime =
 | 
			
		||||
                    relay_state_history::relay_total_runtime(r.id, start_time, end_time)
 | 
			
		||||
                        .unwrap_or(3600 * 24);
 | 
			
		||||
                let total_runtime = relay_state_history::relay_total_runtime_adjusted(r);
 | 
			
		||||
 | 
			
		||||
                if total_runtime > constraints.min_runtime {
 | 
			
		||||
                    continue;
 | 
			
		||||
@@ -454,7 +449,7 @@ mod test {
 | 
			
		||||
    fn run_test(name: &str, conf: &str) {
 | 
			
		||||
        let (devices, mut energy_engine, consumption, states) = parse_test_config(conf);
 | 
			
		||||
 | 
			
		||||
        energy_engine.refresh(consumption, &devices);
 | 
			
		||||
        energy_engine.refresh(consumption, &devices.iter().collect::<Vec<_>>());
 | 
			
		||||
 | 
			
		||||
        for (device_s, device) in states.iter().zip(&devices) {
 | 
			
		||||
            for (relay_s, relay) in device_s.relays.iter().zip(&device.relays) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
use crate::app_config::AppConfig;
 | 
			
		||||
use crate::devices::device::DeviceRelayID;
 | 
			
		||||
use crate::devices::device::{DeviceRelay, DeviceRelayID};
 | 
			
		||||
use crate::utils::files_utils;
 | 
			
		||||
use crate::utils::time_utils::day_number;
 | 
			
		||||
use crate::utils::time_utils::{day_number, time_start_of_day};
 | 
			
		||||
 | 
			
		||||
const TIME_INTERVAL: usize = 30;
 | 
			
		||||
 | 
			
		||||
@@ -119,6 +119,20 @@ pub fn relay_total_runtime(device_id: DeviceRelayID, from: u64, to: u64) -> anyh
 | 
			
		||||
    Ok(total)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get the total runtime of a relay taking account of daily reset time
 | 
			
		||||
pub fn relay_total_runtime_adjusted(relay: &DeviceRelay) -> usize {
 | 
			
		||||
    let reset_time = relay
 | 
			
		||||
        .daily_runtime
 | 
			
		||||
        .as_ref()
 | 
			
		||||
        .map(|r| r.reset_time)
 | 
			
		||||
        .unwrap_or(0);
 | 
			
		||||
 | 
			
		||||
    let time_start_day = time_start_of_day().unwrap_or(1726696800);
 | 
			
		||||
    let start_time = time_start_day + 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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use crate::devices::device::DeviceRelayID;
 | 
			
		||||
 
 | 
			
		||||
@@ -44,8 +44,8 @@ async fn main() -> std::io::Result<()> {
 | 
			
		||||
        .expect("Failed to initialize energy actor!")
 | 
			
		||||
        .start();
 | 
			
		||||
 | 
			
		||||
    let s1 = servers::secure_server(actor);
 | 
			
		||||
    let s2 = servers::unsecure_server();
 | 
			
		||||
    let s1 = servers::secure_server(actor.clone());
 | 
			
		||||
    let s2 = servers::unsecure_server(actor);
 | 
			
		||||
    future::try_join(s1, s2)
 | 
			
		||||
        .await
 | 
			
		||||
        .expect("Failed to start servers!");
 | 
			
		||||
 
 | 
			
		||||
@@ -22,14 +22,15 @@ use openssl::ssl::{SslAcceptor, SslMethod};
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
 | 
			
		||||
/// Start unsecure (HTTP) server
 | 
			
		||||
pub async fn unsecure_server() -> anyhow::Result<()> {
 | 
			
		||||
pub async fn unsecure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()> {
 | 
			
		||||
    log::info!(
 | 
			
		||||
        "Unsecure server starting to listen on {} for {}",
 | 
			
		||||
        AppConfig::get().unsecure_listen_address,
 | 
			
		||||
        AppConfig::get().unsecure_origin()
 | 
			
		||||
    );
 | 
			
		||||
    HttpServer::new(|| {
 | 
			
		||||
    HttpServer::new(move || {
 | 
			
		||||
        App::new()
 | 
			
		||||
            .app_data(web::Data::new(energy_actor.clone()))
 | 
			
		||||
            .wrap(Logger::default())
 | 
			
		||||
            .route(
 | 
			
		||||
                "/",
 | 
			
		||||
@@ -43,6 +44,10 @@ pub async fn unsecure_server() -> anyhow::Result<()> {
 | 
			
		||||
                "/pki/{file}",
 | 
			
		||||
                web::get().to(unsecure_pki_controller::serve_pki_file),
 | 
			
		||||
            )
 | 
			
		||||
            .route(
 | 
			
		||||
                "/relay/{id}/legacy_state",
 | 
			
		||||
                web::get().to(unsecure_relay_controller::legacy_state),
 | 
			
		||||
            )
 | 
			
		||||
    })
 | 
			
		||||
    .bind(&AppConfig::get().unsecure_listen_address)?
 | 
			
		||||
    .run()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,3 @@
 | 
			
		||||
pub mod unsecure_pki_controller;
 | 
			
		||||
pub mod unsecure_relay_controller;
 | 
			
		||||
pub mod unsecure_server_controller;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,60 @@
 | 
			
		||||
use crate::devices::device::DeviceRelayID;
 | 
			
		||||
use crate::energy::{energy_actor, relay_state_history};
 | 
			
		||||
use crate::server::custom_error::HttpResult;
 | 
			
		||||
use crate::server::WebEnergyActor;
 | 
			
		||||
use actix_web::{web, HttpResponse};
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize)]
 | 
			
		||||
pub struct LegacyStateRelay {
 | 
			
		||||
    id: DeviceRelayID,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Legacy relay state
 | 
			
		||||
#[derive(serde::Serialize)]
 | 
			
		||||
pub struct LegacyState {
 | 
			
		||||
    /// Indicates if relay is on or off
 | 
			
		||||
    is_on: bool,
 | 
			
		||||
    /// Relay name
 | 
			
		||||
    name: String,
 | 
			
		||||
    /// Duration since last change of state
 | 
			
		||||
    r#for: usize,
 | 
			
		||||
    /// Current grid consumption
 | 
			
		||||
    prod: i32,
 | 
			
		||||
    /// Total uptime since last reset
 | 
			
		||||
    total_uptime: usize,
 | 
			
		||||
    /// Required uptime during a day
 | 
			
		||||
    ///
 | 
			
		||||
    /// Will be 0 if there is no daily requirements
 | 
			
		||||
    required_uptime: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get the state of a relay, adapted for old system components
 | 
			
		||||
pub async fn legacy_state(
 | 
			
		||||
    energy_actor: WebEnergyActor,
 | 
			
		||||
    path: web::Path<LegacyStateRelay>,
 | 
			
		||||
) -> HttpResult {
 | 
			
		||||
    let Some(relay) = energy_actor
 | 
			
		||||
        .send(energy_actor::GetSingleRelay(path.id))
 | 
			
		||||
        .await?
 | 
			
		||||
    else {
 | 
			
		||||
        return Ok(HttpResponse::NotFound().body("Relay not found!"));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let all_states = energy_actor.send(energy_actor::GetAllRelaysState).await?;
 | 
			
		||||
    let Some(state) = all_states.into_iter().find(|r| r.id == path.id) else {
 | 
			
		||||
        return Ok(HttpResponse::InternalServerError().body("Relay status unavailable!"));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let production = energy_actor.send(energy_actor::GetCurrConsumption).await?;
 | 
			
		||||
 | 
			
		||||
    let total_uptime = relay_state_history::relay_total_runtime_adjusted(&relay);
 | 
			
		||||
 | 
			
		||||
    Ok(HttpResponse::Ok().json(LegacyState {
 | 
			
		||||
        name: relay.name,
 | 
			
		||||
        is_on: state.on,
 | 
			
		||||
        r#for: state.r#for.min(3600 * 24 * 7),
 | 
			
		||||
        prod: production,
 | 
			
		||||
        total_uptime,
 | 
			
		||||
        required_uptime: relay.daily_runtime.map(|r| r.min_runtime).unwrap_or(0),
 | 
			
		||||
    }))
 | 
			
		||||
}
 | 
			
		||||
@@ -12,6 +12,7 @@ pub async fn secure_home() -> HttpResponse {
 | 
			
		||||
struct ServerConfig {
 | 
			
		||||
    auth_disabled: bool,
 | 
			
		||||
    constraints: StaticConstraints,
 | 
			
		||||
    unsecure_origin: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for ServerConfig {
 | 
			
		||||
@@ -19,6 +20,7 @@ impl Default for ServerConfig {
 | 
			
		||||
        Self {
 | 
			
		||||
            auth_disabled: AppConfig::get().unsecure_disable_login,
 | 
			
		||||
            constraints: Default::default(),
 | 
			
		||||
            unsecure_origin: AppConfig::get().unsecure_origin(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import { APIClient } from "./ApiClient";
 | 
			
		||||
export interface ServerConfig {
 | 
			
		||||
  auth_disabled: boolean;
 | 
			
		||||
  constraints: ServerConstraint;
 | 
			
		||||
  unsecure_origin: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ServerConstraint {
 | 
			
		||||
 
 | 
			
		||||
@@ -129,7 +129,9 @@ export function DeviceRelays(p: {
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function RelayEntryStatus(p: { relay: DeviceRelay }): React.ReactElement {
 | 
			
		||||
function RelayEntryStatus(
 | 
			
		||||
  p: Readonly<{ relay: DeviceRelay }>
 | 
			
		||||
): React.ReactElement {
 | 
			
		||||
  const [state, setState] = React.useState<RelayStatus | undefined>();
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import LinkIcon from "@mui/icons-material/Link";
 | 
			
		||||
import RefreshIcon from "@mui/icons-material/Refresh";
 | 
			
		||||
import {
 | 
			
		||||
  IconButton,
 | 
			
		||||
@@ -19,6 +20,8 @@ import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
			
		||||
import { BoolText } from "../widgets/BoolText";
 | 
			
		||||
import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer";
 | 
			
		||||
import { TimeWidget } from "../widgets/TimeWidget";
 | 
			
		||||
import { CopyToClipboard } from "../widgets/CopyToClipboard";
 | 
			
		||||
import { ServerApi } from "../api/ServerApi";
 | 
			
		||||
 | 
			
		||||
export function RelaysListRoute(p: {
 | 
			
		||||
  homeWidget?: boolean;
 | 
			
		||||
@@ -101,6 +104,7 @@ function RelaysList(p: {
 | 
			
		||||
            <TableCell>Priority</TableCell>
 | 
			
		||||
            <TableCell>Consumption</TableCell>
 | 
			
		||||
            <TableCell>Status</TableCell>
 | 
			
		||||
            <TableCell></TableCell>
 | 
			
		||||
          </TableRow>
 | 
			
		||||
        </TableHead>
 | 
			
		||||
        <TableBody>
 | 
			
		||||
@@ -125,6 +129,20 @@ function RelaysList(p: {
 | 
			
		||||
                />{" "}
 | 
			
		||||
                for <TimeWidget diff time={p.status.get(row.id)!.for} />
 | 
			
		||||
              </TableCell>
 | 
			
		||||
              <TableCell>
 | 
			
		||||
                <Tooltip title="Copy legacy api status">
 | 
			
		||||
                  <CopyToClipboard
 | 
			
		||||
                    content={
 | 
			
		||||
                      ServerApi.Config.unsecure_origin +
 | 
			
		||||
                      `/relay/${row.id}/legacy_state`
 | 
			
		||||
                    }
 | 
			
		||||
                  >
 | 
			
		||||
                    <IconButton>
 | 
			
		||||
                      <LinkIcon />
 | 
			
		||||
                    </IconButton>
 | 
			
		||||
                  </CopyToClipboard>
 | 
			
		||||
                </Tooltip>
 | 
			
		||||
              </TableCell>
 | 
			
		||||
            </TableRow>
 | 
			
		||||
          ))}
 | 
			
		||||
        </TableBody>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								central_frontend/src/widgets/CopyToClipboard.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								central_frontend/src/widgets/CopyToClipboard.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
import { ButtonBase } from "@mui/material";
 | 
			
		||||
import { PropsWithChildren } from "react";
 | 
			
		||||
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
 | 
			
		||||
 | 
			
		||||
export function CopyToClipboard(
 | 
			
		||||
  p: PropsWithChildren<{ content: string }>
 | 
			
		||||
): React.ReactElement {
 | 
			
		||||
  const snackbar = useSnackbar();
 | 
			
		||||
 | 
			
		||||
  const copy = () => {
 | 
			
		||||
    navigator.clipboard.writeText(p.content);
 | 
			
		||||
    snackbar(`${p.content} copied to clipboard.`);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <ButtonBase
 | 
			
		||||
      onClick={copy}
 | 
			
		||||
      style={{
 | 
			
		||||
        display: "inline-block",
 | 
			
		||||
        alignItems: "unset",
 | 
			
		||||
        textAlign: "unset",
 | 
			
		||||
        position: "relative",
 | 
			
		||||
        padding: "0px",
 | 
			
		||||
      }}
 | 
			
		||||
      disableRipple
 | 
			
		||||
    >
 | 
			
		||||
      {p.children}
 | 
			
		||||
    </ButtonBase>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user