iobroker.homewizard 0.6.2 → 0.6.4
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/README.md +10 -19
- package/build/lib/coerce.js +59 -0
- package/build/lib/coerce.js.map +7 -0
- package/build/lib/state-manager.js +187 -132
- package/build/lib/state-manager.js.map +2 -2
- package/build/lib/websocket-client.js +26 -10
- package/build/lib/websocket-client.js.map +2 -2
- package/build/main.js +32 -14
- package/build/main.js.map +2 -2
- package/io-package.json +52 -27
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -45,8 +45,6 @@ Real-time energy monitoring from [HomeWizard](https://www.homewizard.com) Energy
|
|
|
45
45
|
| kWh Meter 3-Phase | HWE-KWH3 / SDM630 | Yes | Yes (as controller) |
|
|
46
46
|
| Plug-In Battery | HWE-BAT | Yes | Controlled via P1/kWh |
|
|
47
47
|
|
|
48
|
-
> **Note:** Energy Socket (HWE-SKT) and Watermeter (HWE-WTR) only support API v1 and are not yet supported. Support will be added when HomeWizard releases API v2 for these devices.
|
|
49
|
-
|
|
50
48
|
---
|
|
51
49
|
|
|
52
50
|
## Configuration
|
|
@@ -114,6 +112,7 @@ homewizard.0.
|
|
|
114
112
|
│ ├── cycles — Battery charge cycles (number)
|
|
115
113
|
│ ├── average_power_15m_w — 15-min average power (number, W, Belgium)
|
|
116
114
|
│ ├── monthly_power_peak_w — Monthly power peak (number, W, Belgium)
|
|
115
|
+
│ ├── monthly_power_peak_timestamp — Monthly peak timestamp (string)
|
|
117
116
|
│ ├── meter_model — Meter model identifier (string)
|
|
118
117
|
│ ├── timestamp — Measurement timestamp (string)
|
|
119
118
|
│ ├── quality/ — Power quality counters
|
|
@@ -165,6 +164,15 @@ homewizard.0.
|
|
|
165
164
|
---
|
|
166
165
|
|
|
167
166
|
## Changelog
|
|
167
|
+
### 0.6.4 (2026-04-23)
|
|
168
|
+
- Separate test-build output (`build-test/`) from production `build/`, so `npm test` no longer risks leaving duplicated `build/src` + `build/test` trees in the published package.
|
|
169
|
+
- Wrap async `onReady` and `onStateChange` handlers with `.catch()` to prevent unhandled promise rejections from SIGKILLing the adapter.
|
|
170
|
+
- Declare `pairingIp` as an instance object (11-language name) instead of creating it dynamically in `onReady`.
|
|
171
|
+
|
|
172
|
+
### 0.6.3 (2026-04-18)
|
|
173
|
+
- Harden WebSocket and REST input handling against unexpected API responses
|
|
174
|
+
- Stop endless reconnect when the device token is invalid (fires once after 3 failed auth attempts)
|
|
175
|
+
- Avoid creating an empty `external/` channel when a device reports no external meters
|
|
168
176
|
|
|
169
177
|
### 0.6.2 (2026-04-13)
|
|
170
178
|
- Fix hanging promise when response stream errors mid-transfer (`res.on("error")`)
|
|
@@ -184,23 +192,6 @@ homewizard.0.
|
|
|
184
192
|
- Persistent REST fallback for unstable devices (30s interval instead of stopping)
|
|
185
193
|
- Automatic mode switch: stabilizes after 10 min connected, detects instability after 3 short-lived connections
|
|
186
194
|
|
|
187
|
-
### 0.5.1 (2026-04-08)
|
|
188
|
-
- Restore standard GitHub-based tests, remove CHANGELOG.md, add FORBIDDEN_CHARS reference
|
|
189
|
-
|
|
190
|
-
### 0.5.0 (2026-04-05)
|
|
191
|
-
- Robust reconnect: never give up after WiFi loss, retry every 5 minutes indefinitely
|
|
192
|
-
- Periodic mDNS IP recovery (~hourly), only WebSocket controls online state
|
|
193
|
-
|
|
194
|
-
### 0.4.2 (2026-04-05)
|
|
195
|
-
- Consistent donation labels and about text across all adapters
|
|
196
|
-
|
|
197
|
-
### 0.4.1 (2026-04-05)
|
|
198
|
-
- Move measurement data into `measurement/` channel for cleaner object tree
|
|
199
|
-
|
|
200
|
-
Older entries have been moved to [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
201
|
-
|
|
202
|
-
---
|
|
203
|
-
|
|
204
195
|
## Support
|
|
205
196
|
|
|
206
197
|
- [ioBroker Forum](https://forum.iobroker.net/)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var coerce_exports = {};
|
|
20
|
+
__export(coerce_exports, {
|
|
21
|
+
coerceBoolean: () => coerceBoolean,
|
|
22
|
+
coerceFiniteNumber: () => coerceFiniteNumber,
|
|
23
|
+
coerceString: () => coerceString,
|
|
24
|
+
isPlainObject: () => isPlainObject
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(coerce_exports);
|
|
27
|
+
function coerceFiniteNumber(value) {
|
|
28
|
+
if (typeof value === "number") {
|
|
29
|
+
return Number.isFinite(value) ? value : null;
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === "string" && value.length > 0) {
|
|
32
|
+
const n = Number(value);
|
|
33
|
+
return Number.isFinite(n) ? n : null;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function coerceString(value) {
|
|
38
|
+
if (typeof value === "string" && value.length > 0) {
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function coerceBoolean(value) {
|
|
44
|
+
if (typeof value === "boolean") {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
function isPlainObject(value) {
|
|
50
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
51
|
+
}
|
|
52
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
53
|
+
0 && (module.exports = {
|
|
54
|
+
coerceBoolean,
|
|
55
|
+
coerceFiniteNumber,
|
|
56
|
+
coerceString,
|
|
57
|
+
isPlainObject
|
|
58
|
+
});
|
|
59
|
+
//# sourceMappingURL=coerce.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/coerce.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Boundary coercion helpers for external API data (REST + WebSocket).\n * HomeWizard API v2 is well-documented but field types still drift in practice\n * (firmware bugs, future additions, null values). These helpers guard against\n * NaN/Infinity/non-string values reaching ioBroker states.\n */\n\n/**\n * Coerce to a finite number or null.\n * Accepts numbers directly; parses numeric strings; rejects NaN/Infinity/other.\n *\n * @param value Unknown external value\n */\nexport function coerceFiniteNumber(value: unknown): number | null {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null;\n }\n if (typeof value === \"string\" && value.length > 0) {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/**\n * Coerce to a non-empty string, or null.\n *\n * @param value Unknown external value\n */\nexport function coerceString(value: unknown): string | null {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n return null;\n}\n\n/**\n * Coerce to a boolean (only `true`/`false` accepted \u2014 no truthy/falsy JS rules).\n *\n * @param value Unknown external value\n */\nexport function coerceBoolean(value: unknown): boolean | null {\n if (typeof value === \"boolean\") {\n return value;\n }\n return null;\n}\n\n/**\n * Guard for plain objects (not arrays, not null).\n *\n * @param value Unknown external value\n */\nexport function isPlainObject(\n value: unknown,\n): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaO,SAAS,mBAAmB,OAA+B;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AAOO,SAAS,aAAa,OAA+B;AAC1D,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,cAAc,OAAgC;AAC5D,MAAI,OAAO,UAAU,WAAW;AAC9B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,cACd,OACkC;AAClC,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -21,6 +21,7 @@ __export(state_manager_exports, {
|
|
|
21
21
|
StateManager: () => StateManager
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(state_manager_exports);
|
|
24
|
+
var import_coerce = require("./coerce");
|
|
24
25
|
function sanitize(str) {
|
|
25
26
|
return str.replace(/[^a-zA-Z0-9_-]/g, "_").toLowerCase();
|
|
26
27
|
}
|
|
@@ -582,7 +583,9 @@ class StateManager {
|
|
|
582
583
|
* @param data Measurement data
|
|
583
584
|
*/
|
|
584
585
|
async updateMeasurement(config, data) {
|
|
585
|
-
|
|
586
|
+
if (!(0, import_coerce.isPlainObject)(data)) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
586
589
|
const prefix = this.devicePrefix(config);
|
|
587
590
|
const mPrefix = `${prefix}.measurement`;
|
|
588
591
|
await this.adapter.setObjectNotExistsAsync(mPrefix, {
|
|
@@ -590,55 +593,83 @@ class StateManager {
|
|
|
590
593
|
common: { name: "Measurement" },
|
|
591
594
|
native: {}
|
|
592
595
|
});
|
|
593
|
-
const
|
|
594
|
-
for (const def of
|
|
595
|
-
const
|
|
596
|
-
|
|
596
|
+
const record = data;
|
|
597
|
+
for (const def of MEASUREMENT_STATE_DEFS) {
|
|
598
|
+
const raw = record[def.key];
|
|
599
|
+
let coerced = null;
|
|
600
|
+
if (def.type === "number") {
|
|
601
|
+
coerced = (0, import_coerce.coerceFiniteNumber)(raw);
|
|
602
|
+
} else if (def.type === "string") {
|
|
603
|
+
coerced = (0, import_coerce.coerceString)(raw);
|
|
604
|
+
}
|
|
605
|
+
if (coerced !== null) {
|
|
597
606
|
await this.ensureAndSet(
|
|
598
607
|
`${mPrefix}.${def.id}`,
|
|
599
608
|
def.name,
|
|
600
609
|
def.type,
|
|
601
610
|
def.role,
|
|
602
|
-
|
|
611
|
+
coerced,
|
|
603
612
|
def.unit
|
|
604
613
|
);
|
|
605
614
|
}
|
|
606
615
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const
|
|
616
|
+
const external = record.external;
|
|
617
|
+
if (Array.isArray(external) && external.length > 0) {
|
|
618
|
+
let extChannelEnsured = false;
|
|
619
|
+
for (const rawExt of external) {
|
|
620
|
+
if (!(0, import_coerce.isPlainObject)(rawExt)) {
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
const type = (0, import_coerce.coerceString)(rawExt.type);
|
|
624
|
+
const uniqueId = (0, import_coerce.coerceString)(rawExt.unique_id);
|
|
625
|
+
if (!type || !uniqueId) {
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
const value = (0, import_coerce.coerceFiniteNumber)(rawExt.value);
|
|
629
|
+
const unit = (0, import_coerce.coerceString)(rawExt.unit);
|
|
630
|
+
const timestamp = (0, import_coerce.coerceString)(rawExt.timestamp);
|
|
631
|
+
if (!extChannelEnsured) {
|
|
632
|
+
await this.adapter.setObjectNotExistsAsync(`${mPrefix}.external`, {
|
|
633
|
+
type: "channel",
|
|
634
|
+
common: { name: "External Meters" },
|
|
635
|
+
native: {}
|
|
636
|
+
});
|
|
637
|
+
extChannelEnsured = true;
|
|
638
|
+
}
|
|
639
|
+
const extId = `${mPrefix}.external.${sanitize(type)}_${sanitize(uniqueId)}`;
|
|
615
640
|
await this.adapter.setObjectNotExistsAsync(extId, {
|
|
616
641
|
type: "channel",
|
|
617
|
-
common: { name:
|
|
642
|
+
common: { name: type },
|
|
618
643
|
native: {}
|
|
619
644
|
});
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
645
|
+
if (value !== null) {
|
|
646
|
+
await this.ensureAndSet(
|
|
647
|
+
`${extId}.value`,
|
|
648
|
+
"Value",
|
|
649
|
+
"number",
|
|
650
|
+
"value",
|
|
651
|
+
value,
|
|
652
|
+
unit != null ? unit : void 0
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
if (unit) {
|
|
656
|
+
await this.ensureAndSet(
|
|
657
|
+
`${extId}.unit`,
|
|
658
|
+
"Unit",
|
|
659
|
+
"string",
|
|
660
|
+
"text",
|
|
661
|
+
unit
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
if (timestamp) {
|
|
665
|
+
await this.ensureAndSet(
|
|
666
|
+
`${extId}.timestamp`,
|
|
667
|
+
"Timestamp",
|
|
668
|
+
"string",
|
|
669
|
+
"date",
|
|
670
|
+
timestamp
|
|
671
|
+
);
|
|
672
|
+
}
|
|
642
673
|
}
|
|
643
674
|
}
|
|
644
675
|
}
|
|
@@ -649,53 +680,70 @@ class StateManager {
|
|
|
649
680
|
* @param system System info data
|
|
650
681
|
*/
|
|
651
682
|
async updateSystem(config, system) {
|
|
683
|
+
if (!(0, import_coerce.isPlainObject)(system)) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
652
686
|
const prefix = this.devicePrefix(config);
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
687
|
+
const record = system;
|
|
688
|
+
const rssi = (0, import_coerce.coerceFiniteNumber)(record.wifi_rssi_db);
|
|
689
|
+
if (rssi !== null) {
|
|
690
|
+
await this.ensureAndSet(
|
|
691
|
+
`${prefix}.info.wifi_rssi_db`,
|
|
692
|
+
"WiFi signal strength",
|
|
693
|
+
"number",
|
|
694
|
+
"value",
|
|
695
|
+
rssi,
|
|
696
|
+
"dB"
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
const uptime = (0, import_coerce.coerceFiniteNumber)(record.uptime_s);
|
|
700
|
+
if (uptime !== null) {
|
|
701
|
+
await this.ensureAndSet(
|
|
702
|
+
`${prefix}.info.uptime_s`,
|
|
703
|
+
"Uptime",
|
|
704
|
+
"number",
|
|
705
|
+
"value",
|
|
706
|
+
uptime,
|
|
707
|
+
"s"
|
|
708
|
+
);
|
|
709
|
+
}
|
|
669
710
|
await this.adapter.setObjectNotExistsAsync(`${prefix}.system`, {
|
|
670
711
|
type: "channel",
|
|
671
712
|
common: { name: "System Settings" },
|
|
672
713
|
native: {}
|
|
673
714
|
});
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
715
|
+
const cloudEnabled = (0, import_coerce.coerceBoolean)(record.cloud_enabled);
|
|
716
|
+
if (cloudEnabled !== null) {
|
|
717
|
+
await this.ensureAndSet(
|
|
718
|
+
`${prefix}.system.cloud_enabled`,
|
|
719
|
+
"Cloud enabled",
|
|
720
|
+
"boolean",
|
|
721
|
+
"switch",
|
|
722
|
+
cloudEnabled,
|
|
723
|
+
void 0,
|
|
724
|
+
true
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
const ledPct = (0, import_coerce.coerceFiniteNumber)(record.status_led_brightness_pct);
|
|
728
|
+
if (ledPct !== null) {
|
|
729
|
+
await this.ensureAndSet(
|
|
730
|
+
`${prefix}.system.status_led_brightness_pct`,
|
|
731
|
+
"LED brightness",
|
|
732
|
+
"number",
|
|
733
|
+
"level",
|
|
734
|
+
ledPct,
|
|
735
|
+
"%",
|
|
736
|
+
true
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
const apiV1 = (0, import_coerce.coerceBoolean)(record.api_v1_enabled);
|
|
740
|
+
if (apiV1 !== null) {
|
|
693
741
|
await this.ensureAndSet(
|
|
694
742
|
`${prefix}.system.api_v1_enabled`,
|
|
695
743
|
"API v1 enabled",
|
|
696
744
|
"boolean",
|
|
697
745
|
"switch",
|
|
698
|
-
|
|
746
|
+
apiV1,
|
|
699
747
|
void 0,
|
|
700
748
|
true
|
|
701
749
|
);
|
|
@@ -713,80 +761,87 @@ class StateManager {
|
|
|
713
761
|
* @param battery Battery control data
|
|
714
762
|
*/
|
|
715
763
|
async updateBattery(config, battery) {
|
|
764
|
+
if (!(0, import_coerce.isPlainObject)(battery)) {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
716
767
|
const prefix = this.devicePrefix(config);
|
|
768
|
+
const record = battery;
|
|
717
769
|
await this.adapter.setObjectNotExistsAsync(`${prefix}.battery`, {
|
|
718
770
|
type: "channel",
|
|
719
771
|
common: { name: "Battery Control" },
|
|
720
772
|
native: {}
|
|
721
773
|
});
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
"Battery mode",
|
|
725
|
-
"string",
|
|
726
|
-
"text",
|
|
727
|
-
battery.mode,
|
|
728
|
-
void 0,
|
|
729
|
-
true
|
|
730
|
-
);
|
|
731
|
-
if (battery.permissions !== void 0) {
|
|
774
|
+
const mode = (0, import_coerce.coerceString)(record.mode);
|
|
775
|
+
if (mode) {
|
|
732
776
|
await this.ensureAndSet(
|
|
733
|
-
`${prefix}.battery.
|
|
734
|
-
"Battery
|
|
777
|
+
`${prefix}.battery.mode`,
|
|
778
|
+
"Battery mode",
|
|
735
779
|
"string",
|
|
736
|
-
"
|
|
737
|
-
|
|
780
|
+
"text",
|
|
781
|
+
mode,
|
|
738
782
|
void 0,
|
|
739
783
|
true
|
|
740
784
|
);
|
|
741
785
|
}
|
|
742
|
-
if (
|
|
743
|
-
await this.ensureAndSet(
|
|
744
|
-
`${prefix}.battery.battery_count`,
|
|
745
|
-
"Connected batteries",
|
|
746
|
-
"number",
|
|
747
|
-
"value",
|
|
748
|
-
battery.battery_count
|
|
749
|
-
);
|
|
750
|
-
}
|
|
751
|
-
if (battery.power_w !== void 0) {
|
|
752
|
-
await this.ensureAndSet(
|
|
753
|
-
`${prefix}.battery.power_w`,
|
|
754
|
-
"Battery power",
|
|
755
|
-
"number",
|
|
756
|
-
"value.power",
|
|
757
|
-
battery.power_w,
|
|
758
|
-
"W"
|
|
759
|
-
);
|
|
760
|
-
}
|
|
761
|
-
if (battery.target_power_w !== void 0) {
|
|
786
|
+
if (Array.isArray(record.permissions)) {
|
|
762
787
|
await this.ensureAndSet(
|
|
763
|
-
`${prefix}.battery.
|
|
764
|
-
"
|
|
765
|
-
"
|
|
766
|
-
"
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
}
|
|
771
|
-
if (battery.max_consumption_w !== void 0) {
|
|
772
|
-
await this.ensureAndSet(
|
|
773
|
-
`${prefix}.battery.max_consumption_w`,
|
|
774
|
-
"Max consumption",
|
|
775
|
-
"number",
|
|
776
|
-
"value.power",
|
|
777
|
-
battery.max_consumption_w,
|
|
778
|
-
"W"
|
|
788
|
+
`${prefix}.battery.permissions`,
|
|
789
|
+
"Battery permissions",
|
|
790
|
+
"string",
|
|
791
|
+
"json",
|
|
792
|
+
JSON.stringify(record.permissions),
|
|
793
|
+
void 0,
|
|
794
|
+
true
|
|
779
795
|
);
|
|
780
796
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
"
|
|
786
|
-
"value
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
797
|
+
const numberFields = [
|
|
798
|
+
{
|
|
799
|
+
key: "battery_count",
|
|
800
|
+
id: "battery_count",
|
|
801
|
+
name: "Connected batteries",
|
|
802
|
+
role: "value"
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
key: "power_w",
|
|
806
|
+
id: "power_w",
|
|
807
|
+
name: "Battery power",
|
|
808
|
+
role: "value.power",
|
|
809
|
+
unit: "W"
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
key: "target_power_w",
|
|
813
|
+
id: "target_power_w",
|
|
814
|
+
name: "Target power",
|
|
815
|
+
role: "value.power",
|
|
816
|
+
unit: "W"
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
key: "max_consumption_w",
|
|
820
|
+
id: "max_consumption_w",
|
|
821
|
+
name: "Max consumption",
|
|
822
|
+
role: "value.power",
|
|
823
|
+
unit: "W"
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
key: "max_production_w",
|
|
827
|
+
id: "max_production_w",
|
|
828
|
+
name: "Max production",
|
|
829
|
+
role: "value.power",
|
|
830
|
+
unit: "W"
|
|
831
|
+
}
|
|
832
|
+
];
|
|
833
|
+
for (const field of numberFields) {
|
|
834
|
+
const coerced = (0, import_coerce.coerceFiniteNumber)(record[field.key]);
|
|
835
|
+
if (coerced !== null) {
|
|
836
|
+
await this.ensureAndSet(
|
|
837
|
+
`${prefix}.battery.${field.id}`,
|
|
838
|
+
field.name,
|
|
839
|
+
"number",
|
|
840
|
+
field.role,
|
|
841
|
+
coerced,
|
|
842
|
+
field.unit
|
|
843
|
+
);
|
|
844
|
+
}
|
|
790
845
|
}
|
|
791
846
|
}
|
|
792
847
|
/**
|