@camstack/addon-provider-homeassistant 1.2.625 → 1.2.627
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/dist/addon.js +268 -119
- package/dist/addon.mjs +268 -119
- package/dist/{dist-DvZEnx9C.mjs → dist-BFnk4bwH.mjs} +1314 -497
- package/dist/{dist-oNUlRdur.js → dist-CRwUHjY7.js} +1349 -496
- package/dist/ha-export/ha-export.addon.js +30 -19
- package/dist/ha-export/ha-export.addon.mjs +30 -19
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_dist = require("./dist-
|
|
2
|
+
const require_dist = require("./dist-CRwUHjY7.js");
|
|
3
3
|
let node_crypto = require("node:crypto");
|
|
4
4
|
let node_zlib = require("node:zlib");
|
|
5
5
|
//#region src/ha-admin-link.ts
|
|
@@ -766,6 +766,30 @@ var HaSwitchDevice = class extends HaBaseChildDevice {
|
|
|
766
766
|
}
|
|
767
767
|
};
|
|
768
768
|
//#endregion
|
|
769
|
+
//#region src/ha-reading.ts
|
|
770
|
+
/**
|
|
771
|
+
* Convert one sample into the quantity's base unit. A refusal publishes
|
|
772
|
+
* nothing (the caller stores null) and is logged with the device id —
|
|
773
|
+
* an unknown unit is never guessed (D393, D944).
|
|
774
|
+
*/
|
|
775
|
+
function takeReading(logger, deviceId, value, rawUnit, quantity) {
|
|
776
|
+
const result = require_dist.ingestReading({
|
|
777
|
+
value,
|
|
778
|
+
rawUnit,
|
|
779
|
+
quantity
|
|
780
|
+
});
|
|
781
|
+
if (result.ok) return result.value;
|
|
782
|
+
logger.warn("numeric reading refused", {
|
|
783
|
+
tags: { deviceId },
|
|
784
|
+
meta: {
|
|
785
|
+
quantity,
|
|
786
|
+
unit: rawUnit ?? "",
|
|
787
|
+
reason: result.reason
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
//#endregion
|
|
769
793
|
//#region src/devices/ha-numeric-sensor-device.ts
|
|
770
794
|
var SUPPORTED_CAP_NAMES$1 = [
|
|
771
795
|
"temperature-sensor",
|
|
@@ -782,6 +806,13 @@ var VALUE_FIELD = {
|
|
|
782
806
|
"pressure-sensor": "hpa",
|
|
783
807
|
"numeric-sensor": "value"
|
|
784
808
|
};
|
|
809
|
+
/** Typed sensors already name their quantity. The generic sensor learns it. */
|
|
810
|
+
var FIXED_QUANTITY = {
|
|
811
|
+
"temperature-sensor": "temperature",
|
|
812
|
+
"humidity-sensor": "humidity",
|
|
813
|
+
"ambient-light-sensor": "illuminance",
|
|
814
|
+
"pressure-sensor": "pressure"
|
|
815
|
+
};
|
|
785
816
|
/**
|
|
786
817
|
* Generic HA numeric-sensor child device. One class hosts every
|
|
787
818
|
* single-metric numeric sensor cap (Phase B B2 + the B3 fallback)
|
|
@@ -819,15 +850,14 @@ var HaNumericSensorDevice = class extends HaBaseChildDevice {
|
|
|
819
850
|
this.hasStringFallback = capName === "numeric-sensor";
|
|
820
851
|
this.registerCap(capName);
|
|
821
852
|
if (this.hasStringFallback) this.registerStringFallbackCap();
|
|
822
|
-
this.runtimeState.setCapState(this.capName, this.buildSlice(0, 0, void 0
|
|
853
|
+
this.runtimeState.setCapState(this.capName, this.buildSlice(0, 0, void 0));
|
|
823
854
|
}
|
|
824
855
|
handleStatePush(state) {
|
|
825
856
|
const value = Number(state.state);
|
|
826
857
|
const lastFetchedAt = Date.parse(state.last_updated) || Date.now();
|
|
827
858
|
if (Number.isFinite(value)) {
|
|
828
859
|
const unitRaw = typeof state.attributes["unit_of_measurement"] === "string" ? state.attributes["unit_of_measurement"] : void 0;
|
|
829
|
-
|
|
830
|
-
this.runtimeState.setCapState(this.capName, this.buildSlice(value, lastFetchedAt, unitRaw, precisionRaw));
|
|
860
|
+
this.runtimeState.setCapState(this.capName, this.buildSlice(value, lastFetchedAt, unitRaw));
|
|
831
861
|
return;
|
|
832
862
|
}
|
|
833
863
|
const rawState = state.state;
|
|
@@ -839,24 +869,43 @@ var HaNumericSensorDevice = class extends HaBaseChildDevice {
|
|
|
839
869
|
this.runtimeState.setCapState("enum-sensor", enumSlice);
|
|
840
870
|
}
|
|
841
871
|
}
|
|
842
|
-
buildSlice(value, lastFetchedAt, unitRaw
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
lastFetchedAt,
|
|
848
|
-
...unit !== void 0 ? { unit } : {},
|
|
849
|
-
...precisionRaw !== void 0 ? { precision: precisionRaw } : {}
|
|
850
|
-
};
|
|
851
|
-
}
|
|
852
|
-
const baseSlice = {
|
|
853
|
-
[this.valueKey]: value,
|
|
872
|
+
buildSlice(value, lastFetchedAt, unitRaw) {
|
|
873
|
+
const quantity = this.resolveQuantity(unitRaw);
|
|
874
|
+
const stored = quantity === null ? null : takeReading(this.deviceLogger, this.id, value, unitRaw, quantity);
|
|
875
|
+
if (this.capName === "numeric-sensor") return {
|
|
876
|
+
value: stored,
|
|
854
877
|
lastFetchedAt
|
|
855
878
|
};
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
879
|
+
return {
|
|
880
|
+
[this.valueKey]: stored,
|
|
881
|
+
lastFetchedAt
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Typed sensors already name their quantity. A generic numeric sensor
|
|
886
|
+
* learns it once from the unit and stores it on the device, so later
|
|
887
|
+
* samples convert against the same quantity.
|
|
888
|
+
*/
|
|
889
|
+
resolveQuantity(unitRaw) {
|
|
890
|
+
const fixed = FIXED_QUANTITY[this.capName];
|
|
891
|
+
if (fixed !== void 0) return fixed;
|
|
892
|
+
const stored = this.sourceInfo.quantity;
|
|
893
|
+
if (stored !== void 0 && stored !== "device") return stored;
|
|
894
|
+
if (unitRaw === void 0) return "none";
|
|
895
|
+
const learned = require_dist.quantityFromUnit(unitRaw);
|
|
896
|
+
if (learned === void 0 || learned === "device") {
|
|
897
|
+
this.deviceLogger.warn("numeric reading refused", {
|
|
898
|
+
tags: { deviceId: this.id },
|
|
899
|
+
meta: {
|
|
900
|
+
quantity: "device",
|
|
901
|
+
unit: unitRaw,
|
|
902
|
+
reason: "unknown-unit"
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
this.updateSourceInfo({ quantity: learned });
|
|
908
|
+
return learned;
|
|
860
909
|
}
|
|
861
910
|
/**
|
|
862
911
|
* Register an `enum-sensor` cap backed by the same `runtimeState` so that
|
|
@@ -1615,6 +1664,22 @@ var HaClimateDevice = class extends HaBaseChildDevice {
|
|
|
1615
1664
|
if (state.state === "unavailable" || state.state === "unknown") return;
|
|
1616
1665
|
this.swingModes = readStringArray$4(state.attributes, "swing_modes");
|
|
1617
1666
|
this.swingHorizontalModes = readStringArray$4(state.attributes, "swing_horizontal_modes");
|
|
1667
|
+
const unit = readStringAttr$6(state.attributes, "temperature_unit");
|
|
1668
|
+
if (unit !== null) {
|
|
1669
|
+
const probe = require_dist.ingestReading({
|
|
1670
|
+
value: 0,
|
|
1671
|
+
rawUnit: unit,
|
|
1672
|
+
quantity: "temperature"
|
|
1673
|
+
});
|
|
1674
|
+
if (!probe.ok) this.deviceLogger.warn("numeric reading refused", {
|
|
1675
|
+
tags: { deviceId: this.id },
|
|
1676
|
+
meta: {
|
|
1677
|
+
quantity: "temperature",
|
|
1678
|
+
unit,
|
|
1679
|
+
reason: probe.reason
|
|
1680
|
+
}
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1618
1683
|
this.runtimeState.setCapState("climate-control", climateSliceFromState(state));
|
|
1619
1684
|
}
|
|
1620
1685
|
registerClimateCap() {
|
|
@@ -1673,6 +1738,16 @@ function climateSliceFromState(state) {
|
|
|
1673
1738
|
const lastFetchedAt = Date.parse(state.last_updated) || Date.now();
|
|
1674
1739
|
const attrs = state.attributes;
|
|
1675
1740
|
const mode = sanitizeHvacMode(state.state);
|
|
1741
|
+
const tempUnit = readStringAttr$6(attrs, "temperature_unit") ?? void 0;
|
|
1742
|
+
const celsius = (raw) => {
|
|
1743
|
+
if (raw === null) return null;
|
|
1744
|
+
const result = require_dist.ingestReading({
|
|
1745
|
+
value: raw,
|
|
1746
|
+
rawUnit: tempUnit,
|
|
1747
|
+
quantity: "temperature"
|
|
1748
|
+
});
|
|
1749
|
+
return result.ok ? result.value : null;
|
|
1750
|
+
};
|
|
1676
1751
|
const availableModes = readStringArray$4(attrs, "hvac_modes").map(sanitizeHvacMode).filter((m) => m !== null);
|
|
1677
1752
|
const fanMode = readStringAttr$6(attrs, "fan_mode") ?? "";
|
|
1678
1753
|
const availableFanModes = readStringArray$4(attrs, "fan_modes");
|
|
@@ -1685,16 +1760,16 @@ function climateSliceFromState(state) {
|
|
|
1685
1760
|
availableFanModes,
|
|
1686
1761
|
preset,
|
|
1687
1762
|
availablePresets,
|
|
1688
|
-
target: readNumberAttr$5(attrs, "temperature"),
|
|
1689
|
-
targetHigh: readNumberAttr$5(attrs, "target_temp_high"),
|
|
1690
|
-
targetLow: readNumberAttr$5(attrs, "target_temp_low"),
|
|
1763
|
+
target: celsius(readNumberAttr$5(attrs, "temperature")),
|
|
1764
|
+
targetHigh: celsius(readNumberAttr$5(attrs, "target_temp_high")),
|
|
1765
|
+
targetLow: celsius(readNumberAttr$5(attrs, "target_temp_low")),
|
|
1691
1766
|
targetHumidity: readNumberAttr$5(attrs, "target_humidity"),
|
|
1692
|
-
currentTemp: readNumberAttr$5(attrs, "current_temperature"),
|
|
1767
|
+
currentTemp: celsius(readNumberAttr$5(attrs, "current_temperature")),
|
|
1693
1768
|
currentHumidity: readNumberAttr$5(attrs, "current_humidity"),
|
|
1694
1769
|
...readSwing(attrs),
|
|
1695
|
-
minTemp: readNumberAttr$5(attrs, "min_temp"),
|
|
1696
|
-
maxTemp: readNumberAttr$5(attrs, "max_temp"),
|
|
1697
|
-
tempStep: readNumberAttr$5(attrs, "target_temp_step"),
|
|
1770
|
+
minTemp: celsius(readNumberAttr$5(attrs, "min_temp")),
|
|
1771
|
+
maxTemp: celsius(readNumberAttr$5(attrs, "max_temp")),
|
|
1772
|
+
tempStep: celsius(readNumberAttr$5(attrs, "target_temp_step")),
|
|
1698
1773
|
lastFetchedAt
|
|
1699
1774
|
};
|
|
1700
1775
|
}
|
|
@@ -2517,10 +2592,10 @@ function readNumberAttr$1(attrs, key) {
|
|
|
2517
2592
|
* - number / input_number → kind='numeric', service set_value({value})
|
|
2518
2593
|
* - select / input_select → kind='select', service select_option({option})
|
|
2519
2594
|
* - text / input_text → kind='text', service set_value({value})
|
|
2520
|
-
* - date → kind='datetime',
|
|
2521
|
-
* - time → kind='datetime',
|
|
2522
|
-
* - datetime → kind='datetime',
|
|
2523
|
-
* - input_datetime → kind='datetime',
|
|
2595
|
+
* - date → kind='datetime', shape date, set_value({date})
|
|
2596
|
+
* - time → kind='datetime', shape time, set_value({time})
|
|
2597
|
+
* - datetime → kind='datetime', shape datetime, set_value({datetime})
|
|
2598
|
+
* - input_datetime → kind='datetime', shape derived from
|
|
2524
2599
|
* has_date/has_time, service set_datetime({datetime})
|
|
2525
2600
|
*
|
|
2526
2601
|
* The datetime `value` is kept as the HA-native string (date.* → 'YYYY-MM-DD',
|
|
@@ -2531,6 +2606,8 @@ function readNumberAttr$1(attrs, key) {
|
|
|
2531
2606
|
var HaControlDevice = class extends HaBaseChildDevice {
|
|
2532
2607
|
kind;
|
|
2533
2608
|
haDomain;
|
|
2609
|
+
/** Unit the upstream entity speaks. Writes convert the base value back to it. */
|
|
2610
|
+
upstreamUnit;
|
|
2534
2611
|
constructor(ctx) {
|
|
2535
2612
|
const persisted = ctx.persistedConfig ?? {};
|
|
2536
2613
|
super(ctx, persisted.role !== void 0 ? { role: persisted.role } : {});
|
|
@@ -2538,18 +2615,30 @@ var HaControlDevice = class extends HaBaseChildDevice {
|
|
|
2538
2615
|
this.kind = roleToKind(persisted.role);
|
|
2539
2616
|
const seedOptions = Array.isArray(persisted.options) ? persisted.options.filter((x) => typeof x === "string") : [];
|
|
2540
2617
|
this.registerControlCap();
|
|
2618
|
+
if (this.kind === "datetime") this.updateSourceInfo({ dateTimeShape: this.datetimeFormat(void 0) });
|
|
2541
2619
|
this.runtimeState.setCapState("control", {
|
|
2542
2620
|
kind: this.kind,
|
|
2543
2621
|
value: this.kind === "numeric" ? 0 : "",
|
|
2544
2622
|
options: seedOptions,
|
|
2545
|
-
lastChangedAt: 0
|
|
2546
|
-
...this.kind === "datetime" ? { format: this.datetimeFormat(void 0) } : {}
|
|
2623
|
+
lastChangedAt: 0
|
|
2547
2624
|
});
|
|
2548
2625
|
}
|
|
2549
2626
|
handleStatePush(state) {
|
|
2550
2627
|
if (state.state === "unavailable" || state.state === "unknown") return;
|
|
2551
2628
|
const lastChangedAt = Date.parse(state.last_changed) || Date.now();
|
|
2552
|
-
const slice = decodeControlSlice(this.kind, state, lastChangedAt,
|
|
2629
|
+
const slice = decodeControlSlice(this.kind, state, lastChangedAt, {
|
|
2630
|
+
logger: this.deviceLogger,
|
|
2631
|
+
deviceId: this.id,
|
|
2632
|
+
quantity: this.sourceInfo.quantity,
|
|
2633
|
+
note: (quantity, unit) => {
|
|
2634
|
+
this.upstreamUnit = unit;
|
|
2635
|
+
if (this.sourceInfo.quantity !== quantity) this.updateSourceInfo({ quantity });
|
|
2636
|
+
},
|
|
2637
|
+
noteShape: (shape) => {
|
|
2638
|
+
if (this.sourceInfo.dateTimeShape !== shape) this.updateSourceInfo({ dateTimeShape: shape });
|
|
2639
|
+
},
|
|
2640
|
+
shape: () => this.datetimeFormat(state.attributes)
|
|
2641
|
+
});
|
|
2553
2642
|
if (slice !== null) this.runtimeState.setCapState("control", slice);
|
|
2554
2643
|
}
|
|
2555
2644
|
registerControlCap() {
|
|
@@ -2559,8 +2648,7 @@ var HaControlDevice = class extends HaBaseChildDevice {
|
|
|
2559
2648
|
kind: this.kind,
|
|
2560
2649
|
value: this.kind === "numeric" ? 0 : "",
|
|
2561
2650
|
options: [],
|
|
2562
|
-
lastChangedAt: 0
|
|
2563
|
-
...this.kind === "datetime" ? { format: this.datetimeFormat(void 0) } : {}
|
|
2651
|
+
lastChangedAt: 0
|
|
2564
2652
|
};
|
|
2565
2653
|
},
|
|
2566
2654
|
setValue: async ({ control }) => {
|
|
@@ -2590,11 +2678,32 @@ var HaControlDevice = class extends HaBaseChildDevice {
|
|
|
2590
2678
|
default: return "datetime";
|
|
2591
2679
|
}
|
|
2592
2680
|
}
|
|
2681
|
+
/** Base-unit value → the unit the upstream entity still speaks. */
|
|
2682
|
+
toUpstream(value) {
|
|
2683
|
+
const quantity = this.sourceInfo.quantity;
|
|
2684
|
+
const base = quantity === void 0 ? void 0 : require_dist.baseUnitOf(quantity);
|
|
2685
|
+
const upstream = this.upstreamUnit;
|
|
2686
|
+
if (base === void 0 || upstream === void 0 || upstream === base) return value;
|
|
2687
|
+
const converted = require_dist.tryConvertUnit(value, base, upstream);
|
|
2688
|
+
if (converted !== void 0) return converted;
|
|
2689
|
+
this.deviceLogger.warn("numeric reading refused", {
|
|
2690
|
+
tags: { deviceId: this.id },
|
|
2691
|
+
meta: {
|
|
2692
|
+
quantity: quantity ?? "device",
|
|
2693
|
+
unit: upstream,
|
|
2694
|
+
reason: "unknown-unit"
|
|
2695
|
+
}
|
|
2696
|
+
});
|
|
2697
|
+
return null;
|
|
2698
|
+
}
|
|
2593
2699
|
async dispatchSet(control) {
|
|
2594
2700
|
switch (control.kind) {
|
|
2595
|
-
case "numeric":
|
|
2596
|
-
|
|
2701
|
+
case "numeric": {
|
|
2702
|
+
const outbound = this.toUpstream(control.value);
|
|
2703
|
+
if (outbound === null) return;
|
|
2704
|
+
await this.callHaService(this.haDomain, "set_value", { value: outbound });
|
|
2597
2705
|
return;
|
|
2706
|
+
}
|
|
2598
2707
|
case "select":
|
|
2599
2708
|
await this.callHaService(this.haDomain, "select_option", { option: control.value });
|
|
2600
2709
|
return;
|
|
@@ -2643,47 +2752,9 @@ function roleToKind(role) {
|
|
|
2643
2752
|
default: return "text";
|
|
2644
2753
|
}
|
|
2645
2754
|
}
|
|
2646
|
-
|
|
2647
|
-
* Derive display precision from a numeric step value when HA does not provide
|
|
2648
|
-
* `suggested_display_precision` explicitly. Returns the number of decimal
|
|
2649
|
-
* places implied by the step granularity (e.g. 0.5 → 1, 0.01 → 2, 1 → 0).
|
|
2650
|
-
* Returns `undefined` when step is absent or is a whole number (let the UI
|
|
2651
|
-
* apply its own auto-formatting for integer steps).
|
|
2652
|
-
*/
|
|
2653
|
-
function precisionFromStep(step) {
|
|
2654
|
-
if (step === void 0 || step <= 0) return void 0;
|
|
2655
|
-
if (Number.isInteger(step)) return 0;
|
|
2656
|
-
const str = step.toString();
|
|
2657
|
-
const dotIndex = str.indexOf(".");
|
|
2658
|
-
if (dotIndex === -1) return 0;
|
|
2659
|
-
return str.length - dotIndex - 1;
|
|
2660
|
-
}
|
|
2661
|
-
function decodeControlSlice(kind, state, lastChangedAt, resolveFormat) {
|
|
2755
|
+
function decodeControlSlice(kind, state, lastChangedAt, decode) {
|
|
2662
2756
|
switch (kind) {
|
|
2663
|
-
case "numeric":
|
|
2664
|
-
const v = Number(state.state);
|
|
2665
|
-
if (!Number.isFinite(v)) return null;
|
|
2666
|
-
const rawUnit = state.attributes["unit_of_measurement"];
|
|
2667
|
-
const rawMin = state.attributes["min"];
|
|
2668
|
-
const rawMax = state.attributes["max"];
|
|
2669
|
-
const rawStep = state.attributes["step"];
|
|
2670
|
-
const rawPrecision = state.attributes["suggested_display_precision"];
|
|
2671
|
-
const unit = typeof rawUnit === "string" && rawUnit.length > 0 ? rawUnit : void 0;
|
|
2672
|
-
const min = typeof rawMin === "number" && Number.isFinite(rawMin) ? rawMin : void 0;
|
|
2673
|
-
const max = typeof rawMax === "number" && Number.isFinite(rawMax) ? rawMax : void 0;
|
|
2674
|
-
const step = typeof rawStep === "number" && Number.isFinite(rawStep) && rawStep > 0 ? rawStep : void 0;
|
|
2675
|
-
return {
|
|
2676
|
-
kind: "numeric",
|
|
2677
|
-
value: v,
|
|
2678
|
-
options: [],
|
|
2679
|
-
lastChangedAt,
|
|
2680
|
-
unit,
|
|
2681
|
-
min,
|
|
2682
|
-
max,
|
|
2683
|
-
step,
|
|
2684
|
-
precision: typeof rawPrecision === "number" && Number.isFinite(rawPrecision) ? rawPrecision : precisionFromStep(step)
|
|
2685
|
-
};
|
|
2686
|
-
}
|
|
2757
|
+
case "numeric": return decodeNumeric(state, lastChangedAt, decode);
|
|
2687
2758
|
case "select": {
|
|
2688
2759
|
const options = Array.isArray(state.attributes["options"]) ? state.attributes["options"].filter((x) => typeof x === "string") : [];
|
|
2689
2760
|
return {
|
|
@@ -2699,14 +2770,72 @@ function decodeControlSlice(kind, state, lastChangedAt, resolveFormat) {
|
|
|
2699
2770
|
options: [],
|
|
2700
2771
|
lastChangedAt
|
|
2701
2772
|
};
|
|
2702
|
-
case "datetime":
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2773
|
+
case "datetime":
|
|
2774
|
+
decode.noteShape(decode.shape());
|
|
2775
|
+
return {
|
|
2776
|
+
kind: "datetime",
|
|
2777
|
+
value: state.state,
|
|
2778
|
+
options: [],
|
|
2779
|
+
lastChangedAt
|
|
2780
|
+
};
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
function decodeNumeric(state, lastChangedAt, decode) {
|
|
2784
|
+
const v = Number(state.state);
|
|
2785
|
+
if (!Number.isFinite(v)) return null;
|
|
2786
|
+
const rawUnit = state.attributes["unit_of_measurement"];
|
|
2787
|
+
const unit = typeof rawUnit === "string" && rawUnit.length > 0 ? rawUnit : void 0;
|
|
2788
|
+
const incoming = unit === void 0 ? void 0 : require_dist.quantityFromUnit(unit);
|
|
2789
|
+
if (unit !== void 0 && (incoming === void 0 || incoming === "device")) {
|
|
2790
|
+
decode.logger.warn("numeric reading refused", {
|
|
2791
|
+
tags: { deviceId: decode.deviceId },
|
|
2792
|
+
meta: {
|
|
2793
|
+
quantity: "device",
|
|
2794
|
+
unit,
|
|
2795
|
+
reason: "unknown-unit"
|
|
2796
|
+
}
|
|
2797
|
+
});
|
|
2798
|
+
return null;
|
|
2709
2799
|
}
|
|
2800
|
+
const stored = decode.quantity;
|
|
2801
|
+
const quantity = stored !== void 0 && stored !== "device" ? stored : incoming ?? "none";
|
|
2802
|
+
if (incoming !== void 0 && incoming !== "device" && incoming !== quantity) {
|
|
2803
|
+
decode.logger.warn("numeric reading refused", {
|
|
2804
|
+
tags: { deviceId: decode.deviceId },
|
|
2805
|
+
meta: {
|
|
2806
|
+
quantity,
|
|
2807
|
+
unit: unit ?? "",
|
|
2808
|
+
reason: "cross-dimension"
|
|
2809
|
+
}
|
|
2810
|
+
});
|
|
2811
|
+
return null;
|
|
2812
|
+
}
|
|
2813
|
+
const value = takeReading(decode.logger, decode.deviceId, v, unit, quantity);
|
|
2814
|
+
if (value === null) return null;
|
|
2815
|
+
decode.note(quantity, unit === void 0 ? void 0 : require_dist.normalizeUnit(unit));
|
|
2816
|
+
const rawMin = state.attributes["min"];
|
|
2817
|
+
const rawMax = state.attributes["max"];
|
|
2818
|
+
const rawStep = state.attributes["step"];
|
|
2819
|
+
const min = typeof rawMin === "number" ? takeReading(decode.logger, decode.deviceId, rawMin, unit, quantity) : void 0;
|
|
2820
|
+
const max = typeof rawMax === "number" ? takeReading(decode.logger, decode.deviceId, rawMax, unit, quantity) : void 0;
|
|
2821
|
+
const step = convertStep(typeof rawStep === "number" && rawStep > 0 ? rawStep : void 0, unit, quantity);
|
|
2822
|
+
return {
|
|
2823
|
+
kind: "numeric",
|
|
2824
|
+
value,
|
|
2825
|
+
options: [],
|
|
2826
|
+
lastChangedAt,
|
|
2827
|
+
...min !== void 0 && min !== null ? { min } : {},
|
|
2828
|
+
...max !== void 0 && max !== null ? { max } : {},
|
|
2829
|
+
...step !== void 0 ? { step } : {}
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
2832
|
+
/** A step is a difference: °F's offset must not move it. */
|
|
2833
|
+
function convertStep(step, unit, quantity) {
|
|
2834
|
+
if (step === void 0) return void 0;
|
|
2835
|
+
const base = require_dist.baseUnitOf(quantity);
|
|
2836
|
+
const from = unit === void 0 ? void 0 : require_dist.normalizeUnit(unit);
|
|
2837
|
+
if (base === void 0 || from === void 0 || from === base) return step;
|
|
2838
|
+
return require_dist.tryConvertDelta(step, from, base);
|
|
2710
2839
|
}
|
|
2711
2840
|
//#endregion
|
|
2712
2841
|
//#region src/devices/ha-script-device.ts
|
|
@@ -2952,15 +3081,15 @@ var HaPowerMeterDevice = class extends HaBaseChildDevice {
|
|
|
2952
3081
|
const value = Number(state.state);
|
|
2953
3082
|
if (!Number.isFinite(value)) return;
|
|
2954
3083
|
const lastFetchedAt = Date.parse(state.last_updated) || Date.now();
|
|
2955
|
-
const
|
|
2956
|
-
const
|
|
2957
|
-
const
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
3084
|
+
const deviceClassRaw = state.attributes["device_class"];
|
|
3085
|
+
const deviceClass = typeof deviceClassRaw === "string" ? deviceClassRaw : this.haDeviceClass;
|
|
3086
|
+
const quantity = powerQuantity(deviceClass);
|
|
3087
|
+
if (quantity === void 0) return;
|
|
3088
|
+
const unitRaw = state.attributes["unit_of_measurement"];
|
|
3089
|
+
const stored = takeReading(this.deviceLogger, this.id, value, typeof unitRaw === "string" ? unitRaw : void 0, quantity);
|
|
3090
|
+
if (stored === null) return;
|
|
3091
|
+
const update = applyDeviceClass$1(this.runtimeState.getCapState("power-meter") ?? COLD_START$4, deviceClass, stored, lastFetchedAt);
|
|
3092
|
+
if (update !== null) this.runtimeState.setCapState("power-meter", update);
|
|
2964
3093
|
}
|
|
2965
3094
|
registerPowerMeterCap() {
|
|
2966
3095
|
this.ctx.registerNativeCap(require_dist.powerMeterCapability, { getStatus: async () => {
|
|
@@ -2968,6 +3097,15 @@ var HaPowerMeterDevice = class extends HaBaseChildDevice {
|
|
|
2968
3097
|
} });
|
|
2969
3098
|
}
|
|
2970
3099
|
};
|
|
3100
|
+
function powerQuantity(deviceClass) {
|
|
3101
|
+
switch (deviceClass) {
|
|
3102
|
+
case "power": return "power";
|
|
3103
|
+
case "energy": return "energy";
|
|
3104
|
+
case "voltage": return "voltage";
|
|
3105
|
+
case "current": return "current";
|
|
3106
|
+
default: return;
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
2971
3109
|
function applyDeviceClass$1(prev, deviceClass, value, lastFetchedAt) {
|
|
2972
3110
|
switch (deviceClass) {
|
|
2973
3111
|
case "power": return {
|
|
@@ -3022,15 +3160,15 @@ var HaAirQualityDevice = class extends HaBaseChildDevice {
|
|
|
3022
3160
|
const value = Number(state.state);
|
|
3023
3161
|
if (!Number.isFinite(value)) return;
|
|
3024
3162
|
const lastFetchedAt = Date.parse(state.last_updated) || Date.now();
|
|
3025
|
-
const
|
|
3026
|
-
const
|
|
3027
|
-
const
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3163
|
+
const deviceClassRaw = state.attributes["device_class"];
|
|
3164
|
+
const deviceClass = typeof deviceClassRaw === "string" ? deviceClassRaw : null;
|
|
3165
|
+
const quantity = airQuantity(deviceClass);
|
|
3166
|
+
if (quantity === void 0) return;
|
|
3167
|
+
const unitRaw = state.attributes["unit_of_measurement"];
|
|
3168
|
+
const stored = takeReading(this.deviceLogger, this.id, value, typeof unitRaw === "string" ? unitRaw : void 0, quantity);
|
|
3169
|
+
if (stored === null) return;
|
|
3170
|
+
const update = applyDeviceClass(this.runtimeState.getCapState("air-quality-sensor") ?? COLD_START$3, deviceClass, stored, lastFetchedAt);
|
|
3171
|
+
if (update !== null) this.runtimeState.setCapState("air-quality-sensor", update);
|
|
3034
3172
|
}
|
|
3035
3173
|
registerCap() {
|
|
3036
3174
|
this.ctx.registerNativeCap(require_dist.airQualitySensorCapability, { getStatus: async () => {
|
|
@@ -3038,6 +3176,17 @@ var HaAirQualityDevice = class extends HaBaseChildDevice {
|
|
|
3038
3176
|
} });
|
|
3039
3177
|
}
|
|
3040
3178
|
};
|
|
3179
|
+
function airQuantity(deviceClass) {
|
|
3180
|
+
switch (deviceClass) {
|
|
3181
|
+
case "co2": return "concentration-volume";
|
|
3182
|
+
case "volatile_organic_compounds": return "concentration-ppb";
|
|
3183
|
+
case "pm25":
|
|
3184
|
+
case "pm2_5":
|
|
3185
|
+
case "pm10": return "concentration-mass";
|
|
3186
|
+
case "aqi": return "index";
|
|
3187
|
+
default: return;
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3041
3190
|
function applyDeviceClass(prev, deviceClass, value, lastFetchedAt) {
|
|
3042
3191
|
switch (deviceClass) {
|
|
3043
3192
|
case "co2": return {
|
|
@@ -3089,9 +3238,9 @@ var HaEnumSensorDevice = class extends HaBaseChildDevice {
|
|
|
3089
3238
|
/**
|
|
3090
3239
|
* Locale-render shape for a `DateTimeSensor`-role sensor (HA
|
|
3091
3240
|
* `device_class=timestamp` → `'datetime'`, `date` → `'date'`). Read from the
|
|
3092
|
-
* persisted child config (threaded by the HA mapping) and
|
|
3093
|
-
*
|
|
3094
|
-
*
|
|
3241
|
+
* persisted child config (threaded by the HA mapping) and stored once on the
|
|
3242
|
+
* device. A timestamp sensor marks the slice so history does not learn the
|
|
3243
|
+
* ISO string as an enum state.
|
|
3095
3244
|
*/
|
|
3096
3245
|
dateTimeFormat;
|
|
3097
3246
|
constructor(ctx) {
|
|
@@ -3099,15 +3248,14 @@ var HaEnumSensorDevice = class extends HaBaseChildDevice {
|
|
|
3099
3248
|
super(ctx, persisted.role !== void 0 ? { role: persisted.role } : {});
|
|
3100
3249
|
const parsedFormat = require_dist.EnumSensorDateTimeFormatSchema.safeParse(persisted.dateTimeFormat);
|
|
3101
3250
|
this.dateTimeFormat = parsedFormat.success ? parsedFormat.data : void 0;
|
|
3251
|
+
if (this.dateTimeFormat !== void 0) this.updateSourceInfo({ dateTimeShape: this.dateTimeFormat });
|
|
3102
3252
|
this.registerCap();
|
|
3103
3253
|
this.runtimeState.setCapState("enum-sensor", this.coldStart());
|
|
3104
3254
|
}
|
|
3105
|
-
/** Cold-start slice — carries the format up-front so the UI can render the
|
|
3106
|
-
* correct shape the instant a value lands, with no slice churn. */
|
|
3107
3255
|
coldStart() {
|
|
3108
3256
|
return {
|
|
3109
3257
|
...COLD_START$2,
|
|
3110
|
-
...this.dateTimeFormat !== void 0 ? {
|
|
3258
|
+
...this.dateTimeFormat !== void 0 ? { timestamp: true } : {}
|
|
3111
3259
|
};
|
|
3112
3260
|
}
|
|
3113
3261
|
handleStatePush(state) {
|
|
@@ -3115,7 +3263,7 @@ var HaEnumSensorDevice = class extends HaBaseChildDevice {
|
|
|
3115
3263
|
const lastFetchedAt = Date.parse(state.last_updated) || Date.now();
|
|
3116
3264
|
this.runtimeState.setCapState("enum-sensor", {
|
|
3117
3265
|
value: state.state,
|
|
3118
|
-
...this.dateTimeFormat !== void 0 ? {
|
|
3266
|
+
...this.dateTimeFormat !== void 0 ? { timestamp: true } : {},
|
|
3119
3267
|
lastFetchedAt
|
|
3120
3268
|
});
|
|
3121
3269
|
}
|
|
@@ -3272,12 +3420,9 @@ function readEventTypesFromAttributes(attributes) {
|
|
|
3272
3420
|
var COLD_START$1 = {
|
|
3273
3421
|
condition: null,
|
|
3274
3422
|
temperature: null,
|
|
3275
|
-
temperatureUnit: null,
|
|
3276
3423
|
humidity: null,
|
|
3277
3424
|
pressure: null,
|
|
3278
|
-
pressureUnit: null,
|
|
3279
3425
|
windSpeed: null,
|
|
3280
|
-
windSpeedUnit: null,
|
|
3281
3426
|
windBearing: null,
|
|
3282
3427
|
lastFetchedAt: 0
|
|
3283
3428
|
};
|
|
@@ -3309,13 +3454,10 @@ var HaWeatherDevice = class extends HaBaseChildDevice {
|
|
|
3309
3454
|
const attrs = state.attributes;
|
|
3310
3455
|
const slice = {
|
|
3311
3456
|
condition: state.state,
|
|
3312
|
-
temperature:
|
|
3313
|
-
temperatureUnit: readStringAttr$1(attrs, "temperature_unit"),
|
|
3457
|
+
temperature: this.readBase(attrs, "temperature", "temperature_unit", "temperature"),
|
|
3314
3458
|
humidity: clampOrNull(readNumberAttr(attrs, "humidity"), 0, 100),
|
|
3315
|
-
pressure:
|
|
3316
|
-
|
|
3317
|
-
windSpeed: readNumberAttr(attrs, "wind_speed"),
|
|
3318
|
-
windSpeedUnit: readStringAttr$1(attrs, "wind_speed_unit"),
|
|
3459
|
+
pressure: this.readBase(attrs, "pressure", "pressure_unit", "pressure"),
|
|
3460
|
+
windSpeed: this.readBase(attrs, "wind_speed", "wind_speed_unit", "speed"),
|
|
3319
3461
|
windBearing: readNumberAttr(attrs, "wind_bearing"),
|
|
3320
3462
|
lastFetchedAt
|
|
3321
3463
|
};
|
|
@@ -3326,6 +3468,13 @@ var HaWeatherDevice = class extends HaBaseChildDevice {
|
|
|
3326
3468
|
return this.runtimeState.getCapState("weather") ?? COLD_START$1;
|
|
3327
3469
|
} });
|
|
3328
3470
|
}
|
|
3471
|
+
/** One weather attribute, converted into the quantity's base unit. */
|
|
3472
|
+
readBase(attrs, valueKey, unitKey, quantity) {
|
|
3473
|
+
const raw = readNumberAttr(attrs, valueKey);
|
|
3474
|
+
if (raw === null) return null;
|
|
3475
|
+
const unit = readStringAttr$1(attrs, unitKey);
|
|
3476
|
+
return takeReading(this.deviceLogger, this.id, raw, unit ?? void 0, quantity);
|
|
3477
|
+
}
|
|
3329
3478
|
};
|
|
3330
3479
|
function readNumberAttr(attrs, key) {
|
|
3331
3480
|
const v = attrs[key];
|