@pellebot/homebridge-fritz-platform-hb2 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-notes-v1.1.0.md +42 -0
- package/.release-notes-v1.1.1.md +54 -0
- package/.release-notes-v1.2.0.md +46 -0
- package/CHANGELOG.md +59 -0
- package/README.md +7 -2
- package/config.schema.json +8 -2
- package/homebridge-ui/public/index.html +268 -26
- package/package.json +1 -1
- package/src/accessories/smarthome/energy-light/energy-light.accessory.js +85 -0
- package/src/accessories/smarthome/energy-meter/energy-meter.accessory.js +1 -4
- package/src/accessories/smarthome/smarthome.config.js +14 -1
- package/src/accessories/smarthome/smarthome.handler.js +61 -12
- package/src/accessories/smarthome/smarthome.js +1 -0
- package/src/platform.js +8 -3
- package/src/types/custom.types.js +45 -22
- package/src/types/eve.types.js +41 -16
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
## Setup-Wizard Release
|
|
2
|
+
|
|
3
|
+
Beim Klick auf das Plugin-Settings-Icon in Config UI X startet ab dieser Version ein geführter 5-Schritt-Wizard — kein Raten mehr bei Feldern wie `accType`, `obisChannel` oder AIN-Format.
|
|
4
|
+
|
|
5
|
+
### Highlights
|
|
6
|
+
|
|
7
|
+
- 🧙 **5-Schritt-Wizard:** FritzBox-Verbindung → Geräte-Auswahl → Smart Energy 250 → Router-Switches → Speichern
|
|
8
|
+
- 🔍 **Auto-Discovery** aller DECT-Geräte mit `accType`-Vorschlag pro Gerät anhand der Geräte-Eigenschaften
|
|
9
|
+
- ⚡ **Smart Energy 250 OBIS-Reader Auto-Setup:** erkennt Sub-AIN-Paare (-1/-2) und legt 4 HomeKit-Tiles auto-an (Aktueller/Gesamt Verbrauch + Aktuelle/Gesamt Einspeisung)
|
|
10
|
+
- 🔘 **Router-Switches Section:** Gast-WLAN, WPS, WLAN 2.4/5 GHz, DECT-Basis, AB, Rufumleitung, Reconnect — alles als Checkboxen
|
|
11
|
+
- 💾 **Save + Apply:** Zusammenfassung mit JSON-Vorschau (Passwort maskiert), direkter Homebridge-Restart-Button
|
|
12
|
+
- ✏️ **Auto-Pre-fill** aus bestehender Config im Verbindungs-Schritt und bei Router-Switches
|
|
13
|
+
|
|
14
|
+
### Bug-Fixes
|
|
15
|
+
|
|
16
|
+
- AIN-Suffix-Detection korrekt priorisiert: DECT 500 Lampen (`-1` als HAN-FUN-Sub-Unit) werden nicht mehr fälschlich als OBIS-Reader interpretiert
|
|
17
|
+
- Korrekte `@seydx/fritzbox`-Feldnamen verwendet: `.thermostat` (nicht `.hkr`), `.light` (nicht `.colorcontrol`/`.levelcontrol`), `.alert` (boolean)
|
|
18
|
+
|
|
19
|
+
### Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
sudo npm install -g @pellebot/homebridge-fritz-platform-hb2@1.1.0
|
|
23
|
+
sudo hb-service restart
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Bei npm-Cache-Permission-Fehler vorab:
|
|
27
|
+
```bash
|
|
28
|
+
sudo chown -R 501:20 ~/.npm
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Breaking Changes
|
|
32
|
+
|
|
33
|
+
Keine. Bestehende Configs werden unverändert übernommen. Das alte JSON-Schema-Form ist über den "Save"-Button (bottom-right) im Plugin-Settings-Modal weiterhin erreichbar als Fallback.
|
|
34
|
+
|
|
35
|
+
### Bekannte Limitationen
|
|
36
|
+
|
|
37
|
+
- Standalone Config UI X: `homebridge.savePluginConfig()` kann in Standalone-Modus die WebSocket-ACK nicht empfangen → Save mit 3-Sek-Timeout gerennt. Datei wird trotzdem geschrieben.
|
|
38
|
+
- Custom-UI-Icon: erscheint im Config UI X Plugin-Tile erst nach Homebridge-Verified-Plugin-Approval (frühestens 13. Juni 2026, siehe Roadmap).
|
|
39
|
+
|
|
40
|
+
### Vollständiger Changelog
|
|
41
|
+
|
|
42
|
+
Siehe [CHANGELOG.md](./CHANGELOG.md)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
## LightSensor variant for energy tiles
|
|
2
|
+
|
|
3
|
+
Neuer **accType `energy-light`** als Alternative zu `energy-temperature`. Beide nutzen dieselbe OBIS-Datenflow-Logik (Fritz!Smart Energy 250 sub-AINs), aber `energy-light` rendert die Werte als **LightSensor** (Einheit `lx`) statt **TemperatureSensor** (Einheit `°C`).
|
|
4
|
+
|
|
5
|
+
### Hintergrund
|
|
6
|
+
|
|
7
|
+
Apple Home rendert beide Sensor-Typen unterschiedlich:
|
|
8
|
+
|
|
9
|
+
- **TemperatureSensor** (`energy-temperature`): prominentes Tile im Raum-View, `°C`-Einheit
|
|
10
|
+
- **LightSensor** (`energy-light`): "passiver Sensor" in der Status-Zeile oben, `lx`-Einheit — per Long-Press → "Im Zuhause anzeigen" als Favorit ins Home-Tab holbar
|
|
11
|
+
|
|
12
|
+
Beide Einheiten (°C, lx) sind semantisch falsch für Energie-Werte. Manche Nutzer empfinden `lx` als abstrakter und weniger irreführend als `°C` (das auf Temperaturmessung schließen lässt).
|
|
13
|
+
|
|
14
|
+
### Highlights
|
|
15
|
+
|
|
16
|
+
- 🆕 Neuer `accType: energy-light` parallel zu `energy-temperature` (beide bleiben verfügbar)
|
|
17
|
+
- 🧙 Setup-Wizard Schritt 3 (Smart Energy 250) bekommt **Display-Typ-Auswahl** (Radio: Temperatur / Lichtsensor)
|
|
18
|
+
- 🔄 Auto-Migration: wenn man via Wizard von Temperatur auf Lichtsensor wechselt, entfernt das Plugin den alten TemperatureSensor-Service sauber
|
|
19
|
+
|
|
20
|
+
### Breaking Changes
|
|
21
|
+
|
|
22
|
+
Keine. Bestehende Configs mit `energy-temperature` laufen unverändert weiter.
|
|
23
|
+
|
|
24
|
+
### Migration für bestehende Nutzer
|
|
25
|
+
|
|
26
|
+
Pro Smart-Home-Eintrag den `accType` von `"energy-temperature"` auf `"energy-light"` ändern, Homebridge neu starten:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"active": true,
|
|
31
|
+
"name": "Aktueller Verbrauch",
|
|
32
|
+
"ain": "...-1",
|
|
33
|
+
"accType": "energy-light",
|
|
34
|
+
"obisChannel": "current_power"
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Oder den Setup-Wizard noch einmal durchlaufen und in Schritt 3 die LightSensor-Option wählen.
|
|
39
|
+
|
|
40
|
+
### Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
sudo npm install -g @pellebot/homebridge-fritz-platform-hb2@1.1.1
|
|
44
|
+
sudo hb-service restart
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Bekannte Limitationen
|
|
48
|
+
|
|
49
|
+
- **HAP minValue für AmbientLightLevel:** 0.0001 (nicht 0 möglich). Bei aktuellem Wert = 0 W zeigt das Tile `0.0001 lx`. Funktional kein Unterschied, kosmetisch leicht verwirrend.
|
|
50
|
+
- **LightSensor in Apple Home Status-Bereich:** das Tile erscheint nicht direkt im Raum sondern im Status-Bereich oben. Per Long-Press als Favorit ins Home-Tab holen (siehe README).
|
|
51
|
+
|
|
52
|
+
### Vollständiger Changelog
|
|
53
|
+
|
|
54
|
+
Siehe [CHANGELOG.md](./CHANGELOG.md)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
## Wizard Edit-Mode (Phase G) — Re-Runs überschreiben deine Einstellungen nicht mehr
|
|
2
|
+
|
|
3
|
+
Der Setup-Wizard hat eine echte **Aktualisierungs-Routine** bekommen. Wenn du den Wizard ein zweites Mal aufrufst — zum Beispiel um ein neues Smart-Home-Gerät zu integrieren — werden deine bestehenden Anpassungen jetzt aus der `config.json` vorgeladen statt überschrieben.
|
|
4
|
+
|
|
5
|
+
### Was das konkret fixt
|
|
6
|
+
|
|
7
|
+
In v1.1.x sind beim Wizard-Re-Run folgende User-Anpassungen verloren gegangen:
|
|
8
|
+
|
|
9
|
+
- **Tile-Namen** wie „PV Einspeisung Hausanschluss" wurden auf „Aktuelle Einspeisung" zurückgesetzt
|
|
10
|
+
- **`accType`-Wahl** zwischen `energy-temperature` (°C-Tile) und `energy-light` (lx-Sensor) wurde immer auf `energy-temperature` zurückgesetzt
|
|
11
|
+
- **Custom-Namen** für DECT-Geräte wurden durch den AVM-Auto-Discovery-Namen ersetzt
|
|
12
|
+
- **Nur Router-Switches** waren korrekt vorausgefüllt
|
|
13
|
+
|
|
14
|
+
Phase G dehnt diesen Hydration-Pattern auf alle Schritte aus.
|
|
15
|
+
|
|
16
|
+
### Highlights
|
|
17
|
+
|
|
18
|
+
- 🔁 **Schritt 1 (Verbindung):** Host/User/PW/SSL/TR064 wie bisher vorausgefüllt — jetzt über einen zentralen Loader
|
|
19
|
+
- 🏷️ **Schritt 2 (Geräte):** für jedes AIN, das bereits in `config.json` steht, wird der bestehende `accType` und Custom-Name übernommen statt Auto-Detection-Default
|
|
20
|
+
- ⚡ **Schritt 3 (Energy 250):** Tile-Namen aus existierender Config übernommen, °C/lx-Radio aus den vorhandenen Einträgen vorausgewählt
|
|
21
|
+
- 🔘 **Schritt 4 (Router-Switches):** unverändert (war schon korrekt)
|
|
22
|
+
- 📝 **Schritt 5 (Speichern):** **Neue Diff-Vorschau** zeigt vor dem Speichern, was sich gegenüber der aktuellen Config ändert — keine bösen Überraschungen mehr
|
|
23
|
+
|
|
24
|
+
### Breaking Changes
|
|
25
|
+
|
|
26
|
+
Keine. Bestehende Configs werden ohne Migration übernommen.
|
|
27
|
+
|
|
28
|
+
### Migration für bestehende Nutzer
|
|
29
|
+
|
|
30
|
+
Keine Action nötig. Wenn du den Wizard das nächste Mal öffnest, sind deine Einstellungen einfach da. Beim Speichern siehst du in der neuen Diff-Vorschau exakt, was sich ändern wird — falls die Liste leer ist, war alles schon korrekt.
|
|
31
|
+
|
|
32
|
+
### Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
sudo npm install -g @pellebot/homebridge-fritz-platform-hb2@1.2.0
|
|
36
|
+
sudo hb-service restart
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Bekannte Limitationen
|
|
40
|
+
|
|
41
|
+
- **Globaler statt per-Tile `energyAccType`:** Die Wizard-UI behandelt °C/lx als globale Einstellung für alle OBIS-Tiles. Mix-Setups (einige Tiles `energy-temperature`, andere `energy-light`) werden beim Wizard-Save auf eine einheitliche Wahl reduziert (light gewinnt). Wer einen Mix braucht: `config.json` direkt editieren, nicht den Wizard zum Speichern nutzen.
|
|
42
|
+
- **Diff zeigt nur Änderungen gegenüber der gespeicherten Config**, nicht gegenüber dem letzten Wizard-Lauf. By design — aber bedeutet: wenn du den Wizard mehrmals öffnest und schließt ohne zu speichern, siehst du jedes Mal denselben Diff zur Live-Version.
|
|
43
|
+
|
|
44
|
+
### Vollständiger Changelog
|
|
45
|
+
|
|
46
|
+
Siehe [CHANGELOG.md](./CHANGELOG.md)
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,65 @@
|
|
|
2
2
|
|
|
3
3
|
> **Hinweis zum Format:** Releases ab `v1.0.0` werden unter dem npm-Paket `@pellebot/homebridge-fritz-platform-hb2` veröffentlicht. Die `6.1.0-hb2patch.x` Pre-Releases sind Vorstufen unter dem alten Paketnamen `@pellebot/homebridge-fritz-platform` (deprecated). Die `registerPlatform`-Kennung bleibt durchgängig `homebridge-fritz-platform`, sodass cached Accessories über den Rename hinweg matchen — kein Re-Pairing nötig.
|
|
4
4
|
|
|
5
|
+
# v1.2.0 — 2026-05-14 (Wizard Edit-Mode / Phase G)
|
|
6
|
+
|
|
7
|
+
**Setup-Wizard erhält Aktualisierungs-Routine.** Beim erneuten Aufruf des Wizards (z.B. um ein weiteres Gerät hinzuzufügen) werden die in `config.json` gespeicherten Einstellungen jetzt vorgeladen statt überschrieben. Damit ist der Hauptschmerz aus v1.1.x adressiert: manuell vergebene Tile-Namen, gewählter `accType` (z.B. `energy-light` statt `energy-temperature`) und Router-Switch-Zustände überleben einen Wizard-Re-Run unverändert.
|
|
8
|
+
|
|
9
|
+
## Hintergrund
|
|
10
|
+
|
|
11
|
+
Der Wizard in v1.1.x baute den `smarthome[]`-Array beim Speichern (Schritt 5) **immer aus dem In-Memory-State neu auf**. Dieser State wurde aber nur teilweise (Schritt 4 / Router-Switches) aus der bestehenden Config hydratisiert — Schritte 1–3 fielen jedes Mal auf Auto-Detection-Defaults zurück. Die sichtbare Folge: nach einem Wizard-Re-Run zeigte Apple Home wieder °C statt lx, und Tile-Namen wie „PV Einspeisung Hausanschluss" wurden auf „Aktuelle Einspeisung" zurückgesetzt.
|
|
12
|
+
|
|
13
|
+
## Neuerungen
|
|
14
|
+
|
|
15
|
+
- **`loadExistingConfig()`** — zentrale Lade-Funktion, die beim Wizard-Start einmal `homebridge.getPluginConfig()` aufruft und Master-Device + `smarthome[]` in `wizardState.existingConfig` ablegt. Single source of truth für alle Step-Hydrations.
|
|
16
|
+
- **Schritt 1 (Verbindung):** Connection-Felder wie bisher vorausgefüllt, jetzt aber über `loadExistingConfig()` statt eines lokalen IIFE — konsistenter Pattern.
|
|
17
|
+
- **Schritt 2 (Geräte):** Für jedes durch Discovery gefundene AIN wird geprüft, ob bereits ein `smarthome[]`-Eintrag existiert. Wenn ja: User-`accType` und Custom-Name übernehmen statt Auto-Detection-Default. `energy-light`-Einträge werden auf Wizard-State-Ebene zu `energy-temperature` normalisiert, damit Schritt 3 die AINs weiterhin erkennt (die °C/lx-Wahl lebt nun in `energyAccType`).
|
|
18
|
+
- **Schritt 3 (Energy 250 / OBIS-Tiles):** Tile-Namen aus existierender Config gruppiert nach Sub-AIN übernehmen — fehlende Channels (`current_power`, `total_energy`) werden mit Defaults aufgefüllt. Der `energyAccType`-Radio (°C vs lx) wird global aus der Existing-Config vorausgewählt: wenn irgendein OBIS-Tile `energy-light` nutzt, ist der Light-Radio vorausgewählt.
|
|
19
|
+
- **Schritt 4 (Router-Switches):** unverändert, war schon korrekt — fungierte als Pattern-Vorlage.
|
|
20
|
+
- **Schritt 5 (Speichern):** Neue **Diff-Vorschau** zwischen alter und neuer Config: zeigt Tile-Umbenennungen, accType-Wechsel, neue/entfernte Geräte und geänderte Router-Switches als Liste. Wenn nichts sich ändert: „Keine Änderungen"-Hinweis. Macht klar was passieren wird, bevor man auf Speichern klickt.
|
|
21
|
+
- **Bonus:** Summary-Counter in Schritt 5 zählen jetzt `energy-temperature` UND `energy-light` als „Energy-Tiles" (vorher wurde `energy-light` als „normales Gerät" gezählt).
|
|
22
|
+
|
|
23
|
+
## Breaking Changes
|
|
24
|
+
|
|
25
|
+
Keine. Bestehende Configs werden ohne Migration übernommen. First-Run-Verhalten (keine FritzPlatform-Sektion in `config.json`) ist unverändert: alle Defaults wie in v1.1.x.
|
|
26
|
+
|
|
27
|
+
## Bekannte Limitationen
|
|
28
|
+
|
|
29
|
+
- **Globaler vs. per-Tile-`energyAccType`:** Die Wizard-UI behandelt °C/lx als globale Einstellung für alle OBIS-Tiles. Wenn jemand manuell in der `config.json` gemischte Tiles erstellt hat (einige `energy-temperature`, einige `energy-light`), zeigt der Wizard nur den Wert „energy-light" an (sobald ein Tile light ist, gilt global light). Beim Speichern werden dann alle Tiles auf `energy-light` vereinheitlicht. Workaround für Mix-Setups: nicht den Wizard zum Speichern benutzen, sondern `config.json` direkt bearbeiten.
|
|
30
|
+
- **Diff zeigt nur das was sich gegenüber der gespeicherten Config unterscheidet** — nicht das was sich gegenüber dem letzten Wizard-Lauf unterscheidet. Das ist by design, aber bedeutet: wenn man den Wizard mehrmals öffnet und schließt, ohne zu speichern, sieht man trotzdem nur den Diff zur letzten gespeicherten Version.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
# v1.1.1 — 2026-05-14 (LightSensor variant for energy tiles)
|
|
35
|
+
|
|
36
|
+
**Neuer accType `energy-light` als Alternative zu `energy-temperature`.** Beide nutzen dieselbe OBIS-Datenflow-Logik (Fritz!Smart Energy 250 sub-AINs), aber `energy-light` rendert die Werte als **LightSensor** (Einheit `lx`) statt **TemperatureSensor** (Einheit `°C`).
|
|
37
|
+
|
|
38
|
+
## Hintergrund
|
|
39
|
+
|
|
40
|
+
Apple Home rendert die zwei Sensor-Typen unterschiedlich:
|
|
41
|
+
- **TemperatureSensor:** prominentes Tile im Raum-View, °C-Einheit
|
|
42
|
+
- **LightSensor:** "passive sensor" im Status-Bereich oben, lx-Einheit — per Long-Press als Favorit ins Home-Tab holbar
|
|
43
|
+
|
|
44
|
+
Beide Einheiten (°C, lx) sind semantisch falsch für Energie-Werte. Manche Nutzer empfinden `lx` als abstrakter und weniger irreführend als `°C` (das auf Temperaturmessung schließen lässt). Außerdem kollidiert LightSensor weniger mit echten Temperatur-Sensoren wenn man viele davon hat (Thermostate etc.).
|
|
45
|
+
|
|
46
|
+
## Neuerungen
|
|
47
|
+
|
|
48
|
+
- **Neuer accType `energy-light`** (siehe `src/accessories/smarthome/energy-light/energy-light.accessory.js`). Eigenständiger Service, parallel zu `energy-temperature`. Beide bleiben dauerhaft verfügbar.
|
|
49
|
+
- **`config.schema.json`** mit neuer Option in der accType-Auswahl: "Energy Light (LightSensor, lx unit)".
|
|
50
|
+
- **Wizard Schritt 3** ergänzt um **Display-Auswahl** (Radio-Buttons): "Temperatur-Sensor (°C)" oder "Licht-Sensor (lx)". Pro Tile gilt die Auswahl beim Save.
|
|
51
|
+
- **Auto-Migration:** wenn ein Accessory den accType von `energy-temperature` zu `energy-light` wechselt (z.B. via Wizard-Re-Run), entfernt das Plugin den alten TemperatureSensor-Service sauber vor dem Anlegen des LightSensors.
|
|
52
|
+
|
|
53
|
+
## Breaking Changes
|
|
54
|
+
|
|
55
|
+
Keine. Bestehende `energy-temperature`-Configs laufen unverändert weiter. Wer auf LightSensor wechseln möchte: pro Smart-Home-Eintrag den `accType`-String von `"energy-temperature"` auf `"energy-light"` ändern, HB neu starten.
|
|
56
|
+
|
|
57
|
+
## Bekannte Limitationen
|
|
58
|
+
|
|
59
|
+
- **HAP minValue für AmbientLightLevel:** 0.0001 (nicht 0 möglich). Bei aktuellem Wert = 0 W (keine Last oder keine Einspeisung) zeigt das Tile `0.0001 lx` statt `0 lx`. Funktional kein Unterschied, kosmetisch leicht verwirrend für die Sekunde wo nix fließt.
|
|
60
|
+
- **LightSensor in Apple Home Status-Bereich:** standardmäßig wird das Tile nicht direkt im Raum angezeigt sondern in der Status-Zeile oben. Per Long-Press → "Im Zuhause anzeigen" als Favorit setzbar (siehe README).
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
5
64
|
# v1.1.0 — 2026-05-13 (Setup-Wizard Release)
|
|
6
65
|
|
|
7
66
|
**Neue Custom-UI mit Setup-Wizard.** Config UI X öffnet beim Klick auf das Plugin-Settings-Icon ab dieser Version unseren geführten 5-Schritt-Wizard statt des bisherigen JSON-Schema-Formulars. Das macht das Erstsetup deutlich freundlicher — kein Raten mehr bei Feldern wie `accType`, `obisChannel` oder AIN-Format.
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Production: läuft produktiv beim Maintainer mit 12 DECT-Thermostaten, 1 DECT-La
|
|
|
25
25
|
| FRITZ!Smart Light 500 / FRITZ!DECT 500 (DECT-Lampe) | `lightbulb` | An/Aus, Helligkeit, Farbe |
|
|
26
26
|
| FRITZ!Smart Control 440 (Temperatursensor) | `temperature` | Temperatur, Batterie |
|
|
27
27
|
| FRITZ!DECT 200 / 210 (schaltbare Steckdose) | `switch` | An/Aus + optional Energiemessung |
|
|
28
|
-
| FRITZ!Smart Energy 250 (OBIS-Reader, an ISKRA o.ä.) | `energy-temperature` | Aktuelle Leistung in W, kumulierte Energie in kWh — als TemperatureSensor
|
|
28
|
+
| FRITZ!Smart Energy 250 (OBIS-Reader, an ISKRA o.ä.) | `energy-temperature` oder `energy-light` | Aktuelle Leistung in W, kumulierte Energie in kWh — entweder als TemperatureSensor (°C-Tile, prominent im Raum) oder LightSensor (lx-Tile, passive Sensor-Anzeige, per Favorit ins Home-Tab holbar) |
|
|
29
29
|
| FRITZ!Box Router (Master) | (via `options`) | Gast-WLAN-Switch, WPS-Switch, optional weitere Toggles |
|
|
30
30
|
|
|
31
31
|
## Architektur
|
|
@@ -182,7 +182,12 @@ Für ein typisches PV-Setup mit Bezug + Einspeisung legst du **vier `smarthome[]
|
|
|
182
182
|
}
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
-
**Wieso TemperatureSensor?** HomeKit hat keinen nativen Service für Leistung oder Energie.
|
|
185
|
+
**Wieso TemperatureSensor (oder LightSensor)?** HomeKit hat keinen nativen Service für Leistung oder Energie. Es wird einer der beiden Sensor-Typen als Anzeige-Vehikel "missbraucht":
|
|
186
|
+
|
|
187
|
+
- **`energy-temperature`** (Standard) — TemperatureSensor mit `°C`-Einheit. Apple Home zeigt's prominent als eigenes Tile im Raum-View.
|
|
188
|
+
- **`energy-light`** — LightSensor mit `lx`-Einheit. Apple Home zeigt's als passiven Sensor (Status-Zeile oben). Per Long-Press → "Im Zuhause anzeigen" als Favorit-Tile aktivierbar. Die `lx`-Einheit ist semantisch genauso falsch wie `°C` — viele empfinden sie aber als weniger irreführend, weil seltener im Alltag verwendet.
|
|
189
|
+
|
|
190
|
+
Beide werden seit v1.1.1 unterstützt. Standard ist `energy-temperature`. Empfehlung: probier beide aus, behalte was dir besser gefällt. In der Home-App das Tile umbenennen ("Aktueller Verbrauch (W)", "Gesamt Einspeisung (kWh)") macht den Werte-Kontext eindeutig.
|
|
186
191
|
|
|
187
192
|
**Richtungs-Trennung:** AVM spiegelt den NET-Power-Wert auf beiden Sub-AINs (gleiche Zahl mit Vorzeichen). Das Plugin trennt das anhand des AIN-Suffixes — `-1` zeigt den Wert nur wenn `power > 0` (Bezug), `-2` zeigt `|power|` nur wenn `power < 0` (Einspeisung). Im Ruhe-/Inverse-Zustand zeigt die jeweils andere Kachel `0`.
|
|
188
193
|
|
package/config.schema.json
CHANGED
|
@@ -667,14 +667,20 @@
|
|
|
667
667
|
]
|
|
668
668
|
},
|
|
669
669
|
{
|
|
670
|
-
"title": "Energy Temperature (TemperatureSensor,
|
|
670
|
+
"title": "Energy Temperature (TemperatureSensor, °C unit)",
|
|
671
671
|
"enum": [
|
|
672
672
|
"energy-temperature"
|
|
673
673
|
]
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
"title": "Energy Light (LightSensor, lx unit)",
|
|
677
|
+
"enum": [
|
|
678
|
+
"energy-light"
|
|
679
|
+
]
|
|
674
680
|
}
|
|
675
681
|
],
|
|
676
682
|
"required": true,
|
|
677
|
-
"description": "The accessory type of the device. Use 'energy-temperature' for Fritz!Smart Energy 250 OBIS sub-AINs
|
|
683
|
+
"description": "The accessory type of the device. Use 'energy-temperature' or 'energy-light' for Fritz!Smart Energy 250 OBIS sub-AINs — both expose the value as a numeric sensor tile in Apple Home. 'energy-light' uses LightSensor (lx unit) which is a passive sensor and can be added to favorites; 'energy-temperature' uses TemperatureSensor (°C unit) which gets its own tile by default."
|
|
678
684
|
},
|
|
679
685
|
"obisChannel": {
|
|
680
686
|
"title": "OBIS Channel",
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
Custom UI for @pellebot/homebridge-fritz-platform-hb2
|
|
3
3
|
|
|
4
|
-
Phases A
|
|
4
|
+
Phases A–G implemented:
|
|
5
5
|
A. Foundation (server.js, public/index.html, customUi flag)
|
|
6
6
|
B. Connection test — Step 1 with form + /test-connection endpoint
|
|
7
7
|
C. Device discovery — Step 2 with auto-detected accType per device
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
D. Energy 250 OBIS-Reader auto-setup — Step 3 (4 tiles per reader)
|
|
9
|
+
E. Router-Switches — Step 4 (master.options pre-hydrated from config)
|
|
10
|
+
F. Save + Apply — Step 5 (writes config.json, restarts Homebridge)
|
|
11
|
+
G. Edit-Mode — re-run preserves user edits (tile names, accType, energy
|
|
12
|
+
display type, router-switches) by hydrating wizardState from the
|
|
13
|
+
existing config.json. Step 5 shows a diff of pending changes.
|
|
10
14
|
-->
|
|
11
15
|
<style>
|
|
12
16
|
.wizard-step-indicator {
|
|
@@ -198,6 +202,22 @@
|
|
|
198
202
|
kumulierte Energie (kWh) je Richtung (Bezug + Einspeisung) anlegen.
|
|
199
203
|
</div>
|
|
200
204
|
|
|
205
|
+
<div id="energy250DisplaySelector" style="margin-top: 1rem; display: none;">
|
|
206
|
+
<label style="font-weight: 600;">Anzeige-Typ in Apple Home:</label>
|
|
207
|
+
<div class="form-check" style="margin-top: 0.5rem;">
|
|
208
|
+
<input type="radio" name="energyDisplay" id="energyDisplay-temp" class="form-check-input" value="energy-temperature" checked>
|
|
209
|
+
<label class="form-check-label" for="energyDisplay-temp">
|
|
210
|
+
<strong>Temperatur-Sensor (°C)</strong> — bekommt automatisch eigene Tiles im Raum-View. Einheit °C ist semantisch falsch aber prominenter dargestellt.
|
|
211
|
+
</label>
|
|
212
|
+
</div>
|
|
213
|
+
<div class="form-check" style="margin-top: 0.5rem;">
|
|
214
|
+
<input type="radio" name="energyDisplay" id="energyDisplay-light" class="form-check-input" value="energy-light">
|
|
215
|
+
<label class="form-check-label" for="energyDisplay-light">
|
|
216
|
+
<strong>Licht-Sensor (lx)</strong> — Apple Home behandelt sie als passive Sensoren (oben im Status-Bereich). Per Long-Press kannst du sie als Favorit ins Home-Tab holen. Einheit lx ist abstrakter als °C, viele User finden's weniger irreführend.
|
|
217
|
+
</label>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
201
221
|
<div id="energy250Content" style="margin-top: 1rem;">
|
|
202
222
|
<!-- Generated dynamically from wizardState.deviceSelections -->
|
|
203
223
|
</div>
|
|
@@ -304,6 +324,9 @@
|
|
|
304
324
|
|
|
305
325
|
<div id="saveSummary" style="margin-top: 1rem;"></div>
|
|
306
326
|
|
|
327
|
+
<!-- Phase G: diff against existing config (empty on first run) -->
|
|
328
|
+
<div id="saveDiff" style="margin-top: 1rem;"></div>
|
|
329
|
+
|
|
307
330
|
<details style="margin-top: 1rem;">
|
|
308
331
|
<summary style="cursor: pointer;">Generierte Plugin-Config anzeigen (JSON-Vorschau)</summary>
|
|
309
332
|
<pre id="savePreview" style="background: var(--bs-tertiary-bg, #f8f9fa); padding: 1rem; border-radius: 0.5rem; overflow-x: auto; max-height: 400px; font-size: 0.75rem; margin-top: 0.5rem;"></pre>
|
|
@@ -327,7 +350,9 @@
|
|
|
327
350
|
discoveredDevices: [], // raw discovery output from step 2 load
|
|
328
351
|
deviceSelections: {}, // { ain: { include: bool, accType: string } } after step 2
|
|
329
352
|
energyTiles: {}, // { ain: [{ obisChannel, name }, ...] } after step 3
|
|
353
|
+
energyAccType: 'energy-temperature', // 'energy-temperature' | 'energy-light' — chosen in step 3
|
|
330
354
|
routerSwitches: {}, // { wifi_guest: bool, wps: bool, ... } after step 4
|
|
355
|
+
existingConfig: null, // Phase G: { master, smarthome[] } from current config.json (null = first run)
|
|
331
356
|
};
|
|
332
357
|
|
|
333
358
|
// ============ DOM References ============
|
|
@@ -367,23 +392,59 @@
|
|
|
367
392
|
});
|
|
368
393
|
}
|
|
369
394
|
|
|
370
|
-
// ============
|
|
371
|
-
//
|
|
372
|
-
|
|
395
|
+
// ============ Phase G — Edit-Mode (Existing-Config Hydration) ============
|
|
396
|
+
// Load the existing FritzPlatform config exactly once at wizard start so a
|
|
397
|
+
// re-run of the wizard doesn't blindly overwrite user customizations:
|
|
398
|
+
// - Step 1: prefills connection fields (host/user/pw/ssl/tr064)
|
|
399
|
+
// - Step 2: preserves user-chosen accType + custom names per AIN
|
|
400
|
+
// - Step 3: preserves OBIS tile names + the °C-vs-lx display choice
|
|
401
|
+
// - Step 4: hydrateRouterSwitches() already reads this directly
|
|
402
|
+
// - Step 5: drives the diff preview against the new config
|
|
403
|
+
//
|
|
404
|
+
// Falls back to defaults / auto-detection on first install (no FritzPlatform
|
|
405
|
+
// entry in config.json yet).
|
|
406
|
+
async function loadExistingConfig() {
|
|
373
407
|
try {
|
|
374
408
|
const allConfig = await homebridge.getPluginConfig();
|
|
375
409
|
const fritz = (allConfig || []).find((c) => c && c.platform === 'FritzPlatform');
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
if (typeof master.ssl === 'boolean') inputs.ssl.checked = master.ssl;
|
|
381
|
-
if (typeof master.tr064 === 'boolean') inputs.tr064.checked = master.tr064;
|
|
382
|
-
if (master.username) inputs.username.value = master.username;
|
|
383
|
-
if (master.password) inputs.password.value = master.password;
|
|
384
|
-
}
|
|
410
|
+
if (!fritz) return null;
|
|
411
|
+
const master = (fritz.devices || []).find((d) => d && d.master === true) || null;
|
|
412
|
+
const smarthome = Array.isArray(fritz.smarthome) ? fritz.smarthome : [];
|
|
413
|
+
return { master, smarthome };
|
|
385
414
|
} catch (err) {
|
|
386
|
-
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ============ Step 1 — Connection Test ============
|
|
420
|
+
// Prefill from existing plugin config. Also seeds wizardState.existingConfig
|
|
421
|
+
// and wizardState.energyAccType so later steps can hydrate from it.
|
|
422
|
+
(async () => {
|
|
423
|
+
wizardState.existingConfig = await loadExistingConfig();
|
|
424
|
+
const cfg = wizardState.existingConfig;
|
|
425
|
+
if (!cfg) return;
|
|
426
|
+
|
|
427
|
+
// Step 1 — connection fields
|
|
428
|
+
const master = cfg.master;
|
|
429
|
+
if (master) {
|
|
430
|
+
if (master.host) inputs.host.value = master.host;
|
|
431
|
+
if (master.port) inputs.port.value = master.port;
|
|
432
|
+
if (typeof master.ssl === 'boolean') inputs.ssl.checked = master.ssl;
|
|
433
|
+
if (typeof master.tr064 === 'boolean') inputs.tr064.checked = master.tr064;
|
|
434
|
+
if (master.username) inputs.username.value = master.username;
|
|
435
|
+
if (master.password) inputs.password.value = master.password;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Seed energyAccType from existing OBIS entries so the Step 3 radio
|
|
439
|
+
// preselects the right display type (temperature vs light). If ANY
|
|
440
|
+
// existing tile uses energy-light, treat the whole reader as light —
|
|
441
|
+
// the wizard applies one global type to all OBIS tiles on save.
|
|
442
|
+
const obisEntries = cfg.smarthome.filter(
|
|
443
|
+
(e) => e && (e.accType === 'energy-temperature' || e.accType === 'energy-light')
|
|
444
|
+
);
|
|
445
|
+
if (obisEntries.length > 0) {
|
|
446
|
+
const anyLight = obisEntries.some((e) => e.accType === 'energy-light');
|
|
447
|
+
wizardState.energyAccType = anyLight ? 'energy-light' : 'energy-temperature';
|
|
387
448
|
}
|
|
388
449
|
})();
|
|
389
450
|
|
|
@@ -492,15 +553,41 @@
|
|
|
492
553
|
{ value: 'skip', label: '— ignorieren —' },
|
|
493
554
|
];
|
|
494
555
|
|
|
556
|
+
// Phase G: index existing smarthome entries by AIN for quick lookup.
|
|
557
|
+
// Multiple OBIS entries may share an AIN (one per obisChannel); we just
|
|
558
|
+
// need the first one to recover the accType + custom name.
|
|
559
|
+
const existingSh = (wizardState.existingConfig && wizardState.existingConfig.smarthome) || [];
|
|
560
|
+
const existingByAin = {};
|
|
561
|
+
for (const e of existingSh) {
|
|
562
|
+
if (e && e.ain && !existingByAin[e.ain]) existingByAin[e.ain] = e;
|
|
563
|
+
}
|
|
564
|
+
|
|
495
565
|
const html = wizardState.discoveredDevices.map((d) => {
|
|
496
566
|
// Default-Selection: include if accType detected (not 'unknown' / 'skip')
|
|
497
567
|
const defaultInclude = d.accType !== 'unknown' && d.accType !== 'skip';
|
|
498
568
|
if (!wizardState.deviceSelections[d.ain]) {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
name
|
|
503
|
-
|
|
569
|
+
const existingEntry = existingByAin[d.ain];
|
|
570
|
+
if (existingEntry) {
|
|
571
|
+
// Phase G: AIN is already configured — preserve the user's chosen
|
|
572
|
+
// accType and name instead of overwriting with auto-detection.
|
|
573
|
+
// energy-light is normalized to energy-temperature at this layer
|
|
574
|
+
// so Step 3 picks the AIN up (energyAccType holds the display
|
|
575
|
+
// choice and is applied at save time).
|
|
576
|
+
const normalizedAccType = (existingEntry.accType === 'energy-light')
|
|
577
|
+
? 'energy-temperature'
|
|
578
|
+
: existingEntry.accType;
|
|
579
|
+
wizardState.deviceSelections[d.ain] = {
|
|
580
|
+
include: true,
|
|
581
|
+
accType: normalizedAccType,
|
|
582
|
+
name: existingEntry.name || d.name,
|
|
583
|
+
};
|
|
584
|
+
} else {
|
|
585
|
+
wizardState.deviceSelections[d.ain] = {
|
|
586
|
+
include: defaultInclude,
|
|
587
|
+
accType: d.accType,
|
|
588
|
+
name: d.name,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
504
591
|
}
|
|
505
592
|
const sel = wizardState.deviceSelections[d.ain];
|
|
506
593
|
|
|
@@ -678,8 +765,13 @@
|
|
|
678
765
|
}
|
|
679
766
|
|
|
680
767
|
// 2. OBIS Energy 250 tiles from Phase 3 (one entry per (sub-AIN x obisChannel))
|
|
768
|
+
// The accType is the one selected via the Step-3 display-type radio:
|
|
769
|
+
// 'energy-temperature' (default, °C tile) or 'energy-light' (lx sensor)
|
|
770
|
+
const energyAccType = wizardState.energyAccType || 'energy-temperature';
|
|
681
771
|
for (const [ain, tiles] of Object.entries(wizardState.energyTiles || {})) {
|
|
682
|
-
// Only include if user kept this sub-AIN in Phase 2
|
|
772
|
+
// Only include if user kept this sub-AIN in Phase 2 as energy-temperature
|
|
773
|
+
// (the wizard always uses energy-temperature in Phase 2 detection — display
|
|
774
|
+
// type is selected in Phase 3 and applied here at save time)
|
|
683
775
|
const sel = wizardState.deviceSelections[ain];
|
|
684
776
|
if (!sel || !sel.include || sel.accType !== 'energy-temperature') continue;
|
|
685
777
|
|
|
@@ -688,7 +780,7 @@
|
|
|
688
780
|
active: true,
|
|
689
781
|
name: tile.name,
|
|
690
782
|
ain,
|
|
691
|
-
accType:
|
|
783
|
+
accType: energyAccType,
|
|
692
784
|
obisChannel: tile.obisChannel,
|
|
693
785
|
});
|
|
694
786
|
}
|
|
@@ -705,13 +797,104 @@
|
|
|
705
797
|
};
|
|
706
798
|
}
|
|
707
799
|
|
|
800
|
+
/**
|
|
801
|
+
* Phase G: compute a human-readable list of changes the upcoming save will
|
|
802
|
+
* make against the currently persisted config.json. Empty/null on a first
|
|
803
|
+
* install (nothing to compare to).
|
|
804
|
+
*
|
|
805
|
+
* Key by (ain + obisChannel) for OBIS tiles (multiple per AIN), by ain
|
|
806
|
+
* only for everything else.
|
|
807
|
+
*/
|
|
808
|
+
function renderConfigDiff(newConfig, existing) {
|
|
809
|
+
if (!existing) return ''; // first run — nothing to diff
|
|
810
|
+
|
|
811
|
+
const lines = [];
|
|
812
|
+
|
|
813
|
+
// -- Master device fields --
|
|
814
|
+
const oldMaster = existing.master || {};
|
|
815
|
+
const newMaster = (newConfig.devices && newConfig.devices[0]) || {};
|
|
816
|
+
if ((oldMaster.host || '') !== (newMaster.host || '')) {
|
|
817
|
+
lines.push(`Host: <code>${escapeHtml(oldMaster.host || '—')}</code> → <code>${escapeHtml(newMaster.host || '—')}</code>`);
|
|
818
|
+
}
|
|
819
|
+
if ((oldMaster.username || '') !== (newMaster.username || '')) {
|
|
820
|
+
lines.push(`Username: <code>${escapeHtml(oldMaster.username || '—')}</code> → <code>${escapeHtml(newMaster.username || '—')}</code>`);
|
|
821
|
+
}
|
|
822
|
+
if (!!oldMaster.ssl !== !!newMaster.ssl) {
|
|
823
|
+
lines.push(`SSL: ${oldMaster.ssl ? 'an' : 'aus'} → ${newMaster.ssl ? 'an' : 'aus'}`);
|
|
824
|
+
}
|
|
825
|
+
if ((oldMaster.port || 49000) !== (newMaster.port || 49000)) {
|
|
826
|
+
lines.push(`Port: <code>${oldMaster.port || 49000}</code> → <code>${newMaster.port || 49000}</code>`);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// -- Router-Switches (master.options) --
|
|
830
|
+
const oldOpts = (oldMaster.options) || {};
|
|
831
|
+
const newOpts = (newMaster.options) || {};
|
|
832
|
+
const allOptKeys = new Set([...Object.keys(oldOpts), ...Object.keys(newOpts)]);
|
|
833
|
+
for (const k of allOptKeys) {
|
|
834
|
+
const wasOn = !!oldOpts[k];
|
|
835
|
+
const isOn = !!newOpts[k];
|
|
836
|
+
if (wasOn && !isOn) lines.push(`Router-Switch <code>${escapeHtml(k)}</code> entfernt`);
|
|
837
|
+
else if (!wasOn && isOn) lines.push(`Router-Switch <code>${escapeHtml(k)}</code> hinzugefügt`);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// -- Smarthome entries (devices + OBIS tiles) --
|
|
841
|
+
const oldSh = existing.smarthome || [];
|
|
842
|
+
const newSh = newConfig.smarthome || [];
|
|
843
|
+
const keyOf = (e) => e.obisChannel ? `${e.ain}::${e.obisChannel}` : e.ain;
|
|
844
|
+
const oldByKey = {};
|
|
845
|
+
const newByKey = {};
|
|
846
|
+
for (const e of oldSh) if (e && e.ain) oldByKey[keyOf(e)] = e;
|
|
847
|
+
for (const e of newSh) if (e && e.ain) newByKey[keyOf(e)] = e;
|
|
848
|
+
|
|
849
|
+
for (const k of Object.keys(newByKey)) {
|
|
850
|
+
const n = newByKey[k];
|
|
851
|
+
const o = oldByKey[k];
|
|
852
|
+
if (!o) {
|
|
853
|
+
const chan = n.obisChannel ? ` <small>(${escapeHtml(n.obisChannel)})</small>` : '';
|
|
854
|
+
lines.push(`Neu: <code>${escapeHtml(n.ain)}</code>${chan} — <em>${escapeHtml(n.accType)}</em> "${escapeHtml(n.name || '')}"`);
|
|
855
|
+
} else {
|
|
856
|
+
if ((o.accType || '') !== (n.accType || '')) {
|
|
857
|
+
lines.push(`<code>${escapeHtml(n.ain)}</code>${n.obisChannel ? ` <small>(${escapeHtml(n.obisChannel)})</small>` : ''}: accType <em>${escapeHtml(o.accType || '—')}</em> → <em>${escapeHtml(n.accType || '—')}</em>`);
|
|
858
|
+
}
|
|
859
|
+
if ((o.name || '') !== (n.name || '')) {
|
|
860
|
+
lines.push(`<code>${escapeHtml(n.ain)}</code>${n.obisChannel ? ` <small>(${escapeHtml(n.obisChannel)})</small>` : ''}: Name "${escapeHtml(o.name || '—')}" → "${escapeHtml(n.name || '—')}"`);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
for (const k of Object.keys(oldByKey)) {
|
|
865
|
+
if (newByKey[k]) continue;
|
|
866
|
+
const o = oldByKey[k];
|
|
867
|
+
const chan = o.obisChannel ? ` <small>(${escapeHtml(o.obisChannel)})</small>` : '';
|
|
868
|
+
lines.push(`Entfernt: <code>${escapeHtml(o.ain)}</code>${chan} — war <em>${escapeHtml(o.accType)}</em> "${escapeHtml(o.name || '')}"`);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
if (lines.length === 0) {
|
|
872
|
+
return `
|
|
873
|
+
<div class="alert alert-secondary">
|
|
874
|
+
<strong>Keine Änderungen</strong> gegenüber der aktuellen <code>config.json</code>.
|
|
875
|
+
Du kannst trotzdem speichern (no-op) oder den Wizard verlassen.
|
|
876
|
+
</div>
|
|
877
|
+
`;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
return `
|
|
881
|
+
<div class="alert alert-warning">
|
|
882
|
+
<div style="font-weight: 600; margin-bottom: 0.5rem;">📝 Änderungen gegenüber der aktuellen Konfiguration (${lines.length}):</div>
|
|
883
|
+
<ul style="margin: 0; padding-left: 1.25rem; font-size: 0.9rem;">
|
|
884
|
+
${lines.map((l) => `<li>${l}</li>`).join('')}
|
|
885
|
+
</ul>
|
|
886
|
+
</div>
|
|
887
|
+
`;
|
|
888
|
+
}
|
|
889
|
+
|
|
708
890
|
function renderSavePreview() {
|
|
709
891
|
const config = buildPluginConfig();
|
|
710
892
|
const summary = document.getElementById('saveSummary');
|
|
711
893
|
const preview = document.getElementById('savePreview');
|
|
894
|
+
const diffEl = document.getElementById('saveDiff');
|
|
712
895
|
|
|
713
|
-
const nDevices = config.smarthome.filter((s) => s.accType !== 'energy-temperature').length;
|
|
714
|
-
const nEnergy = config.smarthome.filter((s) => s.accType === 'energy-temperature').length;
|
|
896
|
+
const nDevices = config.smarthome.filter((s) => s.accType !== 'energy-temperature' && s.accType !== 'energy-light').length;
|
|
897
|
+
const nEnergy = config.smarthome.filter((s) => s.accType === 'energy-temperature' || s.accType === 'energy-light').length;
|
|
715
898
|
const nRouter = Object.keys((config.devices[0] && config.devices[0].options) || {}).length;
|
|
716
899
|
|
|
717
900
|
// Mask password in preview for safety
|
|
@@ -733,6 +916,10 @@
|
|
|
733
916
|
`;
|
|
734
917
|
|
|
735
918
|
preview.textContent = JSON.stringify(maskedConfig, null, 2);
|
|
919
|
+
|
|
920
|
+
// Phase G: render diff against the config that was on disk when the
|
|
921
|
+
// wizard opened, so the user sees exactly what will change.
|
|
922
|
+
diffEl.innerHTML = renderConfigDiff(config, wizardState.existingConfig);
|
|
736
923
|
}
|
|
737
924
|
|
|
738
925
|
const saveBtn = document.getElementById('saveBtn');
|
|
@@ -843,7 +1030,10 @@
|
|
|
843
1030
|
const obisSelections = Object.entries(wizardState.deviceSelections)
|
|
844
1031
|
.filter(([ain, sel]) => sel.include && sel.accType === 'energy-temperature' && /-[12]$/.test(ain));
|
|
845
1032
|
|
|
1033
|
+
const displaySelector = document.getElementById('energy250DisplaySelector');
|
|
1034
|
+
|
|
846
1035
|
if (obisSelections.length === 0) {
|
|
1036
|
+
displaySelector.style.display = 'none';
|
|
847
1037
|
container.innerHTML =
|
|
848
1038
|
'<div class="alert alert-secondary">' +
|
|
849
1039
|
'Keine OBIS-Sub-AINs in der Geräte-Auswahl gefunden. ' +
|
|
@@ -855,6 +1045,21 @@
|
|
|
855
1045
|
return;
|
|
856
1046
|
}
|
|
857
1047
|
|
|
1048
|
+
// Show display-type selector when OBIS tiles are present
|
|
1049
|
+
displaySelector.style.display = 'block';
|
|
1050
|
+
|
|
1051
|
+
// Preselect from wizardState (in case user navigated back)
|
|
1052
|
+
const desiredRadio = wizardState.energyAccType === 'energy-light' ? 'energyDisplay-light' : 'energyDisplay-temp';
|
|
1053
|
+
const radioEl = document.getElementById(desiredRadio);
|
|
1054
|
+
if (radioEl) radioEl.checked = true;
|
|
1055
|
+
|
|
1056
|
+
// Wire change handler
|
|
1057
|
+
document.querySelectorAll('input[name="energyDisplay"]').forEach((r) => {
|
|
1058
|
+
r.addEventListener('change', () => {
|
|
1059
|
+
if (r.checked) wizardState.energyAccType = r.value;
|
|
1060
|
+
});
|
|
1061
|
+
});
|
|
1062
|
+
|
|
858
1063
|
// Group OBIS sub-AINs by their base AIN
|
|
859
1064
|
const byBase = {};
|
|
860
1065
|
for (const [ain, sel] of obisSelections) {
|
|
@@ -871,13 +1076,50 @@
|
|
|
871
1076
|
'-2': { current_power: 'Aktuelle Einspeisung', total_energy: 'Gesamt Einspeisung' },
|
|
872
1077
|
};
|
|
873
1078
|
|
|
1079
|
+
// Phase G: index existing OBIS tiles by AIN so we can preserve the
|
|
1080
|
+
// user's custom tile names instead of resetting to "Aktuelle Einspeisung"
|
|
1081
|
+
// etc. on every wizard re-run. Existing config may store one OR two
|
|
1082
|
+
// entries per sub-AIN (current_power, total_energy) — we keep whatever
|
|
1083
|
+
// is there and only fill in the missing channel with the default name.
|
|
1084
|
+
const existingObisByAin = {};
|
|
1085
|
+
const existingShAll = (wizardState.existingConfig && wizardState.existingConfig.smarthome) || [];
|
|
1086
|
+
for (const e of existingShAll) {
|
|
1087
|
+
if (!e || !e.ain) continue;
|
|
1088
|
+
if (e.accType !== 'energy-temperature' && e.accType !== 'energy-light') continue;
|
|
1089
|
+
if (!existingObisByAin[e.ain]) existingObisByAin[e.ain] = [];
|
|
1090
|
+
existingObisByAin[e.ain].push({
|
|
1091
|
+
obisChannel: e.obisChannel || 'current_power',
|
|
1092
|
+
name: e.name,
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
|
|
874
1096
|
// Initialize state for each OBIS AIN if not already there
|
|
875
1097
|
for (const baseAin of Object.keys(byBase)) {
|
|
876
1098
|
const group = byBase[baseAin];
|
|
877
1099
|
for (const dir of ['-1', '-2']) {
|
|
878
1100
|
const entry = dir === '-1' ? group.ain1 : group.ain2;
|
|
879
1101
|
if (!entry) continue;
|
|
880
|
-
if (
|
|
1102
|
+
if (wizardState.energyTiles[entry.ain]) continue;
|
|
1103
|
+
|
|
1104
|
+
const existingTiles = existingObisByAin[entry.ain];
|
|
1105
|
+
if (existingTiles && existingTiles.length > 0) {
|
|
1106
|
+
// Phase G: rebuild from existing config, preserving names.
|
|
1107
|
+
// Ensure both channels exist (wizard UI always renders 2 tiles
|
|
1108
|
+
// per sub-AIN — current_power first, total_energy second).
|
|
1109
|
+
const byChan = {};
|
|
1110
|
+
for (const t of existingTiles) byChan[t.obisChannel] = t;
|
|
1111
|
+
const tiles = [
|
|
1112
|
+
{
|
|
1113
|
+
obisChannel: 'current_power',
|
|
1114
|
+
name: (byChan.current_power && byChan.current_power.name) || defaultNames[dir].current_power,
|
|
1115
|
+
},
|
|
1116
|
+
{
|
|
1117
|
+
obisChannel: 'total_energy',
|
|
1118
|
+
name: (byChan.total_energy && byChan.total_energy.name) || defaultNames[dir].total_energy,
|
|
1119
|
+
},
|
|
1120
|
+
];
|
|
1121
|
+
wizardState.energyTiles[entry.ain] = tiles;
|
|
1122
|
+
} else {
|
|
881
1123
|
wizardState.energyTiles[entry.ain] = [
|
|
882
1124
|
{ obisChannel: 'current_power', name: defaultNames[dir].current_power },
|
|
883
1125
|
{ obisChannel: 'total_energy', name: defaultNames[dir].total_energy },
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellebot/homebridge-fritz-platform-hb2",
|
|
3
3
|
"displayName": "Homebridge FRITZ!Box Platform (HB2)",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"description": "Homebridge 2.0 plugin for AVM FRITZ!DECT devices (thermostats, lamps, sensors, OBIS smart-meter readers) and FRITZ!Box router switches (Guest WLAN, WPS). Node 22 / 24 ready.",
|
|
6
6
|
"icon": "https://raw.githubusercontent.com/PelleBot/homebridge-fritz-platform-ng/main/branding/Icon.png",
|
|
7
7
|
"type": "module",
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const logger = require('../../../utils/logger');
|
|
4
|
+
const Handler = require('../smarthome.handler');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Energy-light accessory — same data flow as energy-temperature, but exposes
|
|
8
|
+
* the value as a LightSensor service (CurrentAmbientLightLevel in lx) instead
|
|
9
|
+
* of a TemperatureSensor.
|
|
10
|
+
*
|
|
11
|
+
* Why two service types for the same data? Apple Home renders sensor types
|
|
12
|
+
* differently:
|
|
13
|
+
* - TemperatureSensor: prominent tile in room view, °C unit, history graph
|
|
14
|
+
* - LightSensor: passive sensor (status bar), lx unit, can be made
|
|
15
|
+
* favorite to get a dedicated tile
|
|
16
|
+
*
|
|
17
|
+
* For users who don't want the °C-mislabeling of energy values, this accType
|
|
18
|
+
* uses LightSensor as a more abstract numeric display vehicle. The lx-unit
|
|
19
|
+
* is also incorrect for energy but psychologically less confusing than °C
|
|
20
|
+
* because it is uncommon to interpret it.
|
|
21
|
+
*
|
|
22
|
+
* Designed for AVM Fritz!Smart Energy 250 in OBIS-reader mode — see
|
|
23
|
+
* energy-temperature.accessory.js for full data-flow rationale; this file
|
|
24
|
+
* is a near-clone with the service swapped.
|
|
25
|
+
*
|
|
26
|
+
* Config:
|
|
27
|
+
* accType: "energy-light"
|
|
28
|
+
* ain: "<base-AIN>-1" (Bezug / OBIS 1.8) or "<base-AIN>-2" (Einspeisung / OBIS 2.8)
|
|
29
|
+
* obisChannel:
|
|
30
|
+
* "current_power" -> Math.abs(powermeter.power) in W (default)
|
|
31
|
+
* "total_energy" -> powermeter.energy in kWh
|
|
32
|
+
*
|
|
33
|
+
* AmbientLightLevel value semantics:
|
|
34
|
+
* - lx range: 0.0001 (HAP minimum) up to 100000 by default — we override to
|
|
35
|
+
* 999999 max so multi-year kWh totals fit. minValue stays at 0.0001 (HAP
|
|
36
|
+
* does not allow 0); a value of 0 in the handler maps to 0.0001 at the
|
|
37
|
+
* characteristic level to satisfy validation.
|
|
38
|
+
*/
|
|
39
|
+
class Accessory {
|
|
40
|
+
constructor(api, accessory, accessories, meshMaster, HistoryService) {
|
|
41
|
+
this.api = api;
|
|
42
|
+
this.accessory = accessory;
|
|
43
|
+
this.HistoryService = HistoryService;
|
|
44
|
+
|
|
45
|
+
this.handler = Handler.configure(api, accessories, accessory.context.config.polling, meshMaster);
|
|
46
|
+
this.getService();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async getService() {
|
|
50
|
+
// Remove any leftover TemperatureSensor service from a previous accType
|
|
51
|
+
// configuration (e.g. user switched from energy-temperature to energy-light).
|
|
52
|
+
const oldTemp = this.accessory.getService(this.api.hap.Service.TemperatureSensor);
|
|
53
|
+
if (oldTemp) {
|
|
54
|
+
logger.info(
|
|
55
|
+
'Removing legacy TemperatureSensor service (accType switched to energy-light)',
|
|
56
|
+
`${this.accessory.displayName} (${this.accessory.context.config.subtype})`
|
|
57
|
+
);
|
|
58
|
+
this.accessory.removeService(oldTemp);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let service = this.accessory.getService(this.api.hap.Service.LightSensor);
|
|
62
|
+
|
|
63
|
+
if (!service) {
|
|
64
|
+
logger.info(
|
|
65
|
+
'Adding LightSensor service (energy-light, read-only)',
|
|
66
|
+
`${this.accessory.displayName} (${this.accessory.context.config.subtype})`
|
|
67
|
+
);
|
|
68
|
+
service = this.accessory.addService(
|
|
69
|
+
this.api.hap.Service.LightSensor,
|
|
70
|
+
this.accessory.displayName,
|
|
71
|
+
this.accessory.context.config.subtype
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Expand the upper bound so high kWh totals (multi-year cumulative) don't
|
|
76
|
+
// get clipped. HAP minValue must stay positive (lx is non-negative).
|
|
77
|
+
service.getCharacteristic(this.api.hap.Characteristic.CurrentAmbientLightLevel).setProps({
|
|
78
|
+
minValue: 0.0001,
|
|
79
|
+
maxValue: 999999,
|
|
80
|
+
minStep: 0.0001,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = Accessory;
|
|
@@ -58,10 +58,7 @@ class Accessory {
|
|
|
58
58
|
'Toggling not allowed for energy-meter (read-only sensor)',
|
|
59
59
|
`${this.accessory.displayName} (${this.accessory.context.config.subtype})`
|
|
60
60
|
);
|
|
61
|
-
setTimeout(
|
|
62
|
-
() => service.getCharacteristic(this.api.hap.Characteristic.On).updateValue(true),
|
|
63
|
-
1000
|
|
64
|
-
);
|
|
61
|
+
setTimeout(() => service.getCharacteristic(this.api.hap.Characteristic.On).updateValue(true), 1000);
|
|
65
62
|
});
|
|
66
63
|
|
|
67
64
|
// OutletInUse always true so the tile renders as active in HomeKit
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Config = (smarthomeConfig) => {
|
|
4
|
-
const validTypes = [
|
|
4
|
+
const validTypes = [
|
|
5
|
+
'switch',
|
|
6
|
+
'contact',
|
|
7
|
+
'thermostat',
|
|
8
|
+
'lightbulb',
|
|
9
|
+
'temperature',
|
|
10
|
+
'window',
|
|
11
|
+
'blind',
|
|
12
|
+
'button',
|
|
13
|
+
'smoke',
|
|
14
|
+
'energy-meter',
|
|
15
|
+
'energy-temperature',
|
|
16
|
+
'energy-light',
|
|
17
|
+
];
|
|
5
18
|
const validObisChannels = ['current_power', 'total_energy'];
|
|
6
19
|
const validTypesGroup = ['switch', 'lightbulb', 'thermostat', 'switch-lightbulb'];
|
|
7
20
|
const validButtons = [1, 4];
|
|
@@ -680,6 +680,63 @@ class Handler {
|
|
|
680
680
|
|
|
681
681
|
return state;
|
|
682
682
|
}
|
|
683
|
+
case 'smarthome-energy-light': {
|
|
684
|
+
// Energy-light: LightSensor variant — same data flow as energy-temperature
|
|
685
|
+
// but uses CurrentAmbientLightLevel (lx) instead of CurrentTemperature (°C)
|
|
686
|
+
// as the display vehicle. HAP requires lx >= 0.0001, so we clamp 0 -> 0.0001.
|
|
687
|
+
let service = accessory.getService(this.api.hap.Service.LightSensor);
|
|
688
|
+
|
|
689
|
+
try {
|
|
690
|
+
let device = this.smarthomeList.devices.find((device) => device.ain.includes(accessory.context.config.ain));
|
|
691
|
+
logger.debug(device, `${accessory.displayName} (${subtype})`);
|
|
692
|
+
|
|
693
|
+
if (device) {
|
|
694
|
+
accessory.context.config.ain = device.ain;
|
|
695
|
+
|
|
696
|
+
if (device.online) {
|
|
697
|
+
if (device.powermeter) {
|
|
698
|
+
const channel = accessory.context.config.obisChannel || 'current_power';
|
|
699
|
+
let value = 0;
|
|
700
|
+
|
|
701
|
+
if (channel === 'current_power') {
|
|
702
|
+
const rawPower = device.powermeter.power || 0;
|
|
703
|
+
const ain = device.ain || accessory.context.config.ain || '';
|
|
704
|
+
if (ain.endsWith('-1')) {
|
|
705
|
+
value = rawPower > 0 ? rawPower : 0;
|
|
706
|
+
} else if (ain.endsWith('-2')) {
|
|
707
|
+
value = rawPower < 0 ? -rawPower : 0;
|
|
708
|
+
} else {
|
|
709
|
+
value = Math.abs(rawPower);
|
|
710
|
+
}
|
|
711
|
+
} else if (channel === 'total_energy') {
|
|
712
|
+
value = device.powermeter.energy || 0;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// HAP minimum for CurrentAmbientLightLevel is 0.0001 — clamp 0 to that
|
|
716
|
+
if (value <= 0) value = 0.0001;
|
|
717
|
+
service.getCharacteristic(this.api.hap.Characteristic.CurrentAmbientLightLevel).updateValue(value);
|
|
718
|
+
} else {
|
|
719
|
+
logger.warn(
|
|
720
|
+
'Can not find powermeter data — sub-AIN (-1 / -2) correct?',
|
|
721
|
+
`${accessory.displayName} (${subtype})`
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
} else {
|
|
725
|
+
logger.warn('Device offline!', `${accessory.displayName} (${subtype})`);
|
|
726
|
+
}
|
|
727
|
+
} else {
|
|
728
|
+
logger.warn(
|
|
729
|
+
`Can not find device with AIN: ${accessory.context.config.ain}`,
|
|
730
|
+
`${accessory.displayName} (${subtype})`
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
} catch (err) {
|
|
734
|
+
logger.warn('An error occured during getting state!', `${accessory.displayName} (${subtype})`);
|
|
735
|
+
logger.error(err, `${accessory.displayName} (${subtype})`);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return true;
|
|
739
|
+
}
|
|
683
740
|
case 'smarthome-energy-temperature': {
|
|
684
741
|
// Energy-temperature: TemperatureSensor used to expose either current
|
|
685
742
|
// power (W) or cumulative energy (kWh) as a numeric value in HomeKit.
|
|
@@ -687,9 +744,7 @@ class Handler {
|
|
|
687
744
|
let service = accessory.getService(this.api.hap.Service.TemperatureSensor);
|
|
688
745
|
|
|
689
746
|
try {
|
|
690
|
-
let device = this.smarthomeList.devices.find((device) =>
|
|
691
|
-
device.ain.includes(accessory.context.config.ain)
|
|
692
|
-
);
|
|
747
|
+
let device = this.smarthomeList.devices.find((device) => device.ain.includes(accessory.context.config.ain));
|
|
693
748
|
logger.debug(device, `${accessory.displayName} (${subtype})`);
|
|
694
749
|
|
|
695
750
|
if (device) {
|
|
@@ -755,9 +810,7 @@ class Handler {
|
|
|
755
810
|
let service = accessory.getService(this.api.hap.Service.Outlet);
|
|
756
811
|
|
|
757
812
|
try {
|
|
758
|
-
let device = this.smarthomeList.devices.find((device) =>
|
|
759
|
-
device.ain.includes(accessory.context.config.ain)
|
|
760
|
-
);
|
|
813
|
+
let device = this.smarthomeList.devices.find((device) => device.ain.includes(accessory.context.config.ain));
|
|
761
814
|
logger.debug(device, `${accessory.displayName} (${subtype})`);
|
|
762
815
|
|
|
763
816
|
if (device) {
|
|
@@ -774,13 +827,9 @@ class Handler {
|
|
|
774
827
|
const currentPower = Math.abs(device.powermeter.power || 0);
|
|
775
828
|
const totalEnergy = device.powermeter.energy || 0;
|
|
776
829
|
|
|
777
|
-
service
|
|
778
|
-
.getCharacteristic(this.api.hap.Characteristic.CurrentConsumption)
|
|
779
|
-
.updateValue(currentPower);
|
|
830
|
+
service.getCharacteristic(this.api.hap.Characteristic.CurrentConsumption).updateValue(currentPower);
|
|
780
831
|
|
|
781
|
-
service
|
|
782
|
-
.getCharacteristic(this.api.hap.Characteristic.TotalConsumption)
|
|
783
|
-
.updateValue(totalEnergy);
|
|
832
|
+
service.getCharacteristic(this.api.hap.Characteristic.TotalConsumption).updateValue(totalEnergy);
|
|
784
833
|
|
|
785
834
|
service.getCharacteristic(this.api.hap.Characteristic.On).updateValue(true);
|
|
786
835
|
service.getCharacteristic(this.api.hap.Characteristic.OutletInUse).updateValue(true);
|
|
@@ -6,6 +6,7 @@ module.exports = {
|
|
|
6
6
|
SHContactAccessory: require('./contact/contact.accessory'),
|
|
7
7
|
SHEnergyMeterAccessory: require('./energy-meter/energy-meter.accessory'),
|
|
8
8
|
SHEnergyTemperatureAccessory: require('./energy-temperature/energy-temperature.accessory'),
|
|
9
|
+
SHEnergyLightAccessory: require('./energy-light/energy-light.accessory'),
|
|
9
10
|
SHHumidityAccessory: require('./humidity/humidity.accessory'),
|
|
10
11
|
SHLightbulbAccessory: require('./lightbulb/lightbulb.accessory'),
|
|
11
12
|
SHOutletAccessory: require('./outlet/outlet.accessory'),
|
package/src/platform.js
CHANGED
|
@@ -27,6 +27,7 @@ const {
|
|
|
27
27
|
SHContactAccessory,
|
|
28
28
|
SHEnergyMeterAccessory,
|
|
29
29
|
SHEnergyTemperatureAccessory,
|
|
30
|
+
SHEnergyLightAccessory,
|
|
30
31
|
SHHumidityAccessory,
|
|
31
32
|
SHLightbulbAccessory,
|
|
32
33
|
SHOutletAccessory,
|
|
@@ -75,7 +76,9 @@ function FritzPlatform(log, config, api) {
|
|
|
75
76
|
HistoryService.prototype.addEntry = function () {};
|
|
76
77
|
HistoryService.prototype.cleanPersist = function () {};
|
|
77
78
|
HistoryService.prototype.setExtraPersistedData = function () {};
|
|
78
|
-
HistoryService.prototype.getExtraPersistedData = function () {
|
|
79
|
+
HistoryService.prototype.getExtraPersistedData = function () {
|
|
80
|
+
return null;
|
|
81
|
+
};
|
|
79
82
|
|
|
80
83
|
this.api = api;
|
|
81
84
|
this.log = log;
|
|
@@ -208,9 +211,9 @@ FritzPlatform.prototype = {
|
|
|
208
211
|
else if (device.subtype === 'smarthome-window-switch')
|
|
209
212
|
new SHWindowSwitchAccessory(this.api, accessory, this.accessories, this.meshMaster);
|
|
210
213
|
else if (device.subtype === 'smarthome-lightbulb')
|
|
211
|
-
|
|
214
|
+
new SHLightbulbAccessory(this.api, accessory, this.accessories, this.meshMaster);
|
|
212
215
|
else if (device.subtype === 'smarthome-smoke')
|
|
213
|
-
|
|
216
|
+
new SHSmokeAccessory(this.api, accessory, this.accessories, this.meshMaster, HistoryService);
|
|
214
217
|
else if (device.subtype === 'smarthome-switch-lightbulb' && device.energy)
|
|
215
218
|
new SHOutletLightbulbAccessory(this.api, accessory, this.accessories, this.meshMaster, HistoryService);
|
|
216
219
|
else if (device.subtype === 'smarthome-switch-lightbulb' && !device.energy)
|
|
@@ -219,6 +222,8 @@ FritzPlatform.prototype = {
|
|
|
219
222
|
new SHEnergyMeterAccessory(this.api, accessory, this.accessories, this.meshMaster, HistoryService);
|
|
220
223
|
else if (device.subtype === 'smarthome-energy-temperature')
|
|
221
224
|
new SHEnergyTemperatureAccessory(this.api, accessory, this.accessories, this.meshMaster, HistoryService);
|
|
225
|
+
else if (device.subtype === 'smarthome-energy-light')
|
|
226
|
+
new SHEnergyLightAccessory(this.api, accessory, this.accessories, this.meshMaster, HistoryService);
|
|
222
227
|
break;
|
|
223
228
|
default:
|
|
224
229
|
logger.warn(`Can not setup accessory, type (${device.type}) unknown!`, device.name);
|
|
@@ -47,82 +47,105 @@ exports.registerWith = (hap) => {
|
|
|
47
47
|
|
|
48
48
|
// ---- WiFi toggles ----
|
|
49
49
|
declare('WifiTwo', 'WIFI 2.4GHZ', '0026e147-5d51-4f42-b157-6aca6050be8e', {
|
|
50
|
-
format: 'bool',
|
|
50
|
+
format: 'bool',
|
|
51
|
+
perms: PR_PW_EV,
|
|
51
52
|
});
|
|
52
53
|
declare('WifiFive', 'WIFI 5GHZ', 'a72aeeca-c6ce-45ce-b026-5d400aab5fc9', {
|
|
53
|
-
format: 'bool',
|
|
54
|
+
format: 'bool',
|
|
55
|
+
perms: PR_PW_EV,
|
|
54
56
|
});
|
|
55
57
|
declare('WifiGuest', 'WIFI Guest', 'a87bbf2b-885c-4713-8169-22abdbf0b2a1', {
|
|
56
|
-
format: 'bool',
|
|
58
|
+
format: 'bool',
|
|
59
|
+
perms: PR_PW_EV,
|
|
57
60
|
});
|
|
58
61
|
declare('WifiWPS', 'WIFI WPS', '991dd58c-7d8c-46b1-acd1-411d8f6902ac', {
|
|
59
|
-
format: 'bool',
|
|
62
|
+
format: 'bool',
|
|
63
|
+
perms: PR_PW_EV,
|
|
60
64
|
});
|
|
61
65
|
|
|
62
66
|
// ---- FritzBox device toggles ----
|
|
63
67
|
declare('Reconnect', 'Reconnect', 'ce1cb310-d2d8-4cd1-9686-c6f2aba6ad15', {
|
|
64
|
-
format: 'bool',
|
|
68
|
+
format: 'bool',
|
|
69
|
+
perms: PR_PW_EV,
|
|
65
70
|
});
|
|
66
71
|
declare('DECT', 'DECT Station', '1718fc65-453b-403a-ab81-79a1c96ba195', {
|
|
67
|
-
format: 'bool',
|
|
72
|
+
format: 'bool',
|
|
73
|
+
perms: PR_PW_EV,
|
|
68
74
|
});
|
|
69
75
|
declare('AnsweringMachine', 'Answering Machine', 'd19ef9d5-3bc6-47ac-b6bb-7bdcf0df20b0', {
|
|
70
|
-
format: 'bool',
|
|
76
|
+
format: 'bool',
|
|
77
|
+
perms: PR_PW_EV,
|
|
71
78
|
});
|
|
72
79
|
declare('Deflection', 'Deflection', '658086c7-274c-4988-bd5e-3c720fa3054d', {
|
|
73
|
-
format: 'bool',
|
|
80
|
+
format: 'bool',
|
|
81
|
+
perms: PR_PW_EV,
|
|
74
82
|
});
|
|
75
83
|
declare('WakeUp', 'Wake Up', 'c60a4aa9-cefb-4c61-8185-ad2b3ba52842', {
|
|
76
|
-
format: 'bool',
|
|
84
|
+
format: 'bool',
|
|
85
|
+
perms: PR_PW_EV,
|
|
77
86
|
});
|
|
78
87
|
|
|
79
88
|
// ---- Device-control toggles ----
|
|
80
89
|
declare('DeviceLED', 'LED', 'fd81f0dc-4324-457e-8164-289743873fb1', {
|
|
81
|
-
format: 'bool',
|
|
90
|
+
format: 'bool',
|
|
91
|
+
perms: PR_PW_EV,
|
|
82
92
|
});
|
|
83
93
|
declare('DeviceLock', 'Device Lock', '56af4239-46bc-4bae-b55b-dbc5be2d0897', {
|
|
84
|
-
format: 'bool',
|
|
94
|
+
format: 'bool',
|
|
95
|
+
perms: PR_PW_EV,
|
|
85
96
|
});
|
|
86
97
|
declare('RingLock', 'Ring Lock', 'cab7d43e-422c-4452-bc9a-11c89454332b', {
|
|
87
|
-
format: 'bool',
|
|
98
|
+
format: 'bool',
|
|
99
|
+
perms: PR_PW_EV,
|
|
88
100
|
});
|
|
89
101
|
|
|
90
102
|
// ---- Phone & alarm ----
|
|
91
103
|
declare('PhoneBook', 'Refresh Phone Book', '8f9aeaa4-092f-4c3a-85f1-dfd064a07c3d', {
|
|
92
|
-
format: 'bool',
|
|
104
|
+
format: 'bool',
|
|
105
|
+
perms: PR_PW_EV,
|
|
93
106
|
});
|
|
94
107
|
declare('DialAlarm', 'Alarm', '8fe6e841-41e4-479f-b334-8af339ce5b30', {
|
|
95
|
-
format: 'bool',
|
|
108
|
+
format: 'bool',
|
|
109
|
+
perms: PR_PW_EV,
|
|
96
110
|
});
|
|
97
111
|
|
|
98
112
|
// ---- Network ----
|
|
99
113
|
declare('DNSServer', 'DNS Server', 'c34f1eb0-92bb-44a8-b399-17f2599639f1', {
|
|
100
|
-
format: 'bool',
|
|
114
|
+
format: 'bool',
|
|
115
|
+
perms: PR_PW_EV,
|
|
101
116
|
});
|
|
102
117
|
declare('FallbackInternet', 'Internet Fallback', 'ef088934-4ec8-4174-b550-e8a8faaed88c', {
|
|
103
|
-
format: 'bool',
|
|
118
|
+
format: 'bool',
|
|
119
|
+
perms: PR_PW_EV,
|
|
104
120
|
});
|
|
105
121
|
|
|
106
122
|
// ---- Read-only display (callmonitor is out of scope but the router
|
|
107
123
|
// accessory may still reference Caller/Called via Service.ContactSensor
|
|
108
124
|
// optionals — registering here keeps router/router.accessory.js safe). ----
|
|
109
125
|
declare('Caller', 'Last Caller', 'eb0b2d83-569b-44aa-989d-190a911b4397', {
|
|
110
|
-
format: 'string',
|
|
126
|
+
format: 'string',
|
|
127
|
+
perms: PR_EV,
|
|
111
128
|
});
|
|
112
129
|
declare('Called', 'Last Called', 'cf42e4a1-ff61-4aa6-9cc5-55d3c09cfbbd', {
|
|
113
|
-
format: 'string',
|
|
130
|
+
format: 'string',
|
|
131
|
+
perms: PR_EV,
|
|
114
132
|
});
|
|
115
133
|
|
|
116
134
|
// ---- Bandwidth / connection stats ----
|
|
117
135
|
declare('Download', 'Download', '37574b8e-2d7c-47ee-8b5e-6bfc42f195d9', {
|
|
118
|
-
format: 'string',
|
|
136
|
+
format: 'string',
|
|
137
|
+
perms: PR_EV,
|
|
119
138
|
});
|
|
120
139
|
declare('Upload', 'Upload', '9b2e94f7-a665-4575-9efd-1b37474d758b', {
|
|
121
|
-
format: 'string',
|
|
140
|
+
format: 'string',
|
|
141
|
+
perms: PR_EV,
|
|
122
142
|
});
|
|
123
143
|
declare('Ping', 'Ping', 'ce18aaef-1026-4538-943b-026501599dc0', {
|
|
124
|
-
format: 'float',
|
|
125
|
-
|
|
144
|
+
format: 'float',
|
|
145
|
+
perms: PR_EV,
|
|
146
|
+
maxValue: 9999,
|
|
147
|
+
minValue: 0,
|
|
148
|
+
minStep: 0.1,
|
|
126
149
|
});
|
|
127
150
|
|
|
128
151
|
// NOTE on Service.Switch: original file overrode the built-in HAP Switch
|
package/src/types/eve.types.js
CHANGED
|
@@ -49,58 +49,83 @@ exports.registerWith = (hap) => {
|
|
|
49
49
|
// History-related (used by fakegato-history; our stub no-ops them but
|
|
50
50
|
// accessory code may still reference these as registered types).
|
|
51
51
|
declare('ResetTotal', 'Reset Total', 'E863F112-079E-48FF-8F27-9C2605A29F52', {
|
|
52
|
-
format: 'uint32',
|
|
52
|
+
format: 'uint32',
|
|
53
|
+
unit: 'seconds',
|
|
54
|
+
perms: PR_EV_PW,
|
|
53
55
|
});
|
|
54
56
|
declare('HistoryStatus', 'History Status', 'E863F116-079E-48FF-8F27-9C2605A29F52', {
|
|
55
|
-
format: 'data',
|
|
57
|
+
format: 'data',
|
|
58
|
+
perms: PR_EV_PW,
|
|
56
59
|
});
|
|
57
60
|
declare('HistoryEntries', 'History Entries', 'E863F117-079E-48FF-8F27-9C2605A29F52', {
|
|
58
|
-
format: 'data',
|
|
61
|
+
format: 'data',
|
|
62
|
+
perms: PR_EV_PW,
|
|
59
63
|
});
|
|
60
64
|
declare('HistoryRequest', 'History Request', 'E863F11C-079E-48FF-8F27-9C2605A29F52', {
|
|
61
|
-
format: 'data',
|
|
65
|
+
format: 'data',
|
|
66
|
+
perms: PR_EV_PW,
|
|
62
67
|
});
|
|
63
68
|
declare('SetTime', 'Set Time', 'E863F121-079E-48FF-8F27-9C2605A29F52', {
|
|
64
|
-
format: 'data',
|
|
69
|
+
format: 'data',
|
|
70
|
+
perms: PR_EV_PW,
|
|
65
71
|
});
|
|
66
72
|
|
|
67
73
|
// Window / contact sensor (Eve-extended)
|
|
68
74
|
declare('LastActivation', 'Last Activation', 'E863F11A-079E-48FF-8F27-9C2605A29F52', {
|
|
69
|
-
format: 'uint32',
|
|
75
|
+
format: 'uint32',
|
|
76
|
+
unit: 'seconds',
|
|
77
|
+
perms: PR_EV,
|
|
70
78
|
});
|
|
71
79
|
declare('TimesOpened', 'Times Opened', 'E863F129-079E-48FF-8F27-9C2605A29F52', {
|
|
72
|
-
format: 'uint32',
|
|
80
|
+
format: 'uint32',
|
|
81
|
+
perms: PR_EV,
|
|
73
82
|
});
|
|
74
83
|
declare('OpenDuration', 'Open Duration', 'E863F118-079E-48FF-8F27-9C2605A29F52', {
|
|
75
|
-
format: 'uint32',
|
|
84
|
+
format: 'uint32',
|
|
85
|
+
unit: 'seconds',
|
|
86
|
+
perms: PR_EV_PW,
|
|
76
87
|
});
|
|
77
88
|
declare('ClosedDuration', 'Closed Duration', 'E863F119-079E-48FF-8F27-9C2605A29F52', {
|
|
78
|
-
format: 'uint32',
|
|
89
|
+
format: 'uint32',
|
|
90
|
+
unit: 'seconds',
|
|
91
|
+
perms: PR_EV_PW,
|
|
79
92
|
});
|
|
80
93
|
|
|
81
94
|
// Outlet / energy (Eve-extended)
|
|
82
95
|
declare('CurrentConsumption', 'Current Consumption', 'E863F10D-079E-48FF-8F27-9C2605A29F52', {
|
|
83
|
-
format: 'float',
|
|
96
|
+
format: 'float',
|
|
97
|
+
unit: 'W',
|
|
98
|
+
perms: PR_EV,
|
|
84
99
|
});
|
|
85
100
|
declare('TotalConsumption', 'Total Consumption', 'E863F10C-079E-48FF-8F27-9C2605A29F52', {
|
|
86
|
-
format: 'float',
|
|
101
|
+
format: 'float',
|
|
102
|
+
unit: 'kWh',
|
|
103
|
+
perms: PR_EV,
|
|
87
104
|
});
|
|
88
105
|
declare('Volts', 'Volts', 'E863F10A-079E-48FF-8F27-9C2605A29F52', {
|
|
89
|
-
format: 'float',
|
|
106
|
+
format: 'float',
|
|
107
|
+
unit: 'V',
|
|
108
|
+
perms: PR_EV,
|
|
90
109
|
});
|
|
91
110
|
declare('Amperes', 'Amperes', 'E863F126-079E-48FF-8F27-9C2605A29F52', {
|
|
92
|
-
format: 'float',
|
|
111
|
+
format: 'float',
|
|
112
|
+
unit: 'A',
|
|
113
|
+
perms: PR_EV,
|
|
93
114
|
});
|
|
94
115
|
|
|
95
116
|
// Thermostat (Eve-extended)
|
|
96
117
|
declare('ValvePosition', 'Valve Position', 'E863F12E-079E-48FF-8F27-9C2605A29F52', {
|
|
97
|
-
format: 'uint8',
|
|
118
|
+
format: 'uint8',
|
|
119
|
+
unit: 'percentage',
|
|
120
|
+
perms: PR_EV,
|
|
98
121
|
});
|
|
99
122
|
declare('ProgramCommand', 'Program Command', 'E863F12C-079E-48FF-8F27-9C2605A29F52', {
|
|
100
|
-
format: 'data',
|
|
123
|
+
format: 'data',
|
|
124
|
+
perms: ['pw'],
|
|
101
125
|
});
|
|
102
126
|
declare('ProgramData', 'Program Data', 'E863F12F-079E-48FF-8F27-9C2605A29F52', {
|
|
103
|
-
format: 'data',
|
|
127
|
+
format: 'data',
|
|
128
|
+
perms: PR_EV,
|
|
104
129
|
});
|
|
105
130
|
|
|
106
131
|
// NOTE: The original file overrode Service.Outlet, Service.Thermostat,
|