Erfahrungsbericht: 11 externe Integrationen und was uns Hin- und Herarbeit gekostet hat (Dokumentation, SDK, Vorlage, Kern)
Hallo zusammen, hallo @pierre-gilles,
Seit August habe ich 11 externe Integrationen veröffentlicht: UniFi, Android TV Remote, IPP, SolarEdge, Plex, Subsonic, Speedtest, ecojoko, Astronomie, Jellyfin & Emby und Dreame. Das System für externe Integrationen ist wirklich angenehm zu verwenden, danke!
Wenn man sich die Historie der Repositories und der Forenbeiträge ansieht, stammt ein großer Teil der „korrigierenden“ Versionen nicht von Fehlern in meinem Code. Sie kamen von Dingen, die Gladys erwartet, ohne dass es irgendwo geschrieben steht, oder die die Dokumentation anders beschreibt. Meistens war der Fehler stumm: ein Gerät, das akzeptiert, aber nie abgefragt wird, eine Widget-Komponente, die verschwindet, eine Integration, die im Store fehlt. Ich habe alles hier zusammengefasst, nach Kosten sortiert, mit Vorschlägen für Dokumentation, SDK oder Kern.
Alles wurde heute auf master überprüft (Kern 03d4696d, SDK 500db06, Vorlage 4ea0c7b, Store a052400), mit Datei und Zeile in den ausgeblendeten Blöcken. Ich habe das, was bereits behoben oder bereits angefordert wurde, aussortiert und liste es am Ende auf. Ich denke dabei insbesondere an die Tickets von @prohand (SDK #36, Vorlage #19) und das Thema Dreame (#3153 bis #3156).
Kurz gesagt: die 5 Punkte, die die meisten Versionen verhindert hätten
- Polling:
should_poll: trueist obligatorisch, aber keine Spezifikation oder die öffentliche Dokumentation erwähnt dies. Die Vorlage veröffentlichtpoll_frequency: 300(in Sekunden, ohneshould_poll), und ihre eigene Entdeckungsseite wird daher mit 400 abgelehnt. Kosten für mich: etwa 11 veröffentlichte Versionen, bei 5 Integrationen. min/maxobligatorisch für alle Funktionen, einschließlichtext. Die Entdeckung akzeptiert sie als fehlend, dann scheitert „Hinzufügen“ mit 422. Die Spezifikation bezeichnet sie sogar als „optional“. 4 Integrationen betroffen.- Kategorie/Typ-Paare: Sie werden in zwei separaten Listen validiert, daher wird jedes Paar akzeptiert. In der Praxis hat ein Paar keine Bezeichnung, kein Icon, oder es wird als Sensor statt als Befehl behandelt. 5 Integrationen betroffen.
- Store: Eine Ablehnung durch den Indexer benachrichtigt niemanden. Der Grund steht nur in einer
rejected.json, deren URL die Dokumentation nicht angibt. 7 Integrationen betroffen, darunter ecojoko, die für ihren Tester 2 Stunden lang nicht auffindbar war. - Zustände, die veröffentlicht werden, bevor das Gerät hinzugefügt wird: Der Kern antwortet mit
200, wirft sie dann aber weg. Eine Integration, die Duplikate entfernt, wie die Dokumentation es empfiehlt, sendet sie nie wieder, daher „keine aktuellen Werte“ direkt nach dem Hinzufügen. 3 Integrationen betroffen.
1. Polling: should_poll, Einheiten und Taktraten
Was Gladys wirklich erwartet:
- Der Planer fragt ein Gerät nur ab, wenn
should_poll === trueundpoll_frequencydefiniert ist. should_pollist standardmäßigfalse. Nichts wird auspoll_frequencyabgeleitet, und die Entdeckungsseite sendet das Gerät so, wie es ist.- Ergebnis: Ein Gerät, das nur mit
poll_frequencyveröffentlicht wird, wird akzeptiert, aber nie abgefragt. Es gibt keinen Fehler oder Log, nur „keine aktuellen Werte“.
Was die Dokumentation sagt:
- Die Spezifikationen
host-api-endpoints.md:40,websocket-protocol.md:12undcommand-routing.md:5beschreiben das Polling „für ein Gerät mit einerpoll_frequency“. Keine Datei indocs/specs/external-integrations/erwähntshould_poll. - Die öffentliche Dokumentation (
/docs/dev/external-integrations/) hat nur eine Zeile zuonPoll. - Das README des SDK auf
masterhat heute eine Sektion „Polling devices“ erhalten (danke @prohand), aber sie ist noch nicht auf npm (0.14.0) veröffentlicht.
Die Vorlage zeigt das Beispiel, das nicht funktioniert:
src/config.js:19enthältpoll_frequency: 300, // Sekunden, übernommen vonweatherStation.js:45undplug.js:64, ohneshould_poll.- Der Kern lehnt diese Charge mit
400 devices[0].poll_frequency: invalid poll frequencyab. Der falsche Gladys des SDK aufmasterreproduziert dies. - Die Entdeckungsseite einer Integration, die aus der Vorlage erstellt wurde, ist daher von Anfang an leer. Dieser Punkt von SDK #36 wurde auf SDK-Seite korrigiert, nicht in der Vorlage.
Weitere Fallstricke desselben Themas:
- Geschlossene Liste von 1 bis 60 s. Nichts ist für eine langsame Taktrate (5 min, 15 min, 1 h) vorgesehen, die jedoch der häufige Fall einer Cloud-API mit Kontingent ist: SolarEdge, ecojoko, Speedtest. Jede Integration implementiert daher ihren eigenen Timer im Container. Wenn dies das gewünschte Verhalten ist, verdient es einen Satz in der Dokumentation.
- Ein einzelnes ungültiges Feld führt zur Ablehnung der gesamten Charge, und das SDK protokolliert einen Handler-Fehler nur auf Debug-Ebene. Bei SolarEdge 1.0.1 war die Entdeckung leer, ohne eine einzige Spur auf der Standard-Log-Ebene.
poll_frequencyundshould_pollwerden nie für ein bereits erstelltes Gerät aktualisiert. Sie sind kein Teil der Signatur vonstructure_changed, daher repariert das Ändern des Pollings in einer neuen Version nicht die bestehenden Geräte (IPP, Subsonic). Und die Geräte-Seite einer externen Integration bietet nur den Namen und den Raum: Der Benutzer kann es nicht selbst korrigieren.- Frontend-Detail: Der generische Frequenzselektor (
UpdateDeviceForm.jsx:57-77) bietet keine 15 s an, obwohl der Planer sie unterstützt.
Vorschläge:
- Kern (am einfachsten): Für eine externe Integration
should_poll = trueannehmen, sobald eine gültigepoll_frequencyveröffentlicht wird. Andernfalls einepoll_frequencyohneshould_pollmit 400 ablehnen. - Kern:
should_pollundpoll_frequencyin die Signatur vonstructure_changedaufnehmen. - Vorlage:
should_poll: true, ein Wert vonDEVICE_POLL_FREQUENCIESin ms und ein Beispiel für einen internen Timer für langsame Taktraten. - Dokumentation:
should_pollin den drei Spezifikationen und auf der Website erwähnen und offen sagen „bei mehr als 60 s, Timer im Container“. - SDK: Fehler der Handler (
onScanRequest…) auf Ebeneerrorprotokollieren, nichtdebug.
Beweise (master 03d4696d)
server/lib/device/device.add.js:37:if (device.should_poll === true && device.poll_frequency)server/models/device.js:46-50:should_pollist standardmäßigfalseserver/lib/external-integration/externalIntegration.setDiscoveredDevices.js:55-56: lehnt einepoll_frequencyaußerhalb der Liste ab, prüft nichtshould_pollfront/src/routes/integration/all/external-integration/discover-page/index.js:136-151: sendet das veröffentlichte Objekt so, wie es istexternalIntegration.getDiscoveredDevices.js:274-283: die Signatur enthält nur Funktionsfelder- Historie: UniFi 1.2.8 → 1.3.2 (5 Versionen an einem Tag), SolarEdge 1.0.1 → 1.0.3, Subsonic 1.0.2 → 1.0.3, Speedtest 1.0.1 → 1.0.2 und IPP und Plex vor der Veröffentlichung
2. min und max obligatorisch, auch für text
Die Feststellung:
t_device_featureerklärtmin,max,read_onlyundhas_feedbackals NOT NULL ohne Standardwert.- Die Entdeckung überprüft keines der vier. Der Fehler tritt erst beim Klicken auf „Zu Gladys hinzufügen“ auf: eine 422. Seit #2733 benennt sie zumindest die fehlerhafte Funktion, was hilft.
- Die Spezifikation sagt das Gegenteil:
host-api-endpoints.md:40spricht von „every other optional column (unit,min,max)“. - Die Typisierungen des SDK sagen auch
min?: number; max?: number.
Das ist bei IPP, Plex, Subsonic und Astronomie passiert. Bei IPP wurden die Grenzen entfernt, weil sie „für Text unnötig“ waren, dann am selben Tag nach der 422 wieder hinzugefügt.
Vorschläge:
- Entweder setzt der Kern standardmäßig
0/0bei der Veröffentlichung, wie es Zigbee2MQTT tut, oder er lehnt mit 400 beiPOST /discovered_deviceab. In beiden Fällen gibt es keine späten Fehler. min/maxinindex.d.tsverpflichtend machen und den Satz der Spezifikation korrigieren.
3. Kategorie/Typ-Paare: jedes Paar wird akzeptiert
Die Feststellung:
- Die Entdeckung testet die Kategorie in einer Liste und den Typ in einer anderen (
setDiscoveredDevices.js:69-74). Die Einheit wird ohne Berücksichtigung der Kategorie kontrolliert, undDEVICE_FEATURE_UNITS_BY_CATEGORYwird nirgendwo auf der Serverseite verwendet. - Die Dokumentation verweist auf die Konstanten, aber generische Typen (
decimal,integer,binary…) werden mit jeder Kategorie akzeptiert. - Man entdeckt also die Bedeutung eines Paares in der Realität. Wofür wir bezahlt haben:
- UniFi: Kategorie
sensorexistiert nicht, dannspeed-sensor/integerdurchdatarate/ratezu ersetzen, dann fehlthas_feedback. Drei Versionen. - Android TV:
button/clickwird als Sensor behandelt, daher nicht klickbare App-Buttons (1.0.5 → 1.1.0). Es musste zu einemtext/selectgewechselt werden. - IPP:
level-sensormit einem generischen Typ hat kein Icon. - Astronomie:
light-sensor/binaryhat keine Bezeichnung, daher „Gerät (undefined)“ in der Entdeckung. Ersetzt durchinput/binary. - ecojoko: Die Leistung, die in
energy-sensor/powermitmin: 0veröffentlicht wird, lässt die Nadel des Messgeräts bei Solarüberschuss aus der Skala. Es solltegrid-sensor/powersein, signiert, mit symmetrischen Grenzen. Weder die Bedeutung „Verbrauch“ des einen noch die Bedeutung „signierter Austausch“ des anderen sind geschrieben.
- UniFi: Kategorie
- Kleiner Bug nebenbei:
front/src/utils/consts.jserklärt den SchlüsselLIGHT_SENSORzweimal inDeviceFeatureCategoriesIcon(Z. 189 und 271). Der zweite überschreibt den ersten, sodasslight-sensor/integerkein Icon mehr hat.
Vorschläge:
- Das Paar (und die Einheit pro Kategorie) in
setDiscoveredDevicesvalidieren. - Aus dem SDK eine Tabelle „Kategorie → zulässige Typen“ exportieren, mit für jeden: Sensor oder Steuerung, erwartetes
read_only, Vorzeichen und typische Grenzen. - All das von der falschen Gladys des SDK überprüfen lassen.
4. Store: Stille Ablehnungen
Die Feststellung:
- Der Store-Validator ist wertvoll, aber ein Repository, das den Indexer nicht besteht, erhält keine Rückmeldung.
- Der Grund steht nur in
rejected.json, dessen URL weder das Store-README noch die Website angeben (https://integration-store-storage.gladysassistant.com/rejected.json).DEFAULT_STORE_BASE_URLzeigt sogar noch auf GitHub Pages. - Wofür wir so bezahlt haben:
- Beschreibung von mehr als 100 Zeichen: ecojoko war 2 Stunden lang nicht im Store, und Pat fand sie nicht;
placeholdernicht mehrsprachig (Speedtest);- Cover von mehr als 150 KB (Astronomie);
- Typen
text/passwordinstring/secretumbenennen unddisplay_ifabgelehnt (UniFi); - eine angekündigte Version ohne Bild: Die Integration verschwindet aus dem Katalog (IPP), oder das Update wird nicht angeboten und der Tester muss deinstallieren und neu installieren (Android TV 1.1.0).
- Cadence: Die Dokumentation kündigt einen „stündlichen“ Durchlauf an (cron
13 * * * *). In der Praxis führt GitHub nur 3 bis 6 geplante Durchläufe pro Tag seit Ende September aus, z. B. heute um 08:56 Uhr und 16:08 Uhr. Besser wäre es, „im Laufe des Tages“ anzukündigen.
Vorschläge:
- Die tatsächliche URL von
rejected.jsonin der Dokumentation angeben. - Den Entwickler benachrichtigen: ein Commit-Status oder ein automatisch geöffnetes Issue in seinem Repository.
npx github:GladysAssistant/integration-store --skip-image-checkin der CI des Templates ausführen. Der Kommentar vonci.yml:15-17, der das Gegenteil behauptet, ist mit--skip-image-checkveraltet.
5. Veröffentlichte Zustände vor der Geräteergänzung: 200, dann verworfen
Die Feststellung:
POST /stateantwortet200 { success: true }für eine Funktion, die noch nicht existiert.- Der Kern sortiert sie dann mit einem einfachen
logger.infoaus (device.newStateEvent.js:16-21), und das Kontingent von 300 Zuständen pro Minute wird trotzdem verbraucht. - Die Dokumentation empfiehlt jedoch zu Recht, nur Änderungen zu veröffentlichen. Die Integration hat also „bereits“ den Wert gesendet, und das frisch hinzugefügte Gerät bleibt auf „kein aktueller Wert“, bis zur nächsten Änderung (Plex, IPP, Subsonic).
- Die Umgehung, den Cache zu leeren und alles in
onDeviceCreatedneu zu veröffentlichen, steht nirgendwo geschrieben.
Vorschläge:
- Das in der Dokumentation und im Template (
onDeviceCreated→ neu veröffentlichen) erwähnen. - Besser: In der Antwort die Liste der unbekannten
external_idzurückgeben, damit das SDK sie in Wartestellung halten kann.
6. Stilles Verhalten, das zu dokumentieren oder zu protokollieren ist
„Aktualisieren“ im Entdeckungstab („structure_changed“).
- Es vergleicht nur
external_id,category,type,unit,min,maxundstepjedes Merkmals sowie die Hinzufügung oder Entfernung eines Merkmals. - Es wird nicht durch Namen,
read_only,has_feedback,params,supported_optionsoder das Polling ausgelöst.supported_optionsundparamswerden jedoch bei jeder Veröffentlichung stillschweigend neu synchronisiert. - Die Spezifikation (
host-api-endpoints.md:52) sagt nur „features added/modified“. Eine explizite Liste hätte zwei Löschungen und Neuerstellungen eines Geräts beim Tester Dreame verhindert. - Zu bestätigen auf deiner Seite (ich habe es im Code gelesen, ohne es in der Realität gesehen zu haben): „Aktualisieren“ scheint den vom Benutzer gewählten Gerätenamen mit dem veröffentlichten Namen zu überschreiben (
device.create.js:140-143). Der Raum selbst wird beibehalten. Das wäre im Widerspruch zur Spezifikation, die besagt, dass Name und Raum dem Benutzer gehören.
Name eines einzigen Merkmals seines Typs.
- Das Dashboard zeigt die generische Bezeichnung des Typs anstelle des veröffentlichten Namens an, außer bei MQTT (
DISPLAY_FEATURE_NAME_FOR_THOSE_SERVICES = { mqtt: true }). Ich hatte das im Dreame-Thema gefragt, ohne Antwort. - Gleicher Effekt im Entdeckungsscreen: Vier UniFi-PoE-Ports wurden als vier „Schalter“ angezeigt, die nicht zu unterscheiden waren.
- Eine externe Integration wählt ihre Namen: Ich schlage vor, sie zu dieser Regel hinzuzufügen.
Sprache des Benutzers.
setValue,poll,scene.action.runund die Konfigurationsaktionen erhalten keine Sprache. Nur die Widgets und das Wetter erhalten sie.- Jede Integration, die Text erzeugt, fügt daher ein Feld
languagezu ihrer Konfiguration hinzu (IPP, Astronomie, Jellyfin, Dreame). - Ich schlage entweder vor,
languagein diesen Payloads zu übertragen, oder die Grenze zu dokumentieren.
Kontingent von 300 Zuständen pro Minute.
- Die Dokumentation erwähnt nur den
429. Es wäre wert, drei Dinge zu präzisieren:- es ist der gesamte Batch, der abgelehnt wird;
- ein abgelehnter Batch mit 400 verbraucht trotzdem das Kontingent;
- jeder akzeptierte Zustand bewertet die Szenen neu.
gladys_version und Updates.
- Ein alter Kern lehnt jedes unbekannte Manifestfeld ab. Die Deklaration eines Widgets erzwingt daher
gladys_version >= 5.1.0, und der Index behält nur das letzte Manifest: Ältere Kerne erhalten keine Updates mehr. - Das ist dokumentiert und verständlich. Andererseits, auf einem alten Kern:
isUpdateAvailable/getLatestVersionvergleichen nur die Nummern, daher leuchtet das Badge „Update verfügbar“ auf;- beim Klicken wird das Manifest mit einer einfachen
warn(update.js:33-49) abgelehnt und der Container identisch neu erstellt; - das Badge bleibt leuchtend, ohne Nachricht für den Benutzer.
- Die Spezifikationen
core/store.md:23undcontracts/management-api.md:13behaupten jedoch, dass der Katalog „nachgladys_versiongefiltert“ wird. Ich schlage vor, die Kompatibilität inisUpdateAvailablezu testen.
Energie.
- Nur ein
energy-sensor/indexkumuliert löst den Verbrauch von 30 Minuten und die Kosten aus. Einenergy-production-sensor/indexleitet nichts ab: Das Merkmalthirty-minutes-productionwird von niemandem erstellt. host-api-endpoints.md:63zitiert jedoch die Produktionsregler, was das Gegenteil vermuten lässt. ecojoko veröffentlicht einen Produktionsindex für Solarproduzenten, und er dient nur dem historischen Verlauf.
7. Widgets (5.1): Was ohne Vorwarnung verschwindet
Die Fähigkeit ist ausgezeichnet, und der Validator des SDK fängt bereits viele Dinge ab. Es bleiben jedoch einige Lücken:
- Das Budget von 8 Komponenten wird vor der Auflösung der Referenzen angewendet (
getWidgetContent.js:62-69).- Eine Kachel, die mit einem noch nicht hinzugefügten Gerät verknüpft ist, belegt einen Platz und wird dann entfernt. Sie könnte eine gültige Komponente verdrängt haben.
- Das verkürzte Ergebnis bleibt im Cache bis zum TTL (bis zu 1 h), da das Hinzufügen des Geräts den Cache nicht ungültig macht.
- Man sollte also daran denken,
requestWidgetRefreshbeionDeviceCreatedaufzurufen: zu dokumentieren oder besser, auf der Core-Seite zu invalidieren.
- Schaltfläche mit bereits belegter Aktionsschlüssel : Sie wird mit einer
warn-Meldung auf der Serverseite verworfen, und die Integration weiß nichts davon. Auf Dreame wurde nur ein Shortcut von drei angezeigt (0.3.0 → 0.4.0). Die Spezifikation sagt „einzigartig“, aber sie sagt nicht, dass das Duplikat entfernt wird. status-Zeile ohnevalue: Sie wird ohne Logs verworfen.- Aktions-Toast : Er wird auf 200 Zeichen durch
slicegekürzt, ohne Auslassungspunkte.- Ein mehrsprachiges Objekt ohne
en-Schlüssel gibt keinen Toast. MAX_WIDGET_MESSAGE_LENGTHexistiert im SDK, aber nichts verwendet es.
- Ein mehrsprachiges Objekt ohne
card-list: Dasdateersetzt den Untertitel, anstatt hinzugefügt zu werden. Die Spezifikation sagt „subtitle or date“; es sollte präzisiert werden „das Datum hat Vorrang“ (Jellyfin, Plex).validateWidgetContentläuft nur im Debug-Modus. Ich schlage vor, es bei jedemonWidgetGetmit einerwarn-Meldung auf der Integrationsseite auszuführen, damit der Entwickler sieht, was das Core entfernen wird.
8. Vorlage und öffentliche Dokumentation
-
.gitignoreund.prettierignore: Die Regeldata/, die für das Volume/datavorgesehen ist, schließt auchsrc/data/aus. Der Ordner fehlt dann im durch die CI erstellten Image (Astronomie, vor der 1.0.0). Er muss mit/data/verankert werden. -
Dezimale
number-Felder : Dies ist aufmasterkorrigiert (#3167,step="any"), aber noch nicht veröffentlicht.- Auf der 5.1.4 bleibt das Beispiel für Breite/Länge der Vorlage (
48.8566) unmöglich einzugeben. Astronomie hat eine Version darüber verloren. - Das Schema des Manifests lehnt
stepimmer noch ab: Man kann keine Auflösung deklarieren.
- Auf der 5.1.4 bleibt das Beispiel für Breite/Länge der Vorlage (
-
Öffentliche Dokumentation :
- Sie kündigt das SDK
0.12.0an, während npm bei 0.14.0 ist; - Sie beschreibt den alten Release-Workflow, ohne CHANGELOG oder GitHub-Release;
- Sie sagt nicht, dass „Changelog dieser Version anzeigen“ das GitHub-Release des Tags öffnet;
- Sie spricht noch nicht über den falschen Gladys.
- Die Vorlage macht jetzt alles das (#20, danke): Es fehlt nur die Seite.
- Sie kündigt das SDK
-
SDK 0.15 veröffentlichen : Der falsche Gladys und die Dokumentation des Pollings warten auf
master. Dieser falsche Gladys könnte das Sicherheitsnetz werden, wenn er auch prüft:poll_frequencyohneshould_poll;- Das Fehlen von
min/max; - Die Paare Kategorie/Typ;
- Das Kontingent von 300 Zuständen pro Minute;
- Die Länge der Toasts.
Heute,
{ poll_frequency: 60000 }ohneshould_poll, mit einer Funktionalitätlevel-sensor/decimalohne Grenzen, gibt es{ success: true }.
Bereits korrigiert oder bereits angefordert: Ich frage nicht noch einmal danach
- Primärer Schaltfläche im dunklen Modus (#3153 → #3162),
secretunddefaultin den Aktionen (#3154 → #3163, #3155 → #3164), Listen des Staubsaugers undsupported_options(#3156 → #3171), dezimalenumber-Felder (#3167). Alles ist aufmaster, nichts ist noch in einer veröffentlichten Version. - SDK #36 und Vorlage #19 von @prohand: Einheit von
poll_frequency, falscher Gladys, Release, das Prettier kaputt gemacht hat, GitHub-Release und Changelog, Grenze von 100 Zeichen, mehrsprachigerplaceholder. - Grenze von 200 Geräten pro Entdeckung, erhöht im August.
Danke, dass du es gelesen hast oder es von Claude lesen lässt
!