@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/plugin.zip CHANGED
Binary file
@@ -40227,9 +40227,21 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
40227
40227
  // Calculate position for the landmark WITHIN the camera's field of view
40228
40228
  let position = suggestion.landmark.position;
40229
40229
  if (!position || (position.x === 0 && position.y === 0)) {
40230
- // Find a camera that can see this landmark
40231
- const visibleCameraId = suggestion.landmark.visibleFromCameras?.[0];
40232
- const camera = visibleCameraId ? topology.cameras.find(c => c.deviceId === visibleCameraId) : null;
40230
+ // Debug logging
40231
+ this.console.log(`[Discovery] Processing landmark "${suggestion.landmark.name}"`);
40232
+ this.console.log(`[Discovery] visibleFromCameras: ${JSON.stringify(suggestion.landmark.visibleFromCameras)}`);
40233
+ 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)
40235
+ 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;
40239
+ if (camera) {
40240
+ this.console.log(`[Discovery] Matched camera: ${camera.name}, position: ${JSON.stringify(camera.floorPlanPosition)}, fov: ${JSON.stringify(camera.fov)}`);
40241
+ }
40242
+ else {
40243
+ this.console.warn(`[Discovery] No camera matched for "${visibleCameraRef}"`);
40244
+ }
40233
40245
  if (camera?.floorPlanPosition) {
40234
40246
  // Get camera's FOV direction and range (cast to any for flexible access)
40235
40247
  const fov = (camera.fov || { mode: 'simple', angle: 90, direction: 0, range: 80 });
@@ -40237,7 +40249,9 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
40237
40249
  const range = fov.range || 80;
40238
40250
  const fovAngle = fov.angle || 90;
40239
40251
  // Count existing landmarks from this camera to spread them out
40240
- const existingFromCamera = (topology.landmarks || []).filter(l => l.visibleFromCameras?.includes(visibleCameraId)).length;
40252
+ const cameraDeviceId = camera.deviceId;
40253
+ const existingFromCamera = (topology.landmarks || []).filter(l => l.visibleFromCameras?.includes(cameraDeviceId) ||
40254
+ l.visibleFromCameras?.includes(camera.name)).length;
40241
40255
  // Calculate position in front of camera within its FOV
40242
40256
  // Convert direction to radians (0 = up/north, 90 = right/east)
40243
40257
  const dirRad = (direction - 90) * Math.PI / 180;
@@ -41533,6 +41547,19 @@ exports.EDITOR_HTML = `<!DOCTYPE html>
41533
41547
  <p>Topology Editor</p>
41534
41548
  </div>
41535
41549
  <div class="sidebar-content">
41550
+ <div class="section" style="background: #1a3a5c; margin: -10px -15px 10px -15px; padding: 15px;">
41551
+ <div class="section-title" style="margin-bottom: 10px;">
41552
+ <span>Floor Plan Scale</span>
41553
+ </div>
41554
+ <div style="display: flex; gap: 10px; align-items: center;">
41555
+ <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)">
41556
+ <span style="font-size: 12px; color: #888;">pixels per foot</span>
41557
+ <button class="btn btn-small" onclick="openScaleHelper()" style="margin-left: auto;">Help</button>
41558
+ </div>
41559
+ <div style="font-size: 11px; color: #666; margin-top: 8px;">
41560
+ Tip: If your floor plan is 800px wide and represents 80ft, scale = 10 px/ft
41561
+ </div>
41562
+ </div>
41536
41563
  <div class="section">
41537
41564
  <div class="section-title">
41538
41565
  <span>Cameras</span>
@@ -41841,6 +41868,14 @@ exports.EDITOR_HTML = `<!DOCTYPE html>
41841
41868
  let currentDrawing = null;
41842
41869
  let blankCanvasMode = false;
41843
41870
 
41871
+ // Floor plan scale: pixels per foot (default assumes ~5 pixels per foot for a typical floor plan)
41872
+ // User can adjust this by setting the scale
41873
+ let floorPlanScale = 5; // pixels per foot
41874
+
41875
+ // Helper functions for scale conversion
41876
+ function feetToPixels(feet) { return feet * floorPlanScale; }
41877
+ function pixelsToFeet(pixels) { return pixels / floorPlanScale; }
41878
+
41844
41879
  // Zone drawing state
41845
41880
  let zoneDrawingMode = false;
41846
41881
  let currentZonePoints = [];
@@ -41896,6 +41931,12 @@ exports.EDITOR_HTML = `<!DOCTYPE html>
41896
41931
  if (response.ok) {
41897
41932
  topology = await response.json();
41898
41933
  if (!topology.drawings) topology.drawings = [];
41934
+ // Load floor plan scale if saved
41935
+ if (topology.floorPlanScale) {
41936
+ floorPlanScale = topology.floorPlanScale;
41937
+ const scaleInput = document.getElementById('scale-input');
41938
+ if (scaleInput) scaleInput.value = floorPlanScale;
41939
+ }
41899
41940
  // Load floor plan from separate storage (handles legacy imageData in topology too)
41900
41941
  if (topology.floorPlan?.imageData) {
41901
41942
  // Legacy: imageData was stored in topology
@@ -43065,6 +43106,8 @@ exports.EDITOR_HTML = `<!DOCTYPE html>
43065
43106
  function showCameraProperties(camera) {
43066
43107
  const panel = document.getElementById('properties-panel');
43067
43108
  const fov = camera.fov || { mode: 'simple', angle: 90, direction: 0, range: 80 };
43109
+ // Convert stored pixel range to feet for display
43110
+ const rangeInFeet = Math.round(pixelsToFeet(fov.range || 80));
43068
43111
  panel.innerHTML = '<h3>Camera Properties</h3>' +
43069
43112
  '<div class="form-group"><label>Name</label><input type="text" value="' + camera.name + '" onchange="updateCameraName(\\'' + camera.deviceId + '\\', this.value)"></div>' +
43070
43113
  '<div class="form-group"><label class="checkbox-group"><input type="checkbox" ' + (camera.isEntryPoint ? 'checked' : '') + ' onchange="updateCameraEntry(\\'' + camera.deviceId + '\\', this.checked)">Entry Point</label></div>' +
@@ -43072,7 +43115,8 @@ exports.EDITOR_HTML = `<!DOCTYPE html>
43072
43115
  '<h4 style="margin-top: 15px; margin-bottom: 10px; color: #888;">Field of View</h4>' +
43073
43116
  '<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>' +
43074
43117
  '<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>' +
43075
- '<div class="form-group"><label>Range (pixels)</label><input type="number" value="' + (fov.range || 80) + '" min="20" max="300" onchange="updateCameraFov(\\'' + camera.deviceId + '\\', \\'range\\', this.value)"></div>' +
43118
+ '<div class="form-group"><label>Range (feet)</label><input type="number" value="' + rangeInFeet + '" min="5" max="200" onchange="updateCameraFovRange(\\'' + camera.deviceId + '\\', this.value)"></div>' +
43119
+ '<div style="font-size: 11px; color: #666; margin-top: -10px; margin-bottom: 15px;">~' + (fov.range || 80) + ' pixels at current scale</div>' +
43076
43120
  '<div class="form-group"><button class="btn" style="width: 100%; background: #f44336;" onclick="deleteCamera(\\'' + camera.deviceId + '\\')">Delete Camera</button></div>';
43077
43121
  }
43078
43122
 
@@ -43091,6 +43135,36 @@ exports.EDITOR_HTML = `<!DOCTYPE html>
43091
43135
  camera.fov[field] = parseFloat(value);
43092
43136
  render();
43093
43137
  }
43138
+ function updateCameraFovRange(id, feetValue) {
43139
+ // Convert feet to pixels and store
43140
+ const camera = topology.cameras.find(c => c.deviceId === id);
43141
+ if (!camera) return;
43142
+ if (!camera.fov) camera.fov = { mode: 'simple', angle: 90, direction: 0, range: 80 };
43143
+ camera.fov.range = feetToPixels(parseFloat(feetValue));
43144
+ render();
43145
+ // Update the pixel display
43146
+ showCameraProperties(camera);
43147
+ }
43148
+ function updateScale(value) {
43149
+ floorPlanScale = parseFloat(value) || 5;
43150
+ // Store in topology for persistence
43151
+ topology.floorPlanScale = floorPlanScale;
43152
+ render();
43153
+ setStatus('Scale updated: ' + floorPlanScale + ' pixels per foot', 'success');
43154
+ }
43155
+ function openScaleHelper() {
43156
+ alert('How to determine your floor plan scale:\\n\\n' +
43157
+ '1. Measure a known distance on your floor plan in pixels\\n' +
43158
+ ' (e.g., measure a room that you know is 20 feet wide)\\n\\n' +
43159
+ '2. Divide the pixel width by the real width in feet\\n' +
43160
+ ' Example: 200 pixels / 20 feet = 10 pixels per foot\\n\\n' +
43161
+ '3. Enter that value in the scale field\\n\\n' +
43162
+ 'Common scales:\\n' +
43163
+ '- Small floor plan (fits on screen): 3-5 px/ft\\n' +
43164
+ '- Medium floor plan: 5-10 px/ft\\n' +
43165
+ '- Large/detailed floor plan: 10-20 px/ft\\n\\n' +
43166
+ 'Tip: Most outdoor cameras see 30-50 feet, indoor 15-30 feet');
43167
+ }
43094
43168
  function updateConnectionName(id, value) { const conn = topology.connections.find(c => c.id === id); if (conn) conn.name = value; updateUI(); }
43095
43169
  function updateTransitTime(id, field, value) { const conn = topology.connections.find(c => c.id === id); if (conn) conn.transitTime[field] = parseInt(value) * 1000; }
43096
43170
  function updateConnectionBidi(id, value) { const conn = topology.connections.find(c => c.id === id); if (conn) conn.bidirectional = value; render(); }