Initial SolarEdge Optimizer Home Assistant App

This commit is contained in:
2026-08-10 09:04:35 +02:00
commit f2887a0ab4
38 changed files with 4253 additions and 0 deletions
+84
View File
@@ -0,0 +1,84 @@
import { SolarEdgePortalClient } from "@solar-dash/solaredgeapi";
import { OptimizerDataCache } from "./cache";
import { collectOptimizerData } from "./collector";
import {
DEFAULT_PORT,
readAppOptions,
resolveHomeAssistantTimeZone,
} from "./config";
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}`,
);
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";
}