@blueharford/scrypted-spatial-awareness 0.6.3 → 0.6.4

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/plugin.zip CHANGED
Binary file
@@ -36081,13 +36081,15 @@ Identify:
36081
36081
  2. Camera connections - How someone could move between camera views and estimated walking time
36082
36082
  3. Overall layout - Describe the property layout based on what you see
36083
36083
 
36084
+ IMPORTANT: For camera references, use the EXACT device ID shown in parentheses (e.g., "device_123"), NOT the camera name.
36085
+
36084
36086
  Respond with ONLY valid JSON:
36085
36087
  {
36086
36088
  "sharedLandmarks": [
36087
- {"name": "Driveway", "type": "access", "seenByCameras": ["camera1", "camera2"], "confidence": 0.8, "description": "Concrete driveway"}
36089
+ {"name": "Driveway", "type": "access", "seenByCameras": ["device_123", "device_456"], "confidence": 0.8, "description": "Concrete driveway"}
36088
36090
  ],
36089
36091
  "connections": [
36090
- {"from": "camera1", "to": "camera2", "transitSeconds": 10, "via": "driveway", "confidence": 0.7, "bidirectional": true}
36092
+ {"from": "device_123", "to": "device_456", "transitSeconds": 10, "via": "driveway", "confidence": 0.7, "bidirectional": true}
36091
36093
  ],
36092
36094
  "layoutDescription": "Single-story house with front yard facing street, driveway on the left side, backyard accessible through side gate"
36093
36095
  }`;
@@ -36410,6 +36412,36 @@ class TopologyDiscoveryEngine {
36410
36412
  return 'west';
36411
36413
  return 'unknown';
36412
36414
  }
36415
+ /** Resolve a camera reference (name or deviceId) to its deviceId */
36416
+ resolveCameraRef(ref) {
36417
+ if (!this.topology?.cameras || !ref)
36418
+ return null;
36419
+ // Try exact deviceId match first
36420
+ const byId = this.topology.cameras.find(c => c.deviceId === ref);
36421
+ if (byId)
36422
+ return byId.deviceId;
36423
+ // Try exact name match
36424
+ const byName = this.topology.cameras.find(c => c.name === ref);
36425
+ if (byName)
36426
+ return byName.deviceId;
36427
+ // Try case-insensitive name match
36428
+ const refLower = ref.toLowerCase();
36429
+ const byNameLower = this.topology.cameras.find(c => c.name.toLowerCase() === refLower);
36430
+ if (byNameLower)
36431
+ return byNameLower.deviceId;
36432
+ // Try partial name match (LLM might truncate or abbreviate)
36433
+ const byPartial = this.topology.cameras.find(c => c.name.toLowerCase().includes(refLower) || refLower.includes(c.name.toLowerCase()));
36434
+ if (byPartial)
36435
+ return byPartial.deviceId;
36436
+ this.console.warn(`[Discovery] Could not resolve camera reference: "${ref}"`);
36437
+ return null;
36438
+ }
36439
+ /** Normalize camera references in an array to deviceIds */
36440
+ normalizeCameraRefs(refs) {
36441
+ return refs
36442
+ .map(ref => this.resolveCameraRef(ref))
36443
+ .filter((id) => id !== null);
36444
+ }
36413
36445
  /** Analyze all cameras and correlate findings */
36414
36446
  async runFullDiscovery() {
36415
36447
  if (!this.topology?.cameras?.length) {
@@ -36502,23 +36534,41 @@ class TopologyDiscoveryEngine {
36502
36534
  timestamp: Date.now(),
36503
36535
  };
36504
36536
  if (Array.isArray(parsed.sharedLandmarks)) {
36505
- correlation.sharedLandmarks = parsed.sharedLandmarks.map((l) => ({
36506
- name: l.name || 'Unknown',
36507
- type: this.mapLandmarkType(l.type),
36508
- seenByCameras: Array.isArray(l.seenByCameras) ? l.seenByCameras : [],
36509
- confidence: typeof l.confidence === 'number' ? l.confidence : 0.7,
36510
- description: l.description,
36511
- }));
36537
+ correlation.sharedLandmarks = parsed.sharedLandmarks.map((l) => {
36538
+ // Normalize camera references to deviceIds
36539
+ const rawRefs = Array.isArray(l.seenByCameras) ? l.seenByCameras : [];
36540
+ const normalizedRefs = this.normalizeCameraRefs(rawRefs);
36541
+ if (rawRefs.length > 0 && normalizedRefs.length === 0) {
36542
+ this.console.warn(`[Discovery] Landmark "${l.name}" has no resolvable camera refs: ${JSON.stringify(rawRefs)}`);
36543
+ }
36544
+ return {
36545
+ name: l.name || 'Unknown',
36546
+ type: this.mapLandmarkType(l.type),
36547
+ seenByCameras: normalizedRefs,
36548
+ confidence: typeof l.confidence === 'number' ? l.confidence : 0.7,
36549
+ description: l.description,
36550
+ };
36551
+ });
36512
36552
  }
36513
36553
  if (Array.isArray(parsed.connections)) {
36514
- correlation.suggestedConnections = parsed.connections.map((c) => ({
36515
- fromCameraId: c.from || c.fromCameraId || '',
36516
- toCameraId: c.to || c.toCameraId || '',
36517
- transitSeconds: typeof c.transitSeconds === 'number' ? c.transitSeconds : 15,
36518
- via: c.via || '',
36519
- confidence: typeof c.confidence === 'number' ? c.confidence : 0.6,
36520
- bidirectional: c.bidirectional !== false,
36521
- }));
36554
+ correlation.suggestedConnections = parsed.connections.map((c) => {
36555
+ // Normalize camera references to deviceIds
36556
+ const fromRef = c.from || c.fromCameraId || '';
36557
+ const toRef = c.to || c.toCameraId || '';
36558
+ const fromId = this.resolveCameraRef(fromRef);
36559
+ const toId = this.resolveCameraRef(toRef);
36560
+ if (!fromId || !toId) {
36561
+ this.console.warn(`[Discovery] Connection has unresolvable camera refs: from="${fromRef}" to="${toRef}"`);
36562
+ }
36563
+ return {
36564
+ fromCameraId: fromId || fromRef,
36565
+ toCameraId: toId || toRef,
36566
+ transitSeconds: typeof c.transitSeconds === 'number' ? c.transitSeconds : 15,
36567
+ via: c.via || '',
36568
+ confidence: typeof c.confidence === 'number' ? c.confidence : 0.6,
36569
+ bidirectional: c.bidirectional !== false,
36570
+ };
36571
+ });
36522
36572
  }
36523
36573
  this.console.log(`[Discovery] Correlation found ${correlation.sharedLandmarks.length} shared landmarks, ${correlation.suggestedConnections.length} connections`);
36524
36574
  return correlation;
@@ -40229,18 +40279,25 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
40229
40279
  if (!position || (position.x === 0 && position.y === 0)) {
40230
40280
  // Debug logging
40231
40281
  this.console.log(`[Discovery] Processing landmark "${suggestion.landmark.name}"`);
40282
+ this.console.log(`[Discovery] sourceCameras: ${JSON.stringify(suggestion.sourceCameras)}`);
40232
40283
  this.console.log(`[Discovery] visibleFromCameras: ${JSON.stringify(suggestion.landmark.visibleFromCameras)}`);
40233
40284
  this.console.log(`[Discovery] Available cameras: ${topology.cameras.map(c => `${c.name}(${c.deviceId})`).join(', ')}`);
40234
- // Find a camera that can see this landmark - use flexible matching (deviceId, name, or case-insensitive)
40285
+ // Find a camera that can see this landmark
40286
+ // PREFER sourceCameras (set from analysis.cameraId) over visibleFromCameras (from LLM parsing)
40287
+ const sourceCameraRef = suggestion.sourceCameras?.[0];
40235
40288
  const visibleCameraRef = suggestion.landmark.visibleFromCameras?.[0];
40236
- const camera = visibleCameraRef ? topology.cameras.find(c => c.deviceId === visibleCameraRef ||
40237
- c.name === visibleCameraRef ||
40238
- c.name.toLowerCase() === visibleCameraRef.toLowerCase()) : null;
40289
+ const cameraRef = sourceCameraRef || visibleCameraRef;
40290
+ // Use flexible matching (deviceId, name, or case-insensitive)
40291
+ const camera = cameraRef ? topology.cameras.find(c => c.deviceId === cameraRef ||
40292
+ c.name === cameraRef ||
40293
+ c.name.toLowerCase() === cameraRef.toLowerCase()) : null;
40239
40294
  if (camera) {
40240
- this.console.log(`[Discovery] Matched camera: ${camera.name}, position: ${JSON.stringify(camera.floorPlanPosition)}, fov: ${JSON.stringify(camera.fov)}`);
40295
+ this.console.log(`[Discovery] Matched camera: ${camera.name} (${camera.deviceId})`);
40296
+ this.console.log(`[Discovery] Camera position: ${JSON.stringify(camera.floorPlanPosition)}`);
40297
+ this.console.log(`[Discovery] Camera FOV: ${JSON.stringify(camera.fov)}`);
40241
40298
  }
40242
40299
  else {
40243
- this.console.warn(`[Discovery] No camera matched for "${visibleCameraRef}"`);
40300
+ this.console.warn(`[Discovery] No camera matched for ref="${cameraRef}" (source="${sourceCameraRef}", visible="${visibleCameraRef}")`);
40244
40301
  }
40245
40302
  if (camera?.floorPlanPosition) {
40246
40303
  // Get camera's FOV direction and range (cast to any for flexible access)
@@ -40301,9 +40358,19 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
40301
40358
  const zone = suggestion.zone;
40302
40359
  // Find cameras that see this zone
40303
40360
  const sourceCameras = suggestion.sourceCameras || [];
40304
- const camera = sourceCameras[0]
40305
- ? topology.cameras.find(c => c.deviceId === sourceCameras[0] || c.name === sourceCameras[0])
40361
+ const cameraRef = sourceCameras[0];
40362
+ const camera = cameraRef
40363
+ ? topology.cameras.find(c => c.deviceId === cameraRef ||
40364
+ c.name === cameraRef ||
40365
+ c.name.toLowerCase() === cameraRef.toLowerCase())
40306
40366
  : null;
40367
+ this.console.log(`[Discovery] Processing zone "${zone.name}" from camera ref="${cameraRef}"`);
40368
+ if (camera) {
40369
+ this.console.log(`[Discovery] Matched camera: ${camera.name} (${camera.deviceId})`);
40370
+ }
40371
+ else if (cameraRef) {
40372
+ this.console.warn(`[Discovery] No camera matched for zone source ref="${cameraRef}"`);
40373
+ }
40307
40374
  // Create zone polygon WITHIN the camera's field of view
40308
40375
  let polygon = [];
40309
40376
  const timestamp = Date.now();
@@ -40375,9 +40442,13 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
40375
40442
  const globalZoneType = this.mapZoneTypeToGlobalType(zone.type);
40376
40443
  const cameraZones = sourceCameras
40377
40444
  .map(camRef => {
40378
- const cam = topology.cameras.find(c => c.deviceId === camRef || c.name === camRef);
40379
- if (!cam)
40445
+ const cam = topology.cameras.find(c => c.deviceId === camRef ||
40446
+ c.name === camRef ||
40447
+ c.name.toLowerCase() === camRef.toLowerCase());
40448
+ if (!cam) {
40449
+ this.console.warn(`[Discovery] GlobalZone: No camera matched for ref="${camRef}"`);
40380
40450
  return null;
40451
+ }
40381
40452
  return {
40382
40453
  cameraId: cam.deviceId,
40383
40454
  zone: [[0, 0], [100, 0], [100, 100], [0, 100]], // Full frame default