Compare commits
2 Commits
78ace02d15
...
e0f0067e89
| Author | SHA1 | Date | |
|---|---|---|---|
| e0f0067e89 | |||
| 3c2fa18d9a |
@@ -360,3 +360,34 @@ impl Handler<GetDevicesState> for EnergyActor {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
pub struct ResRelayState {
|
||||||
|
pub id: DeviceRelayID,
|
||||||
|
on: bool,
|
||||||
|
r#for: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the state of all relays
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "Vec<ResRelayState>")]
|
||||||
|
pub struct GetAllRelaysState;
|
||||||
|
|
||||||
|
impl Handler<GetAllRelaysState> for EnergyActor {
|
||||||
|
type Result = Vec<ResRelayState>;
|
||||||
|
|
||||||
|
fn handle(&mut self, _msg: GetAllRelaysState, _ctx: &mut Context<Self>) -> Self::Result {
|
||||||
|
let mut list = vec![];
|
||||||
|
|
||||||
|
for d in &self.devices.relays_list() {
|
||||||
|
let state = self.engine.relay_state(d.id);
|
||||||
|
list.push(ResRelayState {
|
||||||
|
id: d.id,
|
||||||
|
on: state.is_on(),
|
||||||
|
r#for: state.state_for(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ impl RelayState {
|
|||||||
fn is_off(&self) -> bool {
|
fn is_off(&self) -> bool {
|
||||||
!self.on
|
!self.on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn state_for(&self) -> usize {
|
||||||
|
(time_secs() - self.since as u64) as usize
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RelaysState = HashMap<DeviceRelayID, RelayState>;
|
type RelaysState = HashMap<DeviceRelayID, RelayState>;
|
||||||
|
|||||||
@@ -185,6 +185,14 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
|
|||||||
"/web_api/relay/{id}",
|
"/web_api/relay/{id}",
|
||||||
web::delete().to(relays_controller::delete),
|
web::delete().to(relays_controller::delete),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/web_api/relays/status",
|
||||||
|
web::get().to(relays_controller::status_all),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/web_api/relay/{id}/status",
|
||||||
|
web::get().to(relays_controller::status_single),
|
||||||
|
)
|
||||||
// Devices API
|
// Devices API
|
||||||
.route(
|
.route(
|
||||||
"/devices_api/utils/time",
|
"/devices_api/utils/time",
|
||||||
|
|||||||
@@ -93,3 +93,20 @@ pub async fn delete(actor: WebEnergyActor, path: web::Path<RelayIDInPath>) -> Ht
|
|||||||
|
|
||||||
Ok(HttpResponse::Accepted().finish())
|
Ok(HttpResponse::Accepted().finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the status of all relays
|
||||||
|
pub async fn status_all(actor: WebEnergyActor) -> HttpResult {
|
||||||
|
let list = actor.send(energy_actor::GetAllRelaysState).await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(list))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the state of a single relay
|
||||||
|
pub async fn status_single(actor: WebEnergyActor, path: web::Path<RelayIDInPath>) -> HttpResult {
|
||||||
|
let list = actor.send(energy_actor::GetAllRelaysState).await?;
|
||||||
|
let Some(state) = list.into_iter().find(|r| r.id == path.id) else {
|
||||||
|
return Ok(HttpResponse::NotFound().json("Relay not found!"));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(state))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
import { APIClient } from "./ApiClient";
|
import { APIClient } from "./ApiClient";
|
||||||
import { Device, DeviceRelay } from "./DeviceApi";
|
import { Device, DeviceRelay } from "./DeviceApi";
|
||||||
|
|
||||||
|
export interface RelayStatus {
|
||||||
|
id: string;
|
||||||
|
on: boolean;
|
||||||
|
for: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RelaysStatus = Map<string, RelayStatus>;
|
||||||
|
|
||||||
export class RelayApi {
|
export class RelayApi {
|
||||||
/**
|
/**
|
||||||
* Get the full list of relays
|
* Get the full list of relays
|
||||||
@@ -49,4 +57,34 @@ export class RelayApi {
|
|||||||
uri: `/relay/${relay.id}`,
|
uri: `/relay/${relay.id}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the status of all relays
|
||||||
|
*/
|
||||||
|
static async GetRelaysStatus(): Promise<RelaysStatus> {
|
||||||
|
const data: any[] = (
|
||||||
|
await APIClient.exec({
|
||||||
|
method: "GET",
|
||||||
|
uri: `/relays/status`,
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
|
||||||
|
const map = new Map();
|
||||||
|
for (let r of data) {
|
||||||
|
map.set(r.id, r);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the status of a single relay
|
||||||
|
*/
|
||||||
|
static async SingleStatus(relay: DeviceRelay): Promise<RelayStatus> {
|
||||||
|
return (
|
||||||
|
await APIClient.exec({
|
||||||
|
method: "GET",
|
||||||
|
uri: `/relay/${relay.id}/state`,
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,12 @@ import { EditDeviceRelaysDialog } from "../../dialogs/EditDeviceRelaysDialog";
|
|||||||
import { DeviceRouteCard } from "./DeviceRouteCard";
|
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 } from "../../api/RelayApi";
|
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 { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
|
||||||
|
import { AsyncWidget } from "../../widgets/AsyncWidget";
|
||||||
|
import { TimeWidget } from "../../widgets/TimeWidget";
|
||||||
|
import { BoolText } from "../../widgets/BoolText";
|
||||||
|
|
||||||
export function DeviceRelays(p: {
|
export function DeviceRelays(p: {
|
||||||
device: Device;
|
device: Device;
|
||||||
@@ -115,10 +118,35 @@ export function DeviceRelays(p: {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ListItemText primary={r.name} secondary={"TODO: status"} />
|
<ListItemText
|
||||||
|
primary={r.name}
|
||||||
|
secondary={<RelayEntryStatus relay={r} />}
|
||||||
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
))}
|
))}
|
||||||
</DeviceRouteCard>
|
</DeviceRouteCard>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function RelayEntryStatus(p: { relay: DeviceRelay }): React.ReactElement {
|
||||||
|
const [state, setState] = React.useState<RelayStatus | undefined>();
|
||||||
|
|
||||||
|
const load = async () => {
|
||||||
|
setState(await RelayApi.SingleStatus(p.relay));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AsyncWidget
|
||||||
|
loadKey={p.relay.id}
|
||||||
|
load={load}
|
||||||
|
errMsg="Failed to load relay status!"
|
||||||
|
build={() => (
|
||||||
|
<>
|
||||||
|
<BoolText val={state!.on} positive="ON" negative="OFF" /> for{" "}
|
||||||
|
<TimeWidget diff time={state!.for} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,17 +12,21 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { DeviceRelay } from "../api/DeviceApi";
|
import { DeviceRelay } from "../api/DeviceApi";
|
||||||
import { RelayApi } from "../api/RelayApi";
|
import { RelayApi, RelaysStatus } from "../api/RelayApi";
|
||||||
import { AsyncWidget } from "../widgets/AsyncWidget";
|
import { AsyncWidget } from "../widgets/AsyncWidget";
|
||||||
|
import { BoolText } from "../widgets/BoolText";
|
||||||
import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer";
|
import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer";
|
||||||
|
import { TimeWidget } from "../widgets/TimeWidget";
|
||||||
|
|
||||||
export function RelaysListRoute(): React.ReactElement {
|
export function RelaysListRoute(): React.ReactElement {
|
||||||
const loadKey = React.useRef(1);
|
const loadKey = React.useRef(1);
|
||||||
|
|
||||||
const [list, setList] = React.useState<DeviceRelay[] | undefined>();
|
const [list, setList] = React.useState<DeviceRelay[] | undefined>();
|
||||||
|
const [status, setStatus] = React.useState<RelaysStatus | undefined>();
|
||||||
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
setList(await RelayApi.GetList());
|
setList(await RelayApi.GetList());
|
||||||
|
setStatus(await RelayApi.GetRelaysStatus());
|
||||||
|
|
||||||
list?.sort((a, b) => b.priority - a.priority);
|
list?.sort((a, b) => b.priority - a.priority);
|
||||||
};
|
};
|
||||||
@@ -48,7 +52,9 @@ export function RelaysListRoute(): React.ReactElement {
|
|||||||
ready={!!list}
|
ready={!!list}
|
||||||
errMsg="Failed to load the list of relays!"
|
errMsg="Failed to load the list of relays!"
|
||||||
load={load}
|
load={load}
|
||||||
build={() => <RelaysList onReload={reload} list={list!} />}
|
build={() => (
|
||||||
|
<RelaysList onReload={reload} list={list!} status={status!} />
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</SolarEnergyRouteContainer>
|
</SolarEnergyRouteContainer>
|
||||||
);
|
);
|
||||||
@@ -56,6 +62,7 @@ export function RelaysListRoute(): React.ReactElement {
|
|||||||
|
|
||||||
function RelaysList(p: {
|
function RelaysList(p: {
|
||||||
list: DeviceRelay[];
|
list: DeviceRelay[];
|
||||||
|
status: RelaysStatus;
|
||||||
onReload: () => void;
|
onReload: () => void;
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
@@ -78,15 +85,18 @@ function RelaysList(p: {
|
|||||||
>
|
>
|
||||||
<TableCell>{row.name}</TableCell>
|
<TableCell>{row.name}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{row.enabled ? (
|
<BoolText val={row.enabled} positive="YES" negative="NO" />
|
||||||
<span style={{ color: "green" }}>YES</span>
|
|
||||||
) : (
|
|
||||||
<span style={{ color: "red" }}>NO</span>
|
|
||||||
)}
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{row.priority}</TableCell>
|
<TableCell>{row.priority}</TableCell>
|
||||||
<TableCell>{row.consumption}</TableCell>
|
<TableCell>{row.consumption}</TableCell>
|
||||||
<TableCell>TODO</TableCell>
|
<TableCell>
|
||||||
|
<BoolText
|
||||||
|
val={p.status.get(row.id)!.on}
|
||||||
|
positive="ON"
|
||||||
|
negative="OFF"
|
||||||
|
/>{" "}
|
||||||
|
for <TimeWidget diff time={p.status.get(row.id)!.for} />
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|||||||
11
central_frontend/src/widgets/BoolText.tsx
Normal file
11
central_frontend/src/widgets/BoolText.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export function BoolText(p: {
|
||||||
|
val: boolean;
|
||||||
|
positive: string;
|
||||||
|
negative: string;
|
||||||
|
}): React.ReactElement {
|
||||||
|
return p.val ? (
|
||||||
|
<span style={{ color: "green" }}>{p.positive}</span>
|
||||||
|
) : (
|
||||||
|
<span style={{ color: "red" }}>{p.negative}</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user