@apocaliss92/scrypted-reolink-native 0.1.33 → 0.1.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/README.md +6 -4
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/baichuan-base.ts +4 -4
- package/src/common.ts +179 -47
- package/src/main.ts +2 -2
- package/src/nvr.ts +36 -29
- package/src/utils.ts +510 -111
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/baichuan-base.ts
CHANGED
|
@@ -219,9 +219,9 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
|
|
|
219
219
|
|
|
220
220
|
// Only reuse if both conditions are true
|
|
221
221
|
if (isConnected && isLoggedIn) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
return this.baichuanApi;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
225
|
// If socket is not connected or not logged in, cleanup the stale client
|
|
226
226
|
// This prevents leaking connections when the socket appears connected but isn't
|
|
227
227
|
const logger = this.getBaichuanLogger();
|
|
@@ -358,7 +358,7 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
|
|
|
358
358
|
// Only cleanup if this is still the current API instance
|
|
359
359
|
// This prevents cleanup of a new connection that was created
|
|
360
360
|
// while the old one was closing
|
|
361
|
-
|
|
361
|
+
await this.cleanupBaichuanApi();
|
|
362
362
|
}
|
|
363
363
|
|
|
364
364
|
// Call custom close handler if provided
|
package/src/common.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
selectStreamOption,
|
|
23
23
|
StreamManager
|
|
24
24
|
} from "./stream-utils";
|
|
25
|
-
import { floodlightSuffix, getDeviceInterfaces, pirSuffix, recordingFileToVideoClip, sirenSuffix, updateDeviceInfo } from "./utils";
|
|
25
|
+
import { floodlightSuffix, getDeviceInterfaces, getVideoClipWebhookUrls, pirSuffix, recordingFileToVideoClip, sirenSuffix, updateDeviceInfo, vodSearchResultsToVideoClips } from "./utils";
|
|
26
26
|
|
|
27
27
|
export type CameraType = 'battery' | 'regular' | 'multi-focal' | 'multi-focal-battery';
|
|
28
28
|
|
|
@@ -559,6 +559,14 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
559
559
|
this.updateVideoClipsAutoLoad();
|
|
560
560
|
},
|
|
561
561
|
},
|
|
562
|
+
clipsSource: {
|
|
563
|
+
title: "Clips Source",
|
|
564
|
+
subgroup: 'Videoclips',
|
|
565
|
+
description: "Source for fetching video clips: NVR (fetch from NVR device) or Device (fetch directly from camera).",
|
|
566
|
+
type: "string",
|
|
567
|
+
choices: ["NVR", "Device"],
|
|
568
|
+
immediate: true,
|
|
569
|
+
},
|
|
562
570
|
loadVideoclips: {
|
|
563
571
|
title: "Auto-load Video Clips",
|
|
564
572
|
subgroup: 'Videoclips',
|
|
@@ -709,36 +717,126 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
709
717
|
|
|
710
718
|
const start = new Date(startMs);
|
|
711
719
|
const end = new Date(endMs);
|
|
712
|
-
|
|
720
|
+
// Use UTC to match API's dateToReolinkTime conversion
|
|
721
|
+
start.setUTCHours(0, 0, 0, 0);
|
|
713
722
|
|
|
714
723
|
try {
|
|
715
|
-
const
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
+
const { clipsSource } = this.storageSettings.values;
|
|
725
|
+
const useNvr = clipsSource === "NVR" && this.nvrDevice;
|
|
726
|
+
|
|
727
|
+
if (useNvr) {
|
|
728
|
+
// Fetch from NVR using listEnrichedVodFiles (library handles parsing correctly)
|
|
729
|
+
const channel = this.storageSettings.values.rtspChannel ?? 0;
|
|
730
|
+
logger.debug(`Fetching video clips from NVR for channel ${channel}`);
|
|
731
|
+
|
|
732
|
+
// Use listEnrichedVodFiles which properly parses filenames and extracts detection info
|
|
733
|
+
logger.log(`[NVR VOD] Searching for video clips: channel=${channel}, start=${start.toISOString()}, end=${end.toISOString()}`);
|
|
734
|
+
// Filter to only include recordings within the requested time window
|
|
735
|
+
const enrichedRecordings = await this.nvrDevice.listEnrichedVodFiles({
|
|
736
|
+
channel,
|
|
737
|
+
start,
|
|
738
|
+
end,
|
|
739
|
+
streamType: "main",
|
|
740
|
+
autoSearchByDay: false, // Disable autoSearchByDay to avoid searching past days
|
|
741
|
+
bypassCache: false,
|
|
742
|
+
});
|
|
724
743
|
|
|
725
|
-
|
|
744
|
+
logger.log(`[NVR VOD] Found ${enrichedRecordings.length} enriched recordings from NVR`);
|
|
745
|
+
|
|
746
|
+
// Log sample of enriched recordings to see what the library returned
|
|
747
|
+
if (enrichedRecordings.length > 0) {
|
|
748
|
+
const sampleSize = Math.min(3, enrichedRecordings.length);
|
|
749
|
+
for (let i = 0; i < sampleSize; i++) {
|
|
750
|
+
const rec = enrichedRecordings[i];
|
|
751
|
+
logger.log(`[NVR VOD] Sample enriched recording ${i + 1}/${enrichedRecordings.length}:`, {
|
|
752
|
+
fileName: rec.fileName,
|
|
753
|
+
startTimeMs: rec.startTimeMs,
|
|
754
|
+
endTimeMs: rec.endTimeMs,
|
|
755
|
+
durationMs: rec.durationMs,
|
|
756
|
+
hasPerson: rec.hasPerson,
|
|
757
|
+
hasVehicle: rec.hasVehicle,
|
|
758
|
+
hasAnimal: rec.hasAnimal,
|
|
759
|
+
hasFace: rec.hasFace,
|
|
760
|
+
hasMotion: rec.hasMotion,
|
|
761
|
+
hasDoorbell: rec.hasDoorbell,
|
|
762
|
+
hasPackage: rec.hasPackage,
|
|
763
|
+
recordType: rec.recordType,
|
|
764
|
+
parsedFileName: rec.parsedFileName ? {
|
|
765
|
+
start: rec.parsedFileName.start?.toISOString(),
|
|
766
|
+
end: rec.parsedFileName.end?.toISOString(),
|
|
767
|
+
flags: rec.parsedFileName.flags,
|
|
768
|
+
} : null,
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
}
|
|
726
772
|
|
|
727
|
-
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
773
|
+
// Convert enriched recordings to VideoClip array
|
|
774
|
+
const clips: VideoClip[] = [];
|
|
775
|
+
|
|
776
|
+
for (const rec of enrichedRecordings) {
|
|
777
|
+
// Log detection flags before conversion
|
|
778
|
+
const flags = {
|
|
779
|
+
hasPerson: 'hasPerson' in rec ? rec.hasPerson : false,
|
|
780
|
+
hasVehicle: 'hasVehicle' in rec ? rec.hasVehicle : false,
|
|
781
|
+
hasAnimal: 'hasAnimal' in rec ? rec.hasAnimal : false,
|
|
782
|
+
hasFace: 'hasFace' in rec ? rec.hasFace : false,
|
|
783
|
+
hasMotion: 'hasMotion' in rec ? rec.hasMotion : false,
|
|
784
|
+
hasDoorbell: 'hasDoorbell' in rec ? rec.hasDoorbell : false,
|
|
785
|
+
hasPackage: 'hasPackage' in rec ? rec.hasPackage : false,
|
|
786
|
+
recordType: rec.recordType || 'none',
|
|
787
|
+
};
|
|
788
|
+
logger.debug(`[NVR VOD] Processing recording: fileName=${rec.fileName}, flags=${JSON.stringify(flags)}`);
|
|
789
|
+
|
|
790
|
+
const clip = await recordingFileToVideoClip(rec, {
|
|
791
|
+
fallbackStart: start,
|
|
792
|
+
logger,
|
|
793
|
+
plugin: this,
|
|
794
|
+
deviceId: this.id,
|
|
795
|
+
useWebhook: true,
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
// Log detection classes in the final clip
|
|
799
|
+
logger.debug(`[NVR VOD] Generated clip: id=${clip.id}, detectionClasses=${clip.detectionClasses?.join(',') || 'none'}`);
|
|
800
|
+
clips.push(clip);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Apply count limit if specified
|
|
804
|
+
const finalClips = count ? clips.slice(0, count) : clips;
|
|
805
|
+
logger.log(`[NVR VOD] Converted ${finalClips.length} video clips (limit: ${count || 'none'})`);
|
|
806
|
+
|
|
807
|
+
return finalClips;
|
|
808
|
+
} else {
|
|
809
|
+
// Fetch directly from device using Baichuan API
|
|
810
|
+
const api = await this.ensureClient();
|
|
811
|
+
|
|
812
|
+
const recordings = await api.listEnrichedRecordingsByTime({
|
|
813
|
+
start,
|
|
814
|
+
end,
|
|
815
|
+
count,
|
|
816
|
+
channel: this.storageSettings.values.rtspChannel,
|
|
817
|
+
streamType: 'mainStream',
|
|
818
|
+
httpFallback: false,
|
|
819
|
+
fetchRtmpUrls: true
|
|
735
820
|
});
|
|
736
|
-
clips.push(clip);
|
|
737
|
-
}
|
|
738
821
|
|
|
739
|
-
|
|
822
|
+
const clips: VideoClip[] = [];
|
|
823
|
+
|
|
824
|
+
for (const rec of recordings) {
|
|
825
|
+
const clip = await recordingFileToVideoClip(rec, {
|
|
826
|
+
fallbackStart: start,
|
|
827
|
+
api,
|
|
828
|
+
logger,
|
|
829
|
+
plugin: this,
|
|
830
|
+
deviceId: this.id,
|
|
831
|
+
useWebhook: true,
|
|
832
|
+
});
|
|
833
|
+
clips.push(clip);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
logger.debug(`Videoclips found: ${clips.length}`);
|
|
740
837
|
|
|
741
|
-
|
|
838
|
+
return clips;
|
|
839
|
+
}
|
|
742
840
|
} catch (e: any) {
|
|
743
841
|
const message = e instanceof Error ? e.message : String(e);
|
|
744
842
|
|
|
@@ -948,26 +1046,9 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
948
1046
|
logger: this.getBaichuanLogger(),
|
|
949
1047
|
});
|
|
950
1048
|
} else {
|
|
951
|
-
//
|
|
952
|
-
//
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
if (!api.client.isSocketConnected() || !api.client.loggedIn) {
|
|
956
|
-
logger.warn(`[Thumbnail] Client not ready, waiting for connection: fileId=${thumbnailId}`);
|
|
957
|
-
// ensureClient should have already handled connection, but wait a bit if needed
|
|
958
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
// Get RTMP URL from fileId
|
|
962
|
-
// Note: getRecordingPlaybackUrls internally calls login(), but it should be idempotent
|
|
963
|
-
// if ensureClient() already established the connection
|
|
964
|
-
const { rtmpVodUrl } = await api.getRecordingPlaybackUrls({
|
|
965
|
-
fileName: thumbnailId,
|
|
966
|
-
});
|
|
967
|
-
|
|
968
|
-
if (!rtmpVodUrl) {
|
|
969
|
-
throw new Error(`No playback URL found for video ${thumbnailId}`);
|
|
970
|
-
}
|
|
1049
|
+
// Get RTMP URL using the appropriate API (NVR or Baichuan)
|
|
1050
|
+
// Use forThumbnail=true to prefer Download over Playback (better for ffmpeg)
|
|
1051
|
+
const rtmpVodUrl = await this.getVideoClipRtmpUrl(thumbnailId, true);
|
|
971
1052
|
|
|
972
1053
|
// Use the plugin's thumbnail generation queue with RTMP URL
|
|
973
1054
|
thumbnail = await this.plugin.generateThumbnail({
|
|
@@ -995,6 +1076,56 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
995
1076
|
}
|
|
996
1077
|
}
|
|
997
1078
|
|
|
1079
|
+
/**
|
|
1080
|
+
* Get RTMP URL for a video clip file
|
|
1081
|
+
* Handles both NVR source (full path) and Device source (filename only)
|
|
1082
|
+
* @param fileId - The file ID or full path
|
|
1083
|
+
* @param forThumbnail - If true, prefer Download over Playback (better for ffmpeg thumbnail extraction)
|
|
1084
|
+
*/
|
|
1085
|
+
async getVideoClipRtmpUrl(fileId: string, forThumbnail: boolean = false): Promise<string> {
|
|
1086
|
+
const logger = this.getBaichuanLogger();
|
|
1087
|
+
const { clipsSource } = this.storageSettings.values;
|
|
1088
|
+
const useNvr = clipsSource === "NVR" && this.nvrDevice && fileId.includes('/');
|
|
1089
|
+
|
|
1090
|
+
if (useNvr) {
|
|
1091
|
+
logger.log(`[getVideoClipRtmpUrl] Using NVR API for fileId="${fileId}", forThumbnail=${forThumbnail}`);
|
|
1092
|
+
const nvrApi = await this.nvrDevice.ensureClient();
|
|
1093
|
+
const channel = this.storageSettings.values.rtspChannel ?? 0;
|
|
1094
|
+
|
|
1095
|
+
// For both thumbnails and video streaming, try Download first
|
|
1096
|
+
// Download might return MP4 format which is better supported than FLV from Playback
|
|
1097
|
+
const requestTypes = ["Download", "Playback"];
|
|
1098
|
+
|
|
1099
|
+
for (const requestType of requestTypes) {
|
|
1100
|
+
try {
|
|
1101
|
+
logger.log(`[getVideoClipRtmpUrl] Trying getVodUrl with ${requestType} requestType...`);
|
|
1102
|
+
const url = await nvrApi.getVodUrl(fileId, channel, {
|
|
1103
|
+
requestType: requestType as "Playback" | "Download",
|
|
1104
|
+
streamType: "main",
|
|
1105
|
+
});
|
|
1106
|
+
logger.log(`[getVideoClipRtmpUrl] NVR getVodUrl ${requestType} URL received: url="${url || 'none'}"`);
|
|
1107
|
+
if (url) return url;
|
|
1108
|
+
} catch (e: any) {
|
|
1109
|
+
logger.debug(`[getVideoClipRtmpUrl] getVodUrl ${requestType} failed: ${e.message}`);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
throw new Error(`No streaming URL found from NVR for file ${fileId} after trying Playback and Download methods`);
|
|
1114
|
+
} else {
|
|
1115
|
+
// Camera standalone: DEVE usare RTMP da Baichuan API
|
|
1116
|
+
logger.log(`[getVideoClipRtmpUrl] Getting RTMP URL from Baichuan API for fileId="${fileId}" (camera standalone)`);
|
|
1117
|
+
const api = await this.ensureClient();
|
|
1118
|
+
const result = await api.getRecordingPlaybackUrls({
|
|
1119
|
+
fileName: fileId,
|
|
1120
|
+
});
|
|
1121
|
+
logger.log(`[getVideoClipRtmpUrl] Baichuan RTMP URL received: rtmpVodUrl="${result.rtmpVodUrl || 'none'}"`);
|
|
1122
|
+
if (!result.rtmpVodUrl) {
|
|
1123
|
+
throw new Error(`No RTMP URL found from Baichuan API for file ${fileId}`);
|
|
1124
|
+
}
|
|
1125
|
+
return result.rtmpVodUrl;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
998
1129
|
removeVideoClips(...videoClipIds: string[]): Promise<void> {
|
|
999
1130
|
throw new Error("removeVideoClips is not implemented yet.");
|
|
1000
1131
|
}
|
|
@@ -1055,8 +1186,8 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1055
1186
|
// Get today's date range (start of today to now)
|
|
1056
1187
|
const now = new Date();
|
|
1057
1188
|
const startOfToday = new Date(now);
|
|
1058
|
-
startOfToday.
|
|
1059
|
-
startOfToday.
|
|
1189
|
+
startOfToday.setUTCHours(0, 0, 0, 0);
|
|
1190
|
+
startOfToday.setUTCMinutes(0, 0, 0);
|
|
1060
1191
|
|
|
1061
1192
|
// Fetch today's video clips
|
|
1062
1193
|
const clips = await this.getVideoClips({
|
|
@@ -2293,7 +2424,8 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
2293
2424
|
logger.warn('Failed to connect/refresh during init', e);
|
|
2294
2425
|
}
|
|
2295
2426
|
}
|
|
2296
|
-
|
|
2427
|
+
this.storageSettings.settings.clipsSource.hide = !this.nvrDevice;
|
|
2428
|
+
this.storageSettings.settings.clipsSource.defaultValue = this.nvrDevice ? "NVR" : "Device";
|
|
2297
2429
|
|
|
2298
2430
|
this.storageSettings.settings.videoclipsRegularChecks.defaultValue = this.isBattery ? 120 : 30;
|
|
2299
2431
|
|
package/src/main.ts
CHANGED
|
@@ -347,8 +347,7 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
|
|
|
347
347
|
*/
|
|
348
348
|
async generateThumbnail(request: ThumbnailRequestInput): Promise<MediaObject> {
|
|
349
349
|
const queueLength = this.thumbnailQueue.length;
|
|
350
|
-
|
|
351
|
-
request.logger.log(`[Thumbnail] Adding to queue: fileId=${request.fileId}, queueLength=${queueLength}, processing=${isProcessing}`);
|
|
350
|
+
request.logger.log(`[Thumbnail] Download start: fileId=${request.fileId}, queuePosition=${queueLength + 1}`);
|
|
352
351
|
|
|
353
352
|
return new Promise((resolve, reject) => {
|
|
354
353
|
this.thumbnailQueue.push({
|
|
@@ -376,6 +375,7 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
|
|
|
376
375
|
|
|
377
376
|
try {
|
|
378
377
|
const thumbnail = await extractThumbnailFromVideo(request);
|
|
378
|
+
logger.log(`[Thumbnail] OK: fileId=${request.fileId}`);
|
|
379
379
|
request.resolve(thumbnail);
|
|
380
380
|
} catch (error) {
|
|
381
381
|
logger.error(`[Thumbnail] Error: fileId=${request.fileId}`, error);
|
package/src/nvr.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { DeviceInfoResponse,
|
|
2
|
-
import sdk, { AdoptDevice, Device, DeviceDiscovery, DeviceProvider, DiscoveredDevice, Reboot,
|
|
1
|
+
import type { DeviceInfoResponse, EnrichedRecordingFile, EventsResponse, ReolinkBaichuanApi, ReolinkCgiApi, ReolinkSimpleEvent } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
2
|
+
import sdk, { AdoptDevice, Device, DeviceDiscovery, DeviceProvider, DiscoveredDevice, Reboot, ScryptedDeviceType, ScryptedInterface, Setting, Settings, SettingValue } from "@scrypted/sdk";
|
|
3
3
|
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
|
4
|
-
import { BaseBaichuanClass, type
|
|
4
|
+
import { BaseBaichuanClass, type BaichuanConnectionCallbacks, type BaichuanConnectionConfig } from "./baichuan-base";
|
|
5
5
|
import { ReolinkNativeCamera } from "./camera";
|
|
6
6
|
import { ReolinkNativeBatteryCamera } from "./camera-battery";
|
|
7
7
|
import { normalizeUid } from "./connect";
|
|
@@ -80,8 +80,8 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
async reboot(): Promise<void> {
|
|
83
|
-
const api = await this.
|
|
84
|
-
await api.
|
|
83
|
+
const api = await this.ensureBaichuanClient();
|
|
84
|
+
await api.reboot();
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
// BaseBaichuanClass abstract methods implementation
|
|
@@ -175,16 +175,41 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
const { ReolinkCgiApi } = await import("@apocaliss92/reolink-baichuan-js");
|
|
178
|
+
const logger = this.getBaichuanLogger();
|
|
178
179
|
this.nvrApi = new ReolinkCgiApi({
|
|
179
180
|
host: ipAddress,
|
|
180
181
|
username,
|
|
181
182
|
password,
|
|
183
|
+
logger,
|
|
182
184
|
});
|
|
183
185
|
|
|
184
186
|
await this.nvrApi.login();
|
|
185
187
|
return this.nvrApi;
|
|
186
188
|
}
|
|
187
189
|
|
|
190
|
+
/**
|
|
191
|
+
* List enriched VOD files (with proper parsing and detection info)
|
|
192
|
+
* This uses the library's enrichVodFile which handles all parsing correctly
|
|
193
|
+
*/
|
|
194
|
+
async listEnrichedVodFiles(params: {
|
|
195
|
+
channel: number;
|
|
196
|
+
start: Date;
|
|
197
|
+
end: Date;
|
|
198
|
+
streamType?: "main" | "sub";
|
|
199
|
+
autoSearchByDay?: boolean;
|
|
200
|
+
bypassCache?: boolean;
|
|
201
|
+
}): Promise<Array<EnrichedRecordingFile>> {
|
|
202
|
+
const api = await this.ensureClient();
|
|
203
|
+
return await api.listEnrichedVodFiles({
|
|
204
|
+
channel: params.channel,
|
|
205
|
+
start: params.start,
|
|
206
|
+
end: params.end,
|
|
207
|
+
streamType: params.streamType,
|
|
208
|
+
autoSearchByDay: params.autoSearchByDay,
|
|
209
|
+
bypassCache: params.bypassCache,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
188
213
|
private forwardNativeEvent(ev: ReolinkSimpleEvent): void {
|
|
189
214
|
const logger = this.getBaichuanLogger();
|
|
190
215
|
|
|
@@ -467,33 +492,15 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
467
492
|
async syncEntitiesFromRemote() {
|
|
468
493
|
const logger = this.getBaichuanLogger();
|
|
469
494
|
|
|
470
|
-
|
|
471
|
-
const
|
|
472
|
-
const baichuanApi = await this.ensureBaichuanClient();
|
|
473
|
-
|
|
474
|
-
// Wait for Baichuan connection to be fully established
|
|
475
|
-
if (baichuanApi?.client) {
|
|
476
|
-
// Check if already connected
|
|
477
|
-
if (!baichuanApi.client.isSocketConnected()) {
|
|
478
|
-
logger.debug('Waiting for Baichuan connection to be established...');
|
|
479
|
-
// Wait up to 5 seconds for connection
|
|
480
|
-
let attempts = 0;
|
|
481
|
-
while (!baichuanApi.client.isSocketConnected() && attempts < 50) {
|
|
482
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
483
|
-
attempts++;
|
|
484
|
-
}
|
|
485
|
-
if (!baichuanApi.client.isSocketConnected()) {
|
|
486
|
-
logger.warn('Baichuan connection not established after waiting, proceeding anyway');
|
|
487
|
-
} else {
|
|
488
|
-
logger.debug('Baichuan connection established');
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
495
|
+
const cgiApi = await this.ensureClient();
|
|
496
|
+
const { devicesData, channels } = await cgiApi.getDevicesInfo();
|
|
492
497
|
|
|
493
|
-
const
|
|
498
|
+
// const api = await this.ensureBaichuanClient();
|
|
499
|
+
// const devicesMap = api.getDevicesInfo();
|
|
500
|
+
// const deviceEntries = Object.entries(devicesMap);
|
|
494
501
|
|
|
495
502
|
if (!channels.length) {
|
|
496
|
-
logger.debug(`No channels found, ${JSON.stringify({
|
|
503
|
+
logger.debug(`No channels found, ${JSON.stringify({ channels, devicesData })}`);
|
|
497
504
|
return;
|
|
498
505
|
}
|
|
499
506
|
|