@blueharford/scrypted-spatial-awareness 0.6.30 → 0.6.31

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/out/plugin.zip CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueharford/scrypted-spatial-awareness",
3
- "version": "0.6.30",
3
+ "version": "0.6.31",
4
4
  "description": "Cross-camera object tracking for Scrypted NVR with spatial awareness",
5
5
  "author": "Joshua Seidel <blueharford>",
6
6
  "license": "Apache-2.0",
@@ -110,6 +110,14 @@ export class AlertManager {
110
110
  ? rule.notifiers
111
111
  : this.getDefaultNotifiers();
112
112
 
113
+ // Debug: log which notifiers we're using
114
+ this.console.log(`[Notification] Rule ${rule.id} has ${rule.notifiers.length} notifiers, using ${notifierIds.length} notifier(s): ${notifierIds.join(', ') || 'NONE'}`);
115
+
116
+ if (notifierIds.length === 0) {
117
+ this.console.warn(`[Notification] No notifiers configured! Configure a notifier in plugin settings.`);
118
+ return;
119
+ }
120
+
113
121
  // Try to get a thumbnail from the camera
114
122
  let mediaObject: MediaObject | undefined;
115
123
  const cameraId = alert.details.toCameraId || alert.details.cameraId;
@@ -212,25 +220,33 @@ export class AlertManager {
212
220
  try {
213
221
  // Try new multiple notifiers setting first
214
222
  const notifiers = this.storage.getItem('defaultNotifiers');
223
+ this.console.log(`[Notifiers] Raw storage value: ${notifiers}`);
224
+
215
225
  if (notifiers) {
216
226
  // Could be JSON array or comma-separated string
217
227
  try {
218
228
  const parsed = JSON.parse(notifiers);
219
229
  if (Array.isArray(parsed)) {
230
+ this.console.log(`[Notifiers] Parsed JSON array: ${parsed.join(', ')}`);
220
231
  return parsed;
221
232
  }
222
233
  } catch {
223
234
  // Not JSON, might be comma-separated or single value
224
235
  if (notifiers.includes(',')) {
225
- return notifiers.split(',').map(s => s.trim()).filter(Boolean);
236
+ const result = notifiers.split(',').map(s => s.trim()).filter(Boolean);
237
+ this.console.log(`[Notifiers] Parsed comma-separated: ${result.join(', ')}`);
238
+ return result;
226
239
  }
240
+ this.console.log(`[Notifiers] Single value: ${notifiers}`);
227
241
  return [notifiers];
228
242
  }
229
243
  }
230
244
  // Fallback to old single notifier setting
231
245
  const defaultNotifier = this.storage.getItem('defaultNotifier');
246
+ this.console.log(`[Notifiers] Fallback single notifier: ${defaultNotifier || 'NONE'}`);
232
247
  return defaultNotifier ? [defaultNotifier] : [];
233
- } catch {
248
+ } catch (e) {
249
+ this.console.error(`[Notifiers] Error reading notifiers:`, e);
234
250
  return [];
235
251
  }
236
252
  }
@@ -466,7 +466,7 @@ export class SpatialReasoningEngine {
466
466
  id: deviceId,
467
467
  name: device.name || deviceId,
468
468
  providerType: providerTypeEnum,
469
- lastUsed: 0,
469
+ lastUsed: Date.now() - 60000, // Initialize to 1 minute ago so all LLMs start equal
470
470
  errorCount: 0,
471
471
  });
472
472
  this.console.log(`[LLM] Using configured LLM: ${device.name}`);
@@ -496,7 +496,7 @@ export class SpatialReasoningEngine {
496
496
  id,
497
497
  name: device.name || id,
498
498
  providerType: providerTypeEnum,
499
- lastUsed: 0,
499
+ lastUsed: Date.now() - 60000, // Initialize to 1 minute ago so all LLMs start equal
500
500
  errorCount: 0,
501
501
  });
502
502
 
@@ -620,32 +620,34 @@ export class TrackingEngine {
620
620
  cameraId: string,
621
621
  eventType: 'entry' | 'exit' | 'movement' = 'entry'
622
622
  ): Promise<void> {
623
+ // Skip if we already have a recent snapshot for this object (within 5 seconds)
624
+ const existingSnapshot = this.snapshotCache.get(globalId);
625
+ if (existingSnapshot && eventType !== 'exit') {
626
+ // For entry/movement, we can reuse existing snapshot
627
+ // For exit, we want a fresh snapshot while they're still visible
628
+ return;
629
+ }
630
+
623
631
  try {
624
632
  const camera = systemManager.getDeviceById<Camera>(cameraId);
625
633
  if (camera?.interfaces?.includes(ScryptedInterface.Camera)) {
626
634
  const mediaObject = await camera.takePicture();
627
635
  if (mediaObject) {
628
636
  this.snapshotCache.set(globalId, mediaObject);
629
- this.console.log(`[Snapshot] Cached snapshot for ${globalId.slice(0, 8)} from ${cameraId}`);
630
637
 
631
638
  // Start LLM analysis immediately in parallel (don't await) - but respect rate limits
632
639
  const tracked = this.state.getObject(globalId);
633
640
  if (tracked && this.config.useLlmDescriptions && this.tryLlmCall()) {
634
- this.console.log(`[LLM Prefetch] Starting ${eventType} analysis for ${globalId.slice(0, 8)}`);
635
641
  const descriptionPromise = eventType === 'exit'
636
642
  ? this.spatialReasoning.generateExitDescription(tracked, cameraId, mediaObject)
637
643
  : this.spatialReasoning.generateEntryDescription(tracked, cameraId, mediaObject);
638
644
 
639
645
  this.pendingDescriptions.set(globalId, descriptionPromise);
640
646
 
641
- // Log when complete (for debugging)
642
- descriptionPromise.then(result => {
643
- this.console.log(`[LLM Prefetch] ${eventType} analysis ready for ${globalId.slice(0, 8)}: "${result.description.substring(0, 40)}..."`);
644
- }).catch(e => {
647
+ // Log when complete (but don't spam logs)
648
+ descriptionPromise.catch(e => {
645
649
  this.console.warn(`[LLM Prefetch] Failed for ${globalId.slice(0, 8)}: ${e}`);
646
650
  });
647
- } else if (tracked && this.config.useLlmDescriptions) {
648
- this.console.log(`[LLM Prefetch] Skipped for ${globalId.slice(0, 8)} - rate limited`);
649
651
  }
650
652
  }
651
653
  }