@7365admin1/core 3.85.0 → 3.86.0

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/index.js CHANGED
@@ -79,6 +79,8 @@ __export(src_exports, {
79
79
  DEFAULT_RING_SECONDS: () => DEFAULT_RING_SECONDS,
80
80
  DEFAULT_SCALE_M_PER_PX: () => DEFAULT_SCALE_M_PER_PX,
81
81
  DEFAULT_SITE_TIMEZONE: () => DEFAULT_SITE_TIMEZONE,
82
+ DEMO_BRAND: () => DEMO_BRAND,
83
+ DEMO_CAPABILITIES: () => DEMO_CAPABILITIES,
82
84
  DEVICE_STATUS: () => DEVICE_STATUS,
83
85
  DOBStatus: () => DOBStatus,
84
86
  DUPLICATE_TERMS_VERSION_MESSAGE: () => DUPLICATE_TERMS_VERSION_MESSAGE,
@@ -300,6 +302,7 @@ __export(src_exports, {
300
302
  VisitorStatus: () => VisitorStatus,
301
303
  WALL_HEIGHT_M: () => WALL_HEIGHT_M,
302
304
  WALL_THICKNESS_M: () => WALL_THICKNESS_M,
305
+ adapterForRobot: () => adapterForRobot,
303
306
  addressSchema: () => addressSchema,
304
307
  allowedFieldsSite: () => allowedFieldsSite,
305
308
  allowedNatures: () => allowedNatures,
@@ -339,13 +342,14 @@ __export(src_exports, {
339
342
  chatSchema: () => chatSchema,
340
343
  claimReadsAsStale: () => claimReadsAsStale,
341
344
  clampPtzSpeed: () => clampPtzSpeed,
342
- clearRobotAdapters: () => clearRobotAdapters,
345
+ clearRobotBrands: () => clearRobotBrands,
343
346
  clockDriftSeconds: () => clockDriftSeconds,
344
347
  commandSupported: () => commandSupported,
345
348
  consoleRoleAllows: () => consoleRoleAllows,
346
349
  consoleRoleAllowsAll: () => consoleRoleAllowsAll,
347
350
  consoleSpellings: () => consoleSpellings,
348
351
  console_audit_namespace_collection: () => console_audit_namespace_collection,
352
+ createDemoAdapter: () => createDemoAdapter,
349
353
  createLiftSimulator: () => createLiftSimulator,
350
354
  createManpowerRemarksDaily: () => createManpowerRemarksDaily,
351
355
  createOunitechAdapter: () => createOunitechAdapter,
@@ -369,6 +373,7 @@ __export(src_exports, {
369
373
  disconnectedState: () => disconnectedState,
370
374
  emitNotificationCreated: () => emitNotificationCreated,
371
375
  emptyPersonalEmergencyChain: () => emptyPersonalEmergencyChain,
376
+ encodeGreyscalePng: () => encodeGreyscalePng,
372
377
  encodeHidPacsCard: () => encodeHidPacsCard,
373
378
  entitledSiteScope: () => entitledSiteScope,
374
379
  events_namespace_collection: () => events_namespace_collection,
@@ -387,6 +392,7 @@ __export(src_exports, {
387
392
  getIO: () => getIO,
388
393
  getSessionIdFromRequest: () => getSessionIdFromRequest,
389
394
  grabWithSubStreamFallback: () => grabWithSubStreamFallback,
395
+ greyscalePngDataUri: () => greyscalePngDataUri,
390
396
  guests_namespace_collection: () => guests_namespace_collection,
391
397
  hasAnyCapability: () => hasAnyCapability,
392
398
  hasAnyPermission: () => hasAnyPermission,
@@ -488,8 +494,9 @@ __export(src_exports, {
488
494
  recordConsoleAction: () => recordConsoleAction,
489
495
  refuseServiceProviderInviteAction: () => refuseServiceProviderInviteAction,
490
496
  registerCameraTransport: () => registerCameraTransport,
497
+ registerDemoBrand: () => registerDemoBrand,
491
498
  registerOunitechBrand: () => registerOunitechBrand,
492
- registerRobotAdapter: () => registerRobotAdapter,
499
+ registerRobotBrand: () => registerRobotBrand,
493
500
  registeredRobotBrands: () => registeredRobotBrands,
494
501
  relayForRecorder: () => relayForRecorder,
495
502
  remarksSchema: () => remarksSchema,
@@ -508,7 +515,7 @@ __export(src_exports, {
508
515
  resolveSiteTimezone: () => resolveSiteTimezone,
509
516
  resolveStaffRole: () => resolveStaffRole,
510
517
  robotActionsFor: () => robotActionsFor,
511
- robotAdapterFor: () => robotAdapterFor,
518
+ robotBrandFor: () => robotBrandFor,
512
519
  robotCloudBody: () => robotCloudBody,
513
520
  robotCloudSign: () => robotCloudSign,
514
521
  robotCloudSigningString: () => robotCloudSigningString,
@@ -20390,8 +20397,8 @@ function useDahuaService() {
20390
20397
  }
20391
20398
  }
20392
20399
  }
20393
- async function handleBuffer(chunk, onDetected2) {
20394
- partialBuffer = Buffer.concat([partialBuffer, chunk]);
20400
+ async function handleBuffer(chunk2, onDetected2) {
20401
+ partialBuffer = Buffer.concat([partialBuffer, chunk2]);
20395
20402
  while (true) {
20396
20403
  const boundaryIndex = partialBuffer.indexOf(Buffer.from(boundary));
20397
20404
  if (boundaryIndex === -1)
@@ -20642,12 +20649,12 @@ function useDahuaService() {
20642
20649
  resolve("aborted");
20643
20650
  };
20644
20651
  signal.addEventListener("abort", onAbort, { once: true });
20645
- response.res.on("data", (chunk) => {
20652
+ response.res.on("data", (chunk2) => {
20646
20653
  if (signal.aborted) {
20647
20654
  response.res.destroy();
20648
20655
  return;
20649
20656
  }
20650
- bufferQueue.enqueue(chunk);
20657
+ bufferQueue.enqueue(chunk2);
20651
20658
  });
20652
20659
  response.res.on("end", () => {
20653
20660
  signal.removeEventListener("abort", onAbort);
@@ -23251,10 +23258,10 @@ function buildAdapter(params) {
23251
23258
  return;
23252
23259
  let buffered = "";
23253
23260
  const decoder = new TextDecoder();
23254
- for await (const chunk of body) {
23261
+ for await (const chunk2 of body) {
23255
23262
  if (params2.signal.aborted)
23256
23263
  return;
23257
- buffered += decoder.decode(chunk, { stream: true });
23264
+ buffered += decoder.decode(chunk2, { stream: true });
23258
23265
  let at2 = buffered.indexOf(marker);
23259
23266
  while (at2 >= 0) {
23260
23267
  const next = buffered.indexOf(marker, at2 + marker.length);
@@ -23566,16 +23573,16 @@ function useCameraViewService() {
23566
23573
  () => finish(new Error("camera-timeout")),
23567
23574
  CAMERA_RTSP_TIMEOUT_MS
23568
23575
  );
23569
- child.stdout.on("data", (chunk) => {
23570
- bytes += chunk.length;
23576
+ child.stdout.on("data", (chunk2) => {
23577
+ bytes += chunk2.length;
23571
23578
  if (bytes > CAMERA_SNAPSHOT_MAX_BYTES) {
23572
23579
  finish(new Error("camera-picture-too-large"));
23573
23580
  return;
23574
23581
  }
23575
- chunks.push(chunk);
23582
+ chunks.push(chunk2);
23576
23583
  });
23577
- child.stderr?.on("data", (chunk) => {
23578
- const text2 = carry + chunk.toString("latin1");
23584
+ child.stderr?.on("data", (chunk2) => {
23585
+ const text2 = carry + chunk2.toString("latin1");
23579
23586
  if (/401\s*unauthorized|\bunauthorized\b/i.test(text2))
23580
23587
  rejected = true;
23581
23588
  carry = text2.slice(-32);
@@ -32871,8 +32878,8 @@ function useOccurrenceBookRepo() {
32871
32878
  const chunkSize = 500;
32872
32879
  let totalInserted = 0;
32873
32880
  for (let i = 0; i < payloads.length; i += chunkSize) {
32874
- const chunk = payloads.slice(i, i + chunkSize);
32875
- const operations = chunk.map((item) => ({
32881
+ const chunk2 = payloads.slice(i, i + chunkSize);
32882
+ const operations = chunk2.map((item) => ({
32876
32883
  insertOne: {
32877
32884
  document: {
32878
32885
  ...item,
@@ -52167,9 +52174,9 @@ function useVisitorTransactionService() {
52167
52174
  expectedCheckIn
52168
52175
  } = value;
52169
52176
  for (let i = 0; i < value.members.length; i += chunkSize) {
52170
- const chunk = value.members.slice(i, i + chunkSize);
52177
+ const chunk2 = value.members.slice(i, i + chunkSize);
52171
52178
  await Promise.all(
52172
- chunk.map(async (member) => {
52179
+ chunk2.map(async (member) => {
52173
52180
  const clonedMember = structuredClone(member);
52174
52181
  const { visitorPass, passKeys, nric: nric2, name: name2, contact: contact2 } = clonedMember;
52175
52182
  if (/^(guest|walk-in|contractor|delivery)$/.test(type2 ?? "invalid") && site2) {
@@ -52411,9 +52418,9 @@ function useVisitorTransactionService() {
52411
52418
  unitName
52412
52419
  } = value;
52413
52420
  for (let i = 0; i < value.members.length; i += chunkSize) {
52414
- const chunk = value.members.slice(i, i + chunkSize);
52421
+ const chunk2 = value.members.slice(i, i + chunkSize);
52415
52422
  await Promise.all(
52416
- chunk.map(async (member) => {
52423
+ chunk2.map(async (member) => {
52417
52424
  const clonedMember = structuredClone(member);
52418
52425
  const { visitorPass, passKeys, nric: nric2, name: name2, contact: contact2 } = clonedMember;
52419
52426
  if (/^(guest|walk-in|contractor|delivery)$/.test(type2 ?? "invalid") && site2) {
@@ -56870,8 +56877,8 @@ function useAccessManagementSvc() {
56870
56877
  return new Promise((resolve, reject) => {
56871
56878
  const fileStream = import_stream.Readable.from(bufferFile);
56872
56879
  let fileBuffer = Buffer.alloc(0);
56873
- fileStream.on("data", (chunk) => {
56874
- fileBuffer = Buffer.concat([fileBuffer, chunk]);
56880
+ fileStream.on("data", (chunk2) => {
56881
+ fileBuffer = Buffer.concat([fileBuffer, chunk2]);
56875
56882
  });
56876
56883
  fileStream.on("end", () => {
56877
56884
  try {
@@ -59710,10 +59717,10 @@ function parseRobotCommand(body) {
59710
59717
  function createSseParser() {
59711
59718
  let buffer = "";
59712
59719
  let dataLines = [];
59713
- function push(chunk) {
59714
- if (!chunk)
59720
+ function push(chunk2) {
59721
+ if (!chunk2)
59715
59722
  return { events: [], sawActivity: false };
59716
- buffer += chunk;
59723
+ buffer += chunk2;
59717
59724
  const events = [];
59718
59725
  buffer = buffer.replace(/\r\n|\r/g, "\n");
59719
59726
  const lines = buffer.split("\n");
@@ -59979,26 +59986,44 @@ function commandSupported(capabilities, kind) {
59979
59986
  return false;
59980
59987
  return supports(capabilities, needed);
59981
59988
  }
59982
- var adapters = /* @__PURE__ */ new Map();
59983
- function registerRobotAdapter(adapter) {
59984
- if (!adapter?.brand)
59985
- throw new Error("A robot adapter must name its brand.");
59986
- adapters.set(adapter.brand, adapter);
59989
+ var brands = /* @__PURE__ */ new Map();
59990
+ function registerRobotBrand(brand) {
59991
+ if (!brand?.brand)
59992
+ throw new Error("A robot brand must name itself.");
59993
+ if (typeof brand.create !== "function") {
59994
+ throw new Error(`Robot brand "${brand.brand}" has no way to build an adapter.`);
59995
+ }
59996
+ brands.set(brand.brand, brand);
59987
59997
  }
59988
- function robotAdapterFor(brand) {
59998
+ function robotBrandFor(brand) {
59989
59999
  if (typeof brand !== "string" || !brand)
59990
60000
  return null;
59991
- return adapters.get(brand) ?? null;
60001
+ return brands.get(brand) ?? null;
60002
+ }
60003
+ function adapterForRobot(robot) {
60004
+ const brandName = (robot?.brand ?? "").trim();
60005
+ const brand = robotBrandFor(brandName);
60006
+ if (!brand) {
60007
+ throw new Error(
60008
+ brandName ? `No robot adapter is registered for the brand "${brandName}".` : `Robot "${robot?.name ?? "unknown"}" does not say which brand it is.`
60009
+ );
60010
+ }
60011
+ const appid = (robot.appKey ?? "").trim();
60012
+ const secret = (robot.appToken ?? "").trim();
60013
+ if (!appid || !secret) {
60014
+ throw new Error(`Robot "${robot?.name ?? brandName}" has no cloud credentials saved.`);
60015
+ }
60016
+ return brand.create({ appid, secret });
59992
60017
  }
59993
60018
  function registeredRobotBrands() {
59994
- return [...adapters.values()].map((a) => ({
59995
- brand: a.brand,
59996
- displayName: a.displayName,
59997
- tier: robotTier(a.capabilities)
60019
+ return [...brands.values()].map((b) => ({
60020
+ brand: b.brand,
60021
+ displayName: b.displayName,
60022
+ tier: robotTier(b.capabilities)
59998
60023
  }));
59999
60024
  }
60000
- function clearRobotAdapters() {
60001
- adapters.clear();
60025
+ function clearRobotBrands() {
60026
+ brands.clear();
60002
60027
  }
60003
60028
 
60004
60029
  // src/utils/lift-contract.util.ts
@@ -60454,122 +60479,6 @@ function useRobotCloudService(credentials, options = {}) {
60454
60479
  };
60455
60480
  }
60456
60481
 
60457
- // src/services/robot-fleet.service.ts
60458
- function credentialsOf(robot) {
60459
- const appid = (robot.appKey ?? "").trim();
60460
- const secret = (robot.appToken ?? "").trim();
60461
- if (!appid || !secret) {
60462
- throw new import_node_server_utils157.BadRequestError(
60463
- `Robot "${robot.name}" has no cloud credentials saved.`
60464
- );
60465
- }
60466
- return { appid, secret };
60467
- }
60468
- function useRobotFleetService(deps) {
60469
- const cloudFor = deps?.cloudFor ?? useRobotCloudService;
60470
- function clientFor(robot) {
60471
- return cloudFor(credentialsOf(robot));
60472
- }
60473
- async function resolveRobotSn(robot) {
60474
- const stored = (robot.robotSn ?? "").trim();
60475
- if (stored)
60476
- return stored;
60477
- const fleet = await clientFor(robot).getRobots();
60478
- if (fleet.length === 0) {
60479
- throw new import_node_server_utils157.NotFoundError(
60480
- `Robot "${robot.name}": the cloud reports no robots for these credentials.`
60481
- );
60482
- }
60483
- if (fleet.length > 1) {
60484
- throw new import_node_server_utils157.BadRequestError(
60485
- `Robot "${robot.name}": these credentials cover ${fleet.length} robots, so the serial cannot be determined automatically. Set it explicitly.`
60486
- );
60487
- }
60488
- const robotSn = fleet[0].robotSn;
60489
- if (!robotSn) {
60490
- throw new import_node_server_utils157.BadRequestError(
60491
- `Robot "${robot.name}": the cloud returned a robot with no serial.`
60492
- );
60493
- }
60494
- const id = robot._id ? String(robot._id) : "";
60495
- if (id && deps?.saveRobotSn) {
60496
- await deps.saveRobotSn(id, robotSn).catch(() => void 0);
60497
- }
60498
- return robotSn;
60499
- }
60500
- async function getFleetRobot(robot) {
60501
- const robotSn = await resolveRobotSn(robot);
60502
- const fleet = await clientFor(robot).getRobots();
60503
- const match = fleet.find(
60504
- (r) => r.robotSn === robotSn
60505
- );
60506
- if (!match) {
60507
- return {
60508
- id: robot._id ? String(robot._id) : "",
60509
- name: robot.name,
60510
- robotSn,
60511
- statusFlag: null,
60512
- statusName: "Not found in the robot cloud",
60513
- mode: "UNAVAILABLE",
60514
- online: false,
60515
- dispatchable: false
60516
- };
60517
- }
60518
- const mode = robotModeFromList(match);
60519
- return {
60520
- id: robot._id ? String(robot._id) : "",
60521
- name: robot.name,
60522
- robotSn,
60523
- statusFlag: typeof match.statusFlag === "number" ? match.statusFlag : null,
60524
- statusName: match.statusName ?? "",
60525
- mode,
60526
- online: match.onlineFlag !== false,
60527
- dispatchable: mode === "IDLE" || mode === "CHARGED"
60528
- };
60529
- }
60530
- async function getMaps(robot) {
60531
- const robotSn = await resolveRobotSn(robot);
60532
- return clientFor(robot).getMaps(robotSn);
60533
- }
60534
- async function getMapImages(robot, fileKeys) {
60535
- const robotSn = await resolveRobotSn(robot);
60536
- return clientFor(robot).getFileUrls(robotSn, fileKeys);
60537
- }
60538
- async function dispatch(robot, command) {
60539
- const robotSn = await resolveRobotSn(robot);
60540
- const cloud = clientFor(robot);
60541
- if (command.kind === "return") {
60542
- return cloud.returnToBase(robotSn);
60543
- }
60544
- const state = await getFleetRobot(robot);
60545
- if (!state.dispatchable) {
60546
- throw new import_node_server_utils157.BadRequestError(
60547
- `Robot "${robot.name}" is ${state.statusName || state.mode} and cannot take a new job.`
60548
- );
60549
- }
60550
- switch (command.kind) {
60551
- case "full":
60552
- return cloud.startFullCleaning(robotSn, command.options);
60553
- case "area":
60554
- return cloud.startAreaCleaning(robotSn, command.areas, command.options);
60555
- case "spot":
60556
- return cloud.startSpotCleaning(robotSn, command.points, command.options);
60557
- }
60558
- }
60559
- async function getPlans(robot) {
60560
- const robotSn = await resolveRobotSn(robot);
60561
- return clientFor(robot).getPlans(robotSn);
60562
- }
60563
- return {
60564
- resolveRobotSn,
60565
- getFleetRobot,
60566
- getMaps,
60567
- getMapImages,
60568
- dispatch,
60569
- getPlans
60570
- };
60571
- }
60572
-
60573
60482
  // src/services/ounitech.adapter.ts
60574
60483
  var OUNITECH_BRAND = "ounitech";
60575
60484
  var OUNITECH_CAPABILITIES = {
@@ -60673,25 +60582,412 @@ function createOunitechAdapter(credentials, options) {
60673
60582
  async getMaps(robotSn) {
60674
60583
  return cloud.getMaps(robotSn);
60675
60584
  },
60585
+ async getMapImages(robotSn, fileKeys) {
60586
+ return cloud.getFileUrls(robotSn, fileKeys);
60587
+ },
60676
60588
  async getPlans(robotSn) {
60677
60589
  return cloud.getPlans(robotSn);
60678
60590
  }
60679
60591
  };
60680
60592
  }
60681
60593
  function registerOunitechBrand() {
60682
- const unconfigured = () => {
60683
- throw new Error(
60684
- "No Ounitech credentials for this robot. Add the app key and token on the robot's record."
60685
- );
60686
- };
60687
- registerRobotAdapter({
60594
+ registerRobotBrand({
60688
60595
  brand: OUNITECH_BRAND,
60689
60596
  displayName: "Ounitech (Q3-G / Q3-W)",
60690
60597
  capabilities: OUNITECH_CAPABILITIES,
60691
- listRobots: async () => unconfigured(),
60692
- getRobot: async () => unconfigured()
60598
+ create: (credentials) => createOunitechAdapter(credentials)
60693
60599
  });
60694
60600
  }
60601
+ registerOunitechBrand();
60602
+
60603
+ // src/utils/png-encoder.util.ts
60604
+ var import_zlib = require("zlib");
60605
+ function crc32(buffer) {
60606
+ let crc = ~0;
60607
+ for (let i = 0; i < buffer.length; i++) {
60608
+ crc ^= buffer[i];
60609
+ for (let bit = 0; bit < 8; bit++) {
60610
+ crc = crc & 1 ? crc >>> 1 ^ 3988292384 : crc >>> 1;
60611
+ }
60612
+ }
60613
+ return ~crc >>> 0;
60614
+ }
60615
+ function chunk(type, data) {
60616
+ const length = Buffer.alloc(4);
60617
+ length.writeUInt32BE(data.length, 0);
60618
+ const typed = Buffer.concat([Buffer.from(type, "ascii"), data]);
60619
+ const crc = Buffer.alloc(4);
60620
+ crc.writeUInt32BE(crc32(typed), 0);
60621
+ return Buffer.concat([length, typed, crc]);
60622
+ }
60623
+ function encodeGreyscalePng(pixels, width, height) {
60624
+ if (width <= 0 || height <= 0)
60625
+ throw new Error("A PNG needs a positive size.");
60626
+ if (pixels.length < width * height) {
60627
+ throw new Error("A PNG needs one byte per pixel.");
60628
+ }
60629
+ const raw = Buffer.alloc((width + 1) * height);
60630
+ for (let y = 0; y < height; y++) {
60631
+ raw[y * (width + 1)] = 0;
60632
+ for (let x = 0; x < width; x++) {
60633
+ raw[y * (width + 1) + 1 + x] = pixels[y * width + x];
60634
+ }
60635
+ }
60636
+ const ihdr = Buffer.alloc(13);
60637
+ ihdr.writeUInt32BE(width, 0);
60638
+ ihdr.writeUInt32BE(height, 4);
60639
+ ihdr[8] = 8;
60640
+ ihdr[9] = 0;
60641
+ ihdr[10] = 0;
60642
+ ihdr[11] = 0;
60643
+ ihdr[12] = 0;
60644
+ return Buffer.concat([
60645
+ Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
60646
+ chunk("IHDR", ihdr),
60647
+ chunk("IDAT", (0, import_zlib.deflateSync)(raw)),
60648
+ chunk("IEND", Buffer.alloc(0))
60649
+ ]);
60650
+ }
60651
+ function greyscalePngDataUri(pixels, width, height) {
60652
+ return `data:image/png;base64,${encodeGreyscalePng(pixels, width, height).toString("base64")}`;
60653
+ }
60654
+
60655
+ // src/services/demo.adapter.ts
60656
+ var DEMO_BRAND = "demo";
60657
+ var DEMO_CAPABILITIES = {
60658
+ readLocation: true,
60659
+ navigateToPoint: true,
60660
+ pauseResume: true,
60661
+ pathAndEta: true,
60662
+ battery: true,
60663
+ sendToDock: true,
60664
+ relocalize: true,
60665
+ startProcess: true,
60666
+ pauseProcess: true,
60667
+ processCompletion: true,
60668
+ floorReporting: true,
60669
+ multiFloor: true
60670
+ };
60671
+ var SCALE = 0.05;
60672
+ var PLAN_W = 260;
60673
+ var PLAN_H = 180;
60674
+ var DEMO_ROBOTS = [
60675
+ { robotSn: "DEMO-0001", name: "Demo Cleaner 01", model: "Demo S1", floorNo: 1, phase: 0 },
60676
+ { robotSn: "DEMO-0002", name: "Demo Cleaner 02", model: "Demo S1", floorNo: 1, phase: 0.37 },
60677
+ { robotSn: "DEMO-0003", name: "Demo Scrubber 03", model: "Demo X2", floorNo: 2, phase: 0.68 }
60678
+ ];
60679
+ var commanded = /* @__PURE__ */ new Map();
60680
+ function demoPlanPixels(floorNo) {
60681
+ const px = new Uint8Array(PLAN_W * PLAN_H).fill(255);
60682
+ const set = (x, y) => {
60683
+ if (x >= 0 && x < PLAN_W && y >= 0 && y < PLAN_H)
60684
+ px[y * PLAN_W + x] = 0;
60685
+ };
60686
+ for (let x = 0; x < PLAN_W; x++) {
60687
+ for (const t of [0, 1]) {
60688
+ set(x, t);
60689
+ set(x, PLAN_H - 1 - t);
60690
+ }
60691
+ }
60692
+ for (let y = 0; y < PLAN_H; y++) {
60693
+ for (const t of [0, 1]) {
60694
+ set(t, y);
60695
+ set(PLAN_W - 1 - t, y);
60696
+ }
60697
+ }
60698
+ const wallX = Math.floor(PLAN_W * (floorNo === 1 ? 0.45 : 0.6));
60699
+ for (let y = 2; y < PLAN_H - 2; y++) {
60700
+ if (y > PLAN_H * 0.45 && y < PLAN_H * 0.6)
60701
+ continue;
60702
+ set(wallX, y);
60703
+ set(wallX + 1, y);
60704
+ }
60705
+ if (floorNo === 2) {
60706
+ const wallY = Math.floor(PLAN_H * 0.5);
60707
+ for (let x = 2; x < wallX; x++) {
60708
+ if (x > PLAN_W * 0.15 && x < PLAN_W * 0.25)
60709
+ continue;
60710
+ set(x, wallY);
60711
+ set(x, wallY + 1);
60712
+ }
60713
+ }
60714
+ return px;
60715
+ }
60716
+ function demoPosition(robot, now) {
60717
+ const t = (now / 9e4 + robot.phase) % 1;
60718
+ const angle = t * Math.PI * 2;
60719
+ const cx = PLAN_W * 0.5 * SCALE;
60720
+ const cy = PLAN_H * 0.5 * SCALE;
60721
+ const rx = PLAN_W * 0.28 * SCALE;
60722
+ const ry = PLAN_H * 0.28 * SCALE;
60723
+ return {
60724
+ x: cx + Math.cos(angle) * rx,
60725
+ y: cy + Math.sin(angle) * ry,
60726
+ // Facing along the direction of travel, which is the tangent.
60727
+ theta: angle + Math.PI / 2
60728
+ };
60729
+ }
60730
+ function demoBattery(robot, now) {
60731
+ const t = (now / 36e5 + robot.phase) % 1;
60732
+ return Math.round(t < 0.8 ? 100 - t / 0.8 * 75 : 25 + (t - 0.8) / 0.2 * 75);
60733
+ }
60734
+ function demoState(robot, now) {
60735
+ const order = commanded.get(robot.robotSn);
60736
+ const active = order && order.until > now ? order.mode : null;
60737
+ const battery = demoBattery(robot, now);
60738
+ const mode = battery <= 25 ? "CHARGING_CYCLE" : active ?? "IDLE";
60739
+ return {
60740
+ robotSn: robot.robotSn,
60741
+ name: robot.name,
60742
+ model: robot.model,
60743
+ online: true,
60744
+ mode,
60745
+ statusName: mode === "WORKING" ? "Cleaning" : mode === "CHARGING_CYCLE" ? "Charging" : "Idle",
60746
+ batteryPercent: battery,
60747
+ floorNo: robot.floorNo,
60748
+ // The demo DOES know its floor - unlike Ounitech today - so the map can
60749
+ // draw it. That contrast is part of what the demo shows.
60750
+ floorKnown: true,
60751
+ position: demoPosition(robot, now)
60752
+ };
60753
+ }
60754
+ function createDemoAdapter(options) {
60755
+ const now = options?.now ?? Date.now;
60756
+ return {
60757
+ brand: DEMO_BRAND,
60758
+ displayName: "Demo robots (simulated)",
60759
+ capabilities: DEMO_CAPABILITIES,
60760
+ async listRobots() {
60761
+ const at2 = now();
60762
+ return DEMO_ROBOTS.map((r) => demoState(r, at2));
60763
+ },
60764
+ async getRobot(robotSn) {
60765
+ const robot = DEMO_ROBOTS.find((r) => r.robotSn === robotSn);
60766
+ return robot ? demoState(robot, now()) : null;
60767
+ },
60768
+ async sendCommand(robotSn, command) {
60769
+ const robot = DEMO_ROBOTS.find((r) => r.robotSn === robotSn);
60770
+ if (!robot)
60771
+ throw new Error(`No demo robot with serial ${robotSn}.`);
60772
+ const at2 = now();
60773
+ switch (command.kind) {
60774
+ case "full":
60775
+ case "area":
60776
+ case "spot":
60777
+ commanded.set(robotSn, { mode: "WORKING", until: at2 + 10 * 6e4 });
60778
+ return;
60779
+ case "return":
60780
+ commanded.set(robotSn, { mode: "CHARGING_CYCLE", until: at2 + 5 * 6e4 });
60781
+ return;
60782
+ case "pause":
60783
+ commanded.set(robotSn, { mode: "PAUSED", until: at2 + 10 * 6e4 });
60784
+ return;
60785
+ case "resume":
60786
+ commanded.set(robotSn, { mode: "WORKING", until: at2 + 10 * 6e4 });
60787
+ return;
60788
+ case "cancel":
60789
+ commanded.delete(robotSn);
60790
+ return;
60791
+ case "navigate":
60792
+ case "relocalize":
60793
+ commanded.set(robotSn, { mode: "WORKING", until: at2 + 6e4 });
60794
+ return;
60795
+ }
60796
+ },
60797
+ async getMaps() {
60798
+ return DEMO_ROBOTS.map((r) => r.floorNo).filter((floorNo, i, all) => all.indexOf(floorNo) === i).sort((a, b) => a - b).map((floorNo) => ({
60799
+ floorNo,
60800
+ mapName: `Demo Building L${floorNo}`,
60801
+ backgroundFileKey: `demo/L${floorNo}.png`,
60802
+ partitionFileKey: `demo/L${floorNo}.png`,
60803
+ originX: 0,
60804
+ originY: 0,
60805
+ resolution: SCALE,
60806
+ totalArea: Math.round(PLAN_W * PLAN_H * SCALE * SCALE),
60807
+ baseX: 12,
60808
+ baseY: 3,
60809
+ baseTheta: 0,
60810
+ createTime: (/* @__PURE__ */ new Date(0)).toISOString(),
60811
+ areaList: [
60812
+ { areaName: `L${floorNo}-LOBBY`, rgba: "FF0B0B83", x: 60, y: 60 },
60813
+ { areaName: `L${floorNo}-OFFICE`, rgba: "B7CAEFFF", x: 190, y: 60 },
60814
+ { areaName: `L${floorNo}-CORRIDOR`, rgba: "BA144BFF", x: 120, y: 140 }
60815
+ ]
60816
+ }));
60817
+ },
60818
+ /**
60819
+ * Data URIs rather than signed links.
60820
+ *
60821
+ * A demo must work with no storage account and no network, and a `data:`
60822
+ * URI is same-origin so the canvas is not tainted — which matters, because
60823
+ * the layout is built by reading these pixels back.
60824
+ */
60825
+ async getMapImages(_robotSn, fileKeys) {
60826
+ return fileKeys.map((fileKey) => {
60827
+ const floorNo = fileKey.includes("L2") ? 2 : 1;
60828
+ return {
60829
+ fileKey,
60830
+ url: greyscalePngDataUri(demoPlanPixels(floorNo), PLAN_W, PLAN_H)
60831
+ };
60832
+ });
60833
+ },
60834
+ async getPlans() {
60835
+ return [
60836
+ {
60837
+ planId: 1,
60838
+ dateList: [1, 2, 3, 4, 5],
60839
+ startTime: "08:00",
60840
+ endTime: "10:00",
60841
+ enableFlag: true,
60842
+ jobList: [
60843
+ {
60844
+ floorNo: 1,
60845
+ rangeFlag: 3,
60846
+ cleanTimes: 1,
60847
+ pointList: [],
60848
+ colorList: [{ r: 255, g: 11, b: 11, a: 131 }],
60849
+ modeFlag: 1,
60850
+ subModeFlag: 0,
60851
+ levelFlag: 1
60852
+ }
60853
+ ]
60854
+ }
60855
+ ];
60856
+ }
60857
+ };
60858
+ }
60859
+ function registerDemoBrand() {
60860
+ registerRobotBrand({
60861
+ brand: DEMO_BRAND,
60862
+ displayName: "Demo robots (simulated)",
60863
+ capabilities: DEMO_CAPABILITIES,
60864
+ create: () => createDemoAdapter()
60865
+ });
60866
+ }
60867
+ registerDemoBrand();
60868
+
60869
+ // src/services/robot-fleet.service.ts
60870
+ function useRobotFleetService(deps) {
60871
+ const adapterFor = deps?.adapterFor ?? ((robot) => adapterForRobot(robot));
60872
+ function capabilitiesOf(robot) {
60873
+ return robotBrandFor(robot.brand ?? "")?.capabilities ?? {};
60874
+ }
60875
+ async function resolveRobotSn(robot) {
60876
+ const stored = (robot.robotSn ?? "").trim();
60877
+ if (stored)
60878
+ return stored;
60879
+ const fleet = await adapterFor(robot).listRobots();
60880
+ if (fleet.length === 0) {
60881
+ throw new import_node_server_utils157.NotFoundError(
60882
+ `Robot "${robot.name}": the robot cloud reports no robots for these credentials.`
60883
+ );
60884
+ }
60885
+ if (fleet.length > 1) {
60886
+ throw new import_node_server_utils157.BadRequestError(
60887
+ `Robot "${robot.name}": these credentials cover ${fleet.length} robots, so the serial cannot be determined automatically. Set it explicitly.`
60888
+ );
60889
+ }
60890
+ const robotSn = fleet[0].robotSn;
60891
+ if (!robotSn) {
60892
+ throw new import_node_server_utils157.BadRequestError(`Robot "${robot.name}": the cloud returned a robot with no serial.`);
60893
+ }
60894
+ const id = robot._id ? String(robot._id) : "";
60895
+ if (id && deps?.saveRobotSn) {
60896
+ await deps.saveRobotSn(id, robotSn).catch(() => void 0);
60897
+ }
60898
+ return robotSn;
60899
+ }
60900
+ async function getFleetRobot(robot) {
60901
+ const adapter = adapterFor(robot);
60902
+ const robotSn = await resolveRobotSn(robot);
60903
+ const found = await adapter.getRobot(robotSn);
60904
+ const id = robot._id ? String(robot._id) : "";
60905
+ const brand = adapter.brand;
60906
+ if (!found) {
60907
+ return {
60908
+ id,
60909
+ name: robot.name,
60910
+ brand,
60911
+ robotSn,
60912
+ statusName: "Not found in the robot cloud",
60913
+ mode: "UNAVAILABLE",
60914
+ online: false,
60915
+ dispatchable: false,
60916
+ batteryPercent: null,
60917
+ floorNo: null,
60918
+ floorKnown: false,
60919
+ position: null
60920
+ };
60921
+ }
60922
+ const free = found.mode === "IDLE" || found.mode === "CHARGED";
60923
+ return {
60924
+ id,
60925
+ name: robot.name,
60926
+ brand,
60927
+ robotSn,
60928
+ statusName: found.statusName ?? "",
60929
+ mode: found.mode,
60930
+ online: found.online,
60931
+ dispatchable: found.online && free && commandSupported(adapter.capabilities, "full"),
60932
+ batteryPercent: found.batteryPercent ?? null,
60933
+ floorNo: found.floorNo ?? null,
60934
+ floorKnown: found.floorKnown === true,
60935
+ position: found.position ?? null
60936
+ };
60937
+ }
60938
+ async function getMaps(robot) {
60939
+ const adapter = adapterFor(robot);
60940
+ if (!adapter.getMaps)
60941
+ return [];
60942
+ return adapter.getMaps(await resolveRobotSn(robot));
60943
+ }
60944
+ async function getMapImages(robot, fileKeys) {
60945
+ const adapter = adapterFor(robot);
60946
+ if (!adapter.getMapImages || !fileKeys.length)
60947
+ return [];
60948
+ return adapter.getMapImages(await resolveRobotSn(robot), fileKeys);
60949
+ }
60950
+ async function getPlans(robot) {
60951
+ const adapter = adapterFor(robot);
60952
+ if (!adapter.getPlans)
60953
+ return [];
60954
+ return adapter.getPlans(await resolveRobotSn(robot));
60955
+ }
60956
+ async function dispatch(robot, command) {
60957
+ const adapter = adapterFor(robot);
60958
+ if (!adapter.sendCommand) {
60959
+ throw new import_node_server_utils157.BadRequestError(
60960
+ `${adapter.displayName} robots cannot be commanded through their API.`
60961
+ );
60962
+ }
60963
+ if (!commandSupported(adapter.capabilities, command.kind)) {
60964
+ throw new import_node_server_utils157.BadRequestError(
60965
+ `${adapter.displayName} does not support "${command.kind}" through its API yet.`
60966
+ );
60967
+ }
60968
+ const robotSn = await resolveRobotSn(robot);
60969
+ if (command.kind === "return") {
60970
+ return adapter.sendCommand(robotSn, command);
60971
+ }
60972
+ const state = await getFleetRobot(robot);
60973
+ if (!state.dispatchable) {
60974
+ throw new import_node_server_utils157.BadRequestError(
60975
+ `Robot "${robot.name}" is ${state.statusName || state.mode} and cannot take a new job.`
60976
+ );
60977
+ }
60978
+ return adapter.sendCommand(robotSn, command);
60979
+ }
60980
+ return {
60981
+ adapterFor,
60982
+ capabilitiesOf,
60983
+ resolveRobotSn,
60984
+ getFleetRobot,
60985
+ getMaps,
60986
+ getMapImages,
60987
+ getPlans,
60988
+ dispatch
60989
+ };
60990
+ }
60695
60991
 
60696
60992
  // src/services/robot-tracking.service.ts
60697
60993
  var import_axios4 = __toESM(require("axios"));
@@ -60793,10 +61089,10 @@ function startRobotTracking(options) {
60793
61089
  onEvent({ type: "reconnected", at: now() });
60794
61090
  everConnected = true;
60795
61091
  attempt = 0;
60796
- stream.on("data", (chunk) => {
61092
+ stream.on("data", (chunk2) => {
60797
61093
  if (stopped)
60798
61094
  return;
60799
- const { events, sawActivity } = parser.push(chunk.toString("utf8"));
61095
+ const { events, sawActivity } = parser.push(chunk2.toString("utf8"));
60800
61096
  if (sawActivity)
60801
61097
  lastActivityAt = now();
60802
61098
  for (const event of events) {
@@ -60958,7 +61254,14 @@ function useRobotFleetController(deps) {
60958
61254
  req.on("aborted", close);
60959
61255
  res.on("error", close);
60960
61256
  }
60961
- return { getState, getMaps, getMapImages, getPlans, sendCommand: sendCommand2, trackRobot };
61257
+ async function getBrands(_req, res, next) {
61258
+ try {
61259
+ res.json({ items: registeredRobotBrands() });
61260
+ } catch (error) {
61261
+ fail(next, error);
61262
+ }
61263
+ }
61264
+ return { getState, getMaps, getMapImages, getPlans, sendCommand: sendCommand2, trackRobot, getBrands };
60962
61265
  }
60963
61266
 
60964
61267
  // src/models/patrol-question.model.ts
@@ -94319,6 +94622,8 @@ function usePersonalEmergencyChainController() {
94319
94622
  DEFAULT_RING_SECONDS,
94320
94623
  DEFAULT_SCALE_M_PER_PX,
94321
94624
  DEFAULT_SITE_TIMEZONE,
94625
+ DEMO_BRAND,
94626
+ DEMO_CAPABILITIES,
94322
94627
  DEVICE_STATUS,
94323
94628
  DOBStatus,
94324
94629
  DUPLICATE_TERMS_VERSION_MESSAGE,
@@ -94540,6 +94845,7 @@ function usePersonalEmergencyChainController() {
94540
94845
  VisitorStatus,
94541
94846
  WALL_HEIGHT_M,
94542
94847
  WALL_THICKNESS_M,
94848
+ adapterForRobot,
94543
94849
  addressSchema,
94544
94850
  allowedFieldsSite,
94545
94851
  allowedNatures,
@@ -94579,13 +94885,14 @@ function usePersonalEmergencyChainController() {
94579
94885
  chatSchema,
94580
94886
  claimReadsAsStale,
94581
94887
  clampPtzSpeed,
94582
- clearRobotAdapters,
94888
+ clearRobotBrands,
94583
94889
  clockDriftSeconds,
94584
94890
  commandSupported,
94585
94891
  consoleRoleAllows,
94586
94892
  consoleRoleAllowsAll,
94587
94893
  consoleSpellings,
94588
94894
  console_audit_namespace_collection,
94895
+ createDemoAdapter,
94589
94896
  createLiftSimulator,
94590
94897
  createManpowerRemarksDaily,
94591
94898
  createOunitechAdapter,
@@ -94609,6 +94916,7 @@ function usePersonalEmergencyChainController() {
94609
94916
  disconnectedState,
94610
94917
  emitNotificationCreated,
94611
94918
  emptyPersonalEmergencyChain,
94919
+ encodeGreyscalePng,
94612
94920
  encodeHidPacsCard,
94613
94921
  entitledSiteScope,
94614
94922
  events_namespace_collection,
@@ -94627,6 +94935,7 @@ function usePersonalEmergencyChainController() {
94627
94935
  getIO,
94628
94936
  getSessionIdFromRequest,
94629
94937
  grabWithSubStreamFallback,
94938
+ greyscalePngDataUri,
94630
94939
  guests_namespace_collection,
94631
94940
  hasAnyCapability,
94632
94941
  hasAnyPermission,
@@ -94728,8 +95037,9 @@ function usePersonalEmergencyChainController() {
94728
95037
  recordConsoleAction,
94729
95038
  refuseServiceProviderInviteAction,
94730
95039
  registerCameraTransport,
95040
+ registerDemoBrand,
94731
95041
  registerOunitechBrand,
94732
- registerRobotAdapter,
95042
+ registerRobotBrand,
94733
95043
  registeredRobotBrands,
94734
95044
  relayForRecorder,
94735
95045
  remarksSchema,
@@ -94748,7 +95058,7 @@ function usePersonalEmergencyChainController() {
94748
95058
  resolveSiteTimezone,
94749
95059
  resolveStaffRole,
94750
95060
  robotActionsFor,
94751
- robotAdapterFor,
95061
+ robotBrandFor,
94752
95062
  robotCloudBody,
94753
95063
  robotCloudSign,
94754
95064
  robotCloudSigningString,