@apocaliss92/nodelink-js 0.4.24 โ 0.4.28
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/README.md +4 -2
- package/dist/{chunk-JYHK2ZSH.js โ chunk-NQ7D5TLR.js} +241 -27
- package/dist/chunk-NQ7D5TLR.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +201 -26
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +1 -1
- package/dist/index.cjs +569 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +263 -6
- package/dist/index.d.ts +269 -5
- package/dist/index.js +290 -5
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/dist/chunk-JYHK2ZSH.js.map +0 -1
package/README.md
CHANGED
|
@@ -39,13 +39,15 @@ await api.onSimpleEvent((event) => {
|
|
|
39
39
|
|
|
40
40
|
## Email Push for Battery Cameras
|
|
41
41
|
|
|
42
|
+
> ๐งช **Experimental** โ the feature is officially enabled but still under active testing. The default flow uses the manager's built-in SMTP server; expect rough edges and please report any issue you hit.
|
|
43
|
+
|
|
42
44
|
Battery cameras (Argus, Go, โฆ) can't reliably keep a TCP/ONVIF push subscription alive while sleeping. The manager app embeds an SMTP server so the camera can deliver motion alerts via email โ the most resilient path for sleep-heavy devices.
|
|
43
45
|
|
|
44
46
|
**Flow**:
|
|
45
47
|
|
|
46
|
-
1. Enable the manager's built-in SMTP server (**Settings โ Email Push**, default port `2525`).
|
|
48
|
+
1. Enable the manager's built-in SMTP server (**Settings โ Email Push**, default port `2525`). All settings (host, port, domain, auth) remain manually editable.
|
|
47
49
|
2. Each camera gets a unique recipient `cam-<id>@<domain>` (`emailPush.getCameraAddress`).
|
|
48
|
-
3. From the camera's **Email Push** tab in the manager UI, click **Auto-configure** โ the manager pushes the right SMTP server, recipients and 24/7 schedule to the camera via Baichuan (`baichuan.setupEmailPushToManager`).
|
|
50
|
+
3. From the camera's **Email Push** tab in the manager UI (Camera Settings modal), click **Auto-configure** โ the manager pushes the right SMTP server, recipients and 24/7 schedule to the camera via Baichuan (`baichuan.setupEmailPushToManager`). You can also fill the fields by hand in the Reolink app: server = your manager `domain`, sender = `authUsername`, password = `authPassword`, port = `2525`, TLS off, receiver = `cam-<id>@<domain>`.
|
|
49
51
|
4. On motion, the camera sends an email. The manager parses it, classifies the trigger (people/vehicle/motion), saves the snapshot under `${DATA_PATH}/email-push/<cameraId>/`, and emits a synthetic motion event into the same bus used by native Baichuan push โ so MQTT, Home Assistant, Frigate, etc. see it transparently.
|
|
50
52
|
|
|
51
53
|
See [documentation/baichuan-api/email.md](./documentation/baichuan-api/email.md) for the full API and [documentation/baichuan-api/time.md](./documentation/baichuan-api/time.md) for the related NTP / DST / system clock setters.
|
|
@@ -5317,6 +5317,21 @@ async function* createNativeStream(api, channel, profile, options) {
|
|
|
5317
5317
|
let audioSampleRate = null;
|
|
5318
5318
|
let streamStarted = false;
|
|
5319
5319
|
let closed = false;
|
|
5320
|
+
const signal = options?.signal;
|
|
5321
|
+
let sleepResolve = null;
|
|
5322
|
+
let sleepTimer = null;
|
|
5323
|
+
const clearSleepTimer = () => {
|
|
5324
|
+
if (sleepTimer) {
|
|
5325
|
+
clearTimeout(sleepTimer);
|
|
5326
|
+
sleepTimer = null;
|
|
5327
|
+
}
|
|
5328
|
+
};
|
|
5329
|
+
const handleAbort = () => {
|
|
5330
|
+
clearSleepTimer();
|
|
5331
|
+
const r = sleepResolve;
|
|
5332
|
+
sleepResolve = null;
|
|
5333
|
+
r?.();
|
|
5334
|
+
};
|
|
5320
5335
|
const onError = (_error) => {
|
|
5321
5336
|
closed = true;
|
|
5322
5337
|
api.logger?.warn?.(
|
|
@@ -5414,7 +5429,13 @@ async function* createNativeStream(api, channel, profile, options) {
|
|
|
5414
5429
|
}
|
|
5415
5430
|
});
|
|
5416
5431
|
streamStarted = true;
|
|
5417
|
-
|
|
5432
|
+
if (signal) {
|
|
5433
|
+
if (signal.aborted) {
|
|
5434
|
+
closed = true;
|
|
5435
|
+
} else {
|
|
5436
|
+
signal.addEventListener("abort", handleAbort);
|
|
5437
|
+
}
|
|
5438
|
+
}
|
|
5418
5439
|
while (!closed && !signal?.aborted) {
|
|
5419
5440
|
if (frameQueue.length > 0) {
|
|
5420
5441
|
const frame = frameQueue.shift();
|
|
@@ -5422,31 +5443,30 @@ async function* createNativeStream(api, channel, profile, options) {
|
|
|
5422
5443
|
} else {
|
|
5423
5444
|
await new Promise((resolve) => {
|
|
5424
5445
|
frameResolve = resolve;
|
|
5425
|
-
|
|
5446
|
+
sleepResolve = resolve;
|
|
5447
|
+
sleepTimer = setTimeout(() => {
|
|
5448
|
+
sleepTimer = null;
|
|
5449
|
+
if (sleepResolve === resolve) sleepResolve = null;
|
|
5426
5450
|
if (frameResolve === resolve) {
|
|
5427
5451
|
frameResolve = null;
|
|
5428
5452
|
resolve();
|
|
5429
5453
|
}
|
|
5430
5454
|
}, 1e3);
|
|
5431
|
-
if (signal) {
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
};
|
|
5437
|
-
if (signal.aborted) {
|
|
5438
|
-
clearTimeout(timer);
|
|
5439
|
-
frameResolve = null;
|
|
5440
|
-
resolve();
|
|
5441
|
-
} else {
|
|
5442
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
5443
|
-
}
|
|
5455
|
+
if (signal?.aborted) {
|
|
5456
|
+
clearSleepTimer();
|
|
5457
|
+
sleepResolve = null;
|
|
5458
|
+
frameResolve = null;
|
|
5459
|
+
resolve();
|
|
5444
5460
|
}
|
|
5445
5461
|
});
|
|
5462
|
+
sleepResolve = null;
|
|
5463
|
+
clearSleepTimer();
|
|
5446
5464
|
}
|
|
5447
5465
|
}
|
|
5448
5466
|
} finally {
|
|
5449
5467
|
closed = true;
|
|
5468
|
+
if (signal) signal.removeEventListener("abort", handleAbort);
|
|
5469
|
+
clearSleepTimer();
|
|
5450
5470
|
try {
|
|
5451
5471
|
await videoStream.stop();
|
|
5452
5472
|
} catch {
|
|
@@ -7483,12 +7503,13 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
7483
7503
|
if (frame.videoType === "H264" || frame.videoType === "H265") {
|
|
7484
7504
|
this.setFlowVideoType(frame.videoType, "native stream");
|
|
7485
7505
|
}
|
|
7486
|
-
this.flow.
|
|
7487
|
-
|
|
7488
|
-
|
|
7506
|
+
if (!this.flow.getFmtp().hasParamSets) {
|
|
7507
|
+
this.flow.extractParameterSets(frame.data);
|
|
7508
|
+
}
|
|
7509
|
+
if (this.flow.getFmtp().hasParamSets) {
|
|
7489
7510
|
this.markFirstFrameReceived();
|
|
7490
7511
|
}
|
|
7491
|
-
const isKeyframe = this.isRawFrameKeyframe(frame);
|
|
7512
|
+
const isKeyframe = typeof frame.isKeyframe === "boolean" ? frame.isKeyframe : this.isRawFrameKeyframe(frame);
|
|
7492
7513
|
this.prebuffer.push({
|
|
7493
7514
|
frame: { ...frame, data: Buffer.from(frame.data) },
|
|
7494
7515
|
time: Date.now(),
|
|
@@ -8668,6 +8689,60 @@ function patchMotionSensitivityListXml(currentXml, bands) {
|
|
|
8668
8689
|
);
|
|
8669
8690
|
}
|
|
8670
8691
|
|
|
8692
|
+
// src/emailPush/bus.ts
|
|
8693
|
+
import { EventEmitter as EventEmitter4 } from "events";
|
|
8694
|
+
var emitter = new EventEmitter4();
|
|
8695
|
+
var cameraResolver = () => void 0;
|
|
8696
|
+
var lastEventByCamera = /* @__PURE__ */ new Map();
|
|
8697
|
+
var MAX_GLOBAL_EVENTS = 300;
|
|
8698
|
+
var globalRecentEvents = [];
|
|
8699
|
+
function setEmailPushCameraResolver(resolver) {
|
|
8700
|
+
cameraResolver = resolver;
|
|
8701
|
+
}
|
|
8702
|
+
function getEmailPushCameraResolver() {
|
|
8703
|
+
return cameraResolver;
|
|
8704
|
+
}
|
|
8705
|
+
function onEmailPushEvent(handler) {
|
|
8706
|
+
emitter.on("event", handler);
|
|
8707
|
+
return () => emitter.off("event", handler);
|
|
8708
|
+
}
|
|
8709
|
+
function emitEmailPushEvent(event) {
|
|
8710
|
+
lastEventByCamera.set(event.cameraId, event);
|
|
8711
|
+
globalRecentEvents.unshift(event);
|
|
8712
|
+
if (globalRecentEvents.length > MAX_GLOBAL_EVENTS) {
|
|
8713
|
+
globalRecentEvents.length = MAX_GLOBAL_EVENTS;
|
|
8714
|
+
}
|
|
8715
|
+
emitter.emit("event", event);
|
|
8716
|
+
}
|
|
8717
|
+
function getLastEmailPushEvent(cameraId) {
|
|
8718
|
+
return lastEventByCamera.get(cameraId);
|
|
8719
|
+
}
|
|
8720
|
+
function getRecentEmailPushEvents(limit = MAX_GLOBAL_EVENTS) {
|
|
8721
|
+
const clamped = Math.max(0, Math.min(limit, MAX_GLOBAL_EVENTS));
|
|
8722
|
+
return globalRecentEvents.slice(0, clamped);
|
|
8723
|
+
}
|
|
8724
|
+
function mapEmailPushInferredType(inferred) {
|
|
8725
|
+
switch (inferred) {
|
|
8726
|
+
case "motion":
|
|
8727
|
+
case "doorbell":
|
|
8728
|
+
case "people":
|
|
8729
|
+
case "vehicle":
|
|
8730
|
+
case "animal":
|
|
8731
|
+
case "face":
|
|
8732
|
+
case "package":
|
|
8733
|
+
return inferred;
|
|
8734
|
+
case "other":
|
|
8735
|
+
default:
|
|
8736
|
+
return "motion";
|
|
8737
|
+
}
|
|
8738
|
+
}
|
|
8739
|
+
function _resetEmailPushBusForTests() {
|
|
8740
|
+
emitter.removeAllListeners();
|
|
8741
|
+
cameraResolver = () => void 0;
|
|
8742
|
+
lastEventByCamera.clear();
|
|
8743
|
+
globalRecentEvents.length = 0;
|
|
8744
|
+
}
|
|
8745
|
+
|
|
8671
8746
|
// src/reolink/baichuan/ReolinkBaichuanApi.ts
|
|
8672
8747
|
import { spawn as spawn2 } from "child_process";
|
|
8673
8748
|
import { mkdir } from "fs/promises";
|
|
@@ -11265,6 +11340,39 @@ var applyFloodlightSettingsToXml = (xml, settings) => {
|
|
|
11265
11340
|
return modifiedXml;
|
|
11266
11341
|
};
|
|
11267
11342
|
|
|
11343
|
+
// src/reolink/baichuan/utils/whiteLedStatusPush.ts
|
|
11344
|
+
var parseFloodlightStatusListPushXml = (xml) => {
|
|
11345
|
+
const out = [];
|
|
11346
|
+
const re = /<channel>\s*(\d+)\s*<\/channel>[\s\S]*?<status>\s*(\d+)\s*<\/status>/gi;
|
|
11347
|
+
let m;
|
|
11348
|
+
while ((m = re.exec(xml)) !== null) {
|
|
11349
|
+
const channel = Number.parseInt(m[1] ?? "", 10);
|
|
11350
|
+
const status = Number.parseInt(m[2] ?? "", 10);
|
|
11351
|
+
if (!Number.isFinite(channel) || !Number.isFinite(status)) continue;
|
|
11352
|
+
out.push({ channel, status });
|
|
11353
|
+
}
|
|
11354
|
+
return out;
|
|
11355
|
+
};
|
|
11356
|
+
|
|
11357
|
+
// src/reolink/baichuan/utils/sirenStatusPush.ts
|
|
11358
|
+
var parseSirenStatusListPushXml = (xml) => {
|
|
11359
|
+
const out = [];
|
|
11360
|
+
const re = /<(?:channelId|channel)>\s*(\d+)\s*<\/(?:channelId|channel)>[\s\S]*?<status>\s*(\d+)\s*<\/status>(?:[\s\S]*?<playing>\s*(\d+)\s*<\/playing>)?/gi;
|
|
11361
|
+
let m;
|
|
11362
|
+
while ((m = re.exec(xml)) !== null) {
|
|
11363
|
+
const channel = Number.parseInt(m[1] ?? "", 10);
|
|
11364
|
+
const status = Number.parseInt(m[2] ?? "", 10);
|
|
11365
|
+
if (!Number.isFinite(channel) || !Number.isFinite(status)) continue;
|
|
11366
|
+
const entry = { channel, status };
|
|
11367
|
+
if (m[3] !== void 0) {
|
|
11368
|
+
const playing = Number.parseInt(m[3], 10);
|
|
11369
|
+
if (Number.isFinite(playing)) entry.playing = playing;
|
|
11370
|
+
}
|
|
11371
|
+
out.push(entry);
|
|
11372
|
+
}
|
|
11373
|
+
return out;
|
|
11374
|
+
};
|
|
11375
|
+
|
|
11268
11376
|
// src/reolink/baichuan/ReolinkBaichuanApi.ts
|
|
11269
11377
|
var DUAL_LENS_DUAL_MOTION_MODELS = /* @__PURE__ */ new Set([
|
|
11270
11378
|
"Reolink Duo PoE",
|
|
@@ -11410,7 +11518,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
11410
11518
|
* general socket is created, logged in, and all event/push/guard listeners
|
|
11411
11519
|
* are re-attached automatically.
|
|
11412
11520
|
*
|
|
11413
|
-
* This is a **no-op** when the API is already
|
|
11521
|
+
* This is a **no-op** when the API is already ready (see `isReadyState()`).
|
|
11414
11522
|
*
|
|
11415
11523
|
* @throws If `close()` was called โ the API is permanently closed and a new
|
|
11416
11524
|
* instance must be created.
|
|
@@ -11491,7 +11599,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
11491
11599
|
/**
|
|
11492
11600
|
* Attach event, push, channelInfo, and guard listeners to the current
|
|
11493
11601
|
* "general" client. Called from the constructor and from
|
|
11494
|
-
*
|
|
11602
|
+
* `reconnectGeneralSocket()`.
|
|
11495
11603
|
*/
|
|
11496
11604
|
setupGeneralClientListeners() {
|
|
11497
11605
|
const client = this.client;
|
|
@@ -11543,7 +11651,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
11543
11651
|
});
|
|
11544
11652
|
client.on("push", (frame) => {
|
|
11545
11653
|
const cmdId = frame.header.cmdId;
|
|
11546
|
-
if (cmdId !== BC_CMD_ID_PUSH_VIDEO_INPUT && cmdId !== BC_CMD_ID_PUSH_SERIAL && cmdId !== BC_CMD_ID_PUSH_NET_INFO && cmdId !== BC_CMD_ID_PUSH_DINGDONG_LIST && cmdId !== BC_CMD_ID_PUSH_SLEEP_STATUS && cmdId !== BC_CMD_ID_PUSH_COORDINATE_POINT_LIST) {
|
|
11654
|
+
if (cmdId !== BC_CMD_ID_PUSH_VIDEO_INPUT && cmdId !== BC_CMD_ID_PUSH_SERIAL && cmdId !== BC_CMD_ID_PUSH_NET_INFO && cmdId !== BC_CMD_ID_PUSH_DINGDONG_LIST && cmdId !== BC_CMD_ID_PUSH_SLEEP_STATUS && cmdId !== BC_CMD_ID_PUSH_COORDINATE_POINT_LIST && cmdId !== BC_CMD_ID_FLOODLIGHT_STATUS_LIST && cmdId !== BC_CMD_ID_GET_AUDIO_ALARM) {
|
|
11547
11655
|
return;
|
|
11548
11656
|
}
|
|
11549
11657
|
try {
|
|
@@ -13080,6 +13188,40 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
13080
13188
|
this.econnresetStormRebootInFlight = void 0;
|
|
13081
13189
|
});
|
|
13082
13190
|
}
|
|
13191
|
+
/**
|
|
13192
|
+
* Bind this API instance to the global email-push bus so that incoming
|
|
13193
|
+
* SMTP-delivered motion / AI events for the matching camera surface on
|
|
13194
|
+
* this instance's standard `onSimpleEvent` channel. The consumer keeps
|
|
13195
|
+
* a single subscription (`onSimpleEvent`) and gets both the native
|
|
13196
|
+
* Baichuan push and the email-push transport on the same stream.
|
|
13197
|
+
*
|
|
13198
|
+
* - `cameraId` shorthand: match events with `event.cameraId === cameraId`.
|
|
13199
|
+
* - `match`: arbitrary predicate (e.g. when the consumer uses a
|
|
13200
|
+
* nickname-based mapping or wants to handle multiple recipients).
|
|
13201
|
+
*
|
|
13202
|
+
* Returns an `off()` handle. Safe to call repeatedly โ each call
|
|
13203
|
+
* registers its own listener.
|
|
13204
|
+
*/
|
|
13205
|
+
subscribeEmailPushEvents(params) {
|
|
13206
|
+
const channel = params.channel ?? 0;
|
|
13207
|
+
const matches = "match" in params ? params.match : (event) => event.cameraId === params.cameraId;
|
|
13208
|
+
const off = onEmailPushEvent((event) => {
|
|
13209
|
+
if (!matches(event)) return;
|
|
13210
|
+
this.dispatchSimpleEvent({
|
|
13211
|
+
type: mapEmailPushInferredType(event.inferredType),
|
|
13212
|
+
channel,
|
|
13213
|
+
timestamp: event.receivedAtMs
|
|
13214
|
+
});
|
|
13215
|
+
if (event.inferredType !== "motion" && event.inferredType !== "doorbell" && event.inferredType !== "other") {
|
|
13216
|
+
this.dispatchSimpleEvent({
|
|
13217
|
+
type: "motion",
|
|
13218
|
+
channel,
|
|
13219
|
+
timestamp: event.receivedAtMs
|
|
13220
|
+
});
|
|
13221
|
+
}
|
|
13222
|
+
});
|
|
13223
|
+
return off;
|
|
13224
|
+
}
|
|
13083
13225
|
/**
|
|
13084
13226
|
* Subscribe to minimal high-level events.
|
|
13085
13227
|
* The API manages Baichuan subscribe/unsubscribe automatically.
|
|
@@ -13138,7 +13280,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
13138
13280
|
* Subscribe to per-frame detection events sourced from the BcMedia
|
|
13139
13281
|
* `additionalHeader` block on active video streams.
|
|
13140
13282
|
*
|
|
13141
|
-
* Mirrors
|
|
13283
|
+
* Mirrors `onSimpleEvent()` but is fed by the streaming side-channel:
|
|
13142
13284
|
* one event fires for every I-frame / P-frame that carries an overlay block.
|
|
13143
13285
|
* Coordinates are reported in normalized [0, 1] fractions of the source
|
|
13144
13286
|
* frame, so the same box renders correctly on mainStream, subStream, and
|
|
@@ -13165,7 +13307,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
13165
13307
|
* Subscribe to AI object detections (people / vehicle / animal / face boxes
|
|
13166
13308
|
* with class label and confidence) without managing a video stream yourself.
|
|
13167
13309
|
*
|
|
13168
|
-
* Mirrors
|
|
13310
|
+
* Mirrors `onSimpleEvent()` end-to-end: on the first listener for a given
|
|
13169
13311
|
* `(channel, profile)` tuple the API ensures the corresponding video stream
|
|
13170
13312
|
* is running (the pool socket may already be shared with a regular consumer),
|
|
13171
13313
|
* forwards every box-bearing `additionalHeader` to your callback, and tears
|
|
@@ -20009,7 +20151,7 @@ ${xml}`
|
|
|
20009
20151
|
* Field meaning per stream:
|
|
20010
20152
|
* - `audio` โ 0/1 toggle
|
|
20011
20153
|
* - `width`/`height` โ resolution in pixels. Must be one of the
|
|
20012
|
-
* resolutions returned by
|
|
20154
|
+
* resolutions returned by `getStreamInfoList()`.
|
|
20013
20155
|
* - `bitRate` โ kbps. Must match the table from `getStreamInfoList`.
|
|
20014
20156
|
* - `frameRate` โ fps. Must match the table from `getStreamInfoList`.
|
|
20015
20157
|
* - `videoEncType` โ `"h264"` or `"h265"`
|
|
@@ -20584,6 +20726,33 @@ ${xml}`
|
|
|
20584
20726
|
};
|
|
20585
20727
|
return;
|
|
20586
20728
|
}
|
|
20729
|
+
if (cmdId === BC_CMD_ID_FLOODLIGHT_STATUS_LIST) {
|
|
20730
|
+
const entries = parseFloodlightStatusListPushXml(xml);
|
|
20731
|
+
if (entries.length === 0) return;
|
|
20732
|
+
for (const entry of entries) {
|
|
20733
|
+
const channel = normalizePushChannel(entry.channel) ?? channelFromHeader;
|
|
20734
|
+
getEntry(channel).floodlightStatus = {
|
|
20735
|
+
updatedAtMs: now,
|
|
20736
|
+
value: { status: entry.status === 1 }
|
|
20737
|
+
};
|
|
20738
|
+
}
|
|
20739
|
+
return;
|
|
20740
|
+
}
|
|
20741
|
+
if (cmdId === BC_CMD_ID_GET_AUDIO_ALARM) {
|
|
20742
|
+
const entries = parseSirenStatusListPushXml(xml);
|
|
20743
|
+
if (entries.length === 0) return;
|
|
20744
|
+
for (const entry of entries) {
|
|
20745
|
+
const channel = normalizePushChannel(entry.channel) ?? channelFromHeader;
|
|
20746
|
+
getEntry(channel).sirenStatus = {
|
|
20747
|
+
updatedAtMs: now,
|
|
20748
|
+
value: {
|
|
20749
|
+
status: entry.status === 1,
|
|
20750
|
+
...entry.playing !== void 0 ? { playing: entry.playing === 1 } : {}
|
|
20751
|
+
}
|
|
20752
|
+
};
|
|
20753
|
+
}
|
|
20754
|
+
return;
|
|
20755
|
+
}
|
|
20587
20756
|
}
|
|
20588
20757
|
/** Read-only snapshot of cached settings pushes (cmd_id 78/79/464/484/623/723). */
|
|
20589
20758
|
getSettingsPushCacheSnapshot() {
|
|
@@ -20616,6 +20785,18 @@ ${xml}`
|
|
|
20616
20785
|
...entry.coordinatePointList,
|
|
20617
20786
|
value: { ...entry.coordinatePointList.value }
|
|
20618
20787
|
}
|
|
20788
|
+
} : {},
|
|
20789
|
+
...entry.floodlightStatus ? {
|
|
20790
|
+
floodlightStatus: {
|
|
20791
|
+
...entry.floodlightStatus,
|
|
20792
|
+
value: { ...entry.floodlightStatus.value }
|
|
20793
|
+
}
|
|
20794
|
+
} : {},
|
|
20795
|
+
...entry.sirenStatus ? {
|
|
20796
|
+
sirenStatus: {
|
|
20797
|
+
...entry.sirenStatus,
|
|
20798
|
+
value: { ...entry.sirenStatus.value }
|
|
20799
|
+
}
|
|
20619
20800
|
} : {}
|
|
20620
20801
|
});
|
|
20621
20802
|
}
|
|
@@ -20639,6 +20820,29 @@ ${xml}`
|
|
|
20639
20820
|
getCoordinatePointListFromPushCache(channel = 0) {
|
|
20640
20821
|
return this.settingsPushCache.get(channel)?.coordinatePointList;
|
|
20641
20822
|
}
|
|
20823
|
+
/**
|
|
20824
|
+
* Last cmd_id 291 (FloodlightStatusList) push observed for the channel.
|
|
20825
|
+
* The camera emits this whenever the floodlight transitions on/off,
|
|
20826
|
+
* including the auto-off after the FloodlightManual duration. This is
|
|
20827
|
+
* the only reliable source for the current manual state because cmd 289
|
|
20828
|
+
* only returns the FloodlightTask config.
|
|
20829
|
+
*
|
|
20830
|
+
* Returns undefined when no push has been received yet.
|
|
20831
|
+
*/
|
|
20832
|
+
getCachedFloodlightStatus(channel = 0) {
|
|
20833
|
+
return this.settingsPushCache.get(channel)?.floodlightStatus;
|
|
20834
|
+
}
|
|
20835
|
+
/**
|
|
20836
|
+
* Last cmd_id 547 (SirenStatusList) push observed for the channel.
|
|
20837
|
+
* Captures the actual on/off transitions including the firmware's
|
|
20838
|
+
* built-in auto-off after the siren playback duration expires โ
|
|
20839
|
+
* polling cmd 547 alone can race that auto-off.
|
|
20840
|
+
*
|
|
20841
|
+
* Returns undefined when no push has been received yet.
|
|
20842
|
+
*/
|
|
20843
|
+
getCachedSirenStatus(channel = 0) {
|
|
20844
|
+
return this.settingsPushCache.get(channel)?.sirenStatus;
|
|
20845
|
+
}
|
|
20642
20846
|
// --------------------
|
|
20643
20847
|
// PCAP-derived settings getters (typed wrappers)
|
|
20644
20848
|
// --------------------
|
|
@@ -21336,10 +21540,12 @@ ${xml}`
|
|
|
21336
21540
|
const triggers = params.triggerTypes ?? ["MD", "people", "vehicle"];
|
|
21337
21541
|
const attachmentType = params.attachmentType ?? "picture";
|
|
21338
21542
|
const interval = params.interval ?? 30;
|
|
21543
|
+
const rawUser = params.authUsername ?? recipient;
|
|
21544
|
+
const wireUser = rawUser.includes("@") ? rawUser : `${rawUser}@${domain}`;
|
|
21339
21545
|
const emailPatch = {
|
|
21340
21546
|
smtpServer: params.managerHost,
|
|
21341
21547
|
smtpPort: port,
|
|
21342
|
-
userName:
|
|
21548
|
+
userName: wireUser,
|
|
21343
21549
|
password: params.authPassword ?? "",
|
|
21344
21550
|
address1: recipient,
|
|
21345
21551
|
address2: "",
|
|
@@ -24488,6 +24694,14 @@ export {
|
|
|
24488
24694
|
patchAiDetectCfgXml,
|
|
24489
24695
|
encodeMotionSensitivityListXml,
|
|
24490
24696
|
patchMotionSensitivityListXml,
|
|
24697
|
+
setEmailPushCameraResolver,
|
|
24698
|
+
getEmailPushCameraResolver,
|
|
24699
|
+
onEmailPushEvent,
|
|
24700
|
+
emitEmailPushEvent,
|
|
24701
|
+
getLastEmailPushEvent,
|
|
24702
|
+
getRecentEmailPushEvents,
|
|
24703
|
+
mapEmailPushInferredType,
|
|
24704
|
+
_resetEmailPushBusForTests,
|
|
24491
24705
|
DUAL_LENS_DUAL_MOTION_MODELS,
|
|
24492
24706
|
DUAL_LENS_SINGLE_MOTION_MODELS,
|
|
24493
24707
|
DUAL_LENS_MODELS,
|
|
@@ -24509,4 +24723,4 @@ export {
|
|
|
24509
24723
|
isTcpFailureThatShouldFallbackToUdp,
|
|
24510
24724
|
autoDetectDeviceType
|
|
24511
24725
|
};
|
|
24512
|
-
//# sourceMappingURL=chunk-
|
|
24726
|
+
//# sourceMappingURL=chunk-NQ7D5TLR.js.map
|