@camstack/addon-provider-reolink 1.2.33 → 1.2.35
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 +64 -1
- package/dist/addon.mjs +64 -1
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -233626,8 +233626,50 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
233626
233626
|
* drained the battery and, by bursting the socket, was itself what starved
|
|
233627
233627
|
* GetEnc.
|
|
233628
233628
|
*/
|
|
233629
|
+
/**
|
|
233630
|
+
* The catalog a SLEEPING hub child can still offer: the parent's RTSP
|
|
233631
|
+
* relay entries, derived from the parent's config (host + credentials +
|
|
233632
|
+
* the Reolink Hub's per-channel relay scheme), with zero contact to the
|
|
233633
|
+
* child. This is the difference between a sleeping hub child being
|
|
233634
|
+
* playable and being a permanent "sleeping" tile: the broker gets a
|
|
233635
|
+
* dialable URL, the relay dial wakes the camera over RF (verified live
|
|
233636
|
+
* against the Home Hub, 2026-08-15), and the catalog never depended on
|
|
233637
|
+
* the child being awake to build.
|
|
233638
|
+
*/
|
|
233639
|
+
async buildHubRelayDescriptors() {
|
|
233640
|
+
if (!this.isHubChild()) return [];
|
|
233641
|
+
const parentId = this.parentDeviceId;
|
|
233642
|
+
if (parentId === null) return [];
|
|
233643
|
+
const cfg = (await this.ctx.devices.getAll()).find((d) => d.id === parentId)?.config.values;
|
|
233644
|
+
const host = cfg?.host;
|
|
233645
|
+
if (typeof host !== "string" || host.length === 0) return [];
|
|
233646
|
+
const user = encodeURIComponent(String(cfg?.username ?? ""));
|
|
233647
|
+
const password = encodeURIComponent(String(cfg?.password ?? ""));
|
|
233648
|
+
const port = typeof cfg?.port === "number" && cfg.port > 0 ? cfg.port : 554;
|
|
233649
|
+
const channel = this.getChannel();
|
|
233650
|
+
const channelCount = this.channelCount();
|
|
233651
|
+
const relayChannel = channel + 1;
|
|
233652
|
+
const out = [];
|
|
233653
|
+
for (const profile of ["main", "sub"]) out.push({
|
|
233654
|
+
camStreamId: buildCamStreamId("rtsp", channel, profile, channelCount),
|
|
233655
|
+
kind: "pull-rtsp",
|
|
233656
|
+
label: `Hub relay (${profile})`,
|
|
233657
|
+
url: `rtsp://${user}:${password}@${host}:${port}/h264Preview_0${relayChannel}_${profile}`,
|
|
233658
|
+
autoEligible: true,
|
|
233659
|
+
deviceFeatures: [...this.features]
|
|
233660
|
+
});
|
|
233661
|
+
return out;
|
|
233662
|
+
}
|
|
233629
233663
|
async buildStreamCatalogUncached() {
|
|
233630
233664
|
if (this.isBattery && this.sleeping) {
|
|
233665
|
+
const relay = await this.buildHubRelayDescriptors().catch(() => []);
|
|
233666
|
+
if (relay.length > 0) {
|
|
233667
|
+
this.ctx.logger.debug("buildStreamCatalog: sleeping hub child — serving hub relay entries", {
|
|
233668
|
+
tags: { deviceId: this.id },
|
|
233669
|
+
meta: { entries: relay.map((r) => r.camStreamId) }
|
|
233670
|
+
});
|
|
233671
|
+
return relay;
|
|
233672
|
+
}
|
|
233631
233673
|
this.ctx.logger.debug("buildStreamCatalog: battery cam sleeping, no cache — deferring to wake", { tags: { deviceId: this.id } });
|
|
233632
233674
|
return [];
|
|
233633
233675
|
}
|
|
@@ -236705,6 +236747,13 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
|
|
|
236705
236747
|
this.ensureApi().catch((err) => {
|
|
236706
236748
|
this.ctx.logger.warn("Reolink Hub initial connect failed — will retry", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
236707
236749
|
});
|
|
236750
|
+
this.eventUnsubscribers.push(this.ctx.eventBus.subscribe({ category: EventCategory.DeviceRegistered }, (event) => {
|
|
236751
|
+
if (event.data.parentDeviceId !== this.id) return;
|
|
236752
|
+
(async () => {
|
|
236753
|
+
const byChannel = await this.loadAdoptedChildrenByChannel();
|
|
236754
|
+
for (const [ch, did] of byChannel) this.channelToDeviceId.set(ch, did);
|
|
236755
|
+
})().catch(() => {});
|
|
236756
|
+
}));
|
|
236708
236757
|
this.eventUnsubscribers.push(this.ctx.eventBus.subscribe({ category: EventCategory.DeviceUnregistered }, (event) => {
|
|
236709
236758
|
const data = event.data;
|
|
236710
236759
|
if (data.parentDeviceId !== this.id) return;
|
|
@@ -236717,6 +236766,9 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
|
|
|
236717
236766
|
}));
|
|
236718
236767
|
}
|
|
236719
236768
|
eventUnsubscribers = [];
|
|
236769
|
+
/** Per-channel throttle for the unrouted-event warning (see
|
|
236770
|
+
* `routeSimpleEvent`) — a chatty unmapped channel must not flood. */
|
|
236771
|
+
unroutedChannelLoggedAt = /* @__PURE__ */ new Map();
|
|
236720
236772
|
async removeDevice() {
|
|
236721
236773
|
this.shutdownRequested = true;
|
|
236722
236774
|
if (this.reconnectTimer) {
|
|
@@ -237273,7 +237325,18 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
|
|
|
237273
237325
|
const channel = ev?.channel;
|
|
237274
237326
|
if (typeof channel !== "number") return;
|
|
237275
237327
|
const deviceId = this.channelToDeviceId.get(channel);
|
|
237276
|
-
if (deviceId === void 0)
|
|
237328
|
+
if (deviceId === void 0) {
|
|
237329
|
+
const last = this.unroutedChannelLoggedAt.get(channel) ?? 0;
|
|
237330
|
+
const now = Date.now();
|
|
237331
|
+
if (now - last > 6e4) {
|
|
237332
|
+
this.unroutedChannelLoggedAt.set(channel, now);
|
|
237333
|
+
this.ctx.logger.warn("Reolink Hub: simpleEvent for an unmapped channel dropped", { meta: {
|
|
237334
|
+
channel,
|
|
237335
|
+
type: ev?.type ?? "unknown"
|
|
237336
|
+
} });
|
|
237337
|
+
}
|
|
237338
|
+
return;
|
|
237339
|
+
}
|
|
237277
237340
|
this.ctx.devices.getAll().then((all) => all.find((d) => d.id === deviceId)).then((child) => {
|
|
237278
237341
|
if (!(child instanceof ReolinkCamera)) return;
|
|
237279
237342
|
child.handleSimpleEvent(ev);
|
package/dist/addon.mjs
CHANGED
|
@@ -233606,8 +233606,50 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
233606
233606
|
* drained the battery and, by bursting the socket, was itself what starved
|
|
233607
233607
|
* GetEnc.
|
|
233608
233608
|
*/
|
|
233609
|
+
/**
|
|
233610
|
+
* The catalog a SLEEPING hub child can still offer: the parent's RTSP
|
|
233611
|
+
* relay entries, derived from the parent's config (host + credentials +
|
|
233612
|
+
* the Reolink Hub's per-channel relay scheme), with zero contact to the
|
|
233613
|
+
* child. This is the difference between a sleeping hub child being
|
|
233614
|
+
* playable and being a permanent "sleeping" tile: the broker gets a
|
|
233615
|
+
* dialable URL, the relay dial wakes the camera over RF (verified live
|
|
233616
|
+
* against the Home Hub, 2026-08-15), and the catalog never depended on
|
|
233617
|
+
* the child being awake to build.
|
|
233618
|
+
*/
|
|
233619
|
+
async buildHubRelayDescriptors() {
|
|
233620
|
+
if (!this.isHubChild()) return [];
|
|
233621
|
+
const parentId = this.parentDeviceId;
|
|
233622
|
+
if (parentId === null) return [];
|
|
233623
|
+
const cfg = (await this.ctx.devices.getAll()).find((d) => d.id === parentId)?.config.values;
|
|
233624
|
+
const host = cfg?.host;
|
|
233625
|
+
if (typeof host !== "string" || host.length === 0) return [];
|
|
233626
|
+
const user = encodeURIComponent(String(cfg?.username ?? ""));
|
|
233627
|
+
const password = encodeURIComponent(String(cfg?.password ?? ""));
|
|
233628
|
+
const port = typeof cfg?.port === "number" && cfg.port > 0 ? cfg.port : 554;
|
|
233629
|
+
const channel = this.getChannel();
|
|
233630
|
+
const channelCount = this.channelCount();
|
|
233631
|
+
const relayChannel = channel + 1;
|
|
233632
|
+
const out = [];
|
|
233633
|
+
for (const profile of ["main", "sub"]) out.push({
|
|
233634
|
+
camStreamId: buildCamStreamId("rtsp", channel, profile, channelCount),
|
|
233635
|
+
kind: "pull-rtsp",
|
|
233636
|
+
label: `Hub relay (${profile})`,
|
|
233637
|
+
url: `rtsp://${user}:${password}@${host}:${port}/h264Preview_0${relayChannel}_${profile}`,
|
|
233638
|
+
autoEligible: true,
|
|
233639
|
+
deviceFeatures: [...this.features]
|
|
233640
|
+
});
|
|
233641
|
+
return out;
|
|
233642
|
+
}
|
|
233609
233643
|
async buildStreamCatalogUncached() {
|
|
233610
233644
|
if (this.isBattery && this.sleeping) {
|
|
233645
|
+
const relay = await this.buildHubRelayDescriptors().catch(() => []);
|
|
233646
|
+
if (relay.length > 0) {
|
|
233647
|
+
this.ctx.logger.debug("buildStreamCatalog: sleeping hub child — serving hub relay entries", {
|
|
233648
|
+
tags: { deviceId: this.id },
|
|
233649
|
+
meta: { entries: relay.map((r) => r.camStreamId) }
|
|
233650
|
+
});
|
|
233651
|
+
return relay;
|
|
233652
|
+
}
|
|
233611
233653
|
this.ctx.logger.debug("buildStreamCatalog: battery cam sleeping, no cache — deferring to wake", { tags: { deviceId: this.id } });
|
|
233612
233654
|
return [];
|
|
233613
233655
|
}
|
|
@@ -236685,6 +236727,13 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
|
|
|
236685
236727
|
this.ensureApi().catch((err) => {
|
|
236686
236728
|
this.ctx.logger.warn("Reolink Hub initial connect failed — will retry", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
236687
236729
|
});
|
|
236730
|
+
this.eventUnsubscribers.push(this.ctx.eventBus.subscribe({ category: EventCategory.DeviceRegistered }, (event) => {
|
|
236731
|
+
if (event.data.parentDeviceId !== this.id) return;
|
|
236732
|
+
(async () => {
|
|
236733
|
+
const byChannel = await this.loadAdoptedChildrenByChannel();
|
|
236734
|
+
for (const [ch, did] of byChannel) this.channelToDeviceId.set(ch, did);
|
|
236735
|
+
})().catch(() => {});
|
|
236736
|
+
}));
|
|
236688
236737
|
this.eventUnsubscribers.push(this.ctx.eventBus.subscribe({ category: EventCategory.DeviceUnregistered }, (event) => {
|
|
236689
236738
|
const data = event.data;
|
|
236690
236739
|
if (data.parentDeviceId !== this.id) return;
|
|
@@ -236697,6 +236746,9 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
|
|
|
236697
236746
|
}));
|
|
236698
236747
|
}
|
|
236699
236748
|
eventUnsubscribers = [];
|
|
236749
|
+
/** Per-channel throttle for the unrouted-event warning (see
|
|
236750
|
+
* `routeSimpleEvent`) — a chatty unmapped channel must not flood. */
|
|
236751
|
+
unroutedChannelLoggedAt = /* @__PURE__ */ new Map();
|
|
236700
236752
|
async removeDevice() {
|
|
236701
236753
|
this.shutdownRequested = true;
|
|
236702
236754
|
if (this.reconnectTimer) {
|
|
@@ -237253,7 +237305,18 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
|
|
|
237253
237305
|
const channel = ev?.channel;
|
|
237254
237306
|
if (typeof channel !== "number") return;
|
|
237255
237307
|
const deviceId = this.channelToDeviceId.get(channel);
|
|
237256
|
-
if (deviceId === void 0)
|
|
237308
|
+
if (deviceId === void 0) {
|
|
237309
|
+
const last = this.unroutedChannelLoggedAt.get(channel) ?? 0;
|
|
237310
|
+
const now = Date.now();
|
|
237311
|
+
if (now - last > 6e4) {
|
|
237312
|
+
this.unroutedChannelLoggedAt.set(channel, now);
|
|
237313
|
+
this.ctx.logger.warn("Reolink Hub: simpleEvent for an unmapped channel dropped", { meta: {
|
|
237314
|
+
channel,
|
|
237315
|
+
type: ev?.type ?? "unknown"
|
|
237316
|
+
} });
|
|
237317
|
+
}
|
|
237318
|
+
return;
|
|
237319
|
+
}
|
|
237257
237320
|
this.ctx.devices.getAll().then((all) => all.find((d) => d.id === deviceId)).then((child) => {
|
|
237258
237321
|
if (!(child instanceof ReolinkCamera)) return;
|
|
237259
237322
|
child.handleSimpleEvent(ev);
|