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