@camstack/types 1.2.143 → 1.2.145
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/capabilities/device-manager.cap.d.ts +12 -0
- package/dist/capabilities/device-provider.cap.d.ts +9 -0
- package/dist/device/base-device-provider.d.ts +26 -0
- package/dist/device/container-primary-child.d.ts +24 -1
- package/dist/index.js +73 -3
- package/dist/index.mjs +73 -3
- package/package.json +1 -1
|
@@ -1365,6 +1365,9 @@ export declare const deviceManagerCapability: {
|
|
|
1365
1365
|
uniqueId: z.ZodOptional<z.ZodString>;
|
|
1366
1366
|
raw: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
1367
1367
|
}, z.core.$strip>>;
|
|
1368
|
+
alreadyOnboarded: z.ZodOptional<z.ZodBoolean>;
|
|
1369
|
+
onboardedDeviceId: z.ZodOptional<z.ZodNumber>;
|
|
1370
|
+
onboardedName: z.ZodOptional<z.ZodString>;
|
|
1368
1371
|
}, z.core.$strip>>, "mutation">;
|
|
1369
1372
|
/** Adopt a discovered device via the device-provider capability. */
|
|
1370
1373
|
readonly adoptDevice: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
|
|
@@ -1380,6 +1383,9 @@ export declare const deviceManagerCapability: {
|
|
|
1380
1383
|
uniqueId: z.ZodOptional<z.ZodString>;
|
|
1381
1384
|
raw: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
1382
1385
|
}, z.core.$strip>>;
|
|
1386
|
+
alreadyOnboarded: z.ZodOptional<z.ZodBoolean>;
|
|
1387
|
+
onboardedDeviceId: z.ZodOptional<z.ZodNumber>;
|
|
1388
|
+
onboardedName: z.ZodOptional<z.ZodString>;
|
|
1383
1389
|
}, z.core.$strip>;
|
|
1384
1390
|
integrationId: z.ZodOptional<z.ZodString>;
|
|
1385
1391
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -1622,6 +1628,9 @@ export declare const deviceManagerCapability: {
|
|
|
1622
1628
|
uniqueId: z.ZodOptional<z.ZodString>;
|
|
1623
1629
|
raw: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
1624
1630
|
}, z.core.$strip>>;
|
|
1631
|
+
alreadyOnboarded: z.ZodOptional<z.ZodBoolean>;
|
|
1632
|
+
onboardedDeviceId: z.ZodOptional<z.ZodNumber>;
|
|
1633
|
+
onboardedName: z.ZodOptional<z.ZodString>;
|
|
1625
1634
|
}, z.core.$strip>>>;
|
|
1626
1635
|
error: z.ZodNullable<z.ZodString>;
|
|
1627
1636
|
}, z.core.$strip>>>;
|
|
@@ -1643,6 +1652,9 @@ export declare const deviceManagerCapability: {
|
|
|
1643
1652
|
uniqueId: z.ZodOptional<z.ZodString>;
|
|
1644
1653
|
raw: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
1645
1654
|
}, z.core.$strip>>;
|
|
1655
|
+
alreadyOnboarded: z.ZodOptional<z.ZodBoolean>;
|
|
1656
|
+
onboardedDeviceId: z.ZodOptional<z.ZodNumber>;
|
|
1657
|
+
onboardedName: z.ZodOptional<z.ZodString>;
|
|
1646
1658
|
}, z.core.$strip>>>;
|
|
1647
1659
|
}, z.core.$strip>, "mutation">;
|
|
1648
1660
|
/** The device type a provider creates via manual add (Camera/Container/Hub),
|
|
@@ -28,6 +28,9 @@ declare const DiscoveryCandidateSchema: z.ZodObject<{
|
|
|
28
28
|
uniqueId: z.ZodOptional<z.ZodString>;
|
|
29
29
|
raw: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
30
30
|
}, z.core.$strip>>;
|
|
31
|
+
alreadyOnboarded: z.ZodOptional<z.ZodBoolean>;
|
|
32
|
+
onboardedDeviceId: z.ZodOptional<z.ZodNumber>;
|
|
33
|
+
onboardedName: z.ZodOptional<z.ZodString>;
|
|
31
34
|
}, z.core.$strip>;
|
|
32
35
|
/**
|
|
33
36
|
* Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
|
|
@@ -132,6 +135,9 @@ export declare const deviceProviderCapability: {
|
|
|
132
135
|
uniqueId: z.ZodOptional<z.ZodString>;
|
|
133
136
|
raw: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
134
137
|
}, z.core.$strip>>;
|
|
138
|
+
alreadyOnboarded: z.ZodOptional<z.ZodBoolean>;
|
|
139
|
+
onboardedDeviceId: z.ZodOptional<z.ZodNumber>;
|
|
140
|
+
onboardedName: z.ZodOptional<z.ZodString>;
|
|
135
141
|
}, z.core.$strip>>, "mutation">;
|
|
136
142
|
/**
|
|
137
143
|
* Optional form schema (`ConfigUISchema`) for the EXTRA per-scan inputs a
|
|
@@ -161,6 +167,9 @@ export declare const deviceProviderCapability: {
|
|
|
161
167
|
uniqueId: z.ZodOptional<z.ZodString>;
|
|
162
168
|
raw: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
163
169
|
}, z.core.$strip>>;
|
|
170
|
+
alreadyOnboarded: z.ZodOptional<z.ZodBoolean>;
|
|
171
|
+
onboardedDeviceId: z.ZodOptional<z.ZodNumber>;
|
|
172
|
+
onboardedName: z.ZodOptional<z.ZodString>;
|
|
164
173
|
}, z.core.$strip>;
|
|
165
174
|
}, z.core.$strip>, z.ZodObject<{
|
|
166
175
|
id: z.ZodNumber;
|
|
@@ -17,6 +17,16 @@ export interface DiscoveryCandidate {
|
|
|
17
17
|
* knows the upstream identity ahead of adoption (HA: entity_id +
|
|
18
18
|
* unit + device_class from `GET /api/states`). */
|
|
19
19
|
readonly sourceInfo?: SourceInfo;
|
|
20
|
+
/** Set when the provider recognises this candidate as a device it ALREADY
|
|
21
|
+
* owns. A scan usually cannot reproduce the identity a device was onboarded
|
|
22
|
+
* under, so a stableId comparison never matches and an owned device looks
|
|
23
|
+
* addable — re-adopting it overwrites its config with scan-derived values.
|
|
24
|
+
* Absent means "not recognised", NOT "known to be new". */
|
|
25
|
+
readonly alreadyOnboarded?: boolean;
|
|
26
|
+
/** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
|
|
27
|
+
readonly onboardedDeviceId?: number;
|
|
28
|
+
/** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
|
|
29
|
+
readonly onboardedName?: string;
|
|
20
30
|
}
|
|
21
31
|
export interface DeviceSummary {
|
|
22
32
|
readonly id: number;
|
|
@@ -263,6 +273,22 @@ export declare abstract class BaseDeviceProvider<TConfig extends object = Record
|
|
|
263
273
|
* failed — logged at ERROR with `tags.deviceId` and surfaced via
|
|
264
274
|
* `getStatus().error`.
|
|
265
275
|
*/
|
|
276
|
+
/**
|
|
277
|
+
* Repair a row's PERSISTED config blob immediately before it is restored.
|
|
278
|
+
* Default: no-op — most providers have nothing to heal.
|
|
279
|
+
*
|
|
280
|
+
* This exists because a restored device self-hydrates from the DB: `create()`
|
|
281
|
+
* passes `{}` and `BaseDevice` parses the stored blob against the device
|
|
282
|
+
* schema. A blob that lost a REQUIRED field therefore fails restore forever,
|
|
283
|
+
* and no later pass revisits it — a hub-adopted Reolink camera whose blob had
|
|
284
|
+
* been emptied failed all four bounded attempts against fields
|
|
285
|
+
* (`host`, `password`) it inherits from its parent and never dials itself.
|
|
286
|
+
*
|
|
287
|
+
* Implementations get every saved row, so a child can read its parent's blob.
|
|
288
|
+
* A heal that throws is treated like any other restore failure: retried under
|
|
289
|
+
* the bound, then reported — never swallowed.
|
|
290
|
+
*/
|
|
291
|
+
protected healSavedConfig(_saved: SavedDevice, _allSaved: readonly SavedDevice[]): Promise<void>;
|
|
266
292
|
protected onRestoreDevices(savedDevices: readonly SavedDevice[]): Promise<DeviceRestoreReport | void>;
|
|
267
293
|
/** Convert an IDevice to the flat DeviceSummary for the cap router. */
|
|
268
294
|
protected toSummary(device: IDevice): DeviceSummary;
|
|
@@ -66,11 +66,34 @@ export interface ContainerChildRef {
|
|
|
66
66
|
*/
|
|
67
67
|
readonly entityId?: string;
|
|
68
68
|
readonly type: string;
|
|
69
|
+
/**
|
|
70
|
+
* The child's `DeviceRole`, when the provider stamped one. Ranked BEFORE
|
|
71
|
+
* `type` — see {@link CONTAINER_CHILD_ROLE_PRIORITY}. Absent is normal and
|
|
72
|
+
* simply defers to type priority.
|
|
73
|
+
*/
|
|
74
|
+
readonly role?: string;
|
|
75
|
+
/** The child's operator-facing name, used only for the namesake tiebreak. */
|
|
76
|
+
readonly name?: string;
|
|
69
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Roles that say what a container IS, most→least defining. Consulted BEFORE
|
|
80
|
+
* type priority, because `DeviceType` cannot tell them apart: Home Assistant
|
|
81
|
+
* maps a door contact, a battery level, a temperature reading and a "last seen"
|
|
82
|
+
* timestamp all to `DeviceType.Sensor`. Measured on container 4127 — children
|
|
83
|
+
* `battery-sensor`, `temperature-sensor`, `datetime-sensor`, `contact-sensor`
|
|
84
|
+
* and a role-less `button` — the type list ranked `button` above `sensor` and
|
|
85
|
+
* the container stood for its "Identifica" button instead of the door contact
|
|
86
|
+
* it is named after.
|
|
87
|
+
*
|
|
88
|
+
* Only STATE-DEFINING roles belong here. A diagnostic reading (battery,
|
|
89
|
+
* temperature, humidity, signal, last-seen) is never what a container is, so
|
|
90
|
+
* they are deliberately absent and fall through to type priority.
|
|
91
|
+
*/
|
|
92
|
+
export declare const CONTAINER_CHILD_ROLE_PRIORITY: readonly string[];
|
|
70
93
|
/**
|
|
71
94
|
* The child a container stands for: the operator's pick when it still exists,
|
|
72
95
|
* else the highest-priority type. `null` for a childless container — a caller
|
|
73
96
|
* must decide what an empty container means for it, rather than being handed a
|
|
74
97
|
* child that is not there.
|
|
75
98
|
*/
|
|
76
|
-
export declare function resolveContainerPrimaryChild(children: readonly ContainerChildRef[], overrideEntityId: string | null | undefined): ContainerChildRef | null;
|
|
99
|
+
export declare function resolveContainerPrimaryChild(children: readonly ContainerChildRef[], overrideEntityId: string | null | undefined, containerName?: string): ContainerChildRef | null;
|
package/dist/index.js
CHANGED
|
@@ -10061,7 +10061,26 @@ var DiscoveryCandidateSchema = zod.z.object({
|
|
|
10061
10061
|
* identity ahead of adoption. Rendering metadata (unit, precision)
|
|
10062
10062
|
* flows live through the cap STATUS SLICE after adoption.
|
|
10063
10063
|
*/
|
|
10064
|
-
sourceInfo: SourceInfoSchema.optional()
|
|
10064
|
+
sourceInfo: SourceInfoSchema.optional(),
|
|
10065
|
+
/**
|
|
10066
|
+
* Set when this candidate is a device the provider ALREADY owns.
|
|
10067
|
+
*
|
|
10068
|
+
* A scan cannot generally produce the identity a device was onboarded under
|
|
10069
|
+
* (Reolink keys on `mac-<mac>`, learned at adopt time), so a stableId
|
|
10070
|
+
* comparison never matches and an owned device looks addable. Re-adopting one
|
|
10071
|
+
* overwrites its config with scan-derived values — that is how a Home Hub's
|
|
10072
|
+
* Baichuan port was overwritten with its ONVIF port, taking the hub and its
|
|
10073
|
+
* three child cameras offline for four hours.
|
|
10074
|
+
*
|
|
10075
|
+
* A provider that can recognise its own devices says so here. Absent means
|
|
10076
|
+
* "not recognised", which is not the same as "known to be new" — a provider
|
|
10077
|
+
* that cannot tell simply never sets it.
|
|
10078
|
+
*/
|
|
10079
|
+
alreadyOnboarded: zod.z.boolean().optional(),
|
|
10080
|
+
/** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
|
|
10081
|
+
onboardedDeviceId: zod.z.number().optional(),
|
|
10082
|
+
/** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
|
|
10083
|
+
onboardedName: zod.z.string().optional()
|
|
10065
10084
|
});
|
|
10066
10085
|
/**
|
|
10067
10086
|
* Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
|
|
@@ -35532,6 +35551,39 @@ var CONTAINER_CHILD_PRIORITY = [
|
|
|
35532
35551
|
"image",
|
|
35533
35552
|
"sensor"
|
|
35534
35553
|
];
|
|
35554
|
+
/**
|
|
35555
|
+
* Roles that say what a container IS, most→least defining. Consulted BEFORE
|
|
35556
|
+
* type priority, because `DeviceType` cannot tell them apart: Home Assistant
|
|
35557
|
+
* maps a door contact, a battery level, a temperature reading and a "last seen"
|
|
35558
|
+
* timestamp all to `DeviceType.Sensor`. Measured on container 4127 — children
|
|
35559
|
+
* `battery-sensor`, `temperature-sensor`, `datetime-sensor`, `contact-sensor`
|
|
35560
|
+
* and a role-less `button` — the type list ranked `button` above `sensor` and
|
|
35561
|
+
* the container stood for its "Identifica" button instead of the door contact
|
|
35562
|
+
* it is named after.
|
|
35563
|
+
*
|
|
35564
|
+
* Only STATE-DEFINING roles belong here. A diagnostic reading (battery,
|
|
35565
|
+
* temperature, humidity, signal, last-seen) is never what a container is, so
|
|
35566
|
+
* they are deliberately absent and fall through to type priority.
|
|
35567
|
+
*/
|
|
35568
|
+
var CONTAINER_CHILD_ROLE_PRIORITY = [
|
|
35569
|
+
"contact-sensor",
|
|
35570
|
+
"motion-sensor",
|
|
35571
|
+
"occupancy-sensor",
|
|
35572
|
+
"smoke-sensor",
|
|
35573
|
+
"co-sensor",
|
|
35574
|
+
"gas-sensor",
|
|
35575
|
+
"leak-sensor",
|
|
35576
|
+
"vibration-sensor",
|
|
35577
|
+
"tamper-sensor",
|
|
35578
|
+
"sound-sensor",
|
|
35579
|
+
"binary-sensor",
|
|
35580
|
+
"binary-helper"
|
|
35581
|
+
];
|
|
35582
|
+
function roleRank(role) {
|
|
35583
|
+
if (role === void 0) return CONTAINER_CHILD_ROLE_PRIORITY.length;
|
|
35584
|
+
const i = CONTAINER_CHILD_ROLE_PRIORITY.indexOf(role);
|
|
35585
|
+
return i === -1 ? CONTAINER_CHILD_ROLE_PRIORITY.length : i;
|
|
35586
|
+
}
|
|
35535
35587
|
function rank(type) {
|
|
35536
35588
|
const i = CONTAINER_CHILD_PRIORITY.indexOf(type);
|
|
35537
35589
|
return i === -1 ? CONTAINER_CHILD_PRIORITY.length : i;
|
|
@@ -35542,12 +35594,13 @@ function rank(type) {
|
|
|
35542
35594
|
* must decide what an empty container means for it, rather than being handed a
|
|
35543
35595
|
* child that is not there.
|
|
35544
35596
|
*/
|
|
35545
|
-
function resolveContainerPrimaryChild(children, overrideEntityId) {
|
|
35597
|
+
function resolveContainerPrimaryChild(children, overrideEntityId, containerName) {
|
|
35546
35598
|
if (overrideEntityId !== void 0 && overrideEntityId !== null) {
|
|
35547
35599
|
const picked = children.find((c) => c.stableId === overrideEntityId) ?? children.find((c) => c.entityId !== void 0 && c.entityId === overrideEntityId);
|
|
35548
35600
|
if (picked !== void 0) return picked;
|
|
35549
35601
|
}
|
|
35550
|
-
|
|
35602
|
+
const namesake = (c) => containerName !== void 0 && containerName.length > 0 && c.name !== void 0 && c.name.toLowerCase() === containerName.toLowerCase() ? 0 : 1;
|
|
35603
|
+
return [...children].toSorted((a, b) => roleRank(a.role) - roleRank(b.role) || namesake(a) - namesake(b) || rank(a.type) - rank(b.type))[0] ?? null;
|
|
35551
35604
|
}
|
|
35552
35605
|
//#endregion
|
|
35553
35606
|
//#region src/device/accessory.ts
|
|
@@ -36904,6 +36957,22 @@ var BaseDeviceProvider = class extends require_sleep.BaseAddon {
|
|
|
36904
36957
|
* failed — logged at ERROR with `tags.deviceId` and surfaced via
|
|
36905
36958
|
* `getStatus().error`.
|
|
36906
36959
|
*/
|
|
36960
|
+
/**
|
|
36961
|
+
* Repair a row's PERSISTED config blob immediately before it is restored.
|
|
36962
|
+
* Default: no-op — most providers have nothing to heal.
|
|
36963
|
+
*
|
|
36964
|
+
* This exists because a restored device self-hydrates from the DB: `create()`
|
|
36965
|
+
* passes `{}` and `BaseDevice` parses the stored blob against the device
|
|
36966
|
+
* schema. A blob that lost a REQUIRED field therefore fails restore forever,
|
|
36967
|
+
* and no later pass revisits it — a hub-adopted Reolink camera whose blob had
|
|
36968
|
+
* been emptied failed all four bounded attempts against fields
|
|
36969
|
+
* (`host`, `password`) it inherits from its parent and never dials itself.
|
|
36970
|
+
*
|
|
36971
|
+
* Implementations get every saved row, so a child can read its parent's blob.
|
|
36972
|
+
* A heal that throws is treated like any other restore failure: retried under
|
|
36973
|
+
* the bound, then reported — never swallowed.
|
|
36974
|
+
*/
|
|
36975
|
+
async healSavedConfig(_saved, _allSaved) {}
|
|
36907
36976
|
async onRestoreDevices(savedDevices) {
|
|
36908
36977
|
const restored = /* @__PURE__ */ new Set();
|
|
36909
36978
|
const failures = [];
|
|
@@ -36912,6 +36981,7 @@ var BaseDeviceProvider = class extends require_sleep.BaseAddon {
|
|
|
36912
36981
|
const Class = this.deviceClasses[saved.type];
|
|
36913
36982
|
if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
|
|
36914
36983
|
if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
|
|
36984
|
+
await this.healSavedConfig(saved, savedDevices);
|
|
36915
36985
|
await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
|
|
36916
36986
|
restored.add(saved.id);
|
|
36917
36987
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -10060,7 +10060,26 @@ var DiscoveryCandidateSchema = z.object({
|
|
|
10060
10060
|
* identity ahead of adoption. Rendering metadata (unit, precision)
|
|
10061
10061
|
* flows live through the cap STATUS SLICE after adoption.
|
|
10062
10062
|
*/
|
|
10063
|
-
sourceInfo: SourceInfoSchema.optional()
|
|
10063
|
+
sourceInfo: SourceInfoSchema.optional(),
|
|
10064
|
+
/**
|
|
10065
|
+
* Set when this candidate is a device the provider ALREADY owns.
|
|
10066
|
+
*
|
|
10067
|
+
* A scan cannot generally produce the identity a device was onboarded under
|
|
10068
|
+
* (Reolink keys on `mac-<mac>`, learned at adopt time), so a stableId
|
|
10069
|
+
* comparison never matches and an owned device looks addable. Re-adopting one
|
|
10070
|
+
* overwrites its config with scan-derived values — that is how a Home Hub's
|
|
10071
|
+
* Baichuan port was overwritten with its ONVIF port, taking the hub and its
|
|
10072
|
+
* three child cameras offline for four hours.
|
|
10073
|
+
*
|
|
10074
|
+
* A provider that can recognise its own devices says so here. Absent means
|
|
10075
|
+
* "not recognised", which is not the same as "known to be new" — a provider
|
|
10076
|
+
* that cannot tell simply never sets it.
|
|
10077
|
+
*/
|
|
10078
|
+
alreadyOnboarded: z.boolean().optional(),
|
|
10079
|
+
/** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
|
|
10080
|
+
onboardedDeviceId: z.number().optional(),
|
|
10081
|
+
/** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
|
|
10082
|
+
onboardedName: z.string().optional()
|
|
10064
10083
|
});
|
|
10065
10084
|
/**
|
|
10066
10085
|
* Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
|
|
@@ -35524,6 +35543,39 @@ var CONTAINER_CHILD_PRIORITY = [
|
|
|
35524
35543
|
"image",
|
|
35525
35544
|
"sensor"
|
|
35526
35545
|
];
|
|
35546
|
+
/**
|
|
35547
|
+
* Roles that say what a container IS, most→least defining. Consulted BEFORE
|
|
35548
|
+
* type priority, because `DeviceType` cannot tell them apart: Home Assistant
|
|
35549
|
+
* maps a door contact, a battery level, a temperature reading and a "last seen"
|
|
35550
|
+
* timestamp all to `DeviceType.Sensor`. Measured on container 4127 — children
|
|
35551
|
+
* `battery-sensor`, `temperature-sensor`, `datetime-sensor`, `contact-sensor`
|
|
35552
|
+
* and a role-less `button` — the type list ranked `button` above `sensor` and
|
|
35553
|
+
* the container stood for its "Identifica" button instead of the door contact
|
|
35554
|
+
* it is named after.
|
|
35555
|
+
*
|
|
35556
|
+
* Only STATE-DEFINING roles belong here. A diagnostic reading (battery,
|
|
35557
|
+
* temperature, humidity, signal, last-seen) is never what a container is, so
|
|
35558
|
+
* they are deliberately absent and fall through to type priority.
|
|
35559
|
+
*/
|
|
35560
|
+
var CONTAINER_CHILD_ROLE_PRIORITY = [
|
|
35561
|
+
"contact-sensor",
|
|
35562
|
+
"motion-sensor",
|
|
35563
|
+
"occupancy-sensor",
|
|
35564
|
+
"smoke-sensor",
|
|
35565
|
+
"co-sensor",
|
|
35566
|
+
"gas-sensor",
|
|
35567
|
+
"leak-sensor",
|
|
35568
|
+
"vibration-sensor",
|
|
35569
|
+
"tamper-sensor",
|
|
35570
|
+
"sound-sensor",
|
|
35571
|
+
"binary-sensor",
|
|
35572
|
+
"binary-helper"
|
|
35573
|
+
];
|
|
35574
|
+
function roleRank(role) {
|
|
35575
|
+
if (role === void 0) return CONTAINER_CHILD_ROLE_PRIORITY.length;
|
|
35576
|
+
const i = CONTAINER_CHILD_ROLE_PRIORITY.indexOf(role);
|
|
35577
|
+
return i === -1 ? CONTAINER_CHILD_ROLE_PRIORITY.length : i;
|
|
35578
|
+
}
|
|
35527
35579
|
function rank(type) {
|
|
35528
35580
|
const i = CONTAINER_CHILD_PRIORITY.indexOf(type);
|
|
35529
35581
|
return i === -1 ? CONTAINER_CHILD_PRIORITY.length : i;
|
|
@@ -35534,12 +35586,13 @@ function rank(type) {
|
|
|
35534
35586
|
* must decide what an empty container means for it, rather than being handed a
|
|
35535
35587
|
* child that is not there.
|
|
35536
35588
|
*/
|
|
35537
|
-
function resolveContainerPrimaryChild(children, overrideEntityId) {
|
|
35589
|
+
function resolveContainerPrimaryChild(children, overrideEntityId, containerName) {
|
|
35538
35590
|
if (overrideEntityId !== void 0 && overrideEntityId !== null) {
|
|
35539
35591
|
const picked = children.find((c) => c.stableId === overrideEntityId) ?? children.find((c) => c.entityId !== void 0 && c.entityId === overrideEntityId);
|
|
35540
35592
|
if (picked !== void 0) return picked;
|
|
35541
35593
|
}
|
|
35542
|
-
|
|
35594
|
+
const namesake = (c) => containerName !== void 0 && containerName.length > 0 && c.name !== void 0 && c.name.toLowerCase() === containerName.toLowerCase() ? 0 : 1;
|
|
35595
|
+
return [...children].toSorted((a, b) => roleRank(a.role) - roleRank(b.role) || namesake(a) - namesake(b) || rank(a.type) - rank(b.type))[0] ?? null;
|
|
35543
35596
|
}
|
|
35544
35597
|
//#endregion
|
|
35545
35598
|
//#region src/device/accessory.ts
|
|
@@ -36896,6 +36949,22 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
36896
36949
|
* failed — logged at ERROR with `tags.deviceId` and surfaced via
|
|
36897
36950
|
* `getStatus().error`.
|
|
36898
36951
|
*/
|
|
36952
|
+
/**
|
|
36953
|
+
* Repair a row's PERSISTED config blob immediately before it is restored.
|
|
36954
|
+
* Default: no-op — most providers have nothing to heal.
|
|
36955
|
+
*
|
|
36956
|
+
* This exists because a restored device self-hydrates from the DB: `create()`
|
|
36957
|
+
* passes `{}` and `BaseDevice` parses the stored blob against the device
|
|
36958
|
+
* schema. A blob that lost a REQUIRED field therefore fails restore forever,
|
|
36959
|
+
* and no later pass revisits it — a hub-adopted Reolink camera whose blob had
|
|
36960
|
+
* been emptied failed all four bounded attempts against fields
|
|
36961
|
+
* (`host`, `password`) it inherits from its parent and never dials itself.
|
|
36962
|
+
*
|
|
36963
|
+
* Implementations get every saved row, so a child can read its parent's blob.
|
|
36964
|
+
* A heal that throws is treated like any other restore failure: retried under
|
|
36965
|
+
* the bound, then reported — never swallowed.
|
|
36966
|
+
*/
|
|
36967
|
+
async healSavedConfig(_saved, _allSaved) {}
|
|
36899
36968
|
async onRestoreDevices(savedDevices) {
|
|
36900
36969
|
const restored = /* @__PURE__ */ new Set();
|
|
36901
36970
|
const failures = [];
|
|
@@ -36904,6 +36973,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
36904
36973
|
const Class = this.deviceClasses[saved.type];
|
|
36905
36974
|
if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
|
|
36906
36975
|
if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
|
|
36976
|
+
await this.healSavedConfig(saved, savedDevices);
|
|
36907
36977
|
await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
|
|
36908
36978
|
restored.add(saved.id);
|
|
36909
36979
|
};
|