@camstack/addon-provider-reolink 1.2.200 → 1.2.202
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 +340 -6
- package/dist/addon.mjs +340 -6
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -36,7 +36,7 @@ let node_os = require("node:os");
|
|
|
36
36
|
node_os = require_chunk.__toESM(node_os, 1);
|
|
37
37
|
let node_fs = require("node:fs");
|
|
38
38
|
node_fs = require_chunk.__toESM(node_fs, 1);
|
|
39
|
-
//#region ../types/dist/event-category-
|
|
39
|
+
//#region ../types/dist/event-category-7pZEBmVg.mjs
|
|
40
40
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
41
41
|
EventCategory["SystemBoot"] = "system.boot";
|
|
42
42
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -724,6 +724,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
724
724
|
EventCategory["BackupRestored"] = "backup.restored";
|
|
725
725
|
EventCategory["NotificationDispatched"] = "notification.dispatched";
|
|
726
726
|
EventCategory["NotificationFailed"] = "notification.failed";
|
|
727
|
+
/**
|
|
728
|
+
* A notification rule that cannot work (D905): its stored row does not load,
|
|
729
|
+
* or it still names a device / delivery target the prune CONFIRMED deleted.
|
|
730
|
+
* One edge per rule per distinct set of reasons; never on an unanswered read.
|
|
731
|
+
*/
|
|
732
|
+
EventCategory["NotificationRuleBroken"] = "notification.rule-broken";
|
|
733
|
+
/** The clear of {@link NotificationRuleBroken}: the rule loads and names nothing deleted. */
|
|
734
|
+
EventCategory["NotificationRuleRepaired"] = "notification.rule-repaired";
|
|
727
735
|
EventCategory["DeviceUpdated"] = "device.updated";
|
|
728
736
|
/**
|
|
729
737
|
* Transport-level connectivity. Emitted by the device driver when
|
|
@@ -5451,7 +5459,7 @@ var ZodIssueCode = {
|
|
|
5451
5459
|
var ZodFirstPartyTypeKind;
|
|
5452
5460
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5453
5461
|
//#endregion
|
|
5454
|
-
//#region ../types/dist/sleep-
|
|
5462
|
+
//#region ../types/dist/sleep-CueztlR1.mjs
|
|
5455
5463
|
/**
|
|
5456
5464
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5457
5465
|
* window to float samples (D455).
|
|
@@ -7735,7 +7743,7 @@ function stringProp(value, key) {
|
|
|
7735
7743
|
return typeof found === "string" ? found : void 0;
|
|
7736
7744
|
}
|
|
7737
7745
|
/** The code an error carries: its own, its cause's, or a tRPC `data.code`. */
|
|
7738
|
-
function errorCode(err) {
|
|
7746
|
+
function errorCode$1(err) {
|
|
7739
7747
|
const out = {};
|
|
7740
7748
|
const cause = typeof err === "object" && err !== null ? Reflect.get(err, "cause") : void 0;
|
|
7741
7749
|
const data = typeof err === "object" && err !== null ? Reflect.get(err, "data") : void 0;
|
|
@@ -7755,7 +7763,7 @@ function errorCode(err) {
|
|
|
7755
7763
|
function classifyStoreError(err) {
|
|
7756
7764
|
const message = err instanceof Error ? err.message : String(err);
|
|
7757
7765
|
if (RESTART_FINGERPRINTS.some((f) => message.includes(f))) return "restart";
|
|
7758
|
-
const code = errorCode(err);
|
|
7766
|
+
const code = errorCode$1(err);
|
|
7759
7767
|
if (code.node !== void 0 && TRANSPORT_CODES.has(code.node)) return "transport";
|
|
7760
7768
|
if (code.trpc !== void 0 && UNAVAILABLE_TRPC_CODES.has(code.trpc)) return "transport";
|
|
7761
7769
|
if (typeof err === "object" && err !== null) {
|
|
@@ -43183,6 +43191,8 @@ var NcSystemEventKindSchema = _enum([
|
|
|
43183
43191
|
"alert-critical",
|
|
43184
43192
|
"alert-error",
|
|
43185
43193
|
"alert-critical-resolved",
|
|
43194
|
+
"rule-broken",
|
|
43195
|
+
"rule-repaired",
|
|
43186
43196
|
"camera-online",
|
|
43187
43197
|
"camera-offline",
|
|
43188
43198
|
"camera-disabled",
|
|
@@ -80718,6 +80728,8 @@ new Map(Object.entries({
|
|
|
80718
80728
|
"alert-critical": "system",
|
|
80719
80729
|
"alert-error": "system",
|
|
80720
80730
|
"alert-critical-resolved": "system",
|
|
80731
|
+
"rule-broken": "system",
|
|
80732
|
+
"rule-repaired": "system",
|
|
80721
80733
|
"storage-location-low": "system",
|
|
80722
80734
|
"storage-location-critical": "system",
|
|
80723
80735
|
"storage-location-ok": "system",
|
|
@@ -80852,6 +80864,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
|
|
|
80852
80864
|
"alert-critical-resolved"
|
|
80853
80865
|
]
|
|
80854
80866
|
},
|
|
80867
|
+
{
|
|
80868
|
+
id: "rule-health",
|
|
80869
|
+
family: "system",
|
|
80870
|
+
label: "Notification rules",
|
|
80871
|
+
kinds: ["rule-broken", "rule-repaired"]
|
|
80872
|
+
},
|
|
80855
80873
|
{
|
|
80856
80874
|
id: "alarm",
|
|
80857
80875
|
family: "alarm",
|
|
@@ -81129,6 +81147,10 @@ var STATE_SUMMARY_VARS = [
|
|
|
81129
81147
|
[...RULE_TEXT],
|
|
81130
81148
|
[...RULE_TEXT],
|
|
81131
81149
|
[...RULE_TEXT],
|
|
81150
|
+
[...RULE_TEXT],
|
|
81151
|
+
[...RULE_TEXT],
|
|
81152
|
+
[...RULE_TEXT],
|
|
81153
|
+
[...RULE_TEXT],
|
|
81132
81154
|
([...RULE_TEXT], [...UPDATES]),
|
|
81133
81155
|
([...RULE_TEXT], [...PACKAGE_UPDATES]),
|
|
81134
81156
|
([...RULE_TEXT], [...PACKAGE_UPDATES]),
|
|
@@ -81167,6 +81189,107 @@ var STATE_SUMMARY_VARS = [
|
|
|
81167
81189
|
[...RULE_TEXT],
|
|
81168
81190
|
...STATE_SUMMARY_VARS
|
|
81169
81191
|
];
|
|
81192
|
+
new Map([
|
|
81193
|
+
{
|
|
81194
|
+
id: "device-connectivity",
|
|
81195
|
+
family: "device-events",
|
|
81196
|
+
label: "Device connectivity (offline / back online)",
|
|
81197
|
+
bad: ["device-offline"],
|
|
81198
|
+
recovery: ["device-online"],
|
|
81199
|
+
snapshotLabel: "Devices offline now",
|
|
81200
|
+
snapshotSection: "device-state"
|
|
81201
|
+
},
|
|
81202
|
+
{
|
|
81203
|
+
id: "device-switch",
|
|
81204
|
+
family: "device-events",
|
|
81205
|
+
label: "Device switched off / back on",
|
|
81206
|
+
bad: ["device-disabled"],
|
|
81207
|
+
recovery: ["device-enabled"],
|
|
81208
|
+
snapshotLabel: "Devices switched off now",
|
|
81209
|
+
snapshotSection: "device-state"
|
|
81210
|
+
},
|
|
81211
|
+
{
|
|
81212
|
+
id: "stream-connectivity",
|
|
81213
|
+
family: "device-events",
|
|
81214
|
+
label: "Stream connectivity (offline / back online)",
|
|
81215
|
+
bad: ["stream-offline"],
|
|
81216
|
+
recovery: ["stream-online"],
|
|
81217
|
+
snapshotLabel: "Streams offline now",
|
|
81218
|
+
snapshotSection: "streams"
|
|
81219
|
+
},
|
|
81220
|
+
{
|
|
81221
|
+
id: "battery",
|
|
81222
|
+
family: "device-events",
|
|
81223
|
+
label: "Battery (low / back to normal)",
|
|
81224
|
+
bad: ["device-battery-low"],
|
|
81225
|
+
recovery: ["device-battery-normal"],
|
|
81226
|
+
snapshotLabel: "Batteries low now",
|
|
81227
|
+
snapshotSection: "battery"
|
|
81228
|
+
},
|
|
81229
|
+
{
|
|
81230
|
+
id: "consumables",
|
|
81231
|
+
family: "device-events",
|
|
81232
|
+
label: "Consumables (low / replaced)",
|
|
81233
|
+
bad: ["device-consumable-low"],
|
|
81234
|
+
recovery: ["device-consumable-normal"],
|
|
81235
|
+
snapshotLabel: "Consumables low now",
|
|
81236
|
+
snapshotSection: "consumables"
|
|
81237
|
+
},
|
|
81238
|
+
{
|
|
81239
|
+
id: "tanks",
|
|
81240
|
+
family: "device-events",
|
|
81241
|
+
label: "Tanks (low or full / back to normal)",
|
|
81242
|
+
bad: ["device-tank-low", "device-tank-full"],
|
|
81243
|
+
recovery: ["device-tank-normal"],
|
|
81244
|
+
snapshotLabel: "Tanks low or full now",
|
|
81245
|
+
snapshotSection: "tanks"
|
|
81246
|
+
},
|
|
81247
|
+
{
|
|
81248
|
+
id: "recording-health",
|
|
81249
|
+
family: "device-events",
|
|
81250
|
+
label: "Recording health (stalled or degraded / recording again)",
|
|
81251
|
+
bad: ["recording-stalled", "recording-degraded"],
|
|
81252
|
+
recovery: ["recording-healthy"],
|
|
81253
|
+
snapshotLabel: "Recordings stalled now",
|
|
81254
|
+
snapshotSection: null
|
|
81255
|
+
},
|
|
81256
|
+
{
|
|
81257
|
+
id: "node-connectivity",
|
|
81258
|
+
family: "system",
|
|
81259
|
+
label: "Node connectivity (offline / back online)",
|
|
81260
|
+
bad: ["node-offline"],
|
|
81261
|
+
recovery: ["node-online"],
|
|
81262
|
+
snapshotLabel: "Nodes offline now",
|
|
81263
|
+
snapshotSection: "nodes"
|
|
81264
|
+
},
|
|
81265
|
+
{
|
|
81266
|
+
id: "storage-space",
|
|
81267
|
+
family: "system",
|
|
81268
|
+
label: "Storage space (low or critical / ok again)",
|
|
81269
|
+
bad: ["storage-location-low", "storage-location-critical"],
|
|
81270
|
+
recovery: ["storage-location-ok"],
|
|
81271
|
+
snapshotLabel: "Storage locations low now",
|
|
81272
|
+
snapshotSection: null
|
|
81273
|
+
},
|
|
81274
|
+
{
|
|
81275
|
+
id: "critical-alerts",
|
|
81276
|
+
family: "system",
|
|
81277
|
+
label: "Critical alerts (raised / resolved)",
|
|
81278
|
+
bad: ["alert-critical"],
|
|
81279
|
+
recovery: ["alert-critical-resolved"],
|
|
81280
|
+
snapshotLabel: "Critical alerts open now",
|
|
81281
|
+
snapshotSection: null
|
|
81282
|
+
},
|
|
81283
|
+
{
|
|
81284
|
+
id: "rule-health",
|
|
81285
|
+
family: "system",
|
|
81286
|
+
label: "Rule health (broken / repaired)",
|
|
81287
|
+
bad: ["rule-broken"],
|
|
81288
|
+
recovery: ["rule-repaired"],
|
|
81289
|
+
snapshotLabel: "Notification rules broken now",
|
|
81290
|
+
snapshotSection: null
|
|
81291
|
+
}
|
|
81292
|
+
].flatMap((spec) => [...spec.bad, ...spec.recovery].map((kind) => [kind, spec])));
|
|
81170
81293
|
new Set([
|
|
81171
81294
|
"disarmed",
|
|
81172
81295
|
"armed_home",
|
|
@@ -193472,6 +193595,137 @@ function motionSensorOnSimpleEvent(event) {
|
|
|
193472
193595
|
if (typeof motion !== "object" || motion === null || !("source" in motion)) return null;
|
|
193473
193596
|
return isMotionSensor(motion.source) ? motion.source : null;
|
|
193474
193597
|
}
|
|
193598
|
+
var CONNECTIVITY_CODES = new Set([
|
|
193599
|
+
"EHOSTUNREACH",
|
|
193600
|
+
"EHOSTDOWN",
|
|
193601
|
+
"ENETUNREACH",
|
|
193602
|
+
"ENETDOWN",
|
|
193603
|
+
"ECONNREFUSED",
|
|
193604
|
+
"ECONNRESET",
|
|
193605
|
+
"ETIMEDOUT",
|
|
193606
|
+
"EADDRNOTAVAIL",
|
|
193607
|
+
"EPIPE"
|
|
193608
|
+
]);
|
|
193609
|
+
var CONNECTIVITY_TEXT = /EHOSTUNREACH|EHOSTDOWN|ENETUNREACH|ENETDOWN|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EADDRNOTAVAIL|EPIPE|timed? ?out|socket hang up|socket closed|not connected/i;
|
|
193610
|
+
function errorCode(err) {
|
|
193611
|
+
if (typeof err !== "object" || err === null || !("code" in err)) return null;
|
|
193612
|
+
const code = err.code;
|
|
193613
|
+
return typeof code === "string" ? code : null;
|
|
193614
|
+
}
|
|
193615
|
+
/**
|
|
193616
|
+
* Did this failure say the camera could not be REACHED? An answer that came
|
|
193617
|
+
* back (wrong password, a rejected command) proves the camera is there, and is
|
|
193618
|
+
* not a reason to call it offline.
|
|
193619
|
+
*/
|
|
193620
|
+
function isConnectivityFailure(err) {
|
|
193621
|
+
const code = errorCode(err);
|
|
193622
|
+
if (code !== null && CONNECTIVITY_CODES.has(code)) return true;
|
|
193623
|
+
const text = err instanceof Error ? err.message : String(err);
|
|
193624
|
+
return CONNECTIVITY_TEXT.test(text);
|
|
193625
|
+
}
|
|
193626
|
+
var ReachabilityOfflineTracker = class {
|
|
193627
|
+
deps;
|
|
193628
|
+
stateValue = "reachable";
|
|
193629
|
+
since = 0;
|
|
193630
|
+
failures = 0;
|
|
193631
|
+
lastReason = "";
|
|
193632
|
+
offlineAt = 0;
|
|
193633
|
+
timer = null;
|
|
193634
|
+
probing = false;
|
|
193635
|
+
disposed = false;
|
|
193636
|
+
windowMs;
|
|
193637
|
+
recoveryProbeMs;
|
|
193638
|
+
constructor(deps) {
|
|
193639
|
+
this.deps = deps;
|
|
193640
|
+
this.windowMs = deps.windowMs ?? 12e4;
|
|
193641
|
+
this.recoveryProbeMs = deps.recoveryProbeMs ?? 3e4;
|
|
193642
|
+
}
|
|
193643
|
+
get state() {
|
|
193644
|
+
return this.stateValue;
|
|
193645
|
+
}
|
|
193646
|
+
/** The camera could not be reached (socket lost, a login refused at the network level). */
|
|
193647
|
+
noteFailure(reason) {
|
|
193648
|
+
if (this.disposed) return;
|
|
193649
|
+
this.failures += 1;
|
|
193650
|
+
this.lastReason = reason;
|
|
193651
|
+
if (this.stateValue !== "reachable") return;
|
|
193652
|
+
this.stateValue = "failing";
|
|
193653
|
+
this.since = Date.now();
|
|
193654
|
+
this.arm(this.windowMs, () => this.confirm());
|
|
193655
|
+
}
|
|
193656
|
+
/** The camera answered (a login, a stream). Closes a window; ends an outage once. */
|
|
193657
|
+
noteReachable() {
|
|
193658
|
+
if (this.disposed) return;
|
|
193659
|
+
const wasOffline = this.stateValue === "offline";
|
|
193660
|
+
const offlineForMs = Date.now() - this.offlineAt;
|
|
193661
|
+
this.clear();
|
|
193662
|
+
if (wasOffline) this.deps.onOnline({ offlineForMs });
|
|
193663
|
+
}
|
|
193664
|
+
/** The firmware said what the camera is (a push): forget our own verdict, emit nothing. */
|
|
193665
|
+
reset() {
|
|
193666
|
+
this.clear();
|
|
193667
|
+
}
|
|
193668
|
+
dispose() {
|
|
193669
|
+
this.disposed = true;
|
|
193670
|
+
this.clear();
|
|
193671
|
+
}
|
|
193672
|
+
clear() {
|
|
193673
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
193674
|
+
this.timer = null;
|
|
193675
|
+
this.stateValue = "reachable";
|
|
193676
|
+
this.failures = 0;
|
|
193677
|
+
this.lastReason = "";
|
|
193678
|
+
}
|
|
193679
|
+
arm(ms, fire) {
|
|
193680
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
193681
|
+
this.timer = setTimeout(() => {
|
|
193682
|
+
this.timer = null;
|
|
193683
|
+
fire();
|
|
193684
|
+
}, ms);
|
|
193685
|
+
if (typeof this.timer.unref === "function") this.timer.unref();
|
|
193686
|
+
}
|
|
193687
|
+
/** End of the window: one fresh probe must agree before the camera is called offline. */
|
|
193688
|
+
confirm() {
|
|
193689
|
+
this.runProbe((result) => {
|
|
193690
|
+
if (this.stateValue !== "failing") return;
|
|
193691
|
+
if (result === "reachable") {
|
|
193692
|
+
this.noteReachable();
|
|
193693
|
+
return;
|
|
193694
|
+
}
|
|
193695
|
+
this.stateValue = "offline";
|
|
193696
|
+
this.offlineAt = Date.now();
|
|
193697
|
+
this.deps.onOffline({
|
|
193698
|
+
since: this.since,
|
|
193699
|
+
failures: this.failures,
|
|
193700
|
+
lastReason: this.lastReason
|
|
193701
|
+
});
|
|
193702
|
+
this.arm(this.recoveryProbeMs, () => this.recover());
|
|
193703
|
+
});
|
|
193704
|
+
}
|
|
193705
|
+
recover() {
|
|
193706
|
+
this.runProbe((result) => {
|
|
193707
|
+
if (this.stateValue !== "offline") return;
|
|
193708
|
+
if (result === "reachable") {
|
|
193709
|
+
this.noteReachable();
|
|
193710
|
+
return;
|
|
193711
|
+
}
|
|
193712
|
+
this.arm(this.recoveryProbeMs, () => this.recover());
|
|
193713
|
+
});
|
|
193714
|
+
}
|
|
193715
|
+
async runProbe(then) {
|
|
193716
|
+
if (this.probing || this.disposed) return;
|
|
193717
|
+
this.probing = true;
|
|
193718
|
+
let result;
|
|
193719
|
+
try {
|
|
193720
|
+
result = await this.deps.probe();
|
|
193721
|
+
} catch {
|
|
193722
|
+
result = "unreachable";
|
|
193723
|
+
} finally {
|
|
193724
|
+
this.probing = false;
|
|
193725
|
+
}
|
|
193726
|
+
if (!this.disposed) then(result);
|
|
193727
|
+
}
|
|
193728
|
+
};
|
|
193475
193729
|
//#endregion
|
|
193476
193730
|
//#region src/reolink-camera.ts
|
|
193477
193731
|
/**
|
|
@@ -193862,6 +194116,17 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
193862
194116
|
/** Pending reconnect timer; cleared on successful reconnect or removeDevice. */
|
|
193863
194117
|
reconnectTimer = null;
|
|
193864
194118
|
/**
|
|
194119
|
+
* D904: offline from SUSTAINED unreachability on a mains camera that owns its
|
|
194120
|
+
* socket. Fed by socket loss and network-level login failures; its verdicts
|
|
194121
|
+
* are the normal DeviceOffline / DeviceOnline. Never fed for a battery camera
|
|
194122
|
+
* (asleep is not gone, D173) nor for a hub child (the Hub owns that socket).
|
|
194123
|
+
*/
|
|
194124
|
+
reachability = new ReachabilityOfflineTracker({
|
|
194125
|
+
probe: () => this.probeReachability(),
|
|
194126
|
+
onOffline: (info) => this.onReachabilityOffline(info),
|
|
194127
|
+
onOnline: (info) => this.onReachabilityOnline(info)
|
|
194128
|
+
});
|
|
194129
|
+
/**
|
|
193865
194130
|
* Read-through to the `battery.sleeping` runtime-state slice with
|
|
193866
194131
|
* a `false` default for non-battery cams (no slice). Internal
|
|
193867
194132
|
* shorthand — driver code uses `this.state.battery.sleeping = b` to write
|
|
@@ -198617,6 +198882,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
198617
198882
|
clearTimeout(this.reconnectTimer);
|
|
198618
198883
|
this.reconnectTimer = null;
|
|
198619
198884
|
}
|
|
198885
|
+
this.reachability.dispose();
|
|
198620
198886
|
this.ctx.logger.info("Removing Reolink camera", { meta: { stableId: this.stableId } });
|
|
198621
198887
|
const ids = [...this.publishedStreamIds];
|
|
198622
198888
|
await Promise.all(ids.map((id) => this.ctx.api.streamBroker.retractCameraStream.mutate({
|
|
@@ -201036,11 +201302,14 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
201036
201302
|
await api.login();
|
|
201037
201303
|
} catch (err) {
|
|
201038
201304
|
this.loginPromise = null;
|
|
201305
|
+
if (isConnectivityFailure(err)) this.noteUnreachable(err instanceof Error ? err.message : String(err));
|
|
201306
|
+
else if (this.ownsReachability()) this.reachability.noteReachable();
|
|
201039
201307
|
throw err;
|
|
201040
201308
|
}
|
|
201041
201309
|
this.api = api;
|
|
201042
201310
|
this.loginPromise = null;
|
|
201043
201311
|
this.reconnectAttempts = 0;
|
|
201312
|
+
if (this.ownsReachability()) this.reachability.noteReachable();
|
|
201044
201313
|
const persistedDeviceType = this.config.get("deviceCache")?.deviceType;
|
|
201045
201314
|
if (persistedDeviceType === "battery-cam") {
|
|
201046
201315
|
this.isBattery = true;
|
|
@@ -201103,8 +201372,10 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
201103
201372
|
* - 'awake' / 'sleeping': emit DeviceAwake / DeviceSleeping. Battery
|
|
201104
201373
|
* power-state only — does NOT touch online state.
|
|
201105
201374
|
* - 'online' / 'offline': emit DeviceOnline / DeviceOffline + flip
|
|
201106
|
-
* `this.online
|
|
201107
|
-
*
|
|
201375
|
+
* `this.online`, and reset the reachability verdict. The only OTHER
|
|
201376
|
+
* source is sustained unreachability on a mains camera (D904,
|
|
201377
|
+
* `onReachabilityOffline`): a single socket close, a reconnect or an
|
|
201378
|
+
* RFC4571 idle teardown still leaves online state alone.
|
|
201108
201379
|
* - 'battery': refresh cache, emit cap event.
|
|
201109
201380
|
* - 'doorbell', 'daynight', 'other': informational debug log only.
|
|
201110
201381
|
*/
|
|
@@ -201215,6 +201486,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
201215
201486
|
}
|
|
201216
201487
|
if (event.type === "online" || event.type === "offline") {
|
|
201217
201488
|
const next = event.type === "online";
|
|
201489
|
+
this.reachability.reset();
|
|
201218
201490
|
if (next !== this.online) {
|
|
201219
201491
|
this.markOnline(next);
|
|
201220
201492
|
this.ctx.eventBus.emit(createEvent(next ? EventCategory.DeviceOnline : EventCategory.DeviceOffline, eventSource, next ? {
|
|
@@ -201425,6 +201697,67 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
201425
201697
|
await current.close({ reason });
|
|
201426
201698
|
} catch {}
|
|
201427
201699
|
}
|
|
201700
|
+
/** D904 applies to a mains camera that dials its own socket — not a battery cam, not a hub child. */
|
|
201701
|
+
ownsReachability() {
|
|
201702
|
+
return !this.isBattery && !this.isHubChild();
|
|
201703
|
+
}
|
|
201704
|
+
/** The camera could not be reached: open (or extend) the reachability window. */
|
|
201705
|
+
noteUnreachable(reason) {
|
|
201706
|
+
if (!this.ownsReachability()) return;
|
|
201707
|
+
this.reachability.noteFailure(reason);
|
|
201708
|
+
}
|
|
201709
|
+
/**
|
|
201710
|
+
* One fresh login, for the tracker's confirmation and recovery probes. A
|
|
201711
|
+
* battery cam or hub child is never probed (D173) and answers `reachable`,
|
|
201712
|
+
* which closes the window instead of calling it offline.
|
|
201713
|
+
*/
|
|
201714
|
+
async probeReachability() {
|
|
201715
|
+
if (!this.ownsReachability()) return "reachable";
|
|
201716
|
+
try {
|
|
201717
|
+
await this.ensureApi();
|
|
201718
|
+
return "reachable";
|
|
201719
|
+
} catch (err) {
|
|
201720
|
+
return isConnectivityFailure(err) ? "unreachable" : "reachable";
|
|
201721
|
+
}
|
|
201722
|
+
}
|
|
201723
|
+
/** Sustained unreachability, confirmed: the normal DeviceOffline, once. */
|
|
201724
|
+
onReachabilityOffline(info) {
|
|
201725
|
+
if (!this.online) {
|
|
201726
|
+
this.ctx.logger.info("Reolink camera unreachable, already offline — nothing to emit", {
|
|
201727
|
+
tags: { deviceId: this.id },
|
|
201728
|
+
meta: { ...info }
|
|
201729
|
+
});
|
|
201730
|
+
return;
|
|
201731
|
+
}
|
|
201732
|
+
this.ctx.logger.warn("Reolink camera unreachable for the whole window — marked offline", {
|
|
201733
|
+
tags: { deviceId: this.id },
|
|
201734
|
+
meta: {
|
|
201735
|
+
...info,
|
|
201736
|
+
unreachableForMs: Date.now() - info.since
|
|
201737
|
+
}
|
|
201738
|
+
});
|
|
201739
|
+
this.markOnline(false);
|
|
201740
|
+
this.ctx.eventBus.emit(createEvent(EventCategory.DeviceOffline, this.eventSource(), {
|
|
201741
|
+
deviceId: this.id,
|
|
201742
|
+
providerId: REOLINK_ADDON_ID,
|
|
201743
|
+
reason: "unreachable"
|
|
201744
|
+
}));
|
|
201745
|
+
}
|
|
201746
|
+
/** The first success after a reachability outage: the normal DeviceOnline, once. */
|
|
201747
|
+
onReachabilityOnline(info) {
|
|
201748
|
+
if (this.online) return;
|
|
201749
|
+
this.ctx.logger.info("Reolink camera reachable again — marked online", {
|
|
201750
|
+
tags: { deviceId: this.id },
|
|
201751
|
+
meta: { ...info }
|
|
201752
|
+
});
|
|
201753
|
+
this.markOnline(true);
|
|
201754
|
+
this.ctx.eventBus.emit(createEvent(EventCategory.DeviceOnline, this.eventSource(), {
|
|
201755
|
+
deviceId: this.id,
|
|
201756
|
+
providerId: REOLINK_ADDON_ID,
|
|
201757
|
+
profileCount: 0,
|
|
201758
|
+
reason: "reachable-again"
|
|
201759
|
+
}));
|
|
201760
|
+
}
|
|
201428
201761
|
scheduleReconnect(reason) {
|
|
201429
201762
|
if (this.reconnectTimer) return;
|
|
201430
201763
|
if (this.isBattery) {
|
|
@@ -201438,6 +201771,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
201438
201771
|
});
|
|
201439
201772
|
return;
|
|
201440
201773
|
}
|
|
201774
|
+
this.noteUnreachable(reason);
|
|
201441
201775
|
const attempt = ++this.reconnectAttempts;
|
|
201442
201776
|
const backoffMs = Math.min(3e4, 1e3 * 2 ** Math.min(attempt - 1, 5));
|
|
201443
201777
|
this.ctx.logger.info("Baichuan reconnect scheduled", {
|
package/dist/addon.mjs
CHANGED
|
@@ -28,7 +28,7 @@ import { URL as URL$1 } from "url";
|
|
|
28
28
|
import { spawn as spawn$1 } from "node:child_process";
|
|
29
29
|
import os, { tmpdir } from "node:os";
|
|
30
30
|
import { createReadStream } from "node:fs";
|
|
31
|
-
//#region ../types/dist/event-category-
|
|
31
|
+
//#region ../types/dist/event-category-7pZEBmVg.mjs
|
|
32
32
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
33
33
|
EventCategory["SystemBoot"] = "system.boot";
|
|
34
34
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -716,6 +716,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
716
716
|
EventCategory["BackupRestored"] = "backup.restored";
|
|
717
717
|
EventCategory["NotificationDispatched"] = "notification.dispatched";
|
|
718
718
|
EventCategory["NotificationFailed"] = "notification.failed";
|
|
719
|
+
/**
|
|
720
|
+
* A notification rule that cannot work (D905): its stored row does not load,
|
|
721
|
+
* or it still names a device / delivery target the prune CONFIRMED deleted.
|
|
722
|
+
* One edge per rule per distinct set of reasons; never on an unanswered read.
|
|
723
|
+
*/
|
|
724
|
+
EventCategory["NotificationRuleBroken"] = "notification.rule-broken";
|
|
725
|
+
/** The clear of {@link NotificationRuleBroken}: the rule loads and names nothing deleted. */
|
|
726
|
+
EventCategory["NotificationRuleRepaired"] = "notification.rule-repaired";
|
|
719
727
|
EventCategory["DeviceUpdated"] = "device.updated";
|
|
720
728
|
/**
|
|
721
729
|
* Transport-level connectivity. Emitted by the device driver when
|
|
@@ -5443,7 +5451,7 @@ var ZodIssueCode = {
|
|
|
5443
5451
|
var ZodFirstPartyTypeKind;
|
|
5444
5452
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5445
5453
|
//#endregion
|
|
5446
|
-
//#region ../types/dist/sleep-
|
|
5454
|
+
//#region ../types/dist/sleep-CueztlR1.mjs
|
|
5447
5455
|
/**
|
|
5448
5456
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5449
5457
|
* window to float samples (D455).
|
|
@@ -7727,7 +7735,7 @@ function stringProp(value, key) {
|
|
|
7727
7735
|
return typeof found === "string" ? found : void 0;
|
|
7728
7736
|
}
|
|
7729
7737
|
/** The code an error carries: its own, its cause's, or a tRPC `data.code`. */
|
|
7730
|
-
function errorCode(err) {
|
|
7738
|
+
function errorCode$1(err) {
|
|
7731
7739
|
const out = {};
|
|
7732
7740
|
const cause = typeof err === "object" && err !== null ? Reflect.get(err, "cause") : void 0;
|
|
7733
7741
|
const data = typeof err === "object" && err !== null ? Reflect.get(err, "data") : void 0;
|
|
@@ -7747,7 +7755,7 @@ function errorCode(err) {
|
|
|
7747
7755
|
function classifyStoreError(err) {
|
|
7748
7756
|
const message = err instanceof Error ? err.message : String(err);
|
|
7749
7757
|
if (RESTART_FINGERPRINTS.some((f) => message.includes(f))) return "restart";
|
|
7750
|
-
const code = errorCode(err);
|
|
7758
|
+
const code = errorCode$1(err);
|
|
7751
7759
|
if (code.node !== void 0 && TRANSPORT_CODES.has(code.node)) return "transport";
|
|
7752
7760
|
if (code.trpc !== void 0 && UNAVAILABLE_TRPC_CODES.has(code.trpc)) return "transport";
|
|
7753
7761
|
if (typeof err === "object" && err !== null) {
|
|
@@ -43175,6 +43183,8 @@ var NcSystemEventKindSchema = _enum([
|
|
|
43175
43183
|
"alert-critical",
|
|
43176
43184
|
"alert-error",
|
|
43177
43185
|
"alert-critical-resolved",
|
|
43186
|
+
"rule-broken",
|
|
43187
|
+
"rule-repaired",
|
|
43178
43188
|
"camera-online",
|
|
43179
43189
|
"camera-offline",
|
|
43180
43190
|
"camera-disabled",
|
|
@@ -80710,6 +80720,8 @@ new Map(Object.entries({
|
|
|
80710
80720
|
"alert-critical": "system",
|
|
80711
80721
|
"alert-error": "system",
|
|
80712
80722
|
"alert-critical-resolved": "system",
|
|
80723
|
+
"rule-broken": "system",
|
|
80724
|
+
"rule-repaired": "system",
|
|
80713
80725
|
"storage-location-low": "system",
|
|
80714
80726
|
"storage-location-critical": "system",
|
|
80715
80727
|
"storage-location-ok": "system",
|
|
@@ -80844,6 +80856,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
|
|
|
80844
80856
|
"alert-critical-resolved"
|
|
80845
80857
|
]
|
|
80846
80858
|
},
|
|
80859
|
+
{
|
|
80860
|
+
id: "rule-health",
|
|
80861
|
+
family: "system",
|
|
80862
|
+
label: "Notification rules",
|
|
80863
|
+
kinds: ["rule-broken", "rule-repaired"]
|
|
80864
|
+
},
|
|
80847
80865
|
{
|
|
80848
80866
|
id: "alarm",
|
|
80849
80867
|
family: "alarm",
|
|
@@ -81121,6 +81139,10 @@ var STATE_SUMMARY_VARS = [
|
|
|
81121
81139
|
[...RULE_TEXT],
|
|
81122
81140
|
[...RULE_TEXT],
|
|
81123
81141
|
[...RULE_TEXT],
|
|
81142
|
+
[...RULE_TEXT],
|
|
81143
|
+
[...RULE_TEXT],
|
|
81144
|
+
[...RULE_TEXT],
|
|
81145
|
+
[...RULE_TEXT],
|
|
81124
81146
|
([...RULE_TEXT], [...UPDATES]),
|
|
81125
81147
|
([...RULE_TEXT], [...PACKAGE_UPDATES]),
|
|
81126
81148
|
([...RULE_TEXT], [...PACKAGE_UPDATES]),
|
|
@@ -81159,6 +81181,107 @@ var STATE_SUMMARY_VARS = [
|
|
|
81159
81181
|
[...RULE_TEXT],
|
|
81160
81182
|
...STATE_SUMMARY_VARS
|
|
81161
81183
|
];
|
|
81184
|
+
new Map([
|
|
81185
|
+
{
|
|
81186
|
+
id: "device-connectivity",
|
|
81187
|
+
family: "device-events",
|
|
81188
|
+
label: "Device connectivity (offline / back online)",
|
|
81189
|
+
bad: ["device-offline"],
|
|
81190
|
+
recovery: ["device-online"],
|
|
81191
|
+
snapshotLabel: "Devices offline now",
|
|
81192
|
+
snapshotSection: "device-state"
|
|
81193
|
+
},
|
|
81194
|
+
{
|
|
81195
|
+
id: "device-switch",
|
|
81196
|
+
family: "device-events",
|
|
81197
|
+
label: "Device switched off / back on",
|
|
81198
|
+
bad: ["device-disabled"],
|
|
81199
|
+
recovery: ["device-enabled"],
|
|
81200
|
+
snapshotLabel: "Devices switched off now",
|
|
81201
|
+
snapshotSection: "device-state"
|
|
81202
|
+
},
|
|
81203
|
+
{
|
|
81204
|
+
id: "stream-connectivity",
|
|
81205
|
+
family: "device-events",
|
|
81206
|
+
label: "Stream connectivity (offline / back online)",
|
|
81207
|
+
bad: ["stream-offline"],
|
|
81208
|
+
recovery: ["stream-online"],
|
|
81209
|
+
snapshotLabel: "Streams offline now",
|
|
81210
|
+
snapshotSection: "streams"
|
|
81211
|
+
},
|
|
81212
|
+
{
|
|
81213
|
+
id: "battery",
|
|
81214
|
+
family: "device-events",
|
|
81215
|
+
label: "Battery (low / back to normal)",
|
|
81216
|
+
bad: ["device-battery-low"],
|
|
81217
|
+
recovery: ["device-battery-normal"],
|
|
81218
|
+
snapshotLabel: "Batteries low now",
|
|
81219
|
+
snapshotSection: "battery"
|
|
81220
|
+
},
|
|
81221
|
+
{
|
|
81222
|
+
id: "consumables",
|
|
81223
|
+
family: "device-events",
|
|
81224
|
+
label: "Consumables (low / replaced)",
|
|
81225
|
+
bad: ["device-consumable-low"],
|
|
81226
|
+
recovery: ["device-consumable-normal"],
|
|
81227
|
+
snapshotLabel: "Consumables low now",
|
|
81228
|
+
snapshotSection: "consumables"
|
|
81229
|
+
},
|
|
81230
|
+
{
|
|
81231
|
+
id: "tanks",
|
|
81232
|
+
family: "device-events",
|
|
81233
|
+
label: "Tanks (low or full / back to normal)",
|
|
81234
|
+
bad: ["device-tank-low", "device-tank-full"],
|
|
81235
|
+
recovery: ["device-tank-normal"],
|
|
81236
|
+
snapshotLabel: "Tanks low or full now",
|
|
81237
|
+
snapshotSection: "tanks"
|
|
81238
|
+
},
|
|
81239
|
+
{
|
|
81240
|
+
id: "recording-health",
|
|
81241
|
+
family: "device-events",
|
|
81242
|
+
label: "Recording health (stalled or degraded / recording again)",
|
|
81243
|
+
bad: ["recording-stalled", "recording-degraded"],
|
|
81244
|
+
recovery: ["recording-healthy"],
|
|
81245
|
+
snapshotLabel: "Recordings stalled now",
|
|
81246
|
+
snapshotSection: null
|
|
81247
|
+
},
|
|
81248
|
+
{
|
|
81249
|
+
id: "node-connectivity",
|
|
81250
|
+
family: "system",
|
|
81251
|
+
label: "Node connectivity (offline / back online)",
|
|
81252
|
+
bad: ["node-offline"],
|
|
81253
|
+
recovery: ["node-online"],
|
|
81254
|
+
snapshotLabel: "Nodes offline now",
|
|
81255
|
+
snapshotSection: "nodes"
|
|
81256
|
+
},
|
|
81257
|
+
{
|
|
81258
|
+
id: "storage-space",
|
|
81259
|
+
family: "system",
|
|
81260
|
+
label: "Storage space (low or critical / ok again)",
|
|
81261
|
+
bad: ["storage-location-low", "storage-location-critical"],
|
|
81262
|
+
recovery: ["storage-location-ok"],
|
|
81263
|
+
snapshotLabel: "Storage locations low now",
|
|
81264
|
+
snapshotSection: null
|
|
81265
|
+
},
|
|
81266
|
+
{
|
|
81267
|
+
id: "critical-alerts",
|
|
81268
|
+
family: "system",
|
|
81269
|
+
label: "Critical alerts (raised / resolved)",
|
|
81270
|
+
bad: ["alert-critical"],
|
|
81271
|
+
recovery: ["alert-critical-resolved"],
|
|
81272
|
+
snapshotLabel: "Critical alerts open now",
|
|
81273
|
+
snapshotSection: null
|
|
81274
|
+
},
|
|
81275
|
+
{
|
|
81276
|
+
id: "rule-health",
|
|
81277
|
+
family: "system",
|
|
81278
|
+
label: "Rule health (broken / repaired)",
|
|
81279
|
+
bad: ["rule-broken"],
|
|
81280
|
+
recovery: ["rule-repaired"],
|
|
81281
|
+
snapshotLabel: "Notification rules broken now",
|
|
81282
|
+
snapshotSection: null
|
|
81283
|
+
}
|
|
81284
|
+
].flatMap((spec) => [...spec.bad, ...spec.recovery].map((kind) => [kind, spec])));
|
|
81162
81285
|
new Set([
|
|
81163
81286
|
"disarmed",
|
|
81164
81287
|
"armed_home",
|
|
@@ -193464,6 +193587,137 @@ function motionSensorOnSimpleEvent(event) {
|
|
|
193464
193587
|
if (typeof motion !== "object" || motion === null || !("source" in motion)) return null;
|
|
193465
193588
|
return isMotionSensor(motion.source) ? motion.source : null;
|
|
193466
193589
|
}
|
|
193590
|
+
var CONNECTIVITY_CODES = new Set([
|
|
193591
|
+
"EHOSTUNREACH",
|
|
193592
|
+
"EHOSTDOWN",
|
|
193593
|
+
"ENETUNREACH",
|
|
193594
|
+
"ENETDOWN",
|
|
193595
|
+
"ECONNREFUSED",
|
|
193596
|
+
"ECONNRESET",
|
|
193597
|
+
"ETIMEDOUT",
|
|
193598
|
+
"EADDRNOTAVAIL",
|
|
193599
|
+
"EPIPE"
|
|
193600
|
+
]);
|
|
193601
|
+
var CONNECTIVITY_TEXT = /EHOSTUNREACH|EHOSTDOWN|ENETUNREACH|ENETDOWN|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EADDRNOTAVAIL|EPIPE|timed? ?out|socket hang up|socket closed|not connected/i;
|
|
193602
|
+
function errorCode(err) {
|
|
193603
|
+
if (typeof err !== "object" || err === null || !("code" in err)) return null;
|
|
193604
|
+
const code = err.code;
|
|
193605
|
+
return typeof code === "string" ? code : null;
|
|
193606
|
+
}
|
|
193607
|
+
/**
|
|
193608
|
+
* Did this failure say the camera could not be REACHED? An answer that came
|
|
193609
|
+
* back (wrong password, a rejected command) proves the camera is there, and is
|
|
193610
|
+
* not a reason to call it offline.
|
|
193611
|
+
*/
|
|
193612
|
+
function isConnectivityFailure(err) {
|
|
193613
|
+
const code = errorCode(err);
|
|
193614
|
+
if (code !== null && CONNECTIVITY_CODES.has(code)) return true;
|
|
193615
|
+
const text = err instanceof Error ? err.message : String(err);
|
|
193616
|
+
return CONNECTIVITY_TEXT.test(text);
|
|
193617
|
+
}
|
|
193618
|
+
var ReachabilityOfflineTracker = class {
|
|
193619
|
+
deps;
|
|
193620
|
+
stateValue = "reachable";
|
|
193621
|
+
since = 0;
|
|
193622
|
+
failures = 0;
|
|
193623
|
+
lastReason = "";
|
|
193624
|
+
offlineAt = 0;
|
|
193625
|
+
timer = null;
|
|
193626
|
+
probing = false;
|
|
193627
|
+
disposed = false;
|
|
193628
|
+
windowMs;
|
|
193629
|
+
recoveryProbeMs;
|
|
193630
|
+
constructor(deps) {
|
|
193631
|
+
this.deps = deps;
|
|
193632
|
+
this.windowMs = deps.windowMs ?? 12e4;
|
|
193633
|
+
this.recoveryProbeMs = deps.recoveryProbeMs ?? 3e4;
|
|
193634
|
+
}
|
|
193635
|
+
get state() {
|
|
193636
|
+
return this.stateValue;
|
|
193637
|
+
}
|
|
193638
|
+
/** The camera could not be reached (socket lost, a login refused at the network level). */
|
|
193639
|
+
noteFailure(reason) {
|
|
193640
|
+
if (this.disposed) return;
|
|
193641
|
+
this.failures += 1;
|
|
193642
|
+
this.lastReason = reason;
|
|
193643
|
+
if (this.stateValue !== "reachable") return;
|
|
193644
|
+
this.stateValue = "failing";
|
|
193645
|
+
this.since = Date.now();
|
|
193646
|
+
this.arm(this.windowMs, () => this.confirm());
|
|
193647
|
+
}
|
|
193648
|
+
/** The camera answered (a login, a stream). Closes a window; ends an outage once. */
|
|
193649
|
+
noteReachable() {
|
|
193650
|
+
if (this.disposed) return;
|
|
193651
|
+
const wasOffline = this.stateValue === "offline";
|
|
193652
|
+
const offlineForMs = Date.now() - this.offlineAt;
|
|
193653
|
+
this.clear();
|
|
193654
|
+
if (wasOffline) this.deps.onOnline({ offlineForMs });
|
|
193655
|
+
}
|
|
193656
|
+
/** The firmware said what the camera is (a push): forget our own verdict, emit nothing. */
|
|
193657
|
+
reset() {
|
|
193658
|
+
this.clear();
|
|
193659
|
+
}
|
|
193660
|
+
dispose() {
|
|
193661
|
+
this.disposed = true;
|
|
193662
|
+
this.clear();
|
|
193663
|
+
}
|
|
193664
|
+
clear() {
|
|
193665
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
193666
|
+
this.timer = null;
|
|
193667
|
+
this.stateValue = "reachable";
|
|
193668
|
+
this.failures = 0;
|
|
193669
|
+
this.lastReason = "";
|
|
193670
|
+
}
|
|
193671
|
+
arm(ms, fire) {
|
|
193672
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
193673
|
+
this.timer = setTimeout(() => {
|
|
193674
|
+
this.timer = null;
|
|
193675
|
+
fire();
|
|
193676
|
+
}, ms);
|
|
193677
|
+
if (typeof this.timer.unref === "function") this.timer.unref();
|
|
193678
|
+
}
|
|
193679
|
+
/** End of the window: one fresh probe must agree before the camera is called offline. */
|
|
193680
|
+
confirm() {
|
|
193681
|
+
this.runProbe((result) => {
|
|
193682
|
+
if (this.stateValue !== "failing") return;
|
|
193683
|
+
if (result === "reachable") {
|
|
193684
|
+
this.noteReachable();
|
|
193685
|
+
return;
|
|
193686
|
+
}
|
|
193687
|
+
this.stateValue = "offline";
|
|
193688
|
+
this.offlineAt = Date.now();
|
|
193689
|
+
this.deps.onOffline({
|
|
193690
|
+
since: this.since,
|
|
193691
|
+
failures: this.failures,
|
|
193692
|
+
lastReason: this.lastReason
|
|
193693
|
+
});
|
|
193694
|
+
this.arm(this.recoveryProbeMs, () => this.recover());
|
|
193695
|
+
});
|
|
193696
|
+
}
|
|
193697
|
+
recover() {
|
|
193698
|
+
this.runProbe((result) => {
|
|
193699
|
+
if (this.stateValue !== "offline") return;
|
|
193700
|
+
if (result === "reachable") {
|
|
193701
|
+
this.noteReachable();
|
|
193702
|
+
return;
|
|
193703
|
+
}
|
|
193704
|
+
this.arm(this.recoveryProbeMs, () => this.recover());
|
|
193705
|
+
});
|
|
193706
|
+
}
|
|
193707
|
+
async runProbe(then) {
|
|
193708
|
+
if (this.probing || this.disposed) return;
|
|
193709
|
+
this.probing = true;
|
|
193710
|
+
let result;
|
|
193711
|
+
try {
|
|
193712
|
+
result = await this.deps.probe();
|
|
193713
|
+
} catch {
|
|
193714
|
+
result = "unreachable";
|
|
193715
|
+
} finally {
|
|
193716
|
+
this.probing = false;
|
|
193717
|
+
}
|
|
193718
|
+
if (!this.disposed) then(result);
|
|
193719
|
+
}
|
|
193720
|
+
};
|
|
193467
193721
|
//#endregion
|
|
193468
193722
|
//#region src/reolink-camera.ts
|
|
193469
193723
|
/**
|
|
@@ -193854,6 +194108,17 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
193854
194108
|
/** Pending reconnect timer; cleared on successful reconnect or removeDevice. */
|
|
193855
194109
|
reconnectTimer = null;
|
|
193856
194110
|
/**
|
|
194111
|
+
* D904: offline from SUSTAINED unreachability on a mains camera that owns its
|
|
194112
|
+
* socket. Fed by socket loss and network-level login failures; its verdicts
|
|
194113
|
+
* are the normal DeviceOffline / DeviceOnline. Never fed for a battery camera
|
|
194114
|
+
* (asleep is not gone, D173) nor for a hub child (the Hub owns that socket).
|
|
194115
|
+
*/
|
|
194116
|
+
reachability = new ReachabilityOfflineTracker({
|
|
194117
|
+
probe: () => this.probeReachability(),
|
|
194118
|
+
onOffline: (info) => this.onReachabilityOffline(info),
|
|
194119
|
+
onOnline: (info) => this.onReachabilityOnline(info)
|
|
194120
|
+
});
|
|
194121
|
+
/**
|
|
193857
194122
|
* Read-through to the `battery.sleeping` runtime-state slice with
|
|
193858
194123
|
* a `false` default for non-battery cams (no slice). Internal
|
|
193859
194124
|
* shorthand — driver code uses `this.state.battery.sleeping = b` to write
|
|
@@ -198609,6 +198874,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
198609
198874
|
clearTimeout(this.reconnectTimer);
|
|
198610
198875
|
this.reconnectTimer = null;
|
|
198611
198876
|
}
|
|
198877
|
+
this.reachability.dispose();
|
|
198612
198878
|
this.ctx.logger.info("Removing Reolink camera", { meta: { stableId: this.stableId } });
|
|
198613
198879
|
const ids = [...this.publishedStreamIds];
|
|
198614
198880
|
await Promise.all(ids.map((id) => this.ctx.api.streamBroker.retractCameraStream.mutate({
|
|
@@ -201028,11 +201294,14 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
201028
201294
|
await api.login();
|
|
201029
201295
|
} catch (err) {
|
|
201030
201296
|
this.loginPromise = null;
|
|
201297
|
+
if (isConnectivityFailure(err)) this.noteUnreachable(err instanceof Error ? err.message : String(err));
|
|
201298
|
+
else if (this.ownsReachability()) this.reachability.noteReachable();
|
|
201031
201299
|
throw err;
|
|
201032
201300
|
}
|
|
201033
201301
|
this.api = api;
|
|
201034
201302
|
this.loginPromise = null;
|
|
201035
201303
|
this.reconnectAttempts = 0;
|
|
201304
|
+
if (this.ownsReachability()) this.reachability.noteReachable();
|
|
201036
201305
|
const persistedDeviceType = this.config.get("deviceCache")?.deviceType;
|
|
201037
201306
|
if (persistedDeviceType === "battery-cam") {
|
|
201038
201307
|
this.isBattery = true;
|
|
@@ -201095,8 +201364,10 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
201095
201364
|
* - 'awake' / 'sleeping': emit DeviceAwake / DeviceSleeping. Battery
|
|
201096
201365
|
* power-state only — does NOT touch online state.
|
|
201097
201366
|
* - 'online' / 'offline': emit DeviceOnline / DeviceOffline + flip
|
|
201098
|
-
* `this.online
|
|
201099
|
-
*
|
|
201367
|
+
* `this.online`, and reset the reachability verdict. The only OTHER
|
|
201368
|
+
* source is sustained unreachability on a mains camera (D904,
|
|
201369
|
+
* `onReachabilityOffline`): a single socket close, a reconnect or an
|
|
201370
|
+
* RFC4571 idle teardown still leaves online state alone.
|
|
201100
201371
|
* - 'battery': refresh cache, emit cap event.
|
|
201101
201372
|
* - 'doorbell', 'daynight', 'other': informational debug log only.
|
|
201102
201373
|
*/
|
|
@@ -201207,6 +201478,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
201207
201478
|
}
|
|
201208
201479
|
if (event.type === "online" || event.type === "offline") {
|
|
201209
201480
|
const next = event.type === "online";
|
|
201481
|
+
this.reachability.reset();
|
|
201210
201482
|
if (next !== this.online) {
|
|
201211
201483
|
this.markOnline(next);
|
|
201212
201484
|
this.ctx.eventBus.emit(createEvent(next ? EventCategory.DeviceOnline : EventCategory.DeviceOffline, eventSource, next ? {
|
|
@@ -201417,6 +201689,67 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
201417
201689
|
await current.close({ reason });
|
|
201418
201690
|
} catch {}
|
|
201419
201691
|
}
|
|
201692
|
+
/** D904 applies to a mains camera that dials its own socket — not a battery cam, not a hub child. */
|
|
201693
|
+
ownsReachability() {
|
|
201694
|
+
return !this.isBattery && !this.isHubChild();
|
|
201695
|
+
}
|
|
201696
|
+
/** The camera could not be reached: open (or extend) the reachability window. */
|
|
201697
|
+
noteUnreachable(reason) {
|
|
201698
|
+
if (!this.ownsReachability()) return;
|
|
201699
|
+
this.reachability.noteFailure(reason);
|
|
201700
|
+
}
|
|
201701
|
+
/**
|
|
201702
|
+
* One fresh login, for the tracker's confirmation and recovery probes. A
|
|
201703
|
+
* battery cam or hub child is never probed (D173) and answers `reachable`,
|
|
201704
|
+
* which closes the window instead of calling it offline.
|
|
201705
|
+
*/
|
|
201706
|
+
async probeReachability() {
|
|
201707
|
+
if (!this.ownsReachability()) return "reachable";
|
|
201708
|
+
try {
|
|
201709
|
+
await this.ensureApi();
|
|
201710
|
+
return "reachable";
|
|
201711
|
+
} catch (err) {
|
|
201712
|
+
return isConnectivityFailure(err) ? "unreachable" : "reachable";
|
|
201713
|
+
}
|
|
201714
|
+
}
|
|
201715
|
+
/** Sustained unreachability, confirmed: the normal DeviceOffline, once. */
|
|
201716
|
+
onReachabilityOffline(info) {
|
|
201717
|
+
if (!this.online) {
|
|
201718
|
+
this.ctx.logger.info("Reolink camera unreachable, already offline — nothing to emit", {
|
|
201719
|
+
tags: { deviceId: this.id },
|
|
201720
|
+
meta: { ...info }
|
|
201721
|
+
});
|
|
201722
|
+
return;
|
|
201723
|
+
}
|
|
201724
|
+
this.ctx.logger.warn("Reolink camera unreachable for the whole window — marked offline", {
|
|
201725
|
+
tags: { deviceId: this.id },
|
|
201726
|
+
meta: {
|
|
201727
|
+
...info,
|
|
201728
|
+
unreachableForMs: Date.now() - info.since
|
|
201729
|
+
}
|
|
201730
|
+
});
|
|
201731
|
+
this.markOnline(false);
|
|
201732
|
+
this.ctx.eventBus.emit(createEvent(EventCategory.DeviceOffline, this.eventSource(), {
|
|
201733
|
+
deviceId: this.id,
|
|
201734
|
+
providerId: REOLINK_ADDON_ID,
|
|
201735
|
+
reason: "unreachable"
|
|
201736
|
+
}));
|
|
201737
|
+
}
|
|
201738
|
+
/** The first success after a reachability outage: the normal DeviceOnline, once. */
|
|
201739
|
+
onReachabilityOnline(info) {
|
|
201740
|
+
if (this.online) return;
|
|
201741
|
+
this.ctx.logger.info("Reolink camera reachable again — marked online", {
|
|
201742
|
+
tags: { deviceId: this.id },
|
|
201743
|
+
meta: { ...info }
|
|
201744
|
+
});
|
|
201745
|
+
this.markOnline(true);
|
|
201746
|
+
this.ctx.eventBus.emit(createEvent(EventCategory.DeviceOnline, this.eventSource(), {
|
|
201747
|
+
deviceId: this.id,
|
|
201748
|
+
providerId: REOLINK_ADDON_ID,
|
|
201749
|
+
profileCount: 0,
|
|
201750
|
+
reason: "reachable-again"
|
|
201751
|
+
}));
|
|
201752
|
+
}
|
|
201420
201753
|
scheduleReconnect(reason) {
|
|
201421
201754
|
if (this.reconnectTimer) return;
|
|
201422
201755
|
if (this.isBattery) {
|
|
@@ -201430,6 +201763,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
201430
201763
|
});
|
|
201431
201764
|
return;
|
|
201432
201765
|
}
|
|
201766
|
+
this.noteUnreachable(reason);
|
|
201433
201767
|
const attempt = ++this.reconnectAttempts;
|
|
201434
201768
|
const backoffMs = Math.min(3e4, 1e3 * 2 ** Math.min(attempt - 1, 5));
|
|
201435
201769
|
this.ctx.logger.info("Baichuan reconnect scheduled", {
|