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 { 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"; }