@camstack/addon-provider-reolink 1.2.88 → 1.2.89
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 +108 -24
- package/dist/addon.mjs +108 -24
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -7592,7 +7592,8 @@ var CameraSwitchAuthoritySchema = discriminatedUnion("kind", [
|
|
|
7592
7592
|
var CameraSwitchUnavailableReasonSchema = _enum([
|
|
7593
7593
|
"no-provider",
|
|
7594
7594
|
"source-unreachable",
|
|
7595
|
-
"not-configured"
|
|
7595
|
+
"not-configured",
|
|
7596
|
+
"device-disabled"
|
|
7596
7597
|
]);
|
|
7597
7598
|
/**
|
|
7598
7599
|
* One switch, resolved for one camera.
|
|
@@ -24325,12 +24326,20 @@ var BatteryStatusSchema = object({
|
|
|
24325
24326
|
* Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
|
|
24326
24327
|
* Reolink-specific for the Solar Panel 2 accessory (will become
|
|
24327
24328
|
* common on other battery cams). `'none'` means running on battery
|
|
24328
|
-
* alone.
|
|
24329
|
+
* alone — a NEGATIVE the firmware actually reported.
|
|
24330
|
+
*
|
|
24331
|
+
* `'unknown'` is the absence of an answer: the provider has not read the
|
|
24332
|
+
* power fields yet, or the firmware reported neither. Before it existed,
|
|
24333
|
+
* two unreadable fields produced `'none'`, and a camera nobody had reached
|
|
24334
|
+
* was drawn "on battery alone" — the same D315 defect `percentage` closed
|
|
24335
|
+
* by going nullable. Consumers SKIP it (no charger state published, no
|
|
24336
|
+
* glyph, no HomeKit ChargingState) rather than coerce it either way.
|
|
24329
24337
|
*/
|
|
24330
24338
|
charging: _enum([
|
|
24331
24339
|
"dc",
|
|
24332
24340
|
"solar",
|
|
24333
|
-
"none"
|
|
24341
|
+
"none",
|
|
24342
|
+
"unknown"
|
|
24334
24343
|
]),
|
|
24335
24344
|
/**
|
|
24336
24345
|
* True when the camera firmware has gone into low-power mode. Battery
|
|
@@ -24420,7 +24429,7 @@ onStatusChanged: { data: object({
|
|
|
24420
24429
|
kind: "push",
|
|
24421
24430
|
empty: {
|
|
24422
24431
|
percentage: 0,
|
|
24423
|
-
charging: "
|
|
24432
|
+
charging: "unknown",
|
|
24424
24433
|
sleeping: false,
|
|
24425
24434
|
lastUpdated: 0
|
|
24426
24435
|
}
|
|
@@ -34128,8 +34137,17 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
34128
34137
|
async onRestoreDevices(savedDevices) {
|
|
34129
34138
|
const restored = /* @__PURE__ */ new Set();
|
|
34130
34139
|
const failures = [];
|
|
34140
|
+
const liveStableIds = new Set((await this.ctx.kernel.devices.getAll()).map((device) => device.stableId));
|
|
34131
34141
|
const attemptRestore = async (saved) => {
|
|
34132
34142
|
if (restored.has(saved.id)) return;
|
|
34143
|
+
if (liveStableIds.has(saved.stableId)) {
|
|
34144
|
+
this.ctx.logger.debug("Restore row already live — not dialled again", { tags: {
|
|
34145
|
+
deviceId: saved.id,
|
|
34146
|
+
stableId: saved.stableId
|
|
34147
|
+
} });
|
|
34148
|
+
restored.add(saved.id);
|
|
34149
|
+
return;
|
|
34150
|
+
}
|
|
34133
34151
|
const Class = this.deviceClasses[saved.type];
|
|
34134
34152
|
if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
|
|
34135
34153
|
if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
|
|
@@ -231801,13 +231819,15 @@ var reolinkDebugActions = defineCustomActions({ debugRawRead: customAction(RawRe
|
|
|
231801
231819
|
* passato da `adapterStatus: 1` a `0`, con `chargeStatus: 0` da entrambe le
|
|
231802
231820
|
* parti — cioè `chargeStatus` da solo NON distingue i due stati.
|
|
231803
231821
|
*
|
|
231804
|
-
*
|
|
231805
|
-
*
|
|
231806
|
-
*
|
|
231807
|
-
* `
|
|
231808
|
-
*
|
|
231822
|
+
* Due campi assenti NON sono `'none'`: `'none'` è un negativo che il firmware
|
|
231823
|
+
* ha detto ("nessun adattatore, cella ferma"), l'assenza di entrambi è
|
|
231824
|
+
* `'unknown'` — lo stesso confine che `percentage` ha chiuso diventando
|
|
231825
|
+
* `nullable` (D315). Il chiamante (`mapBatteryInfo`) non arriva qui con un
|
|
231826
|
+
* push che non parla di alimentazione: `reportsPowerSource` lo filtra prima e
|
|
231827
|
+
* conserva la convinzione precedente.
|
|
231809
231828
|
*/
|
|
231810
231829
|
function deriveChargingSource(adapterStatus, chargeStatus) {
|
|
231830
|
+
if (adapterStatus === void 0 && chargeStatus === void 0) return "unknown";
|
|
231811
231831
|
const adapter = (adapterStatus ?? "").toLowerCase();
|
|
231812
231832
|
const charge = (chargeStatus ?? "").toLowerCase();
|
|
231813
231833
|
if (adapter.includes("solar")) return "solar";
|
|
@@ -236946,7 +236966,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
236946
236966
|
mapBatteryInfo(info) {
|
|
236947
236967
|
return {
|
|
236948
236968
|
percentage: typeof info.batteryPercent === "number" ? Math.max(0, Math.min(100, Math.round(info.batteryPercent))) : this.state.battery.percentage ?? null,
|
|
236949
|
-
charging: reportsPowerSource(info) ? deriveChargingSource(info.adapterStatus, info.chargeStatus) : this.state.battery.charging ?? "
|
|
236969
|
+
charging: reportsPowerSource(info) ? deriveChargingSource(info.adapterStatus, info.chargeStatus) : this.state.battery.charging ?? "unknown",
|
|
236950
236970
|
sleeping: info.sleeping === true || this.sleeping,
|
|
236951
236971
|
lastUpdated: Date.now()
|
|
236952
236972
|
};
|
|
@@ -243003,6 +243023,69 @@ function asDebugSocketFlags(value) {
|
|
|
243003
243023
|
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
243004
243024
|
}
|
|
243005
243025
|
//#endregion
|
|
243026
|
+
//#region src/hub-battery-fields.ts
|
|
243027
|
+
/**
|
|
243028
|
+
* The hub boundary for the per-channel battery power fields.
|
|
243029
|
+
*
|
|
243030
|
+
* The Reolink hub/NVR reports `chargeStatus` and `adapterStatus` as NUMBERS
|
|
243031
|
+
* (0/1/2); the standalone camera path reports the same facts as strings, and
|
|
243032
|
+
* `deriveChargingSource` (`battery-charging.ts`) speaks only the string
|
|
243033
|
+
* vocabulary. This module is the translation, and — the part that was
|
|
243034
|
+
* missing — it NAMES a code it cannot translate instead of dropping the field
|
|
243035
|
+
* silently. A dropped field reads as "the push said nothing about power", so
|
|
243036
|
+
* a new firmware value would have kept the previous belief forever, with not
|
|
243037
|
+
* one log line. The ledger lets the hub warn once per (device, field, code).
|
|
243038
|
+
*/
|
|
243039
|
+
var CHARGE_STATUS_BY_CODE = {
|
|
243040
|
+
0: "none",
|
|
243041
|
+
1: "charging",
|
|
243042
|
+
2: "chargeComplete"
|
|
243043
|
+
};
|
|
243044
|
+
var ADAPTER_STATUS_BY_CODE = {
|
|
243045
|
+
0: "none",
|
|
243046
|
+
1: "dc",
|
|
243047
|
+
2: "solarPanel"
|
|
243048
|
+
};
|
|
243049
|
+
function mapOne(field, value, byCode) {
|
|
243050
|
+
if (value === void 0 || value === null) return {};
|
|
243051
|
+
if (typeof value === "string") return { mapped: value };
|
|
243052
|
+
if (typeof value === "number") {
|
|
243053
|
+
const mapped = byCode[value];
|
|
243054
|
+
if (mapped !== void 0) return { mapped };
|
|
243055
|
+
}
|
|
243056
|
+
return { unmapped: {
|
|
243057
|
+
field,
|
|
243058
|
+
value
|
|
243059
|
+
} };
|
|
243060
|
+
}
|
|
243061
|
+
/** Translate one channel's raw CGI power fields; never throws. */
|
|
243062
|
+
function mapHubBatteryPower(cgi) {
|
|
243063
|
+
const charge = mapOne("chargeStatus", cgi?.["chargeStatus"], CHARGE_STATUS_BY_CODE);
|
|
243064
|
+
const adapter = mapOne("adapterStatus", cgi?.["adapterStatus"], ADAPTER_STATUS_BY_CODE);
|
|
243065
|
+
return {
|
|
243066
|
+
fields: {
|
|
243067
|
+
...charge.mapped !== void 0 ? { chargeStatus: charge.mapped } : {},
|
|
243068
|
+
...adapter.mapped !== void 0 ? { adapterStatus: adapter.mapped } : {}
|
|
243069
|
+
},
|
|
243070
|
+
unmapped: [...charge.unmapped !== void 0 ? [charge.unmapped] : [], ...adapter.unmapped !== void 0 ? [adapter.unmapped] : []]
|
|
243071
|
+
};
|
|
243072
|
+
}
|
|
243073
|
+
/**
|
|
243074
|
+
* Remembers which (device, field, code) triples were already reported, so a
|
|
243075
|
+
* polled read warns once per finding and not once per tick. Unbounded only in
|
|
243076
|
+
* theory: the key space is a few cameras times two fields times the handful of
|
|
243077
|
+
* codes a firmware can invent.
|
|
243078
|
+
*/
|
|
243079
|
+
var UnmappedCodeLedger = class {
|
|
243080
|
+
seen = /* @__PURE__ */ new Set();
|
|
243081
|
+
firstSeen(deviceId, field, value) {
|
|
243082
|
+
const key = `${deviceId}:${field}:${String(value)}`;
|
|
243083
|
+
if (this.seen.has(key)) return false;
|
|
243084
|
+
this.seen.add(key);
|
|
243085
|
+
return true;
|
|
243086
|
+
}
|
|
243087
|
+
};
|
|
243088
|
+
//#endregion
|
|
243006
243089
|
//#region src/reolink-hub.ts
|
|
243007
243090
|
var HUB_DISCOVERY_REFRESH_TIMEOUT_MS = 8e3;
|
|
243008
243091
|
/**
|
|
@@ -243064,6 +243147,8 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
|
|
|
243064
243147
|
/** One-shot: the raw per-channel battery payload is logged once per
|
|
243065
243148
|
* process so the firmware's charge-field naming is on record. */
|
|
243066
243149
|
batteryShapeLogged = false;
|
|
243150
|
+
/** Firmware power codes this hub could not translate — warned once each. */
|
|
243151
|
+
unmappedBatteryCodes = new UnmappedCodeLedger();
|
|
243067
243152
|
/** Channels the last successful discovery refresh listed as present but
|
|
243068
243153
|
* NOT adopted — their pushes are expected drops (debug), not faults. */
|
|
243069
243154
|
knownUnadoptedChannels = /* @__PURE__ */ new Set();
|
|
@@ -243544,10 +243629,21 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
|
|
|
243544
243629
|
const child = all.find((d) => d.id === deviceId);
|
|
243545
243630
|
if (!(child instanceof ReolinkCamera)) continue;
|
|
243546
243631
|
const cgi = info.entries[0];
|
|
243632
|
+
const power = mapHubBatteryPower(cgi);
|
|
243633
|
+
for (const { field, value } of power.unmapped) {
|
|
243634
|
+
if (!this.unmappedBatteryCodes.firstSeen(deviceId, field, value)) continue;
|
|
243635
|
+
this.ctx.logger.warn("Reolink Hub: unmapped battery power code — field dropped", {
|
|
243636
|
+
tags: { deviceId },
|
|
243637
|
+
meta: {
|
|
243638
|
+
channel: Number(chKey),
|
|
243639
|
+
field,
|
|
243640
|
+
value
|
|
243641
|
+
}
|
|
243642
|
+
});
|
|
243643
|
+
}
|
|
243547
243644
|
child.applyHubBatteryInfo({
|
|
243548
243645
|
batteryPercent: info.batteryLevel,
|
|
243549
|
-
...
|
|
243550
|
-
...mapHubAdapterStatus(cgi?.["adapterStatus"]) !== void 0 ? { adapterStatus: mapHubAdapterStatus(cgi?.["adapterStatus"]) } : {}
|
|
243646
|
+
...power.fields
|
|
243551
243647
|
});
|
|
243552
243648
|
}
|
|
243553
243649
|
} catch (err) {
|
|
@@ -243988,18 +244084,6 @@ function discoveredStatusFor(d) {
|
|
|
243988
244084
|
* chargeStatus: 0 none · 1 charging · 2 charge complete
|
|
243989
244085
|
* adapterStatus: 0 none · 1 DC adapter · 2 solar panel
|
|
243990
244086
|
*/
|
|
243991
|
-
function mapHubChargeStatus(value) {
|
|
243992
|
-
if (typeof value === "string") return value;
|
|
243993
|
-
if (value === 0) return "none";
|
|
243994
|
-
if (value === 1) return "charging";
|
|
243995
|
-
if (value === 2) return "chargeComplete";
|
|
243996
|
-
}
|
|
243997
|
-
function mapHubAdapterStatus(value) {
|
|
243998
|
-
if (typeof value === "string") return value;
|
|
243999
|
-
if (value === 0) return "none";
|
|
244000
|
-
if (value === 1) return "dc";
|
|
244001
|
-
if (value === 2) return "solarPanel";
|
|
244002
|
-
}
|
|
244003
244087
|
//#endregion
|
|
244004
244088
|
//#region src/creation-schema.ts
|
|
244005
244089
|
/**
|
package/dist/addon.mjs
CHANGED
|
@@ -7587,7 +7587,8 @@ var CameraSwitchAuthoritySchema = discriminatedUnion("kind", [
|
|
|
7587
7587
|
var CameraSwitchUnavailableReasonSchema = _enum([
|
|
7588
7588
|
"no-provider",
|
|
7589
7589
|
"source-unreachable",
|
|
7590
|
-
"not-configured"
|
|
7590
|
+
"not-configured",
|
|
7591
|
+
"device-disabled"
|
|
7591
7592
|
]);
|
|
7592
7593
|
/**
|
|
7593
7594
|
* One switch, resolved for one camera.
|
|
@@ -24320,12 +24321,20 @@ var BatteryStatusSchema = object({
|
|
|
24320
24321
|
* Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
|
|
24321
24322
|
* Reolink-specific for the Solar Panel 2 accessory (will become
|
|
24322
24323
|
* common on other battery cams). `'none'` means running on battery
|
|
24323
|
-
* alone.
|
|
24324
|
+
* alone — a NEGATIVE the firmware actually reported.
|
|
24325
|
+
*
|
|
24326
|
+
* `'unknown'` is the absence of an answer: the provider has not read the
|
|
24327
|
+
* power fields yet, or the firmware reported neither. Before it existed,
|
|
24328
|
+
* two unreadable fields produced `'none'`, and a camera nobody had reached
|
|
24329
|
+
* was drawn "on battery alone" — the same D315 defect `percentage` closed
|
|
24330
|
+
* by going nullable. Consumers SKIP it (no charger state published, no
|
|
24331
|
+
* glyph, no HomeKit ChargingState) rather than coerce it either way.
|
|
24324
24332
|
*/
|
|
24325
24333
|
charging: _enum([
|
|
24326
24334
|
"dc",
|
|
24327
24335
|
"solar",
|
|
24328
|
-
"none"
|
|
24336
|
+
"none",
|
|
24337
|
+
"unknown"
|
|
24329
24338
|
]),
|
|
24330
24339
|
/**
|
|
24331
24340
|
* True when the camera firmware has gone into low-power mode. Battery
|
|
@@ -24415,7 +24424,7 @@ onStatusChanged: { data: object({
|
|
|
24415
24424
|
kind: "push",
|
|
24416
24425
|
empty: {
|
|
24417
24426
|
percentage: 0,
|
|
24418
|
-
charging: "
|
|
24427
|
+
charging: "unknown",
|
|
24419
24428
|
sleeping: false,
|
|
24420
24429
|
lastUpdated: 0
|
|
24421
24430
|
}
|
|
@@ -34123,8 +34132,17 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
34123
34132
|
async onRestoreDevices(savedDevices) {
|
|
34124
34133
|
const restored = /* @__PURE__ */ new Set();
|
|
34125
34134
|
const failures = [];
|
|
34135
|
+
const liveStableIds = new Set((await this.ctx.kernel.devices.getAll()).map((device) => device.stableId));
|
|
34126
34136
|
const attemptRestore = async (saved) => {
|
|
34127
34137
|
if (restored.has(saved.id)) return;
|
|
34138
|
+
if (liveStableIds.has(saved.stableId)) {
|
|
34139
|
+
this.ctx.logger.debug("Restore row already live — not dialled again", { tags: {
|
|
34140
|
+
deviceId: saved.id,
|
|
34141
|
+
stableId: saved.stableId
|
|
34142
|
+
} });
|
|
34143
|
+
restored.add(saved.id);
|
|
34144
|
+
return;
|
|
34145
|
+
}
|
|
34128
34146
|
const Class = this.deviceClasses[saved.type];
|
|
34129
34147
|
if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
|
|
34130
34148
|
if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
|
|
@@ -231781,13 +231799,15 @@ var reolinkDebugActions = defineCustomActions({ debugRawRead: customAction(RawRe
|
|
|
231781
231799
|
* passato da `adapterStatus: 1` a `0`, con `chargeStatus: 0` da entrambe le
|
|
231782
231800
|
* parti — cioè `chargeStatus` da solo NON distingue i due stati.
|
|
231783
231801
|
*
|
|
231784
|
-
*
|
|
231785
|
-
*
|
|
231786
|
-
*
|
|
231787
|
-
* `
|
|
231788
|
-
*
|
|
231802
|
+
* Due campi assenti NON sono `'none'`: `'none'` è un negativo che il firmware
|
|
231803
|
+
* ha detto ("nessun adattatore, cella ferma"), l'assenza di entrambi è
|
|
231804
|
+
* `'unknown'` — lo stesso confine che `percentage` ha chiuso diventando
|
|
231805
|
+
* `nullable` (D315). Il chiamante (`mapBatteryInfo`) non arriva qui con un
|
|
231806
|
+
* push che non parla di alimentazione: `reportsPowerSource` lo filtra prima e
|
|
231807
|
+
* conserva la convinzione precedente.
|
|
231789
231808
|
*/
|
|
231790
231809
|
function deriveChargingSource(adapterStatus, chargeStatus) {
|
|
231810
|
+
if (adapterStatus === void 0 && chargeStatus === void 0) return "unknown";
|
|
231791
231811
|
const adapter = (adapterStatus ?? "").toLowerCase();
|
|
231792
231812
|
const charge = (chargeStatus ?? "").toLowerCase();
|
|
231793
231813
|
if (adapter.includes("solar")) return "solar";
|
|
@@ -236926,7 +236946,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
236926
236946
|
mapBatteryInfo(info) {
|
|
236927
236947
|
return {
|
|
236928
236948
|
percentage: typeof info.batteryPercent === "number" ? Math.max(0, Math.min(100, Math.round(info.batteryPercent))) : this.state.battery.percentage ?? null,
|
|
236929
|
-
charging: reportsPowerSource(info) ? deriveChargingSource(info.adapterStatus, info.chargeStatus) : this.state.battery.charging ?? "
|
|
236949
|
+
charging: reportsPowerSource(info) ? deriveChargingSource(info.adapterStatus, info.chargeStatus) : this.state.battery.charging ?? "unknown",
|
|
236930
236950
|
sleeping: info.sleeping === true || this.sleeping,
|
|
236931
236951
|
lastUpdated: Date.now()
|
|
236932
236952
|
};
|
|
@@ -242983,6 +243003,69 @@ function asDebugSocketFlags(value) {
|
|
|
242983
243003
|
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
242984
243004
|
}
|
|
242985
243005
|
//#endregion
|
|
243006
|
+
//#region src/hub-battery-fields.ts
|
|
243007
|
+
/**
|
|
243008
|
+
* The hub boundary for the per-channel battery power fields.
|
|
243009
|
+
*
|
|
243010
|
+
* The Reolink hub/NVR reports `chargeStatus` and `adapterStatus` as NUMBERS
|
|
243011
|
+
* (0/1/2); the standalone camera path reports the same facts as strings, and
|
|
243012
|
+
* `deriveChargingSource` (`battery-charging.ts`) speaks only the string
|
|
243013
|
+
* vocabulary. This module is the translation, and — the part that was
|
|
243014
|
+
* missing — it NAMES a code it cannot translate instead of dropping the field
|
|
243015
|
+
* silently. A dropped field reads as "the push said nothing about power", so
|
|
243016
|
+
* a new firmware value would have kept the previous belief forever, with not
|
|
243017
|
+
* one log line. The ledger lets the hub warn once per (device, field, code).
|
|
243018
|
+
*/
|
|
243019
|
+
var CHARGE_STATUS_BY_CODE = {
|
|
243020
|
+
0: "none",
|
|
243021
|
+
1: "charging",
|
|
243022
|
+
2: "chargeComplete"
|
|
243023
|
+
};
|
|
243024
|
+
var ADAPTER_STATUS_BY_CODE = {
|
|
243025
|
+
0: "none",
|
|
243026
|
+
1: "dc",
|
|
243027
|
+
2: "solarPanel"
|
|
243028
|
+
};
|
|
243029
|
+
function mapOne(field, value, byCode) {
|
|
243030
|
+
if (value === void 0 || value === null) return {};
|
|
243031
|
+
if (typeof value === "string") return { mapped: value };
|
|
243032
|
+
if (typeof value === "number") {
|
|
243033
|
+
const mapped = byCode[value];
|
|
243034
|
+
if (mapped !== void 0) return { mapped };
|
|
243035
|
+
}
|
|
243036
|
+
return { unmapped: {
|
|
243037
|
+
field,
|
|
243038
|
+
value
|
|
243039
|
+
} };
|
|
243040
|
+
}
|
|
243041
|
+
/** Translate one channel's raw CGI power fields; never throws. */
|
|
243042
|
+
function mapHubBatteryPower(cgi) {
|
|
243043
|
+
const charge = mapOne("chargeStatus", cgi?.["chargeStatus"], CHARGE_STATUS_BY_CODE);
|
|
243044
|
+
const adapter = mapOne("adapterStatus", cgi?.["adapterStatus"], ADAPTER_STATUS_BY_CODE);
|
|
243045
|
+
return {
|
|
243046
|
+
fields: {
|
|
243047
|
+
...charge.mapped !== void 0 ? { chargeStatus: charge.mapped } : {},
|
|
243048
|
+
...adapter.mapped !== void 0 ? { adapterStatus: adapter.mapped } : {}
|
|
243049
|
+
},
|
|
243050
|
+
unmapped: [...charge.unmapped !== void 0 ? [charge.unmapped] : [], ...adapter.unmapped !== void 0 ? [adapter.unmapped] : []]
|
|
243051
|
+
};
|
|
243052
|
+
}
|
|
243053
|
+
/**
|
|
243054
|
+
* Remembers which (device, field, code) triples were already reported, so a
|
|
243055
|
+
* polled read warns once per finding and not once per tick. Unbounded only in
|
|
243056
|
+
* theory: the key space is a few cameras times two fields times the handful of
|
|
243057
|
+
* codes a firmware can invent.
|
|
243058
|
+
*/
|
|
243059
|
+
var UnmappedCodeLedger = class {
|
|
243060
|
+
seen = /* @__PURE__ */ new Set();
|
|
243061
|
+
firstSeen(deviceId, field, value) {
|
|
243062
|
+
const key = `${deviceId}:${field}:${String(value)}`;
|
|
243063
|
+
if (this.seen.has(key)) return false;
|
|
243064
|
+
this.seen.add(key);
|
|
243065
|
+
return true;
|
|
243066
|
+
}
|
|
243067
|
+
};
|
|
243068
|
+
//#endregion
|
|
242986
243069
|
//#region src/reolink-hub.ts
|
|
242987
243070
|
var HUB_DISCOVERY_REFRESH_TIMEOUT_MS = 8e3;
|
|
242988
243071
|
/**
|
|
@@ -243044,6 +243127,8 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
|
|
|
243044
243127
|
/** One-shot: the raw per-channel battery payload is logged once per
|
|
243045
243128
|
* process so the firmware's charge-field naming is on record. */
|
|
243046
243129
|
batteryShapeLogged = false;
|
|
243130
|
+
/** Firmware power codes this hub could not translate — warned once each. */
|
|
243131
|
+
unmappedBatteryCodes = new UnmappedCodeLedger();
|
|
243047
243132
|
/** Channels the last successful discovery refresh listed as present but
|
|
243048
243133
|
* NOT adopted — their pushes are expected drops (debug), not faults. */
|
|
243049
243134
|
knownUnadoptedChannels = /* @__PURE__ */ new Set();
|
|
@@ -243524,10 +243609,21 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
|
|
|
243524
243609
|
const child = all.find((d) => d.id === deviceId);
|
|
243525
243610
|
if (!(child instanceof ReolinkCamera)) continue;
|
|
243526
243611
|
const cgi = info.entries[0];
|
|
243612
|
+
const power = mapHubBatteryPower(cgi);
|
|
243613
|
+
for (const { field, value } of power.unmapped) {
|
|
243614
|
+
if (!this.unmappedBatteryCodes.firstSeen(deviceId, field, value)) continue;
|
|
243615
|
+
this.ctx.logger.warn("Reolink Hub: unmapped battery power code — field dropped", {
|
|
243616
|
+
tags: { deviceId },
|
|
243617
|
+
meta: {
|
|
243618
|
+
channel: Number(chKey),
|
|
243619
|
+
field,
|
|
243620
|
+
value
|
|
243621
|
+
}
|
|
243622
|
+
});
|
|
243623
|
+
}
|
|
243527
243624
|
child.applyHubBatteryInfo({
|
|
243528
243625
|
batteryPercent: info.batteryLevel,
|
|
243529
|
-
...
|
|
243530
|
-
...mapHubAdapterStatus(cgi?.["adapterStatus"]) !== void 0 ? { adapterStatus: mapHubAdapterStatus(cgi?.["adapterStatus"]) } : {}
|
|
243626
|
+
...power.fields
|
|
243531
243627
|
});
|
|
243532
243628
|
}
|
|
243533
243629
|
} catch (err) {
|
|
@@ -243968,18 +244064,6 @@ function discoveredStatusFor(d) {
|
|
|
243968
244064
|
* chargeStatus: 0 none · 1 charging · 2 charge complete
|
|
243969
244065
|
* adapterStatus: 0 none · 1 DC adapter · 2 solar panel
|
|
243970
244066
|
*/
|
|
243971
|
-
function mapHubChargeStatus(value) {
|
|
243972
|
-
if (typeof value === "string") return value;
|
|
243973
|
-
if (value === 0) return "none";
|
|
243974
|
-
if (value === 1) return "charging";
|
|
243975
|
-
if (value === 2) return "chargeComplete";
|
|
243976
|
-
}
|
|
243977
|
-
function mapHubAdapterStatus(value) {
|
|
243978
|
-
if (typeof value === "string") return value;
|
|
243979
|
-
if (value === 0) return "none";
|
|
243980
|
-
if (value === 1) return "dc";
|
|
243981
|
-
if (value === 2) return "solarPanel";
|
|
243982
|
-
}
|
|
243983
244067
|
//#endregion
|
|
243984
244068
|
//#region src/creation-schema.ts
|
|
243985
244069
|
/**
|