@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/main.nodejs.js +1 -1
- package/dist/main.nodejs.js.map +1 -1
- package/dist/plugin.zip +0 -0
- package/out/main.nodejs.js +98 -27
- package/out/main.nodejs.js.map +1 -1
- package/out/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/core/topology-discovery.ts +71 -17
- package/src/main.ts +38 -11
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/out/main.nodejs.js
CHANGED
|
@@ -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": ["
|
|
36089
|
+
{"name": "Driveway", "type": "access", "seenByCameras": ["device_123", "device_456"], "confidence": 0.8, "description": "Concrete driveway"}
|
|
36088
36090
|
],
|
|
36089
36091
|
"connections": [
|
|
36090
|
-
{"from": "
|
|
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
|
-
|
|
36507
|
-
|
|
36508
|
-
|
|
36509
|
-
|
|
36510
|
-
|
|
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
|
-
|
|
36516
|
-
|
|
36517
|
-
|
|
36518
|
-
|
|
36519
|
-
|
|
36520
|
-
|
|
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
|
|
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
|
|
40237
|
-
|
|
40238
|
-
|
|
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}
|
|
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
|
|
40305
|
-
|
|
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 ||
|
|
40379
|
-
|
|
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
|