Files
homeAutomation/energy-management/backend/src/homeassistant/devices.ts
2026-08-19 18:35:47 +02:00

88 lines
2.6 KiB
TypeScript

import type { DeviceConfig, DeviceStatus } from "../types.js";
import type { HomeAssistantClient } from "./client.js";
function normalizeHaValue(kind: DeviceConfig["kind"], state: string): number | null {
if (kind === "switch") {
if (state === "on") return 1;
if (state === "off") return 0;
return null;
}
const n = Number(state);
return Number.isFinite(n) ? n : null;
}
/** True when both directions are driven by custom endpoints rather than Home Assistant. */
function isExternallyControlled(device: DeviceConfig): boolean {
return device.enableUrl !== null && device.disableUrl !== null;
}
async function callEndpoint(url: string): Promise<void> {
const res = await fetch(url, { method: "POST" });
if (!res.ok) {
throw new Error(`custom endpoint request failed: POST ${url} -> ${res.status} ${await res.text()}`);
}
}
export async function getDeviceStatus(
ha: HomeAssistantClient,
device: DeviceConfig,
shed: boolean,
lastChangedAt: string | null,
): Promise<DeviceStatus> {
// Externally controlled devices have no Home Assistant entity to poll; state is
// whatever we last commanded via the custom endpoints.
if (isExternallyControlled(device)) {
return {
...device,
currentValue: shed ? (device.kind === "switch" ? 0 : device.minValue) : device.kind === "switch" ? 1 : device.maxValue,
shed,
lastChangedAt,
available: true,
};
}
try {
const haState = await ha.getState(device.entityId);
return {
...device,
currentValue: normalizeHaValue(device.kind, haState.state),
shed,
lastChangedAt,
available: haState.state !== "unavailable" && haState.state !== "unknown",
};
} catch {
return {
...device,
currentValue: null,
shed,
lastChangedAt,
available: false,
};
}
}
/** Sheds (reduces load of) a device: turns a switch off, or lowers a number to its floor. */
export async function shedDevice(ha: HomeAssistantClient, device: DeviceConfig): Promise<void> {
if (device.disableUrl) {
await callEndpoint(device.disableUrl);
return;
}
if (device.kind === "switch") {
await ha.setSwitch(device.entityId, false);
} else {
await ha.setNumber(device.entityId, device.minValue);
}
}
/** Restores a device to its normal operating value. */
export async function restoreDevice(ha: HomeAssistantClient, device: DeviceConfig): Promise<void> {
if (device.enableUrl) {
await callEndpoint(device.enableUrl);
return;
}
if (device.kind === "switch") {
await ha.setSwitch(device.entityId, true);
} else {
await ha.setNumber(device.entityId, device.maxValue);
}
}