@blueharford/scrypted-spatial-awareness 0.5.7 → 0.5.9

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
@@ -35076,35 +35076,48 @@ const { systemManager, mediaManager } = sdk_1.default;
35076
35076
  */
35077
35077
  async function mediaObjectToBase64(mediaObject) {
35078
35078
  try {
35079
- console.log(`[Image] Converting MediaObject, mimeType=${mediaObject?.mimeType}`);
35080
- // First convert to JPEG to ensure consistent format
35081
- const jpegMediaObject = await mediaManager.convertMediaObject(mediaObject, 'image/jpeg');
35082
- console.log(`[Image] Converted to JPEG MediaObject`);
35083
- // Get the buffer from the converted media object
35084
- const buffer = await mediaManager.convertMediaObjectToBuffer(jpegMediaObject, 'image/jpeg');
35085
- // Check if we got an actual Buffer (not a proxy)
35086
- const isRealBuffer = Buffer.isBuffer(buffer);
35087
- const bufferLength = isRealBuffer ? buffer.length : 0;
35088
- console.log(`[Image] Buffer: isBuffer=${isRealBuffer}, length=${bufferLength}`);
35089
- if (!isRealBuffer || bufferLength === 0) {
35090
- console.warn('[Image] Did not receive a valid Buffer');
35091
- // Try alternate approach: get raw data using any type
35079
+ const mimeType = mediaObject?.mimeType || 'image/jpeg';
35080
+ console.log(`[Image] Converting MediaObject, mimeType=${mimeType}`);
35081
+ // Use createMediaObject to ensure we have a proper MediaObject with mimeType
35082
+ // Then convert to buffer - this should handle the conversion internally
35083
+ let buffer;
35084
+ try {
35085
+ // Try direct conversion with the source mime type
35086
+ buffer = await mediaManager.convertMediaObjectToBuffer(mediaObject, mimeType);
35087
+ }
35088
+ catch (convErr) {
35089
+ console.warn(`[Image] Direct conversion failed, trying with explicit JPEG:`, convErr);
35090
+ // Try creating a new MediaObject with explicit mimeType
35092
35091
  try {
35092
+ // Get raw data if available
35093
35093
  const anyMedia = mediaObject;
35094
35094
  if (typeof anyMedia.getData === 'function') {
35095
- const data = await anyMedia.getData();
35096
- if (data && Buffer.isBuffer(data)) {
35097
- console.log(`[Image] Got data from getData(): ${data.length} bytes`);
35098
- if (data.length > 1000) {
35099
- const base64 = data.toString('base64');
35100
- return { base64, mediaType: 'image/jpeg' };
35101
- }
35095
+ const rawData = await anyMedia.getData();
35096
+ if (rawData && Buffer.isBuffer(rawData) && rawData.length > 1000) {
35097
+ console.log(`[Image] Got raw data: ${rawData.length} bytes`);
35098
+ buffer = rawData;
35099
+ }
35100
+ else {
35101
+ console.warn(`[Image] getData returned invalid data`);
35102
+ return null;
35102
35103
  }
35103
35104
  }
35105
+ else {
35106
+ console.warn('[Image] No getData method available');
35107
+ return null;
35108
+ }
35104
35109
  }
35105
35110
  catch (dataErr) {
35106
- console.warn('[Image] getData() failed:', dataErr);
35111
+ console.warn('[Image] Alternate data fetch failed:', dataErr);
35112
+ return null;
35107
35113
  }
35114
+ }
35115
+ // Check if we got an actual Buffer (not a proxy)
35116
+ const isRealBuffer = Buffer.isBuffer(buffer);
35117
+ const bufferLength = isRealBuffer ? buffer.length : 0;
35118
+ console.log(`[Image] Buffer: isBuffer=${isRealBuffer}, length=${bufferLength}`);
35119
+ if (!isRealBuffer || bufferLength === 0) {
35120
+ console.warn('[Image] Did not receive a valid Buffer');
35108
35121
  return null;
35109
35122
  }
35110
35123
  // Check if buffer is too small to be a valid image (< 1KB is suspicious)
@@ -36262,7 +36275,7 @@ class TopologyDiscoveryEngine {
36262
36275
  ],
36263
36276
  },
36264
36277
  ],
36265
- max_tokens: 500,
36278
+ max_tokens: 1500,
36266
36279
  temperature: 0.3,
36267
36280
  });
36268
36281
  const content = result?.choices?.[0]?.message?.content;
@@ -36471,7 +36484,7 @@ class TopologyDiscoveryEngine {
36471
36484
  const prompt = CORRELATION_PROMPT.replace('{scenes}', scenesText);
36472
36485
  const result = await llm.getChatCompletion({
36473
36486
  messages: [{ role: 'user', content: prompt }],
36474
- max_tokens: 800,
36487
+ max_tokens: 2000,
36475
36488
  temperature: 0.4,
36476
36489
  });
36477
36490
  const content = result?.choices?.[0]?.message?.content;
@@ -40211,12 +40224,38 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
40211
40224
  const topology = this.trackingEngine.getTopology();
40212
40225
  let updated = false;
40213
40226
  if (suggestion.type === 'landmark' && suggestion.landmark) {
40227
+ // Calculate a reasonable position for the landmark
40228
+ // Use the first visible camera's position as a starting point, or canvas center
40229
+ let position = suggestion.landmark.position;
40230
+ if (!position || (position.x === 0 && position.y === 0)) {
40231
+ // Find a camera that can see this landmark
40232
+ const visibleCameraId = suggestion.landmark.visibleFromCameras?.[0];
40233
+ const camera = visibleCameraId ? topology.cameras.find(c => c.deviceId === visibleCameraId) : null;
40234
+ if (camera?.floorPlanPosition) {
40235
+ // Position near the camera with some offset
40236
+ const offset = (topology.landmarks?.length || 0) * 30;
40237
+ position = {
40238
+ x: camera.floorPlanPosition.x + 50 + (offset % 100),
40239
+ y: camera.floorPlanPosition.y + 50 + Math.floor(offset / 100) * 30,
40240
+ };
40241
+ }
40242
+ else {
40243
+ // Position in a grid pattern starting from center
40244
+ const landmarkCount = topology.landmarks?.length || 0;
40245
+ const gridSize = 80;
40246
+ const cols = 5;
40247
+ position = {
40248
+ x: 200 + (landmarkCount % cols) * gridSize,
40249
+ y: 100 + Math.floor(landmarkCount / cols) * gridSize,
40250
+ };
40251
+ }
40252
+ }
40214
40253
  // Add new landmark to topology
40215
40254
  const landmark = {
40216
40255
  id: `landmark_${Date.now()}`,
40217
40256
  name: suggestion.landmark.name,
40218
40257
  type: suggestion.landmark.type,
40219
- position: suggestion.landmark.position || { x: 0, y: 0 },
40258
+ position,
40220
40259
  description: suggestion.landmark.description,
40221
40260
  visibleFromCameras: suggestion.landmark.visibleFromCameras,
40222
40261
  aiSuggested: true,
@@ -40227,15 +40266,69 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
40227
40266
  }
40228
40267
  topology.landmarks.push(landmark);
40229
40268
  updated = true;
40230
- this.console.log(`[Discovery] Added landmark: ${landmark.name}`);
40269
+ this.console.log(`[Discovery] Added landmark: ${landmark.name} at (${position.x}, ${position.y})`);
40270
+ }
40271
+ if (suggestion.type === 'zone' && suggestion.zone) {
40272
+ // Create a drawn zone from the discovery zone
40273
+ const zone = suggestion.zone;
40274
+ // Find cameras that see this zone type to determine position
40275
+ const cameraWithZone = suggestion.sourceCameras?.[0];
40276
+ const camera = cameraWithZone ? topology.cameras.find(c => c.deviceId === cameraWithZone || c.name === cameraWithZone) : null;
40277
+ // Create a default polygon near the camera or at a default location
40278
+ let centerX = 300;
40279
+ let centerY = 200;
40280
+ if (camera?.floorPlanPosition) {
40281
+ centerX = camera.floorPlanPosition.x;
40282
+ centerY = camera.floorPlanPosition.y + 80;
40283
+ }
40284
+ // Create a rectangular zone (user can edit later)
40285
+ const size = 100;
40286
+ const drawnZone = {
40287
+ id: `zone_${Date.now()}`,
40288
+ name: zone.name,
40289
+ type: (zone.type || 'custom'),
40290
+ description: zone.description,
40291
+ polygon: [
40292
+ { x: centerX - size / 2, y: centerY - size / 2 },
40293
+ { x: centerX + size / 2, y: centerY - size / 2 },
40294
+ { x: centerX + size / 2, y: centerY + size / 2 },
40295
+ { x: centerX - size / 2, y: centerY + size / 2 },
40296
+ ],
40297
+ };
40298
+ if (!topology.drawnZones) {
40299
+ topology.drawnZones = [];
40300
+ }
40301
+ topology.drawnZones.push(drawnZone);
40302
+ updated = true;
40303
+ this.console.log(`[Discovery] Added zone: ${zone.name} (${zone.type})`);
40231
40304
  }
40232
40305
  if (suggestion.type === 'connection' && suggestion.connection) {
40233
40306
  // Add new connection to topology
40234
40307
  const conn = suggestion.connection;
40308
+ // Ensure cameras have floor plan positions for visibility
40309
+ const fromCamera = topology.cameras.find(c => c.deviceId === conn.fromCameraId || c.name === conn.fromCameraId);
40310
+ const toCamera = topology.cameras.find(c => c.deviceId === conn.toCameraId || c.name === conn.toCameraId);
40311
+ // Auto-assign floor plan positions if missing
40312
+ if (fromCamera && !fromCamera.floorPlanPosition) {
40313
+ const idx = topology.cameras.indexOf(fromCamera);
40314
+ fromCamera.floorPlanPosition = {
40315
+ x: 150 + (idx % 3) * 200,
40316
+ y: 150 + Math.floor(idx / 3) * 150,
40317
+ };
40318
+ this.console.log(`[Discovery] Auto-positioned camera: ${fromCamera.name}`);
40319
+ }
40320
+ if (toCamera && !toCamera.floorPlanPosition) {
40321
+ const idx = topology.cameras.indexOf(toCamera);
40322
+ toCamera.floorPlanPosition = {
40323
+ x: 150 + (idx % 3) * 200,
40324
+ y: 150 + Math.floor(idx / 3) * 150,
40325
+ };
40326
+ this.console.log(`[Discovery] Auto-positioned camera: ${toCamera.name}`);
40327
+ }
40235
40328
  const newConnection = {
40236
40329
  id: `conn_${Date.now()}`,
40237
- fromCameraId: conn.fromCameraId,
40238
- toCameraId: conn.toCameraId,
40330
+ fromCameraId: fromCamera?.deviceId || conn.fromCameraId,
40331
+ toCameraId: toCamera?.deviceId || conn.toCameraId,
40239
40332
  bidirectional: conn.bidirectional,
40240
40333
  // Default exit/entry zones covering full frame
40241
40334
  exitZone: [[0, 0], [100, 0], [100, 100], [0, 100]],
@@ -40249,7 +40342,7 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
40249
40342
  };
40250
40343
  topology.connections.push(newConnection);
40251
40344
  updated = true;
40252
- this.console.log(`[Discovery] Added connection: ${conn.fromCameraId} -> ${conn.toCameraId}`);
40345
+ this.console.log(`[Discovery] Added connection: ${fromCamera?.name || conn.fromCameraId} -> ${toCamera?.name || conn.toCameraId}`);
40253
40346
  }
40254
40347
  if (updated) {
40255
40348
  // Save updated topology
@@ -42298,6 +42391,12 @@ exports.EDITOR_HTML = `<!DOCTYPE html>
42298
42391
  for (let i = 1; i < currentZonePoints.length; i++) {
42299
42392
  ctx.lineTo(currentZonePoints[i].x, currentZonePoints[i].y);
42300
42393
  }
42394
+ // Close the polygon if we have 3+ points
42395
+ if (currentZonePoints.length >= 3) {
42396
+ ctx.closePath();
42397
+ ctx.fillStyle = color;
42398
+ ctx.fill();
42399
+ }
42301
42400
  ctx.strokeStyle = strokeColor;
42302
42401
  ctx.lineWidth = 2;
42303
42402
  ctx.stroke();