@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/out/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -83,13 +83,15 @@ Identify:
|
|
|
83
83
|
2. Camera connections - How someone could move between camera views and estimated walking time
|
|
84
84
|
3. Overall layout - Describe the property layout based on what you see
|
|
85
85
|
|
|
86
|
+
IMPORTANT: For camera references, use the EXACT device ID shown in parentheses (e.g., "device_123"), NOT the camera name.
|
|
87
|
+
|
|
86
88
|
Respond with ONLY valid JSON:
|
|
87
89
|
{
|
|
88
90
|
"sharedLandmarks": [
|
|
89
|
-
{"name": "Driveway", "type": "access", "seenByCameras": ["
|
|
91
|
+
{"name": "Driveway", "type": "access", "seenByCameras": ["device_123", "device_456"], "confidence": 0.8, "description": "Concrete driveway"}
|
|
90
92
|
],
|
|
91
93
|
"connections": [
|
|
92
|
-
{"from": "
|
|
94
|
+
{"from": "device_123", "to": "device_456", "transitSeconds": 10, "via": "driveway", "confidence": 0.7, "bidirectional": true}
|
|
93
95
|
],
|
|
94
96
|
"layoutDescription": "Single-story house with front yard facing street, driveway on the left side, backyard accessible through side gate"
|
|
95
97
|
}`;
|
|
@@ -447,6 +449,40 @@ export class TopologyDiscoveryEngine {
|
|
|
447
449
|
return 'unknown';
|
|
448
450
|
}
|
|
449
451
|
|
|
452
|
+
/** Resolve a camera reference (name or deviceId) to its deviceId */
|
|
453
|
+
private resolveCameraRef(ref: string): string | null {
|
|
454
|
+
if (!this.topology?.cameras || !ref) return null;
|
|
455
|
+
|
|
456
|
+
// Try exact deviceId match first
|
|
457
|
+
const byId = this.topology.cameras.find(c => c.deviceId === ref);
|
|
458
|
+
if (byId) return byId.deviceId;
|
|
459
|
+
|
|
460
|
+
// Try exact name match
|
|
461
|
+
const byName = this.topology.cameras.find(c => c.name === ref);
|
|
462
|
+
if (byName) return byName.deviceId;
|
|
463
|
+
|
|
464
|
+
// Try case-insensitive name match
|
|
465
|
+
const refLower = ref.toLowerCase();
|
|
466
|
+
const byNameLower = this.topology.cameras.find(c => c.name.toLowerCase() === refLower);
|
|
467
|
+
if (byNameLower) return byNameLower.deviceId;
|
|
468
|
+
|
|
469
|
+
// Try partial name match (LLM might truncate or abbreviate)
|
|
470
|
+
const byPartial = this.topology.cameras.find(c =>
|
|
471
|
+
c.name.toLowerCase().includes(refLower) || refLower.includes(c.name.toLowerCase())
|
|
472
|
+
);
|
|
473
|
+
if (byPartial) return byPartial.deviceId;
|
|
474
|
+
|
|
475
|
+
this.console.warn(`[Discovery] Could not resolve camera reference: "${ref}"`);
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** Normalize camera references in an array to deviceIds */
|
|
480
|
+
private normalizeCameraRefs(refs: string[]): string[] {
|
|
481
|
+
return refs
|
|
482
|
+
.map(ref => this.resolveCameraRef(ref))
|
|
483
|
+
.filter((id): id is string => id !== null);
|
|
484
|
+
}
|
|
485
|
+
|
|
450
486
|
/** Analyze all cameras and correlate findings */
|
|
451
487
|
async runFullDiscovery(): Promise<TopologyCorrelation | null> {
|
|
452
488
|
if (!this.topology?.cameras?.length) {
|
|
@@ -552,24 +588,42 @@ export class TopologyDiscoveryEngine {
|
|
|
552
588
|
};
|
|
553
589
|
|
|
554
590
|
if (Array.isArray(parsed.sharedLandmarks)) {
|
|
555
|
-
correlation.sharedLandmarks = parsed.sharedLandmarks.map((l: any) =>
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
591
|
+
correlation.sharedLandmarks = parsed.sharedLandmarks.map((l: any) => {
|
|
592
|
+
// Normalize camera references to deviceIds
|
|
593
|
+
const rawRefs = Array.isArray(l.seenByCameras) ? l.seenByCameras : [];
|
|
594
|
+
const normalizedRefs = this.normalizeCameraRefs(rawRefs);
|
|
595
|
+
if (rawRefs.length > 0 && normalizedRefs.length === 0) {
|
|
596
|
+
this.console.warn(`[Discovery] Landmark "${l.name}" has no resolvable camera refs: ${JSON.stringify(rawRefs)}`);
|
|
597
|
+
}
|
|
598
|
+
return {
|
|
599
|
+
name: l.name || 'Unknown',
|
|
600
|
+
type: this.mapLandmarkType(l.type),
|
|
601
|
+
seenByCameras: normalizedRefs,
|
|
602
|
+
confidence: typeof l.confidence === 'number' ? l.confidence : 0.7,
|
|
603
|
+
description: l.description,
|
|
604
|
+
};
|
|
605
|
+
});
|
|
562
606
|
}
|
|
563
607
|
|
|
564
608
|
if (Array.isArray(parsed.connections)) {
|
|
565
|
-
correlation.suggestedConnections = parsed.connections.map((c: any) =>
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
609
|
+
correlation.suggestedConnections = parsed.connections.map((c: any) => {
|
|
610
|
+
// Normalize camera references to deviceIds
|
|
611
|
+
const fromRef = c.from || c.fromCameraId || '';
|
|
612
|
+
const toRef = c.to || c.toCameraId || '';
|
|
613
|
+
const fromId = this.resolveCameraRef(fromRef);
|
|
614
|
+
const toId = this.resolveCameraRef(toRef);
|
|
615
|
+
if (!fromId || !toId) {
|
|
616
|
+
this.console.warn(`[Discovery] Connection has unresolvable camera refs: from="${fromRef}" to="${toRef}"`);
|
|
617
|
+
}
|
|
618
|
+
return {
|
|
619
|
+
fromCameraId: fromId || fromRef,
|
|
620
|
+
toCameraId: toId || toRef,
|
|
621
|
+
transitSeconds: typeof c.transitSeconds === 'number' ? c.transitSeconds : 15,
|
|
622
|
+
via: c.via || '',
|
|
623
|
+
confidence: typeof c.confidence === 'number' ? c.confidence : 0.6,
|
|
624
|
+
bidirectional: c.bidirectional !== false,
|
|
625
|
+
};
|
|
626
|
+
});
|
|
573
627
|
}
|
|
574
628
|
|
|
575
629
|
this.console.log(`[Discovery] Correlation found ${correlation.sharedLandmarks.length} shared landmarks, ${correlation.suggestedConnections.length} connections`);
|
package/src/main.ts
CHANGED
|
@@ -1829,21 +1829,29 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
|
|
|
1829
1829
|
if (!position || (position.x === 0 && position.y === 0)) {
|
|
1830
1830
|
// Debug logging
|
|
1831
1831
|
this.console.log(`[Discovery] Processing landmark "${suggestion.landmark.name}"`);
|
|
1832
|
+
this.console.log(`[Discovery] sourceCameras: ${JSON.stringify(suggestion.sourceCameras)}`);
|
|
1832
1833
|
this.console.log(`[Discovery] visibleFromCameras: ${JSON.stringify(suggestion.landmark.visibleFromCameras)}`);
|
|
1833
1834
|
this.console.log(`[Discovery] Available cameras: ${topology.cameras.map(c => `${c.name}(${c.deviceId})`).join(', ')}`);
|
|
1834
1835
|
|
|
1835
|
-
// Find a camera that can see this landmark
|
|
1836
|
+
// Find a camera that can see this landmark
|
|
1837
|
+
// PREFER sourceCameras (set from analysis.cameraId) over visibleFromCameras (from LLM parsing)
|
|
1838
|
+
const sourceCameraRef = suggestion.sourceCameras?.[0];
|
|
1836
1839
|
const visibleCameraRef = suggestion.landmark.visibleFromCameras?.[0];
|
|
1837
|
-
const
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1840
|
+
const cameraRef = sourceCameraRef || visibleCameraRef;
|
|
1841
|
+
|
|
1842
|
+
// Use flexible matching (deviceId, name, or case-insensitive)
|
|
1843
|
+
const camera = cameraRef ? topology.cameras.find(c =>
|
|
1844
|
+
c.deviceId === cameraRef ||
|
|
1845
|
+
c.name === cameraRef ||
|
|
1846
|
+
c.name.toLowerCase() === cameraRef.toLowerCase()
|
|
1841
1847
|
) : null;
|
|
1842
1848
|
|
|
1843
1849
|
if (camera) {
|
|
1844
|
-
this.console.log(`[Discovery] Matched camera: ${camera.name}
|
|
1850
|
+
this.console.log(`[Discovery] Matched camera: ${camera.name} (${camera.deviceId})`);
|
|
1851
|
+
this.console.log(`[Discovery] Camera position: ${JSON.stringify(camera.floorPlanPosition)}`);
|
|
1852
|
+
this.console.log(`[Discovery] Camera FOV: ${JSON.stringify(camera.fov)}`);
|
|
1845
1853
|
} else {
|
|
1846
|
-
this.console.warn(`[Discovery] No camera matched for "${visibleCameraRef}"`);
|
|
1854
|
+
this.console.warn(`[Discovery] No camera matched for ref="${cameraRef}" (source="${sourceCameraRef}", visible="${visibleCameraRef}")`);
|
|
1847
1855
|
}
|
|
1848
1856
|
|
|
1849
1857
|
if (camera?.floorPlanPosition) {
|
|
@@ -1917,10 +1925,22 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
|
|
|
1917
1925
|
|
|
1918
1926
|
// Find cameras that see this zone
|
|
1919
1927
|
const sourceCameras = suggestion.sourceCameras || [];
|
|
1920
|
-
const
|
|
1921
|
-
|
|
1928
|
+
const cameraRef = sourceCameras[0];
|
|
1929
|
+
const camera = cameraRef
|
|
1930
|
+
? topology.cameras.find(c =>
|
|
1931
|
+
c.deviceId === cameraRef ||
|
|
1932
|
+
c.name === cameraRef ||
|
|
1933
|
+
c.name.toLowerCase() === cameraRef.toLowerCase()
|
|
1934
|
+
)
|
|
1922
1935
|
: null;
|
|
1923
1936
|
|
|
1937
|
+
this.console.log(`[Discovery] Processing zone "${zone.name}" from camera ref="${cameraRef}"`);
|
|
1938
|
+
if (camera) {
|
|
1939
|
+
this.console.log(`[Discovery] Matched camera: ${camera.name} (${camera.deviceId})`);
|
|
1940
|
+
} else if (cameraRef) {
|
|
1941
|
+
this.console.warn(`[Discovery] No camera matched for zone source ref="${cameraRef}"`);
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1924
1944
|
// Create zone polygon WITHIN the camera's field of view
|
|
1925
1945
|
let polygon: { x: number; y: number }[] = [];
|
|
1926
1946
|
const timestamp = Date.now();
|
|
@@ -2004,8 +2024,15 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
|
|
|
2004
2024
|
const globalZoneType = this.mapZoneTypeToGlobalType(zone.type);
|
|
2005
2025
|
const cameraZones: CameraZoneMapping[] = sourceCameras
|
|
2006
2026
|
.map(camRef => {
|
|
2007
|
-
const cam = topology.cameras.find(c =>
|
|
2008
|
-
|
|
2027
|
+
const cam = topology.cameras.find(c =>
|
|
2028
|
+
c.deviceId === camRef ||
|
|
2029
|
+
c.name === camRef ||
|
|
2030
|
+
c.name.toLowerCase() === camRef.toLowerCase()
|
|
2031
|
+
);
|
|
2032
|
+
if (!cam) {
|
|
2033
|
+
this.console.warn(`[Discovery] GlobalZone: No camera matched for ref="${camRef}"`);
|
|
2034
|
+
return null;
|
|
2035
|
+
}
|
|
2009
2036
|
return {
|
|
2010
2037
|
cameraId: cam.deviceId,
|
|
2011
2038
|
zone: [[0, 0], [100, 0], [100, 100], [0, 100]] as ClipPath, // Full frame default
|