@pellebot/homebridge-fritz-platform-hb2 1.2.1 → 1.3.1
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/CHANGELOG.md +62 -0
- package/README.md +48 -2
- package/config.schema.json +127 -0
- package/homebridge-ui/public/index.html +144 -30
- package/package.json +4 -3
- package/src/accessories/presence/motion/motion.accessory.js +84 -0
- package/src/accessories/presence/occupancy/occupancy.accessory.js +55 -0
- package/src/accessories/presence/presence.config.js +36 -0
- package/src/accessories/presence/presence.handler.js +322 -0
- package/src/accessories/presence/presence.js +9 -0
- package/src/accessories/presence/presence.setup.js +102 -0
- package/src/accessories/presence/presence_guest.handler.js +138 -0
- package/src/platform.js +19 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,66 @@
|
|
|
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.3.1 — 2026-11-15 (Wizard: weniger Knöpfe)
|
|
6
|
+
|
|
7
|
+
**Schritt 5 des Setup-Wizards ist aufgeräumt.** Rückmeldung aus dem Betrieb: zu viele Knöpfe für eine Aufgabe.
|
|
8
|
+
|
|
9
|
+
## Änderungen
|
|
10
|
+
|
|
11
|
+
- Es gibt nur noch einen Speichern-Knopf: Der „Save“-Knopf von Config UI X wird im Wizard ausgegraut (`homebridge.disableSaveButton()`, bei älteren Versionen ohne diese Funktion bleibt er unverändert).
|
|
12
|
+
- Der Knopf „Homebridge jetzt neu starten“ entfällt. Er konnte aus dem Wizard heraus nicht neu starten (die Plugin-UI-Schnittstelle bietet das nicht an) und zeigte danach einen zweiten Hinweiskasten mit `sudo hb-service restart`. Nach dem Speichern erscheint jetzt ein einziger Hinweis: Neustart über das Symbol oben rechts in Config UI X.
|
|
13
|
+
- Einleitungstext von Schritt 5 gekürzt.
|
|
14
|
+
|
|
15
|
+
## Breaking Changes
|
|
16
|
+
|
|
17
|
+
Keine. Config-Format unverändert.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# v1.3.0 — 2026-11-15 (Presence als Opt-in)
|
|
22
|
+
|
|
23
|
+
**WLAN-Anwesenheit („Presence“) ist als optionale Funktion zurück.** Auf Anfrage aus der Community (Haushalt nur mit Apple-Geräten) zeigt das Plugin wieder an, ob ein Gerät — typisch ein iPhone — im Netz der FRITZ!Box ist. Standardmäßig ist die Funktion aus; sie wirkt nur für `presence`-Einträge mit `active: true`.
|
|
24
|
+
|
|
25
|
+
## Neuerungen
|
|
26
|
+
|
|
27
|
+
- **Presence-Sensor je Gerät**: Zuordnung über IPv4- oder MAC-Adresse (Groß-/Kleinschreibung egal), Accessory-Typ `occupancy` (Belegungssensor, Standard) oder `motion` (Bewegungssensor). Grundlage ist der vorhandene Code des Original-Plugins in `src/accessories/presence/`, nach dem Minimal-Patch-Prinzip wieder eingebunden.
|
|
28
|
+
- **Abmeldeverzögerung `offDelay`** (Standard **300 s**): Der Sensor meldet „weg“ erst, wenn das Gerät so lange nicht mehr verbunden war. Verhindert das Springen, wenn ein schlafendes iPhone kurz aus dem WLAN geht. Unter 60 s warnt das Log. `onDelay` (Standard 0 s) für die Anmeldung.
|
|
29
|
+
- **Optional „Anyone“** (jemand ist zu Hause) und **„Guest“** (Gerät im Gast-WLAN) über `options.presence`.
|
|
30
|
+
- **`config.schema.json`**: neuer Abschnitt „Presence (WLAN, optional)“ und `options.presence`.
|
|
31
|
+
- **Setup-Wizard**: Schritt 4 hat einen aufklappbaren Abschnitt „Anwesenheit per WLAN“ (Geräte anlegen, Anyone/Guest). Bestehende `presence`-Einträge werden beim erneuten Speichern übernommen und nicht mehr verworfen.
|
|
32
|
+
- **CI**: Mock-Test `ci/presence-mock-test.cjs` (Opt-in, Verzögerung, MAC-Abgleich, Anyone/Guest) auf Node 22 und 24.
|
|
33
|
+
|
|
34
|
+
## Korrekturen am übernommenen Code
|
|
35
|
+
|
|
36
|
+
- MAC-Adressen in Kleinbuchstaben wurden bisher verworfen bzw. nicht gefunden; jetzt werden sie normalisiert und ohne Beachtung der Schreibweise abgeglichen.
|
|
37
|
+
- Fehlt `offDelay`, gilt jetzt 300 s statt 0 s (vorher sprang der Sensor bei jeder kurzen WLAN-Abmeldung). Ein ausdrücklich gesetzter Wert, auch 0, bleibt erhalten.
|
|
38
|
+
- Aufruf von `historyService.getInitialTime()` im Motion-Pfad wird defensiv geprüft (siehe Issue #2).
|
|
39
|
+
- `src/accessories/presence/` ist nicht mehr in `.npmignore` ausgeschlossen (sonst fehlte der Code im npm-Paket).
|
|
40
|
+
|
|
41
|
+
## Nicht übernommen
|
|
42
|
+
|
|
43
|
+
Ping-Prüfung (`ping`, `threshold`) und Telegram-Meldungen (`blockTelegram`) des Original-Plugins.
|
|
44
|
+
|
|
45
|
+
## Breaking Changes
|
|
46
|
+
|
|
47
|
+
Keine. Ohne `presence`-Konfiguration verhält sich das Plugin wie v1.2.x. Wer aus dem Original-Plugin noch `presence`-Einträge mit `active: true` in der Config hat, bekommt diese Sensoren jetzt wieder angezeigt.
|
|
48
|
+
|
|
49
|
+
## Getestet
|
|
50
|
+
|
|
51
|
+
Mock-Test in der CI (Node 22/24) und Hardwaretest am 15.11.2026: Homebridge 2.4.0, FRITZ!Box mit drei FRITZ!Repeatern im Mesh, iPhone mit fester privater WLAN-Adresse. Anmeldung erkannt, 25 min gesperrtes iPhone ohne Fehlwechsel, Abmeldung nach 309 s (300 s Verzögerung + Polling), erneute Anmeldung erkannt. Nicht getestet: Dauerlauf über Nacht, Motion-Typ, Gast-WLAN.
|
|
52
|
+
|
|
53
|
+
## Bekannte Limitationen
|
|
54
|
+
|
|
55
|
+
- iPhones mit „private WLAN-Adresse: Rotierend“ wechseln die MAC; für das Heim-WLAN auf „Fest“ stellen (README).
|
|
56
|
+
- Die am iPhone angezeigte WLAN-Adresse kann von der abweichen, die die FRITZ!Box führt (beobachtet mit FRITZ!Repeatern im Mesh). MAC-Adresse aus der FRITZ!Box-Oberfläche nehmen oder besser die IPv4-Adresse dort dauerhaft zuweisen und die IP eintragen.
|
|
57
|
+
- Je Sensor nur eine Adresse.
|
|
58
|
+
- Die FRITZ!Box meldet nur „verbunden“, nicht „Person anwesend“. Erkennung frühestens nach einem Polling-Zyklus (`options.polling.timer`) plus Verzögerung.
|
|
59
|
+
- Jeder Zyklus fragt die Host-Liste der FRITZ!Box ab (zusätzliche Last).
|
|
60
|
+
- Der Bewegungssensor zeigt in der Eve-App keinen Verlauf (fakegato gestubbt).
|
|
61
|
+
- Die Charakteristik „Last Activation“ des Bewegungssensors zählt ab Start der Bridge (fakegato-Stub mit `getInitialTime()` aus v1.2.1).
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
5
65
|
# v1.2.1 — 2026-11-14 (Bugfix: Absturz bei offenem Fenster)
|
|
6
66
|
|
|
7
67
|
**Behebt #2.** Sobald ein Thermostat ein offenes Fenster meldete (`windowOpen: 1`) oder ein Kontakt-/Fenstersensor öffnete, stürzte die Child-Bridge mit `TypeError: historyService.getInitialTime is not a function` ab. Nach fünf Abstürzen stoppte Homebridge die Child-Bridge, und das ganze Plugin fiel aus, solange das Fenster offen war.
|
|
@@ -19,6 +79,8 @@ Der NoOp-Ersatz für fakegato-history in `src/platform.js` bildete nur `addEntry
|
|
|
19
79
|
|
|
20
80
|
Keine.
|
|
21
81
|
|
|
82
|
+
---
|
|
83
|
+
|
|
22
84
|
# v1.2.0 — 2026-05-14 (Wizard Edit-Mode / Phase G)
|
|
23
85
|
|
|
24
86
|
**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.
|
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ Production: läuft produktiv beim Maintainer mit 12 DECT-Thermostaten, 1 DECT-La
|
|
|
27
27
|
| FRITZ!DECT 200 / 210 (schaltbare Steckdose) | `switch` | An/Aus + optional Energiemessung |
|
|
28
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
|
+
| Handy/Tablet im WLAN (optional, seit v1.3.0) | `presence[]` mit `occupancy` oder `motion` | Anwesenheit: Belegungs- oder Bewegungssensor je Gerät, optional „Anyone“ und „Guest“ |
|
|
30
31
|
|
|
31
32
|
## Architektur
|
|
32
33
|
|
|
@@ -42,13 +43,14 @@ Das **Router-Accessory** (Master FritzBox) wird per default mit `hide: true` aus
|
|
|
42
43
|
Aus dem ursprünglichen Code-Baum entfernt oder nicht migriert — wer eine dieser Funktionen braucht, ist hier nicht richtig:
|
|
43
44
|
|
|
44
45
|
- **Callmonitor** (Anrufüberwachung)
|
|
45
|
-
- **Presence** (WLAN-Anwesenheit)
|
|
46
46
|
- **Network-Watcher**
|
|
47
47
|
- **Wake-on-LAN**
|
|
48
48
|
- **Child Lock** (Internet-Sperren)
|
|
49
49
|
- **Extras-Subsysteme** (DNS-Switch, Fallback-Internet, Phonebook, Telegram-Notifications) — die WiFi/WPS-Switches der Extras-Datei sind ausgenommen und voll funktional
|
|
50
50
|
- **`fakegato-history`** (Eve-App History-Graphen — als No-Op gestubbt weil HAP-v2-inkompatibel)
|
|
51
51
|
|
|
52
|
+
**Presence** (WLAN-Anwesenheit) war bis v1.2.0 ausgeschlossen und ist seit **v1.3.0 als optionale Funktion** zurück — standardmäßig aus, siehe [Anwesenheit per WLAN](#anwesenheit-per-wlan-optional-seit-v130). Telegram-Meldungen und die Ping-Prüfung des Original-Plugins gehören nicht dazu.
|
|
53
|
+
|
|
52
54
|
## Voraussetzungen
|
|
53
55
|
|
|
54
56
|
- **Homebridge** ≥ 1.8.5 oder ≥ 2.0.0
|
|
@@ -191,6 +193,50 @@ Beide werden seit v1.1.1 unterstützt. Standard ist `energy-temperature`. Empfeh
|
|
|
191
193
|
|
|
192
194
|
**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`.
|
|
193
195
|
|
|
196
|
+
### Anwesenheit per WLAN (optional, seit v1.3.0)
|
|
197
|
+
|
|
198
|
+
Zeigt in HomeKit, ob ein Gerät (typisch: iPhone) im Netz der FRITZ!Box ist — als **Belegungssensor** (`occupancy`, Standard) oder **Bewegungssensor** (`motion`). Damit lassen sich Automationen wie „Letzte Person geht → Heizung runter“ bauen, ohne Standortfreigabe in der Home-App.
|
|
199
|
+
|
|
200
|
+
Die Funktion ist **Opt-in**: Ohne `presence`-Einträge mit `active: true` passiert nichts. Einrichtung entweder im Setup-Wizard (Schritt 4, Abschnitt „Anwesenheit per WLAN“) oder in der `config.json`:
|
|
201
|
+
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"presence": [
|
|
205
|
+
{
|
|
206
|
+
"active": true,
|
|
207
|
+
"name": "iPhone Anna",
|
|
208
|
+
"address": "AA:BB:CC:DD:EE:FF",
|
|
209
|
+
"accType": "occupancy",
|
|
210
|
+
"offDelay": 300
|
|
211
|
+
}
|
|
212
|
+
],
|
|
213
|
+
"options": {
|
|
214
|
+
"presence": { "anyone": true, "guest": false, "accType": "occupancy" }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
| Feld | Bedeutung |
|
|
220
|
+
|---|---|
|
|
221
|
+
| `address` | IPv4- oder MAC-Adresse wie in der FRITZ!Box unter *Heimnetz → Netzwerk* angezeigt (Groß-/Kleinschreibung egal) |
|
|
222
|
+
| `accType` | `occupancy` (Belegung) oder `motion` (Bewegung) |
|
|
223
|
+
| `onDelay` | Sekunden, die das Gerät verbunden sein muss, bevor der Sensor „erkannt“ meldet (Standard 0) |
|
|
224
|
+
| `offDelay` | Sekunden, die das Gerät weg sein muss, bevor der Sensor „nicht erkannt“ meldet (**Standard 300**) |
|
|
225
|
+
| `options.presence.anyone` | zusätzlicher Sensor „Anyone“: erkannt, solange mindestens ein Gerät erkannt ist |
|
|
226
|
+
| `options.presence.guest` | zusätzlicher Sensor „Guest“: erkannt, solange ein Gerät im Gast-WLAN ist |
|
|
227
|
+
|
|
228
|
+
**Wichtig für iPhone/iPad — private WLAN-Adresse:** iOS nutzt je WLAN eine eigene „private WLAN-Adresse“, die sich je nach Einstellung (*Rotierend*) regelmäßig ändert. Damit die Zuordnung hält:
|
|
229
|
+
|
|
230
|
+
1. Auf dem iPhone *Einstellungen → WLAN → (i) neben dem Heim-WLAN → Private WLAN-Adresse* auf **Fest** stellen (oder ausschalten). Die neue Adresse gilt erst nach dem nächsten Verbinden: WLAN einmal aus- und wieder einschalten.
|
|
231
|
+
2. In der FRITZ!Box das Gerät öffnen (*Heimnetz → Netzwerk → Gerät → Heimnetz*) und die **MAC-Adresse von dort** übernehmen, **nicht** die am iPhone angezeigte. Beide können abweichen, im Test mit FRITZ!Repeatern im Mesh war das der Fall.
|
|
232
|
+
3. **Besser:** dort **„IPv4-Adresse dauerhaft zuweisen“** aktivieren und diese IP als `address` eintragen. Das ist die robusteste Zuordnung.
|
|
233
|
+
|
|
234
|
+
Bleibt ein Sensor dauerhaft auf „nicht erkannt“, obwohl das Gerät im WLAN ist: in der FRITZ!Box nachsehen, unter welcher MAC/IP es gerade geführt wird, und die Config anpassen.
|
|
235
|
+
|
|
236
|
+
**Warum die Abmeldeverzögerung:** Schlafende iPhones trennen sich zum Stromsparen immer wieder kurz vom WLAN. Ohne Verzögerung springt der Sensor ständig zwischen „zu Hause“ und „weg“. `offDelay` unter 60 s erzeugt deshalb eine Warnung im Log; 300 s (5 min) ist ein guter Startwert, bei Fehlalarmen auf 600–900 s erhöhen. Die Erkennung ist so schnell wie das Polling (`options.polling.timer`, Standard 15 s) plus Verzögerung.
|
|
237
|
+
|
|
238
|
+
**Grenzen:** Die FRITZ!Box meldet nur, ob das Gerät verbunden ist — nicht, ob es wirklich jemand bei sich trägt. Jeder Abfragezyklus fragt die Host-Liste der FRITZ!Box ab (zusätzliche Last bei vielen Geräten oder kurzem Polling-Intervall). Meldet das Log beim Abruf der Host-Liste einen Berechtigungsfehler, dem FRITZ!Box-Benutzer zusätzlich *FRITZ!Box-Einstellungen* geben (wie bei den Router-Switches). Beim Bewegungssensor zeigt die Eve-App keine Verlaufsgrafik (fakegato ist gestubbt).
|
|
239
|
+
|
|
194
240
|
## Migration vom Original-Plugin
|
|
195
241
|
|
|
196
242
|
Wenn du bisher das unmaintained Original `homebridge-fritz-platform@6.0.19` eingesetzt hast:
|
|
@@ -199,7 +245,7 @@ Wenn du bisher das unmaintained Original `homebridge-fritz-platform@6.0.19` eing
|
|
|
199
245
|
2. **Update Homebridge auf 2.x**: `sudo npm install -g homebridge@latest`
|
|
200
246
|
3. **Original-Plugin entfernen**: `sudo npm uninstall -g homebridge-fritz-platform`
|
|
201
247
|
4. **Dieses Plugin installieren**: `sudo npm install -g @pellebot/homebridge-fritz-platform-hb2`
|
|
202
|
-
5. **Config anpassen**: nicht-mehr-unterstützte Subsysteme aus der Config entfernen (`
|
|
248
|
+
5. **Config anpassen**: nicht-mehr-unterstützte Subsysteme aus der Config entfernen (`callmonitor`, `wol`, `childLock`, `network`, `extras`, `telegram` — werden eh ignoriert, aber sauberer wenn weg). Bestehende `presence`-Einträge werden seit v1.3.0 wieder ausgewertet; `offDelay` prüfen (Standard jetzt 300 s), `ping`/`threshold`/`blockTelegram` werden nicht unterstützt.
|
|
203
249
|
6. **Homebridge neu starten**
|
|
204
250
|
|
|
205
251
|
Accessory-UUIDs basieren auf AIN + Name. Wer Device-Namen unverändert lässt, behält das HomeKit-Pairing bei und muss nichts in der Home-App neu zuordnen.
|
package/config.schema.json
CHANGED
|
@@ -708,6 +708,69 @@
|
|
|
708
708
|
}
|
|
709
709
|
}
|
|
710
710
|
},
|
|
711
|
+
"presence": {
|
|
712
|
+
"title": "Presence (WLAN, optional)",
|
|
713
|
+
"type": "array",
|
|
714
|
+
"description": "Opt-in since v1.3.0. Exposes an occupancy or motion sensor that shows whether a device (e.g. an iPhone) is connected to the FRITZ!Box network. Off unless an entry is set to active.",
|
|
715
|
+
"items": {
|
|
716
|
+
"type": "object",
|
|
717
|
+
"properties": {
|
|
718
|
+
"active": {
|
|
719
|
+
"title": "Active",
|
|
720
|
+
"type": "boolean",
|
|
721
|
+
"default": false,
|
|
722
|
+
"description": "If enabled, the presence sensor will be exposed to HomeKit."
|
|
723
|
+
},
|
|
724
|
+
"name": {
|
|
725
|
+
"title": "Name",
|
|
726
|
+
"type": "string",
|
|
727
|
+
"placeholder": "iPhone Andreas",
|
|
728
|
+
"description": "Name of the sensor in HomeKit. Must be unique and must not be \"Anyone\" or \"Guest\".",
|
|
729
|
+
"required": true
|
|
730
|
+
},
|
|
731
|
+
"address": {
|
|
732
|
+
"title": "IP or MAC address",
|
|
733
|
+
"type": "string",
|
|
734
|
+
"placeholder": "AA:BB:CC:DD:EE:FF",
|
|
735
|
+
"description": "IPv4 or MAC address of the device as shown in the FRITZ!Box (Heimnetz > Netzwerk). iPhones use a private Wi-Fi address: set it to \"Fixed\" for the home network, reconnect once, then take the MAC address from the FRITZ!Box device details (not the one shown on the phone) or, better, assign a permanent IPv4 address there and use that IP.",
|
|
736
|
+
"required": true
|
|
737
|
+
},
|
|
738
|
+
"accType": {
|
|
739
|
+
"title": "Accessory Type",
|
|
740
|
+
"type": "string",
|
|
741
|
+
"default": "occupancy",
|
|
742
|
+
"oneOf": [
|
|
743
|
+
{
|
|
744
|
+
"title": "Occupancy Sensor",
|
|
745
|
+
"enum": [
|
|
746
|
+
"occupancy"
|
|
747
|
+
]
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
"title": "Motion Sensor",
|
|
751
|
+
"enum": [
|
|
752
|
+
"motion"
|
|
753
|
+
]
|
|
754
|
+
}
|
|
755
|
+
]
|
|
756
|
+
},
|
|
757
|
+
"onDelay": {
|
|
758
|
+
"title": "Arrival delay (seconds)",
|
|
759
|
+
"type": "integer",
|
|
760
|
+
"minimum": 0,
|
|
761
|
+
"placeholder": 0,
|
|
762
|
+
"description": "Seconds the device must be connected before the sensor switches to detected. Default 0."
|
|
763
|
+
},
|
|
764
|
+
"offDelay": {
|
|
765
|
+
"title": "Leave delay (seconds)",
|
|
766
|
+
"type": "integer",
|
|
767
|
+
"minimum": 0,
|
|
768
|
+
"placeholder": 300,
|
|
769
|
+
"description": "Seconds the device must be disconnected before the sensor switches to not detected. Default 300. Sleeping iPhones leave the Wi-Fi for short periods; values below 60 make the sensor flap."
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
},
|
|
711
774
|
"options": {
|
|
712
775
|
"titel": "Options",
|
|
713
776
|
"type": "object",
|
|
@@ -740,6 +803,43 @@
|
|
|
740
803
|
}
|
|
741
804
|
}
|
|
742
805
|
}
|
|
806
|
+
},
|
|
807
|
+
"presence": {
|
|
808
|
+
"title": "Presence",
|
|
809
|
+
"type": "object",
|
|
810
|
+
"properties": {
|
|
811
|
+
"anyone": {
|
|
812
|
+
"title": "\"Anyone\" sensor",
|
|
813
|
+
"type": "boolean",
|
|
814
|
+
"default": false,
|
|
815
|
+
"description": "Additional sensor that is detected as long as at least one active presence device is detected."
|
|
816
|
+
},
|
|
817
|
+
"guest": {
|
|
818
|
+
"title": "\"Guest\" sensor",
|
|
819
|
+
"type": "boolean",
|
|
820
|
+
"default": false,
|
|
821
|
+
"description": "Additional sensor that is detected while at least one device is connected to the guest WLAN."
|
|
822
|
+
},
|
|
823
|
+
"accType": {
|
|
824
|
+
"title": "Accessory Type for Anyone/Guest",
|
|
825
|
+
"type": "string",
|
|
826
|
+
"default": "occupancy",
|
|
827
|
+
"oneOf": [
|
|
828
|
+
{
|
|
829
|
+
"title": "Occupancy Sensor",
|
|
830
|
+
"enum": [
|
|
831
|
+
"occupancy"
|
|
832
|
+
]
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
"title": "Motion Sensor",
|
|
836
|
+
"enum": [
|
|
837
|
+
"motion"
|
|
838
|
+
]
|
|
839
|
+
}
|
|
840
|
+
]
|
|
841
|
+
}
|
|
842
|
+
}
|
|
743
843
|
}
|
|
744
844
|
}
|
|
745
845
|
}
|
|
@@ -902,6 +1002,33 @@
|
|
|
902
1002
|
}
|
|
903
1003
|
]
|
|
904
1004
|
},
|
|
1005
|
+
{
|
|
1006
|
+
"title": "Presence (WLAN, optional)",
|
|
1007
|
+
"description": "Opt-in: shows in HomeKit whether a phone is in the home network. Leave empty to keep the feature off.",
|
|
1008
|
+
"expandable": true,
|
|
1009
|
+
"expanded": false,
|
|
1010
|
+
"orderable": false,
|
|
1011
|
+
"type": "section",
|
|
1012
|
+
"items": [
|
|
1013
|
+
{
|
|
1014
|
+
"key": "presence",
|
|
1015
|
+
"type": "array",
|
|
1016
|
+
"orderable": false,
|
|
1017
|
+
"buttonText": "Add Presence Device",
|
|
1018
|
+
"items": [
|
|
1019
|
+
"presence[].active",
|
|
1020
|
+
"presence[].name",
|
|
1021
|
+
"presence[].address",
|
|
1022
|
+
"presence[].accType",
|
|
1023
|
+
"presence[].onDelay",
|
|
1024
|
+
"presence[].offDelay"
|
|
1025
|
+
]
|
|
1026
|
+
},
|
|
1027
|
+
"options.presence.anyone",
|
|
1028
|
+
"options.presence.guest",
|
|
1029
|
+
"options.presence.accType"
|
|
1030
|
+
]
|
|
1031
|
+
},
|
|
905
1032
|
{
|
|
906
1033
|
"title": "Options",
|
|
907
1034
|
"orderable": false,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
C. Device discovery — Step 2 with auto-detected accType per device
|
|
8
8
|
D. Energy 250 OBIS-Reader auto-setup — Step 3 (4 tiles per reader)
|
|
9
9
|
E. Router-Switches — Step 4 (master.options pre-hydrated from config)
|
|
10
|
-
F. Save
|
|
10
|
+
F. Save — Step 5 (writes config.json; restart via the Config UI X toolbar)
|
|
11
11
|
G. Edit-Mode — re-run preserves user edits (tile names, accType, energy
|
|
12
12
|
display type, router-switches) by hydrating wizardState from the
|
|
13
13
|
existing config.json. Step 5 shows a diff of pending changes.
|
|
@@ -307,6 +307,40 @@
|
|
|
307
307
|
</div>
|
|
308
308
|
</details>
|
|
309
309
|
|
|
310
|
+
<!-- v1.3.0: optional WLAN presence (opt-in) -->
|
|
311
|
+
<details id="presenceDetails" style="margin-top: 1rem;">
|
|
312
|
+
<summary style="cursor: pointer;"><strong>Anwesenheit per WLAN (optional)</strong> — zeigt in HomeKit, ob ein Handy im Heimnetz ist</summary>
|
|
313
|
+
<div style="margin-top: 0.5rem; padding: 0.5rem; border-left: 3px solid #339DE0;">
|
|
314
|
+
<p style="font-size: 0.9rem;">
|
|
315
|
+
Je Gerät ein Sensor (Belegung oder Bewegung). Zuordnung über die IP- oder MAC-Adresse,
|
|
316
|
+
wie sie die FRITZ!Box unter <em>Heimnetz → Netzwerk</em> anzeigt.
|
|
317
|
+
<strong>iPhone/iPad:</strong> In den WLAN-Einstellungen des Geräts für das Heim-WLAN
|
|
318
|
+
<em>Private WLAN-Adresse → Fest</em> (bzw. „Aus“) wählen, WLAN einmal neu verbinden und dann die
|
|
319
|
+
MAC-Adresse <strong>aus der FRITZ!Box-Oberfläche</strong> nehmen (nicht die am iPhone angezeigte) oder besser
|
|
320
|
+
dort die IPv4-Adresse dauerhaft zuweisen und die IP eintragen.
|
|
321
|
+
Die <em>Abmeldeverzögerung</em> verhindert, dass der Sensor springt, wenn ein schlafendes
|
|
322
|
+
iPhone kurz aus dem WLAN geht (Standard 300 s).
|
|
323
|
+
</p>
|
|
324
|
+
<div id="presenceList"></div>
|
|
325
|
+
<button id="presenceAdd" type="button" class="btn btn-sm btn-outline-primary">+ Gerät hinzufügen</button>
|
|
326
|
+
<div style="margin-top: 0.75rem;">
|
|
327
|
+
<div class="form-check">
|
|
328
|
+
<input type="checkbox" id="presenceAnyone" class="form-check-input">
|
|
329
|
+
<label class="form-check-label" for="presenceAnyone">Zusätzlicher Sensor <em>„Anyone“</em> (jemand ist zu Hause)</label>
|
|
330
|
+
</div>
|
|
331
|
+
<div class="form-check">
|
|
332
|
+
<input type="checkbox" id="presenceGuest" class="form-check-input">
|
|
333
|
+
<label class="form-check-label" for="presenceGuest">Zusätzlicher Sensor <em>„Guest“</em> (Gerät im Gast-WLAN)</label>
|
|
334
|
+
</div>
|
|
335
|
+
<label for="presenceAccType" style="font-size: 0.9rem; margin-top: 0.25rem;">Typ für Anyone/Guest:</label>
|
|
336
|
+
<select id="presenceAccType" class="form-select form-select-sm" style="max-width: 220px; display: inline-block;">
|
|
337
|
+
<option value="occupancy">Belegungssensor</option>
|
|
338
|
+
<option value="motion">Bewegungssensor</option>
|
|
339
|
+
</select>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
</details>
|
|
343
|
+
|
|
310
344
|
<div class="wizard-nav">
|
|
311
345
|
<button id="backTo3" class="btn btn-outline-secondary">← Zurück zu Schritt 3</button>
|
|
312
346
|
<button id="goTo5" class="btn btn-primary">Weiter zu Schritt 5 →</button>
|
|
@@ -317,9 +351,8 @@
|
|
|
317
351
|
<div class="step-pane" id="step-5">
|
|
318
352
|
<div class="alert alert-info">
|
|
319
353
|
<strong>Schritt 5 — Konfiguration speichern</strong><br>
|
|
320
|
-
Zusammenfassung
|
|
321
|
-
|
|
322
|
-
Homebridge neu starten damit die Änderungen wirksam werden.
|
|
354
|
+
Prüfe die Zusammenfassung und speichere. Wirksam wird die Änderung nach einem
|
|
355
|
+
Neustart von Homebridge.
|
|
323
356
|
</div>
|
|
324
357
|
|
|
325
358
|
<div id="saveSummary" style="margin-top: 1rem;"></div>
|
|
@@ -352,9 +385,19 @@
|
|
|
352
385
|
energyTiles: {}, // { ain: [{ obisChannel, name }, ...] } after step 3
|
|
353
386
|
energyAccType: 'energy-temperature', // 'energy-temperature' | 'energy-light' — chosen in step 3
|
|
354
387
|
routerSwitches: {}, // { wifi_guest: bool, wps: bool, ... } after step 4
|
|
355
|
-
existingConfig: null, // Phase G: { master, smarthome[] } from current config.json (null = first run)
|
|
388
|
+
existingConfig: null, // Phase G: { master, smarthome[], presence[], presenceOptions } from current config.json (null = first run)
|
|
389
|
+
presence: null, // v1.3.0: [{ active, name, address, accType, onDelay?, offDelay }] (null = not touched yet)
|
|
390
|
+
presenceOptions: null, // v1.3.0: { anyone, guest, accType }
|
|
356
391
|
};
|
|
357
392
|
|
|
393
|
+
// v1.3.1: the wizard saves via its own button. Grey out the Config UI X
|
|
394
|
+
// "Save" button so there is exactly one way to save.
|
|
395
|
+
try {
|
|
396
|
+
homebridge.disableSaveButton();
|
|
397
|
+
} catch (e) {
|
|
398
|
+
// older Config UI X without this API: leave the button as it is
|
|
399
|
+
}
|
|
400
|
+
|
|
358
401
|
// ============ DOM References ============
|
|
359
402
|
const stepPanes = {
|
|
360
403
|
1: document.getElementById('step-1'),
|
|
@@ -410,7 +453,10 @@
|
|
|
410
453
|
if (!fritz) return null;
|
|
411
454
|
const master = (fritz.devices || []).find((d) => d && d.master === true) || null;
|
|
412
455
|
const smarthome = Array.isArray(fritz.smarthome) ? fritz.smarthome : [];
|
|
413
|
-
|
|
456
|
+
// v1.3.0: keep presence entries so a wizard re-run does not drop them
|
|
457
|
+
const presence = Array.isArray(fritz.presence) ? fritz.presence : [];
|
|
458
|
+
const presenceOptions = (fritz.options && fritz.options.presence) || null;
|
|
459
|
+
return { master, smarthome, presence, presenceOptions };
|
|
414
460
|
} catch (err) {
|
|
415
461
|
return null;
|
|
416
462
|
}
|
|
@@ -670,17 +716,85 @@
|
|
|
670
716
|
goTo4.addEventListener('click', () => {
|
|
671
717
|
showStep(4);
|
|
672
718
|
hydrateRouterSwitches();
|
|
719
|
+
hydratePresence();
|
|
673
720
|
});
|
|
674
721
|
backTo3.addEventListener('click', () => showStep(3));
|
|
675
722
|
|
|
676
723
|
const goTo5 = document.getElementById('goTo5');
|
|
677
724
|
const backTo4 = document.getElementById('backTo4');
|
|
678
725
|
goTo5.addEventListener('click', () => {
|
|
726
|
+
collectPresence();
|
|
679
727
|
showStep(5);
|
|
680
728
|
renderSavePreview();
|
|
681
729
|
});
|
|
682
730
|
backTo4.addEventListener('click', () => showStep(4));
|
|
683
731
|
|
|
732
|
+
// ============ Step 4b — Presence (optional, v1.3.0) ============
|
|
733
|
+
const presenceList = document.getElementById('presenceList');
|
|
734
|
+
|
|
735
|
+
function presenceRow(entry) {
|
|
736
|
+
const e = entry || { active: true, name: '', address: '', accType: 'occupancy', offDelay: 300 };
|
|
737
|
+
const row = document.createElement('div');
|
|
738
|
+
row.className = 'presence-row device-card';
|
|
739
|
+
row.style.cssText = 'display:flex; flex-wrap:wrap; gap:0.5rem; align-items:center; margin-bottom:0.5rem;';
|
|
740
|
+
if (typeof e.onDelay === 'number') row.dataset.onDelay = String(e.onDelay);
|
|
741
|
+
row.innerHTML = `
|
|
742
|
+
<input type="checkbox" class="form-check-input p-active" title="Aktiv" ${e.active ? 'checked' : ''}>
|
|
743
|
+
<input type="text" class="form-control form-control-sm p-name" placeholder="Name, z.B. iPhone Anna" style="max-width:180px;" value="${escapeHtml(e.name || '')}">
|
|
744
|
+
<input type="text" class="form-control form-control-sm p-address" placeholder="IP oder MAC" style="max-width:170px;" value="${escapeHtml(e.address || '')}">
|
|
745
|
+
<select class="form-select form-select-sm p-acctype" style="max-width:150px;">
|
|
746
|
+
<option value="occupancy" ${e.accType !== 'motion' ? 'selected' : ''}>Belegung</option>
|
|
747
|
+
<option value="motion" ${e.accType === 'motion' ? 'selected' : ''}>Bewegung</option>
|
|
748
|
+
</select>
|
|
749
|
+
<label style="font-size:0.8rem;">Abmeldung nach
|
|
750
|
+
<input type="number" min="0" class="form-control form-control-sm p-offdelay" style="width:80px; display:inline-block;" value="${typeof e.offDelay === 'number' ? e.offDelay : 300}"> s</label>
|
|
751
|
+
<button type="button" class="btn btn-sm btn-outline-danger p-remove" title="Entfernen">✕</button>
|
|
752
|
+
`;
|
|
753
|
+
row.querySelector('.p-remove').addEventListener('click', () => row.remove());
|
|
754
|
+
presenceList.appendChild(row);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function hydratePresence() {
|
|
758
|
+
if (presenceList.dataset.hydrated) return; // keep user edits when navigating back
|
|
759
|
+
presenceList.dataset.hydrated = '1';
|
|
760
|
+
const cfg = wizardState.existingConfig || {};
|
|
761
|
+
const entries = cfg.presence || [];
|
|
762
|
+
entries.forEach((e) => presenceRow(e));
|
|
763
|
+
const opts = cfg.presenceOptions || {};
|
|
764
|
+
document.getElementById('presenceAnyone').checked = !!opts.anyone;
|
|
765
|
+
document.getElementById('presenceGuest').checked = !!opts.guest;
|
|
766
|
+
document.getElementById('presenceAccType').value = opts.accType === 'motion' ? 'motion' : 'occupancy';
|
|
767
|
+
if (entries.length || opts.anyone || opts.guest) document.getElementById('presenceDetails').open = true;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
document.getElementById('presenceAdd').addEventListener('click', () => presenceRow(null));
|
|
771
|
+
|
|
772
|
+
function collectPresence() {
|
|
773
|
+
if (!presenceList.dataset.hydrated) return; // step 4 never shown — keep existing as is
|
|
774
|
+
const entries = [];
|
|
775
|
+
presenceList.querySelectorAll('.presence-row').forEach((row) => {
|
|
776
|
+
const name = row.querySelector('.p-name').value.trim();
|
|
777
|
+
const address = row.querySelector('.p-address').value.trim();
|
|
778
|
+
if (!name && !address) return;
|
|
779
|
+
const off = parseInt(row.querySelector('.p-offdelay').value, 10);
|
|
780
|
+
const entry = {
|
|
781
|
+
active: row.querySelector('.p-active').checked,
|
|
782
|
+
name,
|
|
783
|
+
address,
|
|
784
|
+
accType: row.querySelector('.p-acctype').value,
|
|
785
|
+
offDelay: Number.isFinite(off) && off >= 0 ? off : 300,
|
|
786
|
+
};
|
|
787
|
+
if (row.dataset.onDelay) entry.onDelay = parseInt(row.dataset.onDelay, 10);
|
|
788
|
+
entries.push(entry);
|
|
789
|
+
});
|
|
790
|
+
wizardState.presence = entries;
|
|
791
|
+
wizardState.presenceOptions = {
|
|
792
|
+
anyone: document.getElementById('presenceAnyone').checked,
|
|
793
|
+
guest: document.getElementById('presenceGuest').checked,
|
|
794
|
+
accType: document.getElementById('presenceAccType').value,
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
684
798
|
// ============ Step 5 — Build + Save ============
|
|
685
799
|
|
|
686
800
|
/**
|
|
@@ -786,7 +900,7 @@
|
|
|
786
900
|
}
|
|
787
901
|
}
|
|
788
902
|
|
|
789
|
-
|
|
903
|
+
const config = {
|
|
790
904
|
platform: 'FritzPlatform',
|
|
791
905
|
name: 'FritzBox',
|
|
792
906
|
devices: [master],
|
|
@@ -795,6 +909,19 @@
|
|
|
795
909
|
polling: { timer: 30 },
|
|
796
910
|
},
|
|
797
911
|
};
|
|
912
|
+
|
|
913
|
+
// v1.3.0: presence (opt-in). Use the wizard input if step 4 was shown,
|
|
914
|
+
// otherwise carry over what is already in config.json.
|
|
915
|
+
const existing = wizardState.existingConfig || {};
|
|
916
|
+
const presence = wizardState.presence !== null ? wizardState.presence : existing.presence || [];
|
|
917
|
+
const presenceOptions =
|
|
918
|
+
wizardState.presenceOptions !== null ? wizardState.presenceOptions : existing.presenceOptions || null;
|
|
919
|
+
if (presence.length) config.presence = presence;
|
|
920
|
+
if (presenceOptions && (presenceOptions.anyone || presenceOptions.guest)) {
|
|
921
|
+
config.options.presence = presenceOptions;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
return config;
|
|
798
925
|
}
|
|
799
926
|
|
|
800
927
|
/**
|
|
@@ -868,6 +995,13 @@
|
|
|
868
995
|
lines.push(`Entfernt: <code>${escapeHtml(o.ain)}</code>${chan} — war <em>${escapeHtml(o.accType)}</em> "${escapeHtml(o.name || '')}"`);
|
|
869
996
|
}
|
|
870
997
|
|
|
998
|
+
// -- Presence (v1.3.0) --
|
|
999
|
+
const oldPresence = (existing.presence || []).filter((p) => p && p.active).map((p) => `${p.name}|${p.address}|${p.accType || 'occupancy'}|${p.offDelay}`);
|
|
1000
|
+
const newPresence = (newConfig.presence || []).filter((p) => p && p.active).map((p) => `${p.name}|${p.address}|${p.accType || 'occupancy'}|${p.offDelay}`);
|
|
1001
|
+
if (oldPresence.sort().join(';') !== newPresence.sort().join(';')) {
|
|
1002
|
+
lines.push(`Anwesenheit: ${oldPresence.length} → ${newPresence.length} aktive(r) Sensor(en)`);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
871
1005
|
if (lines.length === 0) {
|
|
872
1006
|
return `
|
|
873
1007
|
<div class="alert alert-secondary">
|
|
@@ -911,6 +1045,7 @@
|
|
|
911
1045
|
<li><strong>${nDevices}</strong> Smart-Home-Gerät(e) (Thermostate, Lampen, Sensoren, Schalter)</li>
|
|
912
1046
|
<li><strong>${nEnergy}</strong> Energy-Tile(s) für FRITZ!Smart Energy 250 OBIS-Reader</li>
|
|
913
1047
|
<li><strong>${nRouter}</strong> Router-Switch(es) als HomeKit-Tiles</li>
|
|
1048
|
+
<li><strong>${(config.presence || []).filter((p) => p.active).length}</strong> Anwesenheitssensor(en) (WLAN, optional)</li>
|
|
914
1049
|
</ul>
|
|
915
1050
|
</div>
|
|
916
1051
|
`;
|
|
@@ -948,31 +1083,10 @@
|
|
|
948
1083
|
|
|
949
1084
|
saveResult.innerHTML = `
|
|
950
1085
|
<div class="alert alert-success">
|
|
951
|
-
<strong>✓
|
|
952
|
-
|
|
953
|
-
</div>
|
|
954
|
-
<div class="text-center" style="margin-top: 1rem;">
|
|
955
|
-
<button id="restartBtn" class="btn btn-warning btn-lg">🔄 Homebridge jetzt neu starten</button>
|
|
956
|
-
<div style="margin-top: 0.5rem;">
|
|
957
|
-
<small class="text-muted">Alternativ später via Config UI X "Restart"-Button.</small>
|
|
958
|
-
</div>
|
|
1086
|
+
<strong>✓ Gespeichert.</strong>
|
|
1087
|
+
Zum Aktivieren Homebridge neu starten: in Config UI X oben rechts über das Neustart-Symbol.
|
|
959
1088
|
</div>
|
|
960
1089
|
`;
|
|
961
|
-
|
|
962
|
-
document.getElementById('restartBtn').addEventListener('click', async () => {
|
|
963
|
-
try {
|
|
964
|
-
await homebridge.restartHomebridge();
|
|
965
|
-
} catch (err) {
|
|
966
|
-
// If restartHomebridge isn't available (e.g. standalone mode without
|
|
967
|
-
// hb-service), show a manual instruction.
|
|
968
|
-
saveResult.innerHTML +=
|
|
969
|
-
'<div class="alert alert-info" style="margin-top: 1rem;">' +
|
|
970
|
-
'Automatischer Restart nicht verfügbar. Bitte Homebridge manuell ' +
|
|
971
|
-
'neu starten: <code>sudo hb-service restart</code> oder über die ' +
|
|
972
|
-
'Config UI X Toolbar.' +
|
|
973
|
-
'</div>';
|
|
974
|
-
}
|
|
975
|
-
});
|
|
976
1090
|
} catch (err) {
|
|
977
1091
|
saveResult.innerHTML = `
|
|
978
1092
|
<div class="alert alert-danger">
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellebot/homebridge-fritz-platform-hb2",
|
|
3
3
|
"displayName": "Homebridge FRITZ!Box Platform (HB2)",
|
|
4
|
-
"version": "1.
|
|
5
|
-
"description": "Homebridge 2.0 plugin for AVM FRITZ!DECT devices (thermostats, lamps, sensors, OBIS smart-meter readers)
|
|
4
|
+
"version": "1.3.1",
|
|
5
|
+
"description": "Homebridge 2.0 plugin for AVM FRITZ!DECT devices (thermostats, lamps, sensors, OBIS smart-meter readers) FRITZ!Box router switches (Guest WLAN, WPS) and optional WLAN presence sensors. Node 22 / 24 ready.",
|
|
6
6
|
"icon": "https://raw.githubusercontent.com/PelleBot/homebridge-fritz-platform-ng/main/branding/Icon.png",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "index.mjs",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"avm",
|
|
43
43
|
"thermostat",
|
|
44
44
|
"fritz-dect",
|
|
45
|
-
"homebridge-2"
|
|
45
|
+
"homebridge-2",
|
|
46
|
+
"presence"
|
|
46
47
|
],
|
|
47
48
|
"engines": {
|
|
48
49
|
"node": "^22.12.0 || ^24.0.0",
|