@pellebot/homebridge-fritz-platform-hb2 1.1.1 → 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.2.0.md +46 -0
- package/CHANGELOG.md +29 -0
- package/homebridge-ui/public/index.html +226 -24
- package/package.json +1 -1
- 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 +5 -15
- package/src/platform.js +5 -3
- package/src/types/custom.types.js +45 -22
- package/src/types/eve.types.js +41 -16
|
@@ -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,35 @@
|
|
|
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
|
+
|
|
5
34
|
# v1.1.1 — 2026-05-14 (LightSensor variant for energy tiles)
|
|
6
35
|
|
|
7
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`).
|
|
@@ -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 {
|
|
@@ -320,6 +324,9 @@
|
|
|
320
324
|
|
|
321
325
|
<div id="saveSummary" style="margin-top: 1rem;"></div>
|
|
322
326
|
|
|
327
|
+
<!-- Phase G: diff against existing config (empty on first run) -->
|
|
328
|
+
<div id="saveDiff" style="margin-top: 1rem;"></div>
|
|
329
|
+
|
|
323
330
|
<details style="margin-top: 1rem;">
|
|
324
331
|
<summary style="cursor: pointer;">Generierte Plugin-Config anzeigen (JSON-Vorschau)</summary>
|
|
325
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>
|
|
@@ -345,6 +352,7 @@
|
|
|
345
352
|
energyTiles: {}, // { ain: [{ obisChannel, name }, ...] } after step 3
|
|
346
353
|
energyAccType: 'energy-temperature', // 'energy-temperature' | 'energy-light' — chosen in step 3
|
|
347
354
|
routerSwitches: {}, // { wifi_guest: bool, wps: bool, ... } after step 4
|
|
355
|
+
existingConfig: null, // Phase G: { master, smarthome[] } from current config.json (null = first run)
|
|
348
356
|
};
|
|
349
357
|
|
|
350
358
|
// ============ DOM References ============
|
|
@@ -384,23 +392,59 @@
|
|
|
384
392
|
});
|
|
385
393
|
}
|
|
386
394
|
|
|
387
|
-
// ============
|
|
388
|
-
//
|
|
389
|
-
|
|
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() {
|
|
390
407
|
try {
|
|
391
408
|
const allConfig = await homebridge.getPluginConfig();
|
|
392
409
|
const fritz = (allConfig || []).find((c) => c && c.platform === 'FritzPlatform');
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (typeof master.ssl === 'boolean') inputs.ssl.checked = master.ssl;
|
|
398
|
-
if (typeof master.tr064 === 'boolean') inputs.tr064.checked = master.tr064;
|
|
399
|
-
if (master.username) inputs.username.value = master.username;
|
|
400
|
-
if (master.password) inputs.password.value = master.password;
|
|
401
|
-
}
|
|
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 };
|
|
402
414
|
} catch (err) {
|
|
403
|
-
|
|
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';
|
|
404
448
|
}
|
|
405
449
|
})();
|
|
406
450
|
|
|
@@ -509,15 +553,41 @@
|
|
|
509
553
|
{ value: 'skip', label: '— ignorieren —' },
|
|
510
554
|
];
|
|
511
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
|
+
|
|
512
565
|
const html = wizardState.discoveredDevices.map((d) => {
|
|
513
566
|
// Default-Selection: include if accType detected (not 'unknown' / 'skip')
|
|
514
567
|
const defaultInclude = d.accType !== 'unknown' && d.accType !== 'skip';
|
|
515
568
|
if (!wizardState.deviceSelections[d.ain]) {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
name
|
|
520
|
-
|
|
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
|
+
}
|
|
521
591
|
}
|
|
522
592
|
const sel = wizardState.deviceSelections[d.ain];
|
|
523
593
|
|
|
@@ -727,13 +797,104 @@
|
|
|
727
797
|
};
|
|
728
798
|
}
|
|
729
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
|
+
|
|
730
890
|
function renderSavePreview() {
|
|
731
891
|
const config = buildPluginConfig();
|
|
732
892
|
const summary = document.getElementById('saveSummary');
|
|
733
893
|
const preview = document.getElementById('savePreview');
|
|
894
|
+
const diffEl = document.getElementById('saveDiff');
|
|
734
895
|
|
|
735
|
-
const nDevices = config.smarthome.filter((s) => s.accType !== 'energy-temperature').length;
|
|
736
|
-
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;
|
|
737
898
|
const nRouter = Object.keys((config.devices[0] && config.devices[0].options) || {}).length;
|
|
738
899
|
|
|
739
900
|
// Mask password in preview for safety
|
|
@@ -755,6 +916,10 @@
|
|
|
755
916
|
`;
|
|
756
917
|
|
|
757
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);
|
|
758
923
|
}
|
|
759
924
|
|
|
760
925
|
const saveBtn = document.getElementById('saveBtn');
|
|
@@ -911,13 +1076,50 @@
|
|
|
911
1076
|
'-2': { current_power: 'Aktuelle Einspeisung', total_energy: 'Gesamt Einspeisung' },
|
|
912
1077
|
};
|
|
913
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
|
+
|
|
914
1096
|
// Initialize state for each OBIS AIN if not already there
|
|
915
1097
|
for (const baseAin of Object.keys(byBase)) {
|
|
916
1098
|
const group = byBase[baseAin];
|
|
917
1099
|
for (const dir of ['-1', '-2']) {
|
|
918
1100
|
const entry = dir === '-1' ? group.ain1 : group.ain2;
|
|
919
1101
|
if (!entry) continue;
|
|
920
|
-
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 {
|
|
921
1123
|
wizardState.energyTiles[entry.ain] = [
|
|
922
1124
|
{ obisChannel: 'current_power', name: defaultNames[dir].current_power },
|
|
923
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",
|
|
@@ -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];
|
|
@@ -687,9 +687,7 @@ class Handler {
|
|
|
687
687
|
let service = accessory.getService(this.api.hap.Service.LightSensor);
|
|
688
688
|
|
|
689
689
|
try {
|
|
690
|
-
let device = this.smarthomeList.devices.find((device) =>
|
|
691
|
-
device.ain.includes(accessory.context.config.ain)
|
|
692
|
-
);
|
|
690
|
+
let device = this.smarthomeList.devices.find((device) => device.ain.includes(accessory.context.config.ain));
|
|
693
691
|
logger.debug(device, `${accessory.displayName} (${subtype})`);
|
|
694
692
|
|
|
695
693
|
if (device) {
|
|
@@ -746,9 +744,7 @@ class Handler {
|
|
|
746
744
|
let service = accessory.getService(this.api.hap.Service.TemperatureSensor);
|
|
747
745
|
|
|
748
746
|
try {
|
|
749
|
-
let device = this.smarthomeList.devices.find((device) =>
|
|
750
|
-
device.ain.includes(accessory.context.config.ain)
|
|
751
|
-
);
|
|
747
|
+
let device = this.smarthomeList.devices.find((device) => device.ain.includes(accessory.context.config.ain));
|
|
752
748
|
logger.debug(device, `${accessory.displayName} (${subtype})`);
|
|
753
749
|
|
|
754
750
|
if (device) {
|
|
@@ -814,9 +810,7 @@ class Handler {
|
|
|
814
810
|
let service = accessory.getService(this.api.hap.Service.Outlet);
|
|
815
811
|
|
|
816
812
|
try {
|
|
817
|
-
let device = this.smarthomeList.devices.find((device) =>
|
|
818
|
-
device.ain.includes(accessory.context.config.ain)
|
|
819
|
-
);
|
|
813
|
+
let device = this.smarthomeList.devices.find((device) => device.ain.includes(accessory.context.config.ain));
|
|
820
814
|
logger.debug(device, `${accessory.displayName} (${subtype})`);
|
|
821
815
|
|
|
822
816
|
if (device) {
|
|
@@ -833,13 +827,9 @@ class Handler {
|
|
|
833
827
|
const currentPower = Math.abs(device.powermeter.power || 0);
|
|
834
828
|
const totalEnergy = device.powermeter.energy || 0;
|
|
835
829
|
|
|
836
|
-
service
|
|
837
|
-
.getCharacteristic(this.api.hap.Characteristic.CurrentConsumption)
|
|
838
|
-
.updateValue(currentPower);
|
|
830
|
+
service.getCharacteristic(this.api.hap.Characteristic.CurrentConsumption).updateValue(currentPower);
|
|
839
831
|
|
|
840
|
-
service
|
|
841
|
-
.getCharacteristic(this.api.hap.Characteristic.TotalConsumption)
|
|
842
|
-
.updateValue(totalEnergy);
|
|
832
|
+
service.getCharacteristic(this.api.hap.Characteristic.TotalConsumption).updateValue(totalEnergy);
|
|
843
833
|
|
|
844
834
|
service.getCharacteristic(this.api.hap.Characteristic.On).updateValue(true);
|
|
845
835
|
service.getCharacteristic(this.api.hap.Characteristic.OutletInUse).updateValue(true);
|
package/src/platform.js
CHANGED
|
@@ -76,7 +76,9 @@ function FritzPlatform(log, config, api) {
|
|
|
76
76
|
HistoryService.prototype.addEntry = function () {};
|
|
77
77
|
HistoryService.prototype.cleanPersist = function () {};
|
|
78
78
|
HistoryService.prototype.setExtraPersistedData = function () {};
|
|
79
|
-
HistoryService.prototype.getExtraPersistedData = function () {
|
|
79
|
+
HistoryService.prototype.getExtraPersistedData = function () {
|
|
80
|
+
return null;
|
|
81
|
+
};
|
|
80
82
|
|
|
81
83
|
this.api = api;
|
|
82
84
|
this.log = log;
|
|
@@ -209,9 +211,9 @@ FritzPlatform.prototype = {
|
|
|
209
211
|
else if (device.subtype === 'smarthome-window-switch')
|
|
210
212
|
new SHWindowSwitchAccessory(this.api, accessory, this.accessories, this.meshMaster);
|
|
211
213
|
else if (device.subtype === 'smarthome-lightbulb')
|
|
212
|
-
|
|
214
|
+
new SHLightbulbAccessory(this.api, accessory, this.accessories, this.meshMaster);
|
|
213
215
|
else if (device.subtype === 'smarthome-smoke')
|
|
214
|
-
|
|
216
|
+
new SHSmokeAccessory(this.api, accessory, this.accessories, this.meshMaster, HistoryService);
|
|
215
217
|
else if (device.subtype === 'smarthome-switch-lightbulb' && device.energy)
|
|
216
218
|
new SHOutletLightbulbAccessory(this.api, accessory, this.accessories, this.meshMaster, HistoryService);
|
|
217
219
|
else if (device.subtype === 'smarthome-switch-lightbulb' && !device.energy)
|
|
@@ -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,
|