@blueharford/scrypted-spatial-awareness 0.4.2 → 0.4.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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueharford/scrypted-spatial-awareness",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Cross-camera object tracking for Scrypted NVR with spatial awareness",
5
5
  "author": "Joshua Seidel <blueharford>",
6
6
  "license": "Apache-2.0",
package/src/main.ts CHANGED
@@ -15,6 +15,8 @@ import sdk, {
15
15
  Readme,
16
16
  } from '@scrypted/sdk';
17
17
  import { StorageSettings } from '@scrypted/sdk/storage-settings';
18
+ import * as fs from 'fs';
19
+ import * as path from 'path';
18
20
  import {
19
21
  CameraTopology,
20
22
  createEmptyTopology,
@@ -1044,14 +1046,34 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
1044
1046
  }
1045
1047
  }
1046
1048
 
1049
+ private getFloorPlanPath(): string {
1050
+ // Use plugin's data directory for persistent storage
1051
+ const pluginVolume = process.env.SCRYPTED_PLUGIN_VOLUME || '/server/volume/plugins/@blueharford/scrypted-spatial-awareness';
1052
+ const dataDir = path.join(pluginVolume, 'data');
1053
+ // Ensure directory exists
1054
+ if (!fs.existsSync(dataDir)) {
1055
+ fs.mkdirSync(dataDir, { recursive: true });
1056
+ }
1057
+ return path.join(dataDir, 'floorplan.png');
1058
+ }
1059
+
1047
1060
  private handleFloorPlanRequest(request: HttpRequest, response: HttpResponse): void {
1048
1061
  if (request.method === 'GET') {
1049
- const imageData = this.storage.getItem('floorPlanImage');
1050
- if (imageData) {
1051
- response.send(JSON.stringify({ imageData }), {
1052
- headers: { 'Content-Type': 'application/json' },
1053
- });
1054
- } else {
1062
+ try {
1063
+ const floorPlanPath = this.getFloorPlanPath();
1064
+ if (fs.existsSync(floorPlanPath)) {
1065
+ const imageBuffer = fs.readFileSync(floorPlanPath);
1066
+ const imageData = 'data:image/png;base64,' + imageBuffer.toString('base64');
1067
+ response.send(JSON.stringify({ imageData }), {
1068
+ headers: { 'Content-Type': 'application/json' },
1069
+ });
1070
+ } else {
1071
+ response.send(JSON.stringify({ imageData: null }), {
1072
+ headers: { 'Content-Type': 'application/json' },
1073
+ });
1074
+ }
1075
+ } catch (e) {
1076
+ this.console.error('Failed to read floor plan:', e);
1055
1077
  response.send(JSON.stringify({ imageData: null }), {
1056
1078
  headers: { 'Content-Type': 'application/json' },
1057
1079
  });
@@ -1059,13 +1081,23 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
1059
1081
  } else if (request.method === 'POST') {
1060
1082
  try {
1061
1083
  const body = JSON.parse(request.body!);
1062
- this.storage.setItem('floorPlanImage', body.imageData);
1084
+ const imageData = body.imageData as string;
1085
+
1086
+ // Extract base64 data (remove data:image/xxx;base64, prefix)
1087
+ const base64Data = imageData.replace(/^data:image\/\w+;base64,/, '');
1088
+ const imageBuffer = Buffer.from(base64Data, 'base64');
1089
+
1090
+ const floorPlanPath = this.getFloorPlanPath();
1091
+ fs.writeFileSync(floorPlanPath, imageBuffer);
1092
+
1093
+ this.console.log('Floor plan saved to:', floorPlanPath, 'size:', imageBuffer.length);
1063
1094
  response.send(JSON.stringify({ success: true }), {
1064
1095
  headers: { 'Content-Type': 'application/json' },
1065
1096
  });
1066
1097
  } catch (e) {
1067
- response.send(JSON.stringify({ error: 'Invalid request body' }), {
1068
- code: 400,
1098
+ this.console.error('Failed to save floor plan:', e);
1099
+ response.send(JSON.stringify({ error: 'Failed to save floor plan' }), {
1100
+ code: 500,
1069
1101
  headers: { 'Content-Type': 'application/json' },
1070
1102
  });
1071
1103
  }
@@ -346,19 +346,23 @@ export const EDITOR_HTML = `<!DOCTYPE html>
346
346
  if (topology.floorPlan?.imageData) {
347
347
  // Legacy: imageData was stored in topology
348
348
  await loadFloorPlanImage(topology.floorPlan.imageData);
349
- } else if (topology.floorPlan?.type === 'uploaded') {
350
- // New: load from separate endpoint
349
+ } else if (topology.floorPlan?.type === 'blank') {
350
+ blankCanvasMode = true;
351
+ } else {
352
+ // Always try to load from floor-plan endpoint (handles uploaded and missing cases)
351
353
  try {
352
354
  const fpResponse = await fetch('../api/floor-plan');
353
355
  if (fpResponse.ok) {
354
356
  const fpData = await fpResponse.json();
355
357
  if (fpData.imageData) {
356
358
  await loadFloorPlanImage(fpData.imageData);
359
+ // Update topology reference if not set
360
+ if (!topology.floorPlan || topology.floorPlan.type !== 'uploaded') {
361
+ topology.floorPlan = { type: 'uploaded', width: floorPlanImage.width, height: floorPlanImage.height };
362
+ }
357
363
  }
358
364
  }
359
365
  } catch (err) { console.error('Failed to load floor plan:', err); }
360
- } else if (topology.floorPlan?.type === 'blank') {
361
- blankCanvasMode = true;
362
366
  }
363
367
  }
364
368
  } catch (e) { console.error('Failed to load topology:', e); }