95 lines
2.7 KiB
TypeScript
95 lines
2.7 KiB
TypeScript
import { SolarEdgePortalClient } from "@solar-dash/solaredgeapi";
|
|
|
|
import { OptimizerDataCache } from "./cache";
|
|
import { collectOptimizerData } from "./collector";
|
|
import {
|
|
DEFAULT_PORT,
|
|
readAppOptions,
|
|
resolveHomeAssistantTimeZone,
|
|
} from "./config";
|
|
import { registerHomeAssistantDiscovery } from "./discovery";
|
|
import { createRequestHandler } from "./http";
|
|
|
|
export async function main(): Promise<void> {
|
|
const options = await readAppOptions();
|
|
const timeZone = await resolveHomeAssistantTimeZone();
|
|
const portalClient = new SolarEdgePortalClient({
|
|
username: options.username,
|
|
password: options.password,
|
|
siteId: options.siteId,
|
|
});
|
|
const cache = new OptimizerDataCache({
|
|
pollIntervalMs: options.pollIntervalMinutes * 60_000,
|
|
loadSnapshot: async () => {
|
|
log("info", `Refreshing optimizer data for site ${options.siteId}`);
|
|
const snapshot = await collectOptimizerData({
|
|
reader: portalClient,
|
|
siteId: options.siteId,
|
|
timeZone,
|
|
});
|
|
log(
|
|
snapshot.failedOptimizerCount === 0 ? "info" : "warning",
|
|
`Refresh finished: ${snapshot.successfulOptimizerCount}/${snapshot.optimizerCount} optimizer energy values`,
|
|
);
|
|
return snapshot;
|
|
},
|
|
});
|
|
const port = readPort(process.env.PORT);
|
|
const server = Bun.serve({
|
|
hostname: "0.0.0.0",
|
|
port,
|
|
fetch: createRequestHandler(cache),
|
|
});
|
|
|
|
const shutDown = (): void => {
|
|
log("info", "Stopping SolarEdge Optimizer Data App");
|
|
cache.stop();
|
|
void server.stop(true);
|
|
};
|
|
process.once("SIGTERM", shutDown);
|
|
process.once("SIGINT", shutDown);
|
|
|
|
log(
|
|
"info",
|
|
`Listening on port ${port}; refresh interval ${options.pollIntervalMinutes} minutes; time zone ${timeZone}`,
|
|
);
|
|
try {
|
|
await registerHomeAssistantDiscovery({ port });
|
|
log("info", "Registered optimizer API discovery with Home Assistant");
|
|
} catch (error) {
|
|
log(
|
|
"warning",
|
|
`Home Assistant discovery registration failed: ${formatStartupError(error)}`,
|
|
);
|
|
}
|
|
cache.start();
|
|
}
|
|
|
|
function readPort(value: string | undefined): number {
|
|
const port = value === undefined ? DEFAULT_PORT : Number(value);
|
|
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
|
|
throw new Error("PORT must be an integer from 1 to 65535");
|
|
}
|
|
return port;
|
|
}
|
|
|
|
function log(level: "info" | "warning" | "error", message: string): void {
|
|
console.log(`${new Date().toISOString()} [${level}] ${message}`);
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
try {
|
|
await main();
|
|
} catch (error) {
|
|
log("error", formatStartupError(error));
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
function formatStartupError(error: unknown): string {
|
|
if (error instanceof Error) {
|
|
return `${error.name}: ${error.message}`;
|
|
}
|
|
return "Unknown startup error";
|
|
}
|