@camstack/addon-provider-homeassistant 1.2.649 → 1.2.650
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/addon.js +2 -1
- package/dist/addon.mjs +2 -1
- package/dist/{ha-broker-source-D3FBA6uG.mjs → ha-broker-source-B2v2dnPe.mjs} +57 -31
- package/dist/{ha-broker-source-CE3nHkyW.js → ha-broker-source-DUj9pUWr.js} +57 -31
- package/dist/ha-export/ha-export.addon.js +107 -36
- package/dist/ha-export/ha-export.addon.mjs +107 -36
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.650] - 2026-11-02
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Home Assistant export: a cover offers position and tilt only when the device declares them, shows unavailable while its provider is offline, and a refused command now answers 422 with the reason
|
|
15
|
+
- A cover exports the stop control to Home Assistant only when the device declares cover-stoppable, and a Home Assistant cover with the STOP bit declares it.
|
|
16
|
+
|
|
10
17
|
## [1.2.649] - 2026-11-02
|
|
11
18
|
|
|
12
19
|
### Changed
|
package/dist/addon.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_ha_broker_source = require("./ha-broker-source-
|
|
2
|
+
const require_ha_broker_source = require("./ha-broker-source-DUj9pUWr.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
|
|
@@ -6328,6 +6328,7 @@ function mapCover(attributes, deviceClass) {
|
|
|
6328
6328
|
const features = [];
|
|
6329
6329
|
const sf = readNumericAttr(attributes, "supported_features");
|
|
6330
6330
|
if (sf !== null && (sf & 4) !== 0) features.push(require_ha_broker_source.DeviceFeature.CoverPositionable);
|
|
6331
|
+
if (sf !== null && (sf & 8) !== 0) features.push(require_ha_broker_source.DeviceFeature.CoverStoppable);
|
|
6331
6332
|
if (sf !== null && (sf & 128) !== 0) features.push(require_ha_broker_source.DeviceFeature.CoverTilt);
|
|
6332
6333
|
return {
|
|
6333
6334
|
deviceType: require_ha_broker_source.DeviceType.Cover,
|
package/dist/addon.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as humiditySensorCapability, $t as discriminatedUnion, A as brokerCapability, At as tamperCapability, C as alarmPanelCapability, Ct as runningFromMediaPlayer, D as bestLocationMatch, E as batteryCapability, Et as scriptRunnerCapability, F as carbonMonoxideCapability, Ft as vacuumControlCapability, G as deviceAdoptionCapability, Gt as defineEntryKind, H as controlCapability, Ht as baseUnitOf, I as climateControlCapability, It as valveCapability, J as eventEmitterCapability, Kt as defineRecordKinds, L as colorCapability, Lt as vibrationCapability, M as buildDeviceAdminUrl, Mt as tryConvertDelta, N as buttonCapability, Nt as tryConvertUnit, O as binaryCapability, Ot as smokeCapability, P as calendarSourceCapability, Pt as updateCapability, Q as humidifierCapability, Qt as boolean, R as connectivityCapability, Rt as waterHeaterCapability, S as airQualitySensorCapability, St as runningFromLawnMower, T as automationControlCapability, Tt as runningStateCapability, U as coverCapability, Ut as createEvent, V as contactCapability, Vt as RecordSecretUnavailableError, W as deviceAdminLinkCapability, X as floodCapability, Xt as _enum, Y as fanControlCapability, Z as gasCapability, Zt as array, a as BaseDevice, an as union, at as motionCapability, b as accessoriesCapability, bt as resolveAttachmentUrls, cn as DeviceFeature, ct as notifierCapability, dn as errMsg, dt as parseAdminBase, en as literal, et as imageCapability, fn as EventCategory, ft as powerMeterCapability, gt as pressureSensorCapability, ht as presenceCapability, in as string, it as mediaPlayerCapability, j as buildAddonRouteProvider, jt as temperatureSensorCapability, k as brightnessCapability, kt as switchCapability, ln as DeviceRole, lt as numericSensorCapability, mt as prepareNotification, nn as object, nt as lawnMowerControlCapability, o as BaseDeviceProvider, on as unknown, ot as normalizeUnit, p as EnumSensorDateTimeFormatSchema, q as enumSensorCapability, qt as entryBody, rn as record, rt as lockControlCapability, sn as url, st as notificationOutputCapability, t as HA_BROKER_KIND, tn as number, tt as ingestReading, un as DeviceType, v as RUNNING_STATE_COLD, vt as publicEndpointReaderFrom, w as ambientLightSensorCapability, wt as runningFromVacuumState, x as addonRoutesCapability, y as TargetSchema, yt as quantityFromUnit, zt as weatherCapability } from "./ha-broker-source-
|
|
1
|
+
import { $ as humiditySensorCapability, $t as discriminatedUnion, A as brokerCapability, At as tamperCapability, C as alarmPanelCapability, Ct as runningFromMediaPlayer, D as bestLocationMatch, E as batteryCapability, Et as scriptRunnerCapability, F as carbonMonoxideCapability, Ft as vacuumControlCapability, G as deviceAdoptionCapability, Gt as defineEntryKind, H as controlCapability, Ht as baseUnitOf, I as climateControlCapability, It as valveCapability, J as eventEmitterCapability, Kt as defineRecordKinds, L as colorCapability, Lt as vibrationCapability, M as buildDeviceAdminUrl, Mt as tryConvertDelta, N as buttonCapability, Nt as tryConvertUnit, O as binaryCapability, Ot as smokeCapability, P as calendarSourceCapability, Pt as updateCapability, Q as humidifierCapability, Qt as boolean, R as connectivityCapability, Rt as waterHeaterCapability, S as airQualitySensorCapability, St as runningFromLawnMower, T as automationControlCapability, Tt as runningStateCapability, U as coverCapability, Ut as createEvent, V as contactCapability, Vt as RecordSecretUnavailableError, W as deviceAdminLinkCapability, X as floodCapability, Xt as _enum, Y as fanControlCapability, Z as gasCapability, Zt as array, a as BaseDevice, an as union, at as motionCapability, b as accessoriesCapability, bt as resolveAttachmentUrls, cn as DeviceFeature, ct as notifierCapability, dn as errMsg, dt as parseAdminBase, en as literal, et as imageCapability, fn as EventCategory, ft as powerMeterCapability, gt as pressureSensorCapability, ht as presenceCapability, in as string, it as mediaPlayerCapability, j as buildAddonRouteProvider, jt as temperatureSensorCapability, k as brightnessCapability, kt as switchCapability, ln as DeviceRole, lt as numericSensorCapability, mt as prepareNotification, nn as object, nt as lawnMowerControlCapability, o as BaseDeviceProvider, on as unknown, ot as normalizeUnit, p as EnumSensorDateTimeFormatSchema, q as enumSensorCapability, qt as entryBody, rn as record, rt as lockControlCapability, sn as url, st as notificationOutputCapability, t as HA_BROKER_KIND, tn as number, tt as ingestReading, un as DeviceType, v as RUNNING_STATE_COLD, vt as publicEndpointReaderFrom, w as ambientLightSensorCapability, wt as runningFromVacuumState, x as addonRoutesCapability, y as TargetSchema, yt as quantityFromUnit, zt as weatherCapability } from "./ha-broker-source-B2v2dnPe.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
|
|
@@ -6327,6 +6327,7 @@ function mapCover(attributes, deviceClass) {
|
|
|
6327
6327
|
const features = [];
|
|
6328
6328
|
const sf = readNumericAttr(attributes, "supported_features");
|
|
6329
6329
|
if (sf !== null && (sf & 4) !== 0) features.push(DeviceFeature.CoverPositionable);
|
|
6330
|
+
if (sf !== null && (sf & 8) !== 0) features.push(DeviceFeature.CoverStoppable);
|
|
6330
6331
|
if (sf !== null && (sf & 128) !== 0) features.push(DeviceFeature.CoverTilt);
|
|
6331
6332
|
return {
|
|
6332
6333
|
deviceType: DeviceType.Cover,
|
|
@@ -784,7 +784,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
784
784
|
return EventCategory;
|
|
785
785
|
}({});
|
|
786
786
|
//#endregion
|
|
787
|
-
//#region ../types/dist/device-type-
|
|
787
|
+
//#region ../types/dist/device-type-CfHw_quR.mjs
|
|
788
788
|
/**
|
|
789
789
|
import { errMsg } from '@camstack/types'
|
|
790
790
|
* Extract a human-readable message from an unknown error value.
|
|
@@ -973,6 +973,9 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
|
|
|
973
973
|
DeviceFeature["CoverPositionable"] = "cover-positionable";
|
|
974
974
|
/** Cover exposes slat-tilt control. Gates the tilt slider UI. */
|
|
975
975
|
DeviceFeature["CoverTilt"] = "cover-tilt";
|
|
976
|
+
/** Cover has a real stop: `stop` halts the travel in place. Gates the stop
|
|
977
|
+
* control in exporters; a provider whose `stop` is a no-op never declares it. */
|
|
978
|
+
DeviceFeature["CoverStoppable"] = "cover-stoppable";
|
|
976
979
|
/** Valve exposes intermediate position control (0..100). Gates the
|
|
977
980
|
* position slider / drag surface UI. */
|
|
978
981
|
DeviceFeature["ValvePositionable"] = "valve-positionable";
|
|
@@ -5971,7 +5974,7 @@ var ZodIssueCode = {
|
|
|
5971
5974
|
var ZodFirstPartyTypeKind;
|
|
5972
5975
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5973
5976
|
//#endregion
|
|
5974
|
-
//#region ../types/dist/sleep-
|
|
5977
|
+
//#region ../types/dist/sleep-QsoHs66J.mjs
|
|
5975
5978
|
/**
|
|
5976
5979
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5977
5980
|
* window to float samples (D455).
|
|
@@ -11757,7 +11760,7 @@ object({
|
|
|
11757
11760
|
params: record(string(), union([string(), number()])).optional()
|
|
11758
11761
|
});
|
|
11759
11762
|
//#endregion
|
|
11760
|
-
//#region ../types/dist/composition-
|
|
11763
|
+
//#region ../types/dist/composition-Dh3RfMx5.mjs
|
|
11761
11764
|
/**
|
|
11762
11765
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11763
11766
|
*
|
|
@@ -55740,15 +55743,9 @@ var AvailableIntegrationTypeSchema = object({
|
|
|
55740
55743
|
* branch on CAP — never on addon name. `device-adoption` integrations
|
|
55741
55744
|
* (Home Assistant, …) route through the broker step (Approach A);
|
|
55742
55745
|
* `device-provider` integrations (Reolink/ONVIF) keep the
|
|
55743
|
-
* legacy config → discovery flow.
|
|
55744
|
-
* connection as typed records (`integration-instances`, D985): no devices,
|
|
55745
|
-
* no discovery.
|
|
55746
|
+
* legacy config → discovery flow.
|
|
55746
55747
|
*/
|
|
55747
|
-
kind: _enum([
|
|
55748
|
-
"device-adoption",
|
|
55749
|
-
"device-provider",
|
|
55750
|
-
"connection"
|
|
55751
|
-
]),
|
|
55748
|
+
kind: _enum(["device-adoption", "device-provider"]),
|
|
55752
55749
|
/**
|
|
55753
55750
|
* For `device-adoption` addons: the broker kind to create/link
|
|
55754
55751
|
* (e.g. `home-assistant`), declared in the addon manifest. `null` for
|
|
@@ -55862,18 +55859,17 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
55862
55859
|
});
|
|
55863
55860
|
/**
|
|
55864
55861
|
* `integration-instances` - the connection of an addon that holds ONE or SEVERAL
|
|
55865
|
-
* (an account, a relay, a destination) as typed records,
|
|
55866
|
-
*
|
|
55862
|
+
* (an account, a relay, a destination) as typed records (D985, D986). The addon's
|
|
55863
|
+
* own function page reads and writes them (Logging for Loki); the Integrations
|
|
55864
|
+
* page lists only the integrations that bring devices (D996).
|
|
55867
55865
|
*
|
|
55868
|
-
* A collection of providers: each addon that holds connections registers one
|
|
55869
|
-
*
|
|
55870
|
-
* `addonId`. It is internal and has no route: the operator-facing surface is
|
|
55871
|
-
* the `integrations` cap, which keeps ONE list.
|
|
55866
|
+
* A collection of providers: each addon that holds connections registers one and a
|
|
55867
|
+
* caller reaches a provider with the pinned `addonId`.
|
|
55872
55868
|
*
|
|
55873
|
-
* Identity: the instance id
|
|
55874
|
-
*
|
|
55875
|
-
*
|
|
55876
|
-
* editable label.
|
|
55869
|
+
* Identity: the instance id is immutable and opaque to the caller. A record the
|
|
55870
|
+
* Loki migration made keeps the `integrations` registry id it was created under
|
|
55871
|
+
* (`int_NNNN`); a single instance made by its function page takes the addon id.
|
|
55872
|
+
* `name` is the editable label.
|
|
55877
55873
|
*
|
|
55878
55874
|
* Secrets: every field the instance kind marks `.meta({ secret: true })` reaches
|
|
55879
55875
|
* the form as a `SecretFieldView`, never text (D789). A patch value omitted, `''`
|
|
@@ -55914,13 +55910,25 @@ var IntegrationInstancePatchSchema = object({
|
|
|
55914
55910
|
/** Omitted / '' / redaction token keeps a secret; `{ $secretClear: true }` clears it. */
|
|
55915
55911
|
values: record(string(), unknown())
|
|
55916
55912
|
});
|
|
55917
|
-
method(_void(), IntegrationInstanceDescriptionSchema), method(object({ id: IntegrationInstanceIdSchema.nullable() }), ConfigSchemaPassthrough$3), method(_void(), array(IntegrationInstanceSchema).readonly()), method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable()), method(object({
|
|
55913
|
+
method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }), method(object({ id: IntegrationInstanceIdSchema.nullable() }), ConfigSchemaPassthrough$3, { auth: "admin" }), method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }), method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }), method(object({
|
|
55918
55914
|
id: IntegrationInstanceIdSchema,
|
|
55919
55915
|
name: string().min(1)
|
|
55920
|
-
}), object({ id: string() }), {
|
|
55916
|
+
}), object({ id: string() }), {
|
|
55917
|
+
kind: "mutation",
|
|
55918
|
+
auth: "admin"
|
|
55919
|
+
}), method(IntegrationInstancePatchSchema, object({ ok: literal(true) }), {
|
|
55920
|
+
kind: "mutation",
|
|
55921
|
+
auth: "admin"
|
|
55922
|
+
}), method(object({ id: IntegrationInstanceIdSchema }), object({ ok: literal(true) }), {
|
|
55923
|
+
kind: "mutation",
|
|
55924
|
+
auth: "admin"
|
|
55925
|
+
}), optionalMethod(object({
|
|
55921
55926
|
id: IntegrationInstanceIdSchema.nullable(),
|
|
55922
55927
|
values: record(string(), unknown())
|
|
55923
|
-
}), TestConnectionResultSchema$1, {
|
|
55928
|
+
}), TestConnectionResultSchema$1, {
|
|
55929
|
+
kind: "mutation",
|
|
55930
|
+
auth: "admin"
|
|
55931
|
+
});
|
|
55924
55932
|
/**
|
|
55925
55933
|
* Two-way audio / talk-back cap. Clients open a WebRTC audio-only
|
|
55926
55934
|
* session (server-initiated offer pattern, identical to the main
|
|
@@ -66689,7 +66697,14 @@ var NcMediaPolicySchema = object({
|
|
|
66689
66697
|
*/
|
|
66690
66698
|
profile: CamProfileSchema.optional(),
|
|
66691
66699
|
/** See {@link NcLinkedCameraModeSchema}. */
|
|
66692
|
-
linkedCameras: NcLinkedCameraModeSchema.optional()
|
|
66700
|
+
linkedCameras: NcLinkedCameraModeSchema.optional(),
|
|
66701
|
+
/**
|
|
66702
|
+
* Sensor rules only: may this rule wake a SLEEPING battery camera to take
|
|
66703
|
+
* the linked still it attaches (D998)? Absent means yes, an explicit `false`
|
|
66704
|
+
* opts out. Deliberately not a Zod default: a rule that never chose keeps
|
|
66705
|
+
* following the hub's. One bounded wake per event; nothing is attached later.
|
|
66706
|
+
*/
|
|
66707
|
+
wakeSleepingCamera: boolean().optional()
|
|
66693
66708
|
});
|
|
66694
66709
|
/**
|
|
66695
66710
|
* Cooldown GRANULARITY over the subject's class — how much a fired
|
|
@@ -75270,7 +75285,16 @@ var smokeCapability = {
|
|
|
75270
75285
|
* future fields without breaking the existing call sites.
|
|
75271
75286
|
*/
|
|
75272
75287
|
var EmailAddressSchema = email();
|
|
75288
|
+
/**
|
|
75289
|
+
* Which relay of a multi-relay provider answers (D996). Absent = the provider's first / default
|
|
75290
|
+
* relay, so a caller written before relays existed keeps working unchanged. A provider with one
|
|
75291
|
+
* fixed relay ignores it. This is the integration instance id (`int_NNNN`), NOT the addon id: the
|
|
75292
|
+
* collection pin `addonId` still picks the provider, `instanceId` picks the relay inside it.
|
|
75293
|
+
*/
|
|
75294
|
+
var InstanceIdSchema = string().min(1).optional();
|
|
75295
|
+
var RelayPinSchema = object({ instanceId: InstanceIdSchema });
|
|
75273
75296
|
var SendEmailInputSchema = object({
|
|
75297
|
+
instanceId: InstanceIdSchema,
|
|
75274
75298
|
to: union([EmailAddressSchema, array(EmailAddressSchema).min(1)]),
|
|
75275
75299
|
cc: array(EmailAddressSchema).optional(),
|
|
75276
75300
|
bcc: array(EmailAddressSchema).optional(),
|
|
@@ -75306,11 +75330,11 @@ method(SendEmailInputSchema, SendEmailResultSchema, {
|
|
|
75306
75330
|
kind: "mutation",
|
|
75307
75331
|
auth: "admin",
|
|
75308
75332
|
access: "create"
|
|
75309
|
-
}), method(
|
|
75333
|
+
}), method(RelayPinSchema, SmtpStatusSchema, {
|
|
75310
75334
|
kind: "mutation",
|
|
75311
75335
|
auth: "admin",
|
|
75312
75336
|
access: "view"
|
|
75313
|
-
}), method(
|
|
75337
|
+
}), method(RelayPinSchema, SmtpStatusSchema, { auth: "admin" });
|
|
75314
75338
|
/**
|
|
75315
75339
|
* A single device snapshot returned as base64 JPEG/PNG.
|
|
75316
75340
|
*
|
|
@@ -75342,10 +75366,12 @@ DeviceType.Camera, method(object({
|
|
|
75342
75366
|
* even when the cache is well within the device's
|
|
75343
75367
|
* `snapshotMaxAgeS` window.
|
|
75344
75368
|
*
|
|
75345
|
-
* **`force` is
|
|
75369
|
+
* **`force` is a demand with a named principal, not a freshness
|
|
75370
|
+
* preference** — an operator button, the scheduled refresh (D847), or a
|
|
75371
|
+
* notification rule that left the camera's wake allowed (D998). On a
|
|
75346
75372
|
* battery camera it is the one thing that walks past the wrapper's
|
|
75347
|
-
* sleep gate and wakes the camera, so a
|
|
75348
|
-
*
|
|
75373
|
+
* sleep gate and wakes the camera, so a poller, a thumbnail or a
|
|
75374
|
+
* prestream must NEVER set it. Every such caller
|
|
75349
75375
|
* gets the cached frame, which on a sleeping battery camera is the
|
|
75350
75376
|
* correct answer: stale but honest beats woken.
|
|
75351
75377
|
*/
|
|
@@ -80195,7 +80221,7 @@ function runningFromMediaPlayer(state, previous, fetchedAt, transitionAt) {
|
|
|
80195
80221
|
if (state === "paused") return nativeSlice(false, "paused", previous, transitionAtMs, fetchedAt, null);
|
|
80196
80222
|
return nativeSlice(false, state, previous, transitionAtMs, fetchedAt, null);
|
|
80197
80223
|
}
|
|
80198
|
-
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
80224
|
+
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
80199
80225
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
80200
80226
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
80201
80227
|
DeviceRole.BinarySensor, DeviceRole.MotionSensor;
|
|
@@ -785,7 +785,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
785
785
|
return EventCategory;
|
|
786
786
|
}({});
|
|
787
787
|
//#endregion
|
|
788
|
-
//#region ../types/dist/device-type-
|
|
788
|
+
//#region ../types/dist/device-type-CfHw_quR.mjs
|
|
789
789
|
/**
|
|
790
790
|
import { errMsg } from '@camstack/types'
|
|
791
791
|
* Extract a human-readable message from an unknown error value.
|
|
@@ -974,6 +974,9 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
|
|
|
974
974
|
DeviceFeature["CoverPositionable"] = "cover-positionable";
|
|
975
975
|
/** Cover exposes slat-tilt control. Gates the tilt slider UI. */
|
|
976
976
|
DeviceFeature["CoverTilt"] = "cover-tilt";
|
|
977
|
+
/** Cover has a real stop: `stop` halts the travel in place. Gates the stop
|
|
978
|
+
* control in exporters; a provider whose `stop` is a no-op never declares it. */
|
|
979
|
+
DeviceFeature["CoverStoppable"] = "cover-stoppable";
|
|
977
980
|
/** Valve exposes intermediate position control (0..100). Gates the
|
|
978
981
|
* position slider / drag surface UI. */
|
|
979
982
|
DeviceFeature["ValvePositionable"] = "valve-positionable";
|
|
@@ -5972,7 +5975,7 @@ var ZodIssueCode = {
|
|
|
5972
5975
|
var ZodFirstPartyTypeKind;
|
|
5973
5976
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5974
5977
|
//#endregion
|
|
5975
|
-
//#region ../types/dist/sleep-
|
|
5978
|
+
//#region ../types/dist/sleep-QsoHs66J.mjs
|
|
5976
5979
|
/**
|
|
5977
5980
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5978
5981
|
* window to float samples (D455).
|
|
@@ -11758,7 +11761,7 @@ object({
|
|
|
11758
11761
|
params: record(string(), union([string(), number()])).optional()
|
|
11759
11762
|
});
|
|
11760
11763
|
//#endregion
|
|
11761
|
-
//#region ../types/dist/composition-
|
|
11764
|
+
//#region ../types/dist/composition-Dh3RfMx5.mjs
|
|
11762
11765
|
/**
|
|
11763
11766
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11764
11767
|
*
|
|
@@ -55741,15 +55744,9 @@ var AvailableIntegrationTypeSchema = object({
|
|
|
55741
55744
|
* branch on CAP — never on addon name. `device-adoption` integrations
|
|
55742
55745
|
* (Home Assistant, …) route through the broker step (Approach A);
|
|
55743
55746
|
* `device-provider` integrations (Reolink/ONVIF) keep the
|
|
55744
|
-
* legacy config → discovery flow.
|
|
55745
|
-
* connection as typed records (`integration-instances`, D985): no devices,
|
|
55746
|
-
* no discovery.
|
|
55747
|
+
* legacy config → discovery flow.
|
|
55747
55748
|
*/
|
|
55748
|
-
kind: _enum([
|
|
55749
|
-
"device-adoption",
|
|
55750
|
-
"device-provider",
|
|
55751
|
-
"connection"
|
|
55752
|
-
]),
|
|
55749
|
+
kind: _enum(["device-adoption", "device-provider"]),
|
|
55753
55750
|
/**
|
|
55754
55751
|
* For `device-adoption` addons: the broker kind to create/link
|
|
55755
55752
|
* (e.g. `home-assistant`), declared in the addon manifest. `null` for
|
|
@@ -55863,18 +55860,17 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
55863
55860
|
});
|
|
55864
55861
|
/**
|
|
55865
55862
|
* `integration-instances` - the connection of an addon that holds ONE or SEVERAL
|
|
55866
|
-
* (an account, a relay, a destination) as typed records,
|
|
55867
|
-
*
|
|
55863
|
+
* (an account, a relay, a destination) as typed records (D985, D986). The addon's
|
|
55864
|
+
* own function page reads and writes them (Logging for Loki); the Integrations
|
|
55865
|
+
* page lists only the integrations that bring devices (D996).
|
|
55868
55866
|
*
|
|
55869
|
-
* A collection of providers: each addon that holds connections registers one
|
|
55870
|
-
*
|
|
55871
|
-
* `addonId`. It is internal and has no route: the operator-facing surface is
|
|
55872
|
-
* the `integrations` cap, which keeps ONE list.
|
|
55867
|
+
* A collection of providers: each addon that holds connections registers one and a
|
|
55868
|
+
* caller reaches a provider with the pinned `addonId`.
|
|
55873
55869
|
*
|
|
55874
|
-
* Identity: the instance id
|
|
55875
|
-
*
|
|
55876
|
-
*
|
|
55877
|
-
* editable label.
|
|
55870
|
+
* Identity: the instance id is immutable and opaque to the caller. A record the
|
|
55871
|
+
* Loki migration made keeps the `integrations` registry id it was created under
|
|
55872
|
+
* (`int_NNNN`); a single instance made by its function page takes the addon id.
|
|
55873
|
+
* `name` is the editable label.
|
|
55878
55874
|
*
|
|
55879
55875
|
* Secrets: every field the instance kind marks `.meta({ secret: true })` reaches
|
|
55880
55876
|
* the form as a `SecretFieldView`, never text (D789). A patch value omitted, `''`
|
|
@@ -55915,13 +55911,25 @@ var IntegrationInstancePatchSchema = object({
|
|
|
55915
55911
|
/** Omitted / '' / redaction token keeps a secret; `{ $secretClear: true }` clears it. */
|
|
55916
55912
|
values: record(string(), unknown())
|
|
55917
55913
|
});
|
|
55918
|
-
method(_void(), IntegrationInstanceDescriptionSchema), method(object({ id: IntegrationInstanceIdSchema.nullable() }), ConfigSchemaPassthrough$3), method(_void(), array(IntegrationInstanceSchema).readonly()), method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable()), method(object({
|
|
55914
|
+
method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }), method(object({ id: IntegrationInstanceIdSchema.nullable() }), ConfigSchemaPassthrough$3, { auth: "admin" }), method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }), method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }), method(object({
|
|
55919
55915
|
id: IntegrationInstanceIdSchema,
|
|
55920
55916
|
name: string().min(1)
|
|
55921
|
-
}), object({ id: string() }), {
|
|
55917
|
+
}), object({ id: string() }), {
|
|
55918
|
+
kind: "mutation",
|
|
55919
|
+
auth: "admin"
|
|
55920
|
+
}), method(IntegrationInstancePatchSchema, object({ ok: literal(true) }), {
|
|
55921
|
+
kind: "mutation",
|
|
55922
|
+
auth: "admin"
|
|
55923
|
+
}), method(object({ id: IntegrationInstanceIdSchema }), object({ ok: literal(true) }), {
|
|
55924
|
+
kind: "mutation",
|
|
55925
|
+
auth: "admin"
|
|
55926
|
+
}), optionalMethod(object({
|
|
55922
55927
|
id: IntegrationInstanceIdSchema.nullable(),
|
|
55923
55928
|
values: record(string(), unknown())
|
|
55924
|
-
}), TestConnectionResultSchema$1, {
|
|
55929
|
+
}), TestConnectionResultSchema$1, {
|
|
55930
|
+
kind: "mutation",
|
|
55931
|
+
auth: "admin"
|
|
55932
|
+
});
|
|
55925
55933
|
/**
|
|
55926
55934
|
* Two-way audio / talk-back cap. Clients open a WebRTC audio-only
|
|
55927
55935
|
* session (server-initiated offer pattern, identical to the main
|
|
@@ -66690,7 +66698,14 @@ var NcMediaPolicySchema = object({
|
|
|
66690
66698
|
*/
|
|
66691
66699
|
profile: CamProfileSchema.optional(),
|
|
66692
66700
|
/** See {@link NcLinkedCameraModeSchema}. */
|
|
66693
|
-
linkedCameras: NcLinkedCameraModeSchema.optional()
|
|
66701
|
+
linkedCameras: NcLinkedCameraModeSchema.optional(),
|
|
66702
|
+
/**
|
|
66703
|
+
* Sensor rules only: may this rule wake a SLEEPING battery camera to take
|
|
66704
|
+
* the linked still it attaches (D998)? Absent means yes, an explicit `false`
|
|
66705
|
+
* opts out. Deliberately not a Zod default: a rule that never chose keeps
|
|
66706
|
+
* following the hub's. One bounded wake per event; nothing is attached later.
|
|
66707
|
+
*/
|
|
66708
|
+
wakeSleepingCamera: boolean().optional()
|
|
66694
66709
|
});
|
|
66695
66710
|
/**
|
|
66696
66711
|
* Cooldown GRANULARITY over the subject's class — how much a fired
|
|
@@ -75271,7 +75286,16 @@ var smokeCapability = {
|
|
|
75271
75286
|
* future fields without breaking the existing call sites.
|
|
75272
75287
|
*/
|
|
75273
75288
|
var EmailAddressSchema = email();
|
|
75289
|
+
/**
|
|
75290
|
+
* Which relay of a multi-relay provider answers (D996). Absent = the provider's first / default
|
|
75291
|
+
* relay, so a caller written before relays existed keeps working unchanged. A provider with one
|
|
75292
|
+
* fixed relay ignores it. This is the integration instance id (`int_NNNN`), NOT the addon id: the
|
|
75293
|
+
* collection pin `addonId` still picks the provider, `instanceId` picks the relay inside it.
|
|
75294
|
+
*/
|
|
75295
|
+
var InstanceIdSchema = string().min(1).optional();
|
|
75296
|
+
var RelayPinSchema = object({ instanceId: InstanceIdSchema });
|
|
75274
75297
|
var SendEmailInputSchema = object({
|
|
75298
|
+
instanceId: InstanceIdSchema,
|
|
75275
75299
|
to: union([EmailAddressSchema, array(EmailAddressSchema).min(1)]),
|
|
75276
75300
|
cc: array(EmailAddressSchema).optional(),
|
|
75277
75301
|
bcc: array(EmailAddressSchema).optional(),
|
|
@@ -75307,11 +75331,11 @@ method(SendEmailInputSchema, SendEmailResultSchema, {
|
|
|
75307
75331
|
kind: "mutation",
|
|
75308
75332
|
auth: "admin",
|
|
75309
75333
|
access: "create"
|
|
75310
|
-
}), method(
|
|
75334
|
+
}), method(RelayPinSchema, SmtpStatusSchema, {
|
|
75311
75335
|
kind: "mutation",
|
|
75312
75336
|
auth: "admin",
|
|
75313
75337
|
access: "view"
|
|
75314
|
-
}), method(
|
|
75338
|
+
}), method(RelayPinSchema, SmtpStatusSchema, { auth: "admin" });
|
|
75315
75339
|
/**
|
|
75316
75340
|
* A single device snapshot returned as base64 JPEG/PNG.
|
|
75317
75341
|
*
|
|
@@ -75343,10 +75367,12 @@ DeviceType.Camera, method(object({
|
|
|
75343
75367
|
* even when the cache is well within the device's
|
|
75344
75368
|
* `snapshotMaxAgeS` window.
|
|
75345
75369
|
*
|
|
75346
|
-
* **`force` is
|
|
75370
|
+
* **`force` is a demand with a named principal, not a freshness
|
|
75371
|
+
* preference** — an operator button, the scheduled refresh (D847), or a
|
|
75372
|
+
* notification rule that left the camera's wake allowed (D998). On a
|
|
75347
75373
|
* battery camera it is the one thing that walks past the wrapper's
|
|
75348
|
-
* sleep gate and wakes the camera, so a
|
|
75349
|
-
*
|
|
75374
|
+
* sleep gate and wakes the camera, so a poller, a thumbnail or a
|
|
75375
|
+
* prestream must NEVER set it. Every such caller
|
|
75350
75376
|
* gets the cached frame, which on a sleeping battery camera is the
|
|
75351
75377
|
* correct answer: stale but honest beats woken.
|
|
75352
75378
|
*/
|
|
@@ -80196,7 +80222,7 @@ function runningFromMediaPlayer(state, previous, fetchedAt, transitionAt) {
|
|
|
80196
80222
|
if (state === "paused") return nativeSlice(false, "paused", previous, transitionAtMs, fetchedAt, null);
|
|
80197
80223
|
return nativeSlice(false, state, previous, transitionAtMs, fetchedAt, null);
|
|
80198
80224
|
}
|
|
80199
|
-
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
80225
|
+
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
80200
80226
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
80201
80227
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
80202
80228
|
DeviceRole.BinarySensor, DeviceRole.MotionSensor;
|
|
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
enumerable: true
|
|
25
25
|
}) : target, mod));
|
|
26
26
|
//#endregion
|
|
27
|
-
const require_ha_broker_source = require("../ha-broker-source-
|
|
27
|
+
const require_ha_broker_source = require("../ha-broker-source-DUj9pUWr.js");
|
|
28
28
|
let node_crypto = require("node:crypto");
|
|
29
29
|
require("node:zlib");
|
|
30
30
|
let node_fs = require("node:fs");
|
|
@@ -975,19 +975,22 @@ var NATIVE_CAP_PLATFORMS = {
|
|
|
975
975
|
*/
|
|
976
976
|
cover: {
|
|
977
977
|
platform: "cover",
|
|
978
|
+
availableWhenOnline: true,
|
|
978
979
|
primary: { from: null },
|
|
979
980
|
controls: {
|
|
980
981
|
position: {
|
|
981
982
|
from: "position",
|
|
982
983
|
min: 0,
|
|
983
984
|
max: 100,
|
|
984
|
-
step: 1
|
|
985
|
+
step: 1,
|
|
986
|
+
requiresFeature: "cover-positionable"
|
|
985
987
|
},
|
|
986
988
|
tilt: {
|
|
987
989
|
from: "tilt",
|
|
988
990
|
min: 0,
|
|
989
991
|
max: 100,
|
|
990
|
-
step: 1
|
|
992
|
+
step: 1,
|
|
993
|
+
requiresFeature: "cover-tilt"
|
|
991
994
|
},
|
|
992
995
|
open: {
|
|
993
996
|
from: "open",
|
|
@@ -999,7 +1002,8 @@ var NATIVE_CAP_PLATFORMS = {
|
|
|
999
1002
|
},
|
|
1000
1003
|
stop: {
|
|
1001
1004
|
from: "stop",
|
|
1002
|
-
commandOnly: true
|
|
1005
|
+
commandOnly: true,
|
|
1006
|
+
requiresFeature: "cover-stoppable"
|
|
1003
1007
|
}
|
|
1004
1008
|
}
|
|
1005
1009
|
},
|
|
@@ -1388,9 +1392,11 @@ function buildNativeComponent(input) {
|
|
|
1388
1392
|
if (native === void 0) return null;
|
|
1389
1393
|
const primary = resolveBinding(input, native.primary);
|
|
1390
1394
|
if (primary === null) return null;
|
|
1395
|
+
const features = input.features ?? [];
|
|
1391
1396
|
const absorbed = new Set([derivedEntityId(input.capName), ...primary.entities]);
|
|
1392
1397
|
const controls = {};
|
|
1393
1398
|
for (const [name, binding] of Object.entries(native.controls ?? {})) {
|
|
1399
|
+
if (binding.requiresFeature !== void 0 && !features.includes(binding.requiresFeature)) continue;
|
|
1394
1400
|
const resolved = resolveBinding(input, binding);
|
|
1395
1401
|
if (resolved === null) continue;
|
|
1396
1402
|
controls[name] = resolved.control;
|
|
@@ -1409,6 +1415,11 @@ function buildNativeComponent(input) {
|
|
|
1409
1415
|
...primary.control,
|
|
1410
1416
|
...native.deviceClass !== void 0 ? { device_class: native.deviceClass } : {},
|
|
1411
1417
|
...native.icon !== void 0 ? { icon: native.icon } : {},
|
|
1418
|
+
...native.availableWhenOnline === true ? {
|
|
1419
|
+
availability_topic: stateTopic(input.deviceKey, "online"),
|
|
1420
|
+
payload_available: "true",
|
|
1421
|
+
payload_not_available: "false"
|
|
1422
|
+
} : {},
|
|
1412
1423
|
...Object.keys(controls).length > 0 ? { controls } : {},
|
|
1413
1424
|
...native.constants,
|
|
1414
1425
|
...flags
|
|
@@ -2343,7 +2354,8 @@ var CAP_ENTITY_MAP = {
|
|
|
2343
2354
|
unit: "%",
|
|
2344
2355
|
icon: "mdi:arrow-up-down",
|
|
2345
2356
|
writable: true,
|
|
2346
|
-
range: PERCENT_RANGE
|
|
2357
|
+
range: PERCENT_RANGE,
|
|
2358
|
+
requiresFeature: "cover-positionable"
|
|
2347
2359
|
},
|
|
2348
2360
|
tilt: {
|
|
2349
2361
|
platform: "number",
|
|
@@ -2353,7 +2365,8 @@ var CAP_ENTITY_MAP = {
|
|
|
2353
2365
|
icon: "mdi:angle-acute",
|
|
2354
2366
|
writable: true,
|
|
2355
2367
|
range: PERCENT_RANGE,
|
|
2356
|
-
enabledByDefault: false
|
|
2368
|
+
enabledByDefault: false,
|
|
2369
|
+
requiresFeature: "cover-tilt"
|
|
2357
2370
|
}
|
|
2358
2371
|
},
|
|
2359
2372
|
commands: {
|
|
@@ -2368,7 +2381,8 @@ var CAP_ENTITY_MAP = {
|
|
|
2368
2381
|
stop: {
|
|
2369
2382
|
label: "Stop",
|
|
2370
2383
|
icon: "mdi:stop",
|
|
2371
|
-
enabledByDefault: false
|
|
2384
|
+
enabledByDefault: false,
|
|
2385
|
+
requiresFeature: "cover-stoppable"
|
|
2372
2386
|
}
|
|
2373
2387
|
}
|
|
2374
2388
|
},
|
|
@@ -3375,7 +3389,8 @@ function capEntities(capName) {
|
|
|
3375
3389
|
for (const [suffix, extra] of Object.entries(mapping.extras ?? {})) refs.push({
|
|
3376
3390
|
entity: derivedExtraEntityId(capName, suffix),
|
|
3377
3391
|
label: extra.label,
|
|
3378
|
-
descriptor: extra
|
|
3392
|
+
descriptor: extra,
|
|
3393
|
+
...extra.requiresFeature !== void 0 ? { requiresFeature: extra.requiresFeature } : {}
|
|
3379
3394
|
});
|
|
3380
3395
|
return refs;
|
|
3381
3396
|
}
|
|
@@ -3394,8 +3409,9 @@ function deriveCapValue(derivation, raw) {
|
|
|
3394
3409
|
return derivation.kind === "equals" ? raw === derivation.value : raw !== derivation.value;
|
|
3395
3410
|
}
|
|
3396
3411
|
/** A capability's entities as the catalog builders consume them. */
|
|
3397
|
-
function capEntitySpecs(capName) {
|
|
3398
|
-
const
|
|
3412
|
+
function capEntitySpecs(capName, features = []) {
|
|
3413
|
+
const declared = (required) => required === void 0 || features.includes(required);
|
|
3414
|
+
const specs = capEntities(capName).filter((ref) => declared(ref.requiresFeature)).map((ref) => ({
|
|
3399
3415
|
entity: ref.entity,
|
|
3400
3416
|
platform: ref.descriptor.platform,
|
|
3401
3417
|
label: ref.label,
|
|
@@ -3408,15 +3424,18 @@ function capEntitySpecs(capName) {
|
|
|
3408
3424
|
...ref.descriptor.entityCategory !== void 0 ? { entityCategory: ref.descriptor.entityCategory } : {},
|
|
3409
3425
|
...ref.descriptor.writable === true ? { writable: true } : {}
|
|
3410
3426
|
}));
|
|
3411
|
-
for (const ref of capCommandEntities(capName))
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3427
|
+
for (const ref of capCommandEntities(capName)) {
|
|
3428
|
+
if (!declared(ref.command.requiresFeature)) continue;
|
|
3429
|
+
specs.push({
|
|
3430
|
+
entity: ref.entity,
|
|
3431
|
+
platform: "button",
|
|
3432
|
+
label: ref.label,
|
|
3433
|
+
enabledByDefault: ref.command.enabledByDefault ?? true,
|
|
3434
|
+
writable: true,
|
|
3435
|
+
...ref.command.icon !== void 0 ? { icon: ref.command.icon } : {},
|
|
3436
|
+
...ref.command.entityCategory !== void 0 ? { entityCategory: ref.command.entityCategory } : {}
|
|
3437
|
+
});
|
|
3438
|
+
}
|
|
3420
3439
|
return specs;
|
|
3421
3440
|
}
|
|
3422
3441
|
function readControlOptions(slice) {
|
|
@@ -3794,11 +3813,12 @@ function buildDerivedPlan(device, options = UNNEGOTIATED_PLAN_OPTIONS) {
|
|
|
3794
3813
|
deviceId: device.id,
|
|
3795
3814
|
capName: cap,
|
|
3796
3815
|
label: humanise(cap),
|
|
3816
|
+
features: device.features,
|
|
3797
3817
|
slice: device.capSlices?.[cap],
|
|
3798
3818
|
mapping
|
|
3799
3819
|
});
|
|
3800
3820
|
if (plan === null) {
|
|
3801
|
-
specs.push(...capEntitySpecs(cap).map((spec) => degradeUnsupported(spec, options)));
|
|
3821
|
+
specs.push(...capEntitySpecs(cap, device.features).map((spec) => degradeUnsupported(spec, options)));
|
|
3802
3822
|
continue;
|
|
3803
3823
|
}
|
|
3804
3824
|
native[toComponentKey(plan.component.platform, derivedEntityId(cap))] = plan.component;
|
|
@@ -3809,7 +3829,7 @@ function buildDerivedPlan(device, options = UNNEGOTIATED_PLAN_OPTIONS) {
|
|
|
3809
3829
|
* to make the device look tidy would be a silent loss of a value an
|
|
3810
3830
|
* operator already automates on.
|
|
3811
3831
|
*/
|
|
3812
|
-
specs.push(...capEntitySpecs(cap).filter((spec) => !plan.absorbed.has(spec.entity)));
|
|
3832
|
+
specs.push(...capEntitySpecs(cap, device.features).filter((spec) => !plan.absorbed.has(spec.entity)));
|
|
3813
3833
|
}
|
|
3814
3834
|
const assembled = assemble(device, specs);
|
|
3815
3835
|
return {
|
|
@@ -4671,6 +4691,15 @@ function isCameraCommand(command) {
|
|
|
4671
4691
|
function isCapCommand(command) {
|
|
4672
4692
|
return !isCameraCommand(command);
|
|
4673
4693
|
}
|
|
4694
|
+
var APPLIED = { ok: true };
|
|
4695
|
+
/** The 422 body of a command that was not applied: the reason travels with it. */
|
|
4696
|
+
function commandNotAppliedBody(refusal) {
|
|
4697
|
+
return {
|
|
4698
|
+
error: "command not applied",
|
|
4699
|
+
reason: refusal.reason,
|
|
4700
|
+
...refusal.message !== void 0 ? { message: refusal.message } : {}
|
|
4701
|
+
};
|
|
4702
|
+
}
|
|
4674
4703
|
/**
|
|
4675
4704
|
* Make the call the command names.
|
|
4676
4705
|
*
|
|
@@ -9168,9 +9197,23 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9168
9197
|
reply.send({ error: `unroutable command (${resolution.reason})` });
|
|
9169
9198
|
return;
|
|
9170
9199
|
}
|
|
9171
|
-
const
|
|
9172
|
-
|
|
9173
|
-
|
|
9200
|
+
const outcome = await this.dispatch(resolution.command);
|
|
9201
|
+
if (outcome.ok) {
|
|
9202
|
+
reply.status(200);
|
|
9203
|
+
reply.send({});
|
|
9204
|
+
return;
|
|
9205
|
+
}
|
|
9206
|
+
this.ctx.logger.warn("ha-export: command answered 422", {
|
|
9207
|
+
tags: { deviceId: exported.deviceId },
|
|
9208
|
+
meta: {
|
|
9209
|
+
topic,
|
|
9210
|
+
entity: parsed.entity,
|
|
9211
|
+
reason: outcome.reason,
|
|
9212
|
+
message: outcome.message
|
|
9213
|
+
}
|
|
9214
|
+
});
|
|
9215
|
+
reply.status(422);
|
|
9216
|
+
reply.send(commandNotAppliedBody(outcome));
|
|
9174
9217
|
}
|
|
9175
9218
|
async dispatch(command) {
|
|
9176
9219
|
try {
|
|
@@ -9184,7 +9227,7 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9184
9227
|
*/
|
|
9185
9228
|
if (isCapCommand(command)) {
|
|
9186
9229
|
const outcome = await applyCapCommand(await this.ctx.fetchDevice(command.deviceId), command);
|
|
9187
|
-
if (outcome.ok) return
|
|
9230
|
+
if (outcome.ok) return APPLIED;
|
|
9188
9231
|
if (outcome.reason === "no-provider") return this.noProvider(command.deviceId, outcome.capName);
|
|
9189
9232
|
this.ctx.logger.warn("ha-export: the device refused the command", {
|
|
9190
9233
|
tags: { deviceId: command.deviceId },
|
|
@@ -9194,7 +9237,11 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9194
9237
|
...outcome.error !== void 0 ? { error: outcome.error } : {}
|
|
9195
9238
|
}
|
|
9196
9239
|
});
|
|
9197
|
-
return
|
|
9240
|
+
return {
|
|
9241
|
+
ok: false,
|
|
9242
|
+
reason: outcome.reason,
|
|
9243
|
+
...outcome.error !== void 0 ? { message: outcome.error } : {}
|
|
9244
|
+
};
|
|
9198
9245
|
}
|
|
9199
9246
|
switch (command.kind) {
|
|
9200
9247
|
case "camera-function": {
|
|
@@ -9204,7 +9251,11 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9204
9251
|
tags: { deviceId: command.deviceId },
|
|
9205
9252
|
meta: { switchId: command.switchId }
|
|
9206
9253
|
});
|
|
9207
|
-
return
|
|
9254
|
+
return {
|
|
9255
|
+
ok: false,
|
|
9256
|
+
reason: "unknown-function",
|
|
9257
|
+
message: "the switch id is not a camera function"
|
|
9258
|
+
};
|
|
9208
9259
|
}
|
|
9209
9260
|
/**
|
|
9210
9261
|
* A FRESH read for this one device — the pass-level caches are up to
|
|
@@ -9214,7 +9265,11 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9214
9265
|
const device = await this.ctx.api.deviceManager.getDevice.query({ deviceId: command.deviceId });
|
|
9215
9266
|
if (device === null) {
|
|
9216
9267
|
this.ctx.logger.warn("ha-export: dropping a camera-function command, the device is not in the registry", { tags: { deviceId: command.deviceId } });
|
|
9217
|
-
return
|
|
9268
|
+
return {
|
|
9269
|
+
ok: false,
|
|
9270
|
+
reason: "device-not-found",
|
|
9271
|
+
message: "the device is not in the registry"
|
|
9272
|
+
};
|
|
9218
9273
|
}
|
|
9219
9274
|
const privacySlice = await this.ctx.api.deviceState.getCapSlice.query({
|
|
9220
9275
|
deviceId: command.deviceId,
|
|
@@ -9250,22 +9305,26 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9250
9305
|
tags: { deviceId: command.deviceId },
|
|
9251
9306
|
meta: { switchId: parsed.data }
|
|
9252
9307
|
});
|
|
9253
|
-
return
|
|
9308
|
+
return {
|
|
9309
|
+
ok: false,
|
|
9310
|
+
reason: "function-unavailable",
|
|
9311
|
+
message: "the camera function is not available"
|
|
9312
|
+
};
|
|
9254
9313
|
}
|
|
9255
9314
|
await applyCameraFunctionWrite(this.ctx.api, command.deviceId, write, sourceNodeId);
|
|
9256
|
-
return
|
|
9315
|
+
return APPLIED;
|
|
9257
9316
|
}
|
|
9258
9317
|
case "reboot": {
|
|
9259
9318
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
9260
9319
|
if (device.reboot === void 0) return this.noProvider(command.deviceId, "reboot");
|
|
9261
9320
|
await device.reboot.reboot({});
|
|
9262
|
-
return
|
|
9321
|
+
return APPLIED;
|
|
9263
9322
|
}
|
|
9264
9323
|
case "ptz-move": {
|
|
9265
9324
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
9266
9325
|
if (device.ptz === void 0) return this.noProvider(command.deviceId, "ptz");
|
|
9267
9326
|
await device.ptz.move(PTZ_MOVE_BY_DIRECTION[command.direction] ?? {});
|
|
9268
|
-
return
|
|
9327
|
+
return APPLIED;
|
|
9269
9328
|
}
|
|
9270
9329
|
case "ptz-preset": {
|
|
9271
9330
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
@@ -9276,10 +9335,14 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9276
9335
|
tags: { deviceId: command.deviceId },
|
|
9277
9336
|
meta: { preset: command.preset }
|
|
9278
9337
|
});
|
|
9279
|
-
return
|
|
9338
|
+
return {
|
|
9339
|
+
ok: false,
|
|
9340
|
+
reason: "no-such-preset",
|
|
9341
|
+
message: "no preset by that name"
|
|
9342
|
+
};
|
|
9280
9343
|
}
|
|
9281
9344
|
await device.ptz.goToPreset({ presetId: preset.id });
|
|
9282
|
-
return
|
|
9345
|
+
return APPLIED;
|
|
9283
9346
|
}
|
|
9284
9347
|
}
|
|
9285
9348
|
} catch (err) {
|
|
@@ -9295,7 +9358,11 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9295
9358
|
error: errMsg(err)
|
|
9296
9359
|
}
|
|
9297
9360
|
});
|
|
9298
|
-
return
|
|
9361
|
+
return {
|
|
9362
|
+
ok: false,
|
|
9363
|
+
reason: "error",
|
|
9364
|
+
message: errMsg(err)
|
|
9365
|
+
};
|
|
9299
9366
|
}
|
|
9300
9367
|
}
|
|
9301
9368
|
/** A cap the device does not provide. Refused, and never silently. */
|
|
@@ -9304,7 +9371,11 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9304
9371
|
tags: { deviceId },
|
|
9305
9372
|
meta: { capName }
|
|
9306
9373
|
});
|
|
9307
|
-
return
|
|
9374
|
+
return {
|
|
9375
|
+
ok: false,
|
|
9376
|
+
reason: "no-provider",
|
|
9377
|
+
message: `the device has no provider for ${capName}`
|
|
9378
|
+
};
|
|
9308
9379
|
}
|
|
9309
9380
|
/**
|
|
9310
9381
|
* A public, signed, expiring route Home Assistant can fetch bytes from.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as consumableKindOf, Bt as BaseAddon, Dt as sendToExportedTarget, Ht as baseUnitOf, Jt as isReadingQuantity, K as deviceExportCapability, Wt as defineAddonMigrations, Yt as nodePin, Zt as array, _ as PLUG_APPLIANCE_TYPES, _t as pruneDeletedEntities, c as COCO_TO_MACRO, d as ConsumableItemSchema, f as ConsumableStatusSchema, fn as EventCategory, g as MediaPlayerRepeatSchema, h as HvacModeSchema, i as AlarmArmModeSchema, in as string, j as buildAddonRouteProvider, l as CameraSwitchIdSchema, m as FanDirectionSchema, nn as object, on as unknown, pt as preconditionOf, r as listHomeAssistantBrokers, s as CAMERA_SWITCH_ORDER, u as ConsumableFormSchema, ut as oauthIntegrationCapability, x as addonRoutesCapability, xt as runExportedAction, z as consumableFormOf } from "../ha-broker-source-
|
|
1
|
+
import { B as consumableKindOf, Bt as BaseAddon, Dt as sendToExportedTarget, Ht as baseUnitOf, Jt as isReadingQuantity, K as deviceExportCapability, Wt as defineAddonMigrations, Yt as nodePin, Zt as array, _ as PLUG_APPLIANCE_TYPES, _t as pruneDeletedEntities, c as COCO_TO_MACRO, d as ConsumableItemSchema, f as ConsumableStatusSchema, fn as EventCategory, g as MediaPlayerRepeatSchema, h as HvacModeSchema, i as AlarmArmModeSchema, in as string, j as buildAddonRouteProvider, l as CameraSwitchIdSchema, m as FanDirectionSchema, nn as object, on as unknown, pt as preconditionOf, r as listHomeAssistantBrokers, s as CAMERA_SWITCH_ORDER, u as ConsumableFormSchema, ut as oauthIntegrationCapability, x as addonRoutesCapability, xt as runExportedAction, z as consumableFormOf } from "../ha-broker-source-B2v2dnPe.mjs";
|
|
2
2
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
3
3
|
import "node:zlib";
|
|
4
4
|
import "node:fs";
|
|
@@ -940,19 +940,22 @@ var NATIVE_CAP_PLATFORMS = {
|
|
|
940
940
|
*/
|
|
941
941
|
cover: {
|
|
942
942
|
platform: "cover",
|
|
943
|
+
availableWhenOnline: true,
|
|
943
944
|
primary: { from: null },
|
|
944
945
|
controls: {
|
|
945
946
|
position: {
|
|
946
947
|
from: "position",
|
|
947
948
|
min: 0,
|
|
948
949
|
max: 100,
|
|
949
|
-
step: 1
|
|
950
|
+
step: 1,
|
|
951
|
+
requiresFeature: "cover-positionable"
|
|
950
952
|
},
|
|
951
953
|
tilt: {
|
|
952
954
|
from: "tilt",
|
|
953
955
|
min: 0,
|
|
954
956
|
max: 100,
|
|
955
|
-
step: 1
|
|
957
|
+
step: 1,
|
|
958
|
+
requiresFeature: "cover-tilt"
|
|
956
959
|
},
|
|
957
960
|
open: {
|
|
958
961
|
from: "open",
|
|
@@ -964,7 +967,8 @@ var NATIVE_CAP_PLATFORMS = {
|
|
|
964
967
|
},
|
|
965
968
|
stop: {
|
|
966
969
|
from: "stop",
|
|
967
|
-
commandOnly: true
|
|
970
|
+
commandOnly: true,
|
|
971
|
+
requiresFeature: "cover-stoppable"
|
|
968
972
|
}
|
|
969
973
|
}
|
|
970
974
|
},
|
|
@@ -1353,9 +1357,11 @@ function buildNativeComponent(input) {
|
|
|
1353
1357
|
if (native === void 0) return null;
|
|
1354
1358
|
const primary = resolveBinding(input, native.primary);
|
|
1355
1359
|
if (primary === null) return null;
|
|
1360
|
+
const features = input.features ?? [];
|
|
1356
1361
|
const absorbed = new Set([derivedEntityId(input.capName), ...primary.entities]);
|
|
1357
1362
|
const controls = {};
|
|
1358
1363
|
for (const [name, binding] of Object.entries(native.controls ?? {})) {
|
|
1364
|
+
if (binding.requiresFeature !== void 0 && !features.includes(binding.requiresFeature)) continue;
|
|
1359
1365
|
const resolved = resolveBinding(input, binding);
|
|
1360
1366
|
if (resolved === null) continue;
|
|
1361
1367
|
controls[name] = resolved.control;
|
|
@@ -1374,6 +1380,11 @@ function buildNativeComponent(input) {
|
|
|
1374
1380
|
...primary.control,
|
|
1375
1381
|
...native.deviceClass !== void 0 ? { device_class: native.deviceClass } : {},
|
|
1376
1382
|
...native.icon !== void 0 ? { icon: native.icon } : {},
|
|
1383
|
+
...native.availableWhenOnline === true ? {
|
|
1384
|
+
availability_topic: stateTopic(input.deviceKey, "online"),
|
|
1385
|
+
payload_available: "true",
|
|
1386
|
+
payload_not_available: "false"
|
|
1387
|
+
} : {},
|
|
1377
1388
|
...Object.keys(controls).length > 0 ? { controls } : {},
|
|
1378
1389
|
...native.constants,
|
|
1379
1390
|
...flags
|
|
@@ -2308,7 +2319,8 @@ var CAP_ENTITY_MAP = {
|
|
|
2308
2319
|
unit: "%",
|
|
2309
2320
|
icon: "mdi:arrow-up-down",
|
|
2310
2321
|
writable: true,
|
|
2311
|
-
range: PERCENT_RANGE
|
|
2322
|
+
range: PERCENT_RANGE,
|
|
2323
|
+
requiresFeature: "cover-positionable"
|
|
2312
2324
|
},
|
|
2313
2325
|
tilt: {
|
|
2314
2326
|
platform: "number",
|
|
@@ -2318,7 +2330,8 @@ var CAP_ENTITY_MAP = {
|
|
|
2318
2330
|
icon: "mdi:angle-acute",
|
|
2319
2331
|
writable: true,
|
|
2320
2332
|
range: PERCENT_RANGE,
|
|
2321
|
-
enabledByDefault: false
|
|
2333
|
+
enabledByDefault: false,
|
|
2334
|
+
requiresFeature: "cover-tilt"
|
|
2322
2335
|
}
|
|
2323
2336
|
},
|
|
2324
2337
|
commands: {
|
|
@@ -2333,7 +2346,8 @@ var CAP_ENTITY_MAP = {
|
|
|
2333
2346
|
stop: {
|
|
2334
2347
|
label: "Stop",
|
|
2335
2348
|
icon: "mdi:stop",
|
|
2336
|
-
enabledByDefault: false
|
|
2349
|
+
enabledByDefault: false,
|
|
2350
|
+
requiresFeature: "cover-stoppable"
|
|
2337
2351
|
}
|
|
2338
2352
|
}
|
|
2339
2353
|
},
|
|
@@ -3340,7 +3354,8 @@ function capEntities(capName) {
|
|
|
3340
3354
|
for (const [suffix, extra] of Object.entries(mapping.extras ?? {})) refs.push({
|
|
3341
3355
|
entity: derivedExtraEntityId(capName, suffix),
|
|
3342
3356
|
label: extra.label,
|
|
3343
|
-
descriptor: extra
|
|
3357
|
+
descriptor: extra,
|
|
3358
|
+
...extra.requiresFeature !== void 0 ? { requiresFeature: extra.requiresFeature } : {}
|
|
3344
3359
|
});
|
|
3345
3360
|
return refs;
|
|
3346
3361
|
}
|
|
@@ -3359,8 +3374,9 @@ function deriveCapValue(derivation, raw) {
|
|
|
3359
3374
|
return derivation.kind === "equals" ? raw === derivation.value : raw !== derivation.value;
|
|
3360
3375
|
}
|
|
3361
3376
|
/** A capability's entities as the catalog builders consume them. */
|
|
3362
|
-
function capEntitySpecs(capName) {
|
|
3363
|
-
const
|
|
3377
|
+
function capEntitySpecs(capName, features = []) {
|
|
3378
|
+
const declared = (required) => required === void 0 || features.includes(required);
|
|
3379
|
+
const specs = capEntities(capName).filter((ref) => declared(ref.requiresFeature)).map((ref) => ({
|
|
3364
3380
|
entity: ref.entity,
|
|
3365
3381
|
platform: ref.descriptor.platform,
|
|
3366
3382
|
label: ref.label,
|
|
@@ -3373,15 +3389,18 @@ function capEntitySpecs(capName) {
|
|
|
3373
3389
|
...ref.descriptor.entityCategory !== void 0 ? { entityCategory: ref.descriptor.entityCategory } : {},
|
|
3374
3390
|
...ref.descriptor.writable === true ? { writable: true } : {}
|
|
3375
3391
|
}));
|
|
3376
|
-
for (const ref of capCommandEntities(capName))
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3392
|
+
for (const ref of capCommandEntities(capName)) {
|
|
3393
|
+
if (!declared(ref.command.requiresFeature)) continue;
|
|
3394
|
+
specs.push({
|
|
3395
|
+
entity: ref.entity,
|
|
3396
|
+
platform: "button",
|
|
3397
|
+
label: ref.label,
|
|
3398
|
+
enabledByDefault: ref.command.enabledByDefault ?? true,
|
|
3399
|
+
writable: true,
|
|
3400
|
+
...ref.command.icon !== void 0 ? { icon: ref.command.icon } : {},
|
|
3401
|
+
...ref.command.entityCategory !== void 0 ? { entityCategory: ref.command.entityCategory } : {}
|
|
3402
|
+
});
|
|
3403
|
+
}
|
|
3385
3404
|
return specs;
|
|
3386
3405
|
}
|
|
3387
3406
|
function readControlOptions(slice) {
|
|
@@ -3759,11 +3778,12 @@ function buildDerivedPlan(device, options = UNNEGOTIATED_PLAN_OPTIONS) {
|
|
|
3759
3778
|
deviceId: device.id,
|
|
3760
3779
|
capName: cap,
|
|
3761
3780
|
label: humanise(cap),
|
|
3781
|
+
features: device.features,
|
|
3762
3782
|
slice: device.capSlices?.[cap],
|
|
3763
3783
|
mapping
|
|
3764
3784
|
});
|
|
3765
3785
|
if (plan === null) {
|
|
3766
|
-
specs.push(...capEntitySpecs(cap).map((spec) => degradeUnsupported(spec, options)));
|
|
3786
|
+
specs.push(...capEntitySpecs(cap, device.features).map((spec) => degradeUnsupported(spec, options)));
|
|
3767
3787
|
continue;
|
|
3768
3788
|
}
|
|
3769
3789
|
native[toComponentKey(plan.component.platform, derivedEntityId(cap))] = plan.component;
|
|
@@ -3774,7 +3794,7 @@ function buildDerivedPlan(device, options = UNNEGOTIATED_PLAN_OPTIONS) {
|
|
|
3774
3794
|
* to make the device look tidy would be a silent loss of a value an
|
|
3775
3795
|
* operator already automates on.
|
|
3776
3796
|
*/
|
|
3777
|
-
specs.push(...capEntitySpecs(cap).filter((spec) => !plan.absorbed.has(spec.entity)));
|
|
3797
|
+
specs.push(...capEntitySpecs(cap, device.features).filter((spec) => !plan.absorbed.has(spec.entity)));
|
|
3778
3798
|
}
|
|
3779
3799
|
const assembled = assemble(device, specs);
|
|
3780
3800
|
return {
|
|
@@ -4636,6 +4656,15 @@ function isCameraCommand(command) {
|
|
|
4636
4656
|
function isCapCommand(command) {
|
|
4637
4657
|
return !isCameraCommand(command);
|
|
4638
4658
|
}
|
|
4659
|
+
var APPLIED = { ok: true };
|
|
4660
|
+
/** The 422 body of a command that was not applied: the reason travels with it. */
|
|
4661
|
+
function commandNotAppliedBody(refusal) {
|
|
4662
|
+
return {
|
|
4663
|
+
error: "command not applied",
|
|
4664
|
+
reason: refusal.reason,
|
|
4665
|
+
...refusal.message !== void 0 ? { message: refusal.message } : {}
|
|
4666
|
+
};
|
|
4667
|
+
}
|
|
4639
4668
|
/**
|
|
4640
4669
|
* Make the call the command names.
|
|
4641
4670
|
*
|
|
@@ -9133,9 +9162,23 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9133
9162
|
reply.send({ error: `unroutable command (${resolution.reason})` });
|
|
9134
9163
|
return;
|
|
9135
9164
|
}
|
|
9136
|
-
const
|
|
9137
|
-
|
|
9138
|
-
|
|
9165
|
+
const outcome = await this.dispatch(resolution.command);
|
|
9166
|
+
if (outcome.ok) {
|
|
9167
|
+
reply.status(200);
|
|
9168
|
+
reply.send({});
|
|
9169
|
+
return;
|
|
9170
|
+
}
|
|
9171
|
+
this.ctx.logger.warn("ha-export: command answered 422", {
|
|
9172
|
+
tags: { deviceId: exported.deviceId },
|
|
9173
|
+
meta: {
|
|
9174
|
+
topic,
|
|
9175
|
+
entity: parsed.entity,
|
|
9176
|
+
reason: outcome.reason,
|
|
9177
|
+
message: outcome.message
|
|
9178
|
+
}
|
|
9179
|
+
});
|
|
9180
|
+
reply.status(422);
|
|
9181
|
+
reply.send(commandNotAppliedBody(outcome));
|
|
9139
9182
|
}
|
|
9140
9183
|
async dispatch(command) {
|
|
9141
9184
|
try {
|
|
@@ -9149,7 +9192,7 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9149
9192
|
*/
|
|
9150
9193
|
if (isCapCommand(command)) {
|
|
9151
9194
|
const outcome = await applyCapCommand(await this.ctx.fetchDevice(command.deviceId), command);
|
|
9152
|
-
if (outcome.ok) return
|
|
9195
|
+
if (outcome.ok) return APPLIED;
|
|
9153
9196
|
if (outcome.reason === "no-provider") return this.noProvider(command.deviceId, outcome.capName);
|
|
9154
9197
|
this.ctx.logger.warn("ha-export: the device refused the command", {
|
|
9155
9198
|
tags: { deviceId: command.deviceId },
|
|
@@ -9159,7 +9202,11 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9159
9202
|
...outcome.error !== void 0 ? { error: outcome.error } : {}
|
|
9160
9203
|
}
|
|
9161
9204
|
});
|
|
9162
|
-
return
|
|
9205
|
+
return {
|
|
9206
|
+
ok: false,
|
|
9207
|
+
reason: outcome.reason,
|
|
9208
|
+
...outcome.error !== void 0 ? { message: outcome.error } : {}
|
|
9209
|
+
};
|
|
9163
9210
|
}
|
|
9164
9211
|
switch (command.kind) {
|
|
9165
9212
|
case "camera-function": {
|
|
@@ -9169,7 +9216,11 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9169
9216
|
tags: { deviceId: command.deviceId },
|
|
9170
9217
|
meta: { switchId: command.switchId }
|
|
9171
9218
|
});
|
|
9172
|
-
return
|
|
9219
|
+
return {
|
|
9220
|
+
ok: false,
|
|
9221
|
+
reason: "unknown-function",
|
|
9222
|
+
message: "the switch id is not a camera function"
|
|
9223
|
+
};
|
|
9173
9224
|
}
|
|
9174
9225
|
/**
|
|
9175
9226
|
* A FRESH read for this one device — the pass-level caches are up to
|
|
@@ -9179,7 +9230,11 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9179
9230
|
const device = await this.ctx.api.deviceManager.getDevice.query({ deviceId: command.deviceId });
|
|
9180
9231
|
if (device === null) {
|
|
9181
9232
|
this.ctx.logger.warn("ha-export: dropping a camera-function command, the device is not in the registry", { tags: { deviceId: command.deviceId } });
|
|
9182
|
-
return
|
|
9233
|
+
return {
|
|
9234
|
+
ok: false,
|
|
9235
|
+
reason: "device-not-found",
|
|
9236
|
+
message: "the device is not in the registry"
|
|
9237
|
+
};
|
|
9183
9238
|
}
|
|
9184
9239
|
const privacySlice = await this.ctx.api.deviceState.getCapSlice.query({
|
|
9185
9240
|
deviceId: command.deviceId,
|
|
@@ -9215,22 +9270,26 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9215
9270
|
tags: { deviceId: command.deviceId },
|
|
9216
9271
|
meta: { switchId: parsed.data }
|
|
9217
9272
|
});
|
|
9218
|
-
return
|
|
9273
|
+
return {
|
|
9274
|
+
ok: false,
|
|
9275
|
+
reason: "function-unavailable",
|
|
9276
|
+
message: "the camera function is not available"
|
|
9277
|
+
};
|
|
9219
9278
|
}
|
|
9220
9279
|
await applyCameraFunctionWrite(this.ctx.api, command.deviceId, write, sourceNodeId);
|
|
9221
|
-
return
|
|
9280
|
+
return APPLIED;
|
|
9222
9281
|
}
|
|
9223
9282
|
case "reboot": {
|
|
9224
9283
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
9225
9284
|
if (device.reboot === void 0) return this.noProvider(command.deviceId, "reboot");
|
|
9226
9285
|
await device.reboot.reboot({});
|
|
9227
|
-
return
|
|
9286
|
+
return APPLIED;
|
|
9228
9287
|
}
|
|
9229
9288
|
case "ptz-move": {
|
|
9230
9289
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
9231
9290
|
if (device.ptz === void 0) return this.noProvider(command.deviceId, "ptz");
|
|
9232
9291
|
await device.ptz.move(PTZ_MOVE_BY_DIRECTION[command.direction] ?? {});
|
|
9233
|
-
return
|
|
9292
|
+
return APPLIED;
|
|
9234
9293
|
}
|
|
9235
9294
|
case "ptz-preset": {
|
|
9236
9295
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
@@ -9241,10 +9300,14 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9241
9300
|
tags: { deviceId: command.deviceId },
|
|
9242
9301
|
meta: { preset: command.preset }
|
|
9243
9302
|
});
|
|
9244
|
-
return
|
|
9303
|
+
return {
|
|
9304
|
+
ok: false,
|
|
9305
|
+
reason: "no-such-preset",
|
|
9306
|
+
message: "no preset by that name"
|
|
9307
|
+
};
|
|
9245
9308
|
}
|
|
9246
9309
|
await device.ptz.goToPreset({ presetId: preset.id });
|
|
9247
|
-
return
|
|
9310
|
+
return APPLIED;
|
|
9248
9311
|
}
|
|
9249
9312
|
}
|
|
9250
9313
|
} catch (err) {
|
|
@@ -9260,7 +9323,11 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9260
9323
|
error: errMsg(err)
|
|
9261
9324
|
}
|
|
9262
9325
|
});
|
|
9263
|
-
return
|
|
9326
|
+
return {
|
|
9327
|
+
ok: false,
|
|
9328
|
+
reason: "error",
|
|
9329
|
+
message: errMsg(err)
|
|
9330
|
+
};
|
|
9264
9331
|
}
|
|
9265
9332
|
}
|
|
9266
9333
|
/** A cap the device does not provide. Refused, and never silently. */
|
|
@@ -9269,7 +9336,11 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9269
9336
|
tags: { deviceId },
|
|
9270
9337
|
meta: { capName }
|
|
9271
9338
|
});
|
|
9272
|
-
return
|
|
9339
|
+
return {
|
|
9340
|
+
ok: false,
|
|
9341
|
+
reason: "no-provider",
|
|
9342
|
+
message: `the device has no provider for ${capName}`
|
|
9343
|
+
};
|
|
9273
9344
|
}
|
|
9274
9345
|
/**
|
|
9275
9346
|
* A public, signed, expiring route Home Assistant can fetch bytes from.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-provider-homeassistant",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.650",
|
|
4
4
|
"description": "Home Assistant device provider addon for CamStack",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
"description": "Device provider (import) + native entity export (push). Two addons, one bundle, separate processes."
|
|
128
128
|
},
|
|
129
129
|
"requires": {
|
|
130
|
-
"server": ">=1.2.
|
|
130
|
+
"server": ">=1.2.740"
|
|
131
131
|
}
|
|
132
132
|
},
|
|
133
133
|
"files": [
|