@blueharford/scrypted-spatial-awareness 0.6.1 → 0.6.3
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 +79 -5
- package/out/main.nodejs.js.map +1 -1
- package/out/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/main.ts +21 -4
- package/src/ui/editor-html.ts +61 -1
package/out/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/main.ts
CHANGED
|
@@ -1827,9 +1827,24 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
|
|
|
1827
1827
|
// Calculate position for the landmark WITHIN the camera's field of view
|
|
1828
1828
|
let position = suggestion.landmark.position;
|
|
1829
1829
|
if (!position || (position.x === 0 && position.y === 0)) {
|
|
1830
|
-
//
|
|
1831
|
-
|
|
1832
|
-
|
|
1830
|
+
// Debug logging
|
|
1831
|
+
this.console.log(`[Discovery] Processing landmark "${suggestion.landmark.name}"`);
|
|
1832
|
+
this.console.log(`[Discovery] visibleFromCameras: ${JSON.stringify(suggestion.landmark.visibleFromCameras)}`);
|
|
1833
|
+
this.console.log(`[Discovery] Available cameras: ${topology.cameras.map(c => `${c.name}(${c.deviceId})`).join(', ')}`);
|
|
1834
|
+
|
|
1835
|
+
// Find a camera that can see this landmark - use flexible matching (deviceId, name, or case-insensitive)
|
|
1836
|
+
const visibleCameraRef = suggestion.landmark.visibleFromCameras?.[0];
|
|
1837
|
+
const camera = visibleCameraRef ? topology.cameras.find(c =>
|
|
1838
|
+
c.deviceId === visibleCameraRef ||
|
|
1839
|
+
c.name === visibleCameraRef ||
|
|
1840
|
+
c.name.toLowerCase() === visibleCameraRef.toLowerCase()
|
|
1841
|
+
) : null;
|
|
1842
|
+
|
|
1843
|
+
if (camera) {
|
|
1844
|
+
this.console.log(`[Discovery] Matched camera: ${camera.name}, position: ${JSON.stringify(camera.floorPlanPosition)}, fov: ${JSON.stringify(camera.fov)}`);
|
|
1845
|
+
} else {
|
|
1846
|
+
this.console.warn(`[Discovery] No camera matched for "${visibleCameraRef}"`);
|
|
1847
|
+
}
|
|
1833
1848
|
|
|
1834
1849
|
if (camera?.floorPlanPosition) {
|
|
1835
1850
|
// Get camera's FOV direction and range (cast to any for flexible access)
|
|
@@ -1839,8 +1854,10 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
|
|
|
1839
1854
|
const fovAngle = fov.angle || 90;
|
|
1840
1855
|
|
|
1841
1856
|
// Count existing landmarks from this camera to spread them out
|
|
1857
|
+
const cameraDeviceId = camera.deviceId;
|
|
1842
1858
|
const existingFromCamera = (topology.landmarks || []).filter(l =>
|
|
1843
|
-
l.visibleFromCameras?.includes(
|
|
1859
|
+
l.visibleFromCameras?.includes(cameraDeviceId) ||
|
|
1860
|
+
l.visibleFromCameras?.includes(camera.name)
|
|
1844
1861
|
).length;
|
|
1845
1862
|
|
|
1846
1863
|
// Calculate position in front of camera within its FOV
|
package/src/ui/editor-html.ts
CHANGED
|
@@ -71,6 +71,19 @@ export const EDITOR_HTML = `<!DOCTYPE html>
|
|
|
71
71
|
<p>Topology Editor</p>
|
|
72
72
|
</div>
|
|
73
73
|
<div class="sidebar-content">
|
|
74
|
+
<div class="section" style="background: #1a3a5c; margin: -10px -15px 10px -15px; padding: 15px;">
|
|
75
|
+
<div class="section-title" style="margin-bottom: 10px;">
|
|
76
|
+
<span>Floor Plan Scale</span>
|
|
77
|
+
</div>
|
|
78
|
+
<div style="display: flex; gap: 10px; align-items: center;">
|
|
79
|
+
<input type="number" id="scale-input" value="5" min="1" max="50" style="width: 60px; padding: 6px; background: #0f3460; border: 1px solid #1a4a7a; border-radius: 4px; color: #fff;" onchange="updateScale(this.value)">
|
|
80
|
+
<span style="font-size: 12px; color: #888;">pixels per foot</span>
|
|
81
|
+
<button class="btn btn-small" onclick="openScaleHelper()" style="margin-left: auto;">Help</button>
|
|
82
|
+
</div>
|
|
83
|
+
<div style="font-size: 11px; color: #666; margin-top: 8px;">
|
|
84
|
+
Tip: If your floor plan is 800px wide and represents 80ft, scale = 10 px/ft
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
74
87
|
<div class="section">
|
|
75
88
|
<div class="section-title">
|
|
76
89
|
<span>Cameras</span>
|
|
@@ -379,6 +392,14 @@ export const EDITOR_HTML = `<!DOCTYPE html>
|
|
|
379
392
|
let currentDrawing = null;
|
|
380
393
|
let blankCanvasMode = false;
|
|
381
394
|
|
|
395
|
+
// Floor plan scale: pixels per foot (default assumes ~5 pixels per foot for a typical floor plan)
|
|
396
|
+
// User can adjust this by setting the scale
|
|
397
|
+
let floorPlanScale = 5; // pixels per foot
|
|
398
|
+
|
|
399
|
+
// Helper functions for scale conversion
|
|
400
|
+
function feetToPixels(feet) { return feet * floorPlanScale; }
|
|
401
|
+
function pixelsToFeet(pixels) { return pixels / floorPlanScale; }
|
|
402
|
+
|
|
382
403
|
// Zone drawing state
|
|
383
404
|
let zoneDrawingMode = false;
|
|
384
405
|
let currentZonePoints = [];
|
|
@@ -434,6 +455,12 @@ export const EDITOR_HTML = `<!DOCTYPE html>
|
|
|
434
455
|
if (response.ok) {
|
|
435
456
|
topology = await response.json();
|
|
436
457
|
if (!topology.drawings) topology.drawings = [];
|
|
458
|
+
// Load floor plan scale if saved
|
|
459
|
+
if (topology.floorPlanScale) {
|
|
460
|
+
floorPlanScale = topology.floorPlanScale;
|
|
461
|
+
const scaleInput = document.getElementById('scale-input');
|
|
462
|
+
if (scaleInput) scaleInput.value = floorPlanScale;
|
|
463
|
+
}
|
|
437
464
|
// Load floor plan from separate storage (handles legacy imageData in topology too)
|
|
438
465
|
if (topology.floorPlan?.imageData) {
|
|
439
466
|
// Legacy: imageData was stored in topology
|
|
@@ -1603,6 +1630,8 @@ export const EDITOR_HTML = `<!DOCTYPE html>
|
|
|
1603
1630
|
function showCameraProperties(camera) {
|
|
1604
1631
|
const panel = document.getElementById('properties-panel');
|
|
1605
1632
|
const fov = camera.fov || { mode: 'simple', angle: 90, direction: 0, range: 80 };
|
|
1633
|
+
// Convert stored pixel range to feet for display
|
|
1634
|
+
const rangeInFeet = Math.round(pixelsToFeet(fov.range || 80));
|
|
1606
1635
|
panel.innerHTML = '<h3>Camera Properties</h3>' +
|
|
1607
1636
|
'<div class="form-group"><label>Name</label><input type="text" value="' + camera.name + '" onchange="updateCameraName(\\'' + camera.deviceId + '\\', this.value)"></div>' +
|
|
1608
1637
|
'<div class="form-group"><label class="checkbox-group"><input type="checkbox" ' + (camera.isEntryPoint ? 'checked' : '') + ' onchange="updateCameraEntry(\\'' + camera.deviceId + '\\', this.checked)">Entry Point</label></div>' +
|
|
@@ -1610,7 +1639,8 @@ export const EDITOR_HTML = `<!DOCTYPE html>
|
|
|
1610
1639
|
'<h4 style="margin-top: 15px; margin-bottom: 10px; color: #888;">Field of View</h4>' +
|
|
1611
1640
|
'<div class="form-group"><label>Direction (0=up, 90=right)</label><input type="number" value="' + Math.round(fov.direction || 0) + '" min="0" max="359" onchange="updateCameraFov(\\'' + camera.deviceId + '\\', \\'direction\\', this.value)"></div>' +
|
|
1612
1641
|
'<div class="form-group"><label>FOV Angle (degrees)</label><input type="number" value="' + (fov.angle || 90) + '" min="30" max="180" onchange="updateCameraFov(\\'' + camera.deviceId + '\\', \\'angle\\', this.value)"></div>' +
|
|
1613
|
-
'<div class="form-group"><label>Range (
|
|
1642
|
+
'<div class="form-group"><label>Range (feet)</label><input type="number" value="' + rangeInFeet + '" min="5" max="200" onchange="updateCameraFovRange(\\'' + camera.deviceId + '\\', this.value)"></div>' +
|
|
1643
|
+
'<div style="font-size: 11px; color: #666; margin-top: -10px; margin-bottom: 15px;">~' + (fov.range || 80) + ' pixels at current scale</div>' +
|
|
1614
1644
|
'<div class="form-group"><button class="btn" style="width: 100%; background: #f44336;" onclick="deleteCamera(\\'' + camera.deviceId + '\\')">Delete Camera</button></div>';
|
|
1615
1645
|
}
|
|
1616
1646
|
|
|
@@ -1629,6 +1659,36 @@ export const EDITOR_HTML = `<!DOCTYPE html>
|
|
|
1629
1659
|
camera.fov[field] = parseFloat(value);
|
|
1630
1660
|
render();
|
|
1631
1661
|
}
|
|
1662
|
+
function updateCameraFovRange(id, feetValue) {
|
|
1663
|
+
// Convert feet to pixels and store
|
|
1664
|
+
const camera = topology.cameras.find(c => c.deviceId === id);
|
|
1665
|
+
if (!camera) return;
|
|
1666
|
+
if (!camera.fov) camera.fov = { mode: 'simple', angle: 90, direction: 0, range: 80 };
|
|
1667
|
+
camera.fov.range = feetToPixels(parseFloat(feetValue));
|
|
1668
|
+
render();
|
|
1669
|
+
// Update the pixel display
|
|
1670
|
+
showCameraProperties(camera);
|
|
1671
|
+
}
|
|
1672
|
+
function updateScale(value) {
|
|
1673
|
+
floorPlanScale = parseFloat(value) || 5;
|
|
1674
|
+
// Store in topology for persistence
|
|
1675
|
+
topology.floorPlanScale = floorPlanScale;
|
|
1676
|
+
render();
|
|
1677
|
+
setStatus('Scale updated: ' + floorPlanScale + ' pixels per foot', 'success');
|
|
1678
|
+
}
|
|
1679
|
+
function openScaleHelper() {
|
|
1680
|
+
alert('How to determine your floor plan scale:\\n\\n' +
|
|
1681
|
+
'1. Measure a known distance on your floor plan in pixels\\n' +
|
|
1682
|
+
' (e.g., measure a room that you know is 20 feet wide)\\n\\n' +
|
|
1683
|
+
'2. Divide the pixel width by the real width in feet\\n' +
|
|
1684
|
+
' Example: 200 pixels / 20 feet = 10 pixels per foot\\n\\n' +
|
|
1685
|
+
'3. Enter that value in the scale field\\n\\n' +
|
|
1686
|
+
'Common scales:\\n' +
|
|
1687
|
+
'- Small floor plan (fits on screen): 3-5 px/ft\\n' +
|
|
1688
|
+
'- Medium floor plan: 5-10 px/ft\\n' +
|
|
1689
|
+
'- Large/detailed floor plan: 10-20 px/ft\\n\\n' +
|
|
1690
|
+
'Tip: Most outdoor cameras see 30-50 feet, indoor 15-30 feet');
|
|
1691
|
+
}
|
|
1632
1692
|
function updateConnectionName(id, value) { const conn = topology.connections.find(c => c.id === id); if (conn) conn.name = value; updateUI(); }
|
|
1633
1693
|
function updateTransitTime(id, field, value) { const conn = topology.connections.find(c => c.id === id); if (conn) conn.transitTime[field] = parseInt(value) * 1000; }
|
|
1634
1694
|
function updateConnectionBidi(id, value) { const conn = topology.connections.find(c => c.id === id); if (conn) conn.bidirectional = value; render(); }
|