Add Home Assistant integration discovery

This commit is contained in:
2026-08-10 09:28:42 +02:00
parent f2887a0ab4
commit 224e190fa1
11 changed files with 144 additions and 22 deletions
+5
View File
@@ -19,6 +19,11 @@ Voraussetzung ist Home Assistant OS.
5. Hinterlege im Tab **Konfiguration** Benutzername, Passwort und Site-ID. 5. Hinterlege im Tab **Konfiguration** Benutzername, Passwort und Site-ID.
6. Speichere die Konfiguration und starte die App. 6. Speichere die Konfiguration und starte die App.
Ab App-Version 0.2.0 meldet sich die App automatisch bei Home Assistant. Mit
der Custom Integration aus
`https://git.jensneuber.de/jens/ha-solaredge-optimizers` werden daraus Geräte
und Sensoren für Tagesenergie und aktuelle Leistung jedes Optimierers.
## SolarEdge Optimizer Data ## SolarEdge Optimizer Data
Die App aktualisiert standardmäßig alle zehn Minuten und liefert für jedes Die App aktualisiert standardmäßig alle zehn Minuten und liefert für jedes
+2 -1
View File
@@ -4,6 +4,7 @@
"workspaces": { "workspaces": {
"": { "": {
"name": "solaredge-optimizers", "name": "solaredge-optimizers",
"version": "0.2.0",
}, },
"packages/solaredgeapi": { "packages/solaredgeapi": {
"name": "@solar-dash/solaredgeapi", "name": "@solar-dash/solaredgeapi",
@@ -19,7 +20,7 @@
}, },
"solaredge-optimizer-data": { "solaredge-optimizer-data": {
"name": "@solar-dash/solaredge-optimizer-app", "name": "@solar-dash/solaredge-optimizer-app",
"version": "0.1.0", "version": "0.2.0",
"dependencies": { "dependencies": {
"@solar-dash/solaredgeapi": "workspace:*", "@solar-dash/solaredgeapi": "workspace:*",
}, },
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "solaredge-optimizers", "name": "solaredge-optimizers",
"version": "0.1.0", "version": "0.2.0",
"private": true, "private": true,
"packageManager": "bun@1.3.14", "packageManager": "bun@1.3.14",
"workspaces": [ "workspaces": [
+6
View File
@@ -1,5 +1,11 @@
# Changelog # Changelog
## 0.2.0
- Register the internal optimizer API through Home Assistant Supervisor
discovery.
- Enable automatic setup of the SolarEdge Optimizers custom integration.
## 0.1.0 ## 0.1.0
- Initial Home Assistant App package. - Initial Home Assistant App package.
+13 -4
View File
@@ -83,9 +83,18 @@ im App-Panel erreichbar.
## Zugriff aus Home Assistant ## Zugriff aus Home Assistant
Die App veröffentlicht standardmäßig keinen ungeschützten Port im lokalen Die App veröffentlicht standardmäßig keinen ungeschützten Port im lokalen
Netz. Home Assistant und andere Apps können sie über das interne App-Netzwerk Netz. Ab Version 0.2.0 meldet sie ihren internen Host und Port automatisch über
erreichen. Bei einer lokalen Installation lautet der Hostname typischerweise die Supervisor-Diensterkennung. Dadurch kann die
`local-solaredge-optimizer-data`: [SolarEdge Optimizers Integration](https://git.jensneuber.de/jens/ha-solaredge-optimizers)
ohne manuelle interne URL eingerichtet werden.
Nach Installation und Start der App erscheint unter **Einstellungen → Geräte &
Dienste** die gefundene Integration **SolarEdge Optimizers**. Installiere die
Custom Integration vorher über HACS und bestätige anschließend den Fund.
Home Assistant und andere Apps können die API außerdem direkt über das interne
App-Netzwerk erreichen. Bei einer lokalen Installation lautet der Hostname
typischerweise `local-solaredge-optimizer-data`:
```text ```text
http://local-solaredge-optimizer-data:8099/api/optimizers http://local-solaredge-optimizer-data:8099/api/optimizers
@@ -119,7 +128,7 @@ für eine lokale Installation nach:
Lade anschließend den App Store neu, installiere **SolarEdge Optimizer Data**, Lade anschließend den App Store neu, installiere **SolarEdge Optimizer Data**,
pflege Benutzername, Passwort und Site-ID im Tab **Konfiguration** und starte pflege Benutzername, Passwort und Site-ID im Tab **Konfiguration** und starte
die App. Der Supervisor lädt dabei das zur App-Version passende Multi-Arch-Image die App. Der Supervisor lädt dabei das zur App-Version passende Multi-Arch-Image
`git.jensneuber.de/jens/solaredgeoptimizers:0.1.0`. `git.jensneuber.de/jens/solaredgeoptimizers:0.2.0`.
## Sicherheit und Betrieb ## Sicherheit und Betrieb
+4 -1
View File
@@ -1,5 +1,5 @@
name: "SolarEdge Optimizer Data" name: "SolarEdge Optimizer Data"
version: "0.1.0" version: "0.2.0"
slug: "solaredge_optimizer_data" slug: "solaredge_optimizer_data"
description: >- description: >-
Cached daily energy and current power data for every SolarEdge optimizer. Cached daily energy and current power data for every SolarEdge optimizer.
@@ -7,6 +7,9 @@ arch:
- aarch64 - aarch64
- amd64 - amd64
image: git.jensneuber.de/jens/solaredgeoptimizers image: git.jensneuber.de/jens/solaredgeoptimizers
discovery:
- solaredge_optimizers
hassio_api: true
startup: application startup: application
boot: auto boot: auto
init: false init: false
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@solar-dash/solaredge-optimizer-app", "name": "@solar-dash/solaredge-optimizer-app",
"version": "0.1.0", "version": "0.2.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -0,0 +1,48 @@
import { describe, expect, test } from "bun:test";
import { registerHomeAssistantDiscovery } from "./discovery";
describe("registerHomeAssistantDiscovery", () => {
test("registers the internal App endpoint with Supervisor", async () => {
let requestUrl: string | undefined;
let requestInit: RequestInit | undefined;
const fetchImpl = (async (
input: string | URL | Request,
init?: RequestInit,
) => {
requestUrl = String(input);
requestInit = init;
return Response.json({ result: "ok", data: { uuid: "test-uuid" } });
}) as typeof fetch;
await registerHomeAssistantDiscovery({
port: 8099,
host: "abc-solaredge-optimizer-data",
supervisorToken: "secret-token",
fetchImpl,
});
expect(requestUrl).toBe("http://supervisor/discovery");
expect(requestInit?.method).toBe("POST");
expect(requestInit?.headers).toEqual({
Accept: "application/json",
Authorization: "Bearer secret-token",
"Content-Type": "application/json",
});
expect(JSON.parse(String(requestInit?.body))).toEqual({
service: "solaredge_optimizers",
config: { host: "abc-solaredge-optimizer-data", port: 8099 },
});
});
test("does not start without a Supervisor token", async () => {
await expect(
registerHomeAssistantDiscovery({
port: 8099,
host: "abc-solaredge-optimizer-data",
supervisorToken: "",
}),
).rejects.toThrow("Supervisor token is unavailable");
});
});
+40
View File
@@ -0,0 +1,40 @@
import { hostname as systemHostname } from "node:os";
const DISCOVERY_SERVICE = "solaredge_optimizers";
const SUPERVISOR_DISCOVERY_URL = "http://supervisor/discovery";
export async function registerHomeAssistantDiscovery({
port,
host = process.env.HOSTNAME || systemHostname(),
supervisorToken = process.env.SUPERVISOR_TOKEN,
fetchImpl = fetch,
}: {
port: number;
host?: string;
supervisorToken?: string;
fetchImpl?: typeof fetch;
}): Promise<void> {
if (!host) {
throw new Error("App hostname is unavailable");
}
if (!supervisorToken) {
throw new Error("Supervisor token is unavailable");
}
const response = await fetchImpl(SUPERVISOR_DISCOVERY_URL, {
method: "POST",
headers: {
Accept: "application/json",
Authorization: `Bearer ${supervisorToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
service: DISCOVERY_SERVICE,
config: { host, port },
}),
});
if (!response.ok) {
throw new Error(`Supervisor discovery failed with HTTP ${response.status}`);
}
}
+10
View File
@@ -7,6 +7,7 @@ import {
readAppOptions, readAppOptions,
resolveHomeAssistantTimeZone, resolveHomeAssistantTimeZone,
} from "./config"; } from "./config";
import { registerHomeAssistantDiscovery } from "./discovery";
import { createRequestHandler } from "./http"; import { createRequestHandler } from "./http";
export async function main(): Promise<void> { export async function main(): Promise<void> {
@@ -52,6 +53,15 @@ export async function main(): Promise<void> {
"info", "info",
`Listening on port ${port}; refresh interval ${options.pollIntervalMinutes} minutes; time zone ${timeZone}`, `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(); cache.start();
} }