@7365admin1/core 3.82.0 → 3.84.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.mjs CHANGED
@@ -58577,6 +58577,7 @@ var robotSchema = Joi74.object({
58577
58577
  // Optional, so every caller written before this field existed still validates.
58578
58578
  type: Joi74.string().valid(...ROBOT_TYPES).optional().allow("", null),
58579
58579
  robotSn: Joi74.string().optional().allow("", null),
58580
+ brand: Joi74.string().optional().allow("", null),
58580
58581
  appKey: Joi74.string().required(),
58581
58582
  appToken: Joi74.string().required(),
58582
58583
  createdBy: Joi74.string().hex().required(),
@@ -58638,6 +58639,7 @@ function MRobot(value) {
58638
58639
  */
58639
58640
  type: value.type || "cleaning",
58640
58641
  robotSn: value.robotSn ?? "",
58642
+ brand: value.brand || "ounitech",
58641
58643
  appKey: value.appKey ?? "",
58642
58644
  appToken: value.appToken ?? "",
58643
58645
  createdBy: value.createdBy ?? "",
@@ -58801,6 +58803,9 @@ function useRobotRepo() {
58801
58803
  // Not a secret - it is the vendor's device serial, and the apps
58802
58804
  // need it to address a robot. The CREDENTIALS stay out.
58803
58805
  robotSn: 1,
58806
+ // Not a secret - it names which protocol the robot speaks, and a
58807
+ // screen needs it to know which controls to draw.
58808
+ brand: 1,
58804
58809
  createdByName: 1,
58805
58810
  createdAt: 1,
58806
58811
  status: 1
@@ -59194,30 +59199,30 @@ function parseRobotCommand(body) {
59194
59199
  if (kind !== "full" && kind !== "area" && kind !== "spot" && kind !== "return") {
59195
59200
  return { ok: false, message: 'kind must be one of "full", "area", "spot", "return".' };
59196
59201
  }
59197
- const has = (k) => Object.prototype.hasOwnProperty.call(b, k);
59202
+ const has2 = (k) => Object.prototype.hasOwnProperty.call(b, k);
59198
59203
  const optionKeys = ["cleanTimes", "modeFlag", "subModeFlag", "levelFlag"];
59199
59204
  if (kind === "return") {
59200
59205
  for (const k of [...optionKeys, "areas", "points"]) {
59201
- if (has(k))
59206
+ if (has2(k))
59202
59207
  return { ok: false, message: `${k} is not valid for a return command.` };
59203
59208
  }
59204
59209
  return { ok: true, command: { kind: "return" } };
59205
59210
  }
59206
- if (kind !== "area" && has("areas")) {
59211
+ if (kind !== "area" && has2("areas")) {
59207
59212
  return { ok: false, message: "areas is only valid for an area command." };
59208
59213
  }
59209
- if (kind !== "spot" && has("points")) {
59214
+ if (kind !== "spot" && has2("points")) {
59210
59215
  return { ok: false, message: "points is only valid for a spot command." };
59211
59216
  }
59212
59217
  const int = (k, allowed, fallback) => {
59213
- if (!has(k))
59218
+ if (!has2(k))
59214
59219
  return fallback;
59215
59220
  const v = b[k];
59216
59221
  if (typeof v !== "number" || !Number.isInteger(v) || !allowed.includes(v))
59217
59222
  return null;
59218
59223
  return v;
59219
59224
  };
59220
- const cleanTimes = has("cleanTimes") ? typeof b.cleanTimes === "number" && Number.isInteger(b.cleanTimes) && b.cleanTimes >= 0 && b.cleanTimes <= 99 ? b.cleanTimes : null : DEFAULTS.cleanTimes;
59225
+ const cleanTimes = has2("cleanTimes") ? typeof b.cleanTimes === "number" && Number.isInteger(b.cleanTimes) && b.cleanTimes >= 0 && b.cleanTimes <= 99 ? b.cleanTimes : null : DEFAULTS.cleanTimes;
59221
59226
  if (cleanTimes === null)
59222
59227
  return { ok: false, message: "cleanTimes must be an integer 0-99." };
59223
59228
  const modeFlag = int("modeFlag", [1, 2, 3], DEFAULTS.modeFlag);
@@ -59433,6 +59438,125 @@ function robotActionsFor(actor) {
59433
59438
  );
59434
59439
  }
59435
59440
 
59441
+ // src/utils/robot-adapter.util.ts
59442
+ var ROBOT_CAPABILITIES = [
59443
+ /** Read the current location of the robot [x, y, yaw]. */
59444
+ "readLocation",
59445
+ /** Request the robot move to an [x, y, yaw] coordinate. */
59446
+ "navigateToPoint",
59447
+ /** Pause a robot while it is navigating, and resume it. */
59448
+ "pauseResume",
59449
+ /** Read the route/path taken, or the ETA to the destination. */
59450
+ "pathAndEta",
59451
+ /** Read the battery level. */
59452
+ "battery",
59453
+ /** Send the robot to its docking/charging station. */
59454
+ "sendToDock",
59455
+ /** Switch the on-board map and re-localise the robot. */
59456
+ "relocalize",
59457
+ /** Start a process — "clean zone A". */
59458
+ "startProcess",
59459
+ /** Pause, resume or stop a running process. */
59460
+ "pauseProcess",
59461
+ /** Know, rather than infer, when a process has finished. */
59462
+ "processCompletion",
59463
+ /** Report which floor the robot is on. */
59464
+ "floorReporting",
59465
+ /** Ride a lift between floors under our coordination. */
59466
+ "multiFloor"
59467
+ ];
59468
+ var TIER_REQUIREMENTS = {
59469
+ "full-control": [
59470
+ "readLocation",
59471
+ "navigateToPoint",
59472
+ "pauseResume",
59473
+ "pathAndEta",
59474
+ "battery",
59475
+ "sendToDock",
59476
+ "relocalize",
59477
+ "startProcess",
59478
+ "pauseProcess",
59479
+ "processCompletion"
59480
+ ],
59481
+ "traffic-light": [
59482
+ "readLocation",
59483
+ "pauseResume",
59484
+ "battery",
59485
+ "sendToDock",
59486
+ "startProcess",
59487
+ "pauseProcess",
59488
+ "processCompletion"
59489
+ ],
59490
+ "read-only": ["readLocation", "pathAndEta", "battery", "processCompletion"]
59491
+ };
59492
+ var has = (caps, c) => Object.prototype.hasOwnProperty.call(caps, c) && caps[c] === true;
59493
+ function robotTier(capabilities) {
59494
+ const caps = capabilities ?? {};
59495
+ for (const tier of ["full-control", "traffic-light", "read-only"]) {
59496
+ if (TIER_REQUIREMENTS[tier].every((c) => has(caps, c)))
59497
+ return tier;
59498
+ }
59499
+ return "no-interface";
59500
+ }
59501
+ function missingForTier(capabilities, tier) {
59502
+ const caps = capabilities ?? {};
59503
+ return TIER_REQUIREMENTS[tier].filter((c) => !has(caps, c));
59504
+ }
59505
+ function supports(capabilities, capability) {
59506
+ return has(capabilities ?? {}, capability);
59507
+ }
59508
+ function uncontrollableFleetWarning(tiers) {
59509
+ const count = tiers.filter(
59510
+ (t) => t === "read-only" || t === "no-interface"
59511
+ ).length;
59512
+ if (count <= 1) {
59513
+ return { ok: true, count, message: "" };
59514
+ }
59515
+ return {
59516
+ ok: false,
59517
+ count,
59518
+ message: `This site has ${count} robot fleets that cannot be controlled (read-only or no interface). A shared space supports at most one: beyond that, robots cannot reliably be kept out of each other's way.`
59519
+ };
59520
+ }
59521
+ var COMMAND_NEEDS = {
59522
+ full: "startProcess",
59523
+ area: "startProcess",
59524
+ spot: "startProcess",
59525
+ return: "sendToDock",
59526
+ pause: "pauseProcess",
59527
+ resume: "pauseProcess",
59528
+ cancel: "pauseProcess",
59529
+ navigate: "navigateToPoint",
59530
+ relocalize: "relocalize"
59531
+ };
59532
+ function commandSupported(capabilities, kind) {
59533
+ const needed = Object.prototype.hasOwnProperty.call(COMMAND_NEEDS, kind) ? COMMAND_NEEDS[kind] : void 0;
59534
+ if (!needed)
59535
+ return false;
59536
+ return supports(capabilities, needed);
59537
+ }
59538
+ var adapters = /* @__PURE__ */ new Map();
59539
+ function registerRobotAdapter(adapter) {
59540
+ if (!adapter?.brand)
59541
+ throw new Error("A robot adapter must name its brand.");
59542
+ adapters.set(adapter.brand, adapter);
59543
+ }
59544
+ function robotAdapterFor(brand) {
59545
+ if (typeof brand !== "string" || !brand)
59546
+ return null;
59547
+ return adapters.get(brand) ?? null;
59548
+ }
59549
+ function registeredRobotBrands() {
59550
+ return [...adapters.values()].map((a) => ({
59551
+ brand: a.brand,
59552
+ displayName: a.displayName,
59553
+ tier: robotTier(a.capabilities)
59554
+ }));
59555
+ }
59556
+ function clearRobotAdapters() {
59557
+ adapters.clear();
59558
+ }
59559
+
59436
59560
  // src/utils/lift-contract.util.ts
59437
59561
  var LIFT_DOOR = {
59438
59562
  CLOSED: 0,
@@ -59572,6 +59696,179 @@ function createLiftSimulator(options) {
59572
59696
  };
59573
59697
  }
59574
59698
 
59699
+ // src/utils/building-map.util.ts
59700
+ var WALL_HEIGHT_M = 2.5;
59701
+ var WALL_THICKNESS_M = 0.1;
59702
+ var DEFAULT_SCALE_M_PER_PX = 0.05;
59703
+ function scaleFromMeasurement(pixelDistance, realDistanceM) {
59704
+ if (!Number.isFinite(pixelDistance) || pixelDistance <= 0) {
59705
+ throw new Error("A measurement needs a positive pixel distance.");
59706
+ }
59707
+ if (!Number.isFinite(realDistanceM) || realDistanceM <= 0) {
59708
+ throw new Error("A measurement needs a positive real distance in metres.");
59709
+ }
59710
+ return realDistanceM / pixelDistance;
59711
+ }
59712
+ function vertexDistancePx(a, b) {
59713
+ return Math.hypot(b.x - a.x, b.y - a.y);
59714
+ }
59715
+ function levelScale(level) {
59716
+ const measurement = level.measurements?.find((m) => {
59717
+ const a = level.vertices[m.start];
59718
+ const b = level.vertices[m.end];
59719
+ return a && b && m.distanceM > 0 && vertexDistancePx(a, b) > 0;
59720
+ });
59721
+ if (measurement) {
59722
+ return scaleFromMeasurement(
59723
+ vertexDistancePx(level.vertices[measurement.start], level.vertices[measurement.end]),
59724
+ measurement.distanceM
59725
+ );
59726
+ }
59727
+ return level.scaleMPerPx > 0 ? level.scaleMPerPx : DEFAULT_SCALE_M_PER_PX;
59728
+ }
59729
+ function levelPixelToMetres(level, px, py, imageHeightPx) {
59730
+ const scale = levelScale(level);
59731
+ return {
59732
+ x: level.originXM + px * scale,
59733
+ y: level.originYM + (imageHeightPx - py) * scale
59734
+ };
59735
+ }
59736
+ function levelSolids(level, imageHeightPx) {
59737
+ const toM = (v) => levelPixelToMetres(level, v.x, v.y, imageHeightPx);
59738
+ const walls = [];
59739
+ for (const wall of level.walls ?? []) {
59740
+ const a = level.vertices[wall.start];
59741
+ const b = level.vertices[wall.end];
59742
+ if (!a || !b)
59743
+ continue;
59744
+ const pa = toM(a);
59745
+ const pb = toM(b);
59746
+ const lengthM = Math.hypot(pb.x - pa.x, pb.y - pa.y);
59747
+ if (lengthM <= 0)
59748
+ continue;
59749
+ const heightM = wall.heightM ?? WALL_HEIGHT_M;
59750
+ walls.push({
59751
+ centre: {
59752
+ x: (pa.x + pb.x) / 2,
59753
+ y: (pa.y + pb.y) / 2,
59754
+ // Boxes are centred, so a wall standing ON the floor sits at half its
59755
+ // own height above the level's elevation.
59756
+ z: level.elevationM + heightM / 2
59757
+ },
59758
+ lengthM,
59759
+ thicknessM: wall.thicknessM ?? WALL_THICKNESS_M,
59760
+ heightM,
59761
+ yaw: Math.atan2(pb.y - pa.y, pb.x - pa.x)
59762
+ });
59763
+ }
59764
+ const floors = [];
59765
+ for (const floor of level.floors ?? []) {
59766
+ const points = (floor.vertices ?? []).map((i) => level.vertices[i]).filter(Boolean).map(toM);
59767
+ if (points.length < 3)
59768
+ continue;
59769
+ floors.push({ points, elevationM: level.elevationM, hole: floor.hole === true });
59770
+ }
59771
+ return { level: level.name, elevationM: level.elevationM, walls, floors };
59772
+ }
59773
+ function buildingSolids(map, imageHeightFor) {
59774
+ return (map.levels ?? []).map((level) => levelSolids(level, imageHeightFor(level)));
59775
+ }
59776
+ function wallsFromOccupancyGrid(grid, options) {
59777
+ const threshold = options?.threshold ?? 100;
59778
+ const minRun = Math.max(2, options?.minRunPx ?? 6);
59779
+ const { data, width, height } = grid;
59780
+ if (!width || !height || !data || data.length < width * height) {
59781
+ return { vertices: [], walls: [] };
59782
+ }
59783
+ const occupied = (x, y) => Number(data[y * width + x]) <= threshold;
59784
+ const consumed = new Uint8Array(width * height);
59785
+ const vertices = [];
59786
+ const walls = [];
59787
+ const index = /* @__PURE__ */ new Map();
59788
+ const vertexAt = (x, y) => {
59789
+ const key = `${x}:${y}`;
59790
+ const found = index.get(key);
59791
+ if (found !== void 0)
59792
+ return found;
59793
+ vertices.push({ x, y, z: 0 });
59794
+ index.set(key, vertices.length - 1);
59795
+ return vertices.length - 1;
59796
+ };
59797
+ const addRun = (x1, y1, x2, y2) => {
59798
+ walls.push({ start: vertexAt(x1, y1), end: vertexAt(x2, y2) });
59799
+ };
59800
+ for (let y = 0; y < height; y++) {
59801
+ let run = 0;
59802
+ for (let x = 0; x <= width; x++) {
59803
+ const on = x < width && occupied(x, y);
59804
+ if (on) {
59805
+ run++;
59806
+ continue;
59807
+ }
59808
+ if (run >= minRun) {
59809
+ const startX = x - run;
59810
+ addRun(startX, y, x - 1, y);
59811
+ for (let i = startX; i < x; i++)
59812
+ consumed[y * width + i] = 1;
59813
+ }
59814
+ run = 0;
59815
+ }
59816
+ }
59817
+ for (let x = 0; x < width; x++) {
59818
+ let run = 0;
59819
+ for (let y = 0; y <= height; y++) {
59820
+ const on = y < height && occupied(x, y) && !consumed[y * width + x];
59821
+ if (on) {
59822
+ run++;
59823
+ continue;
59824
+ }
59825
+ if (run >= minRun) {
59826
+ addRun(x, y - run, x, y - 1);
59827
+ }
59828
+ run = 0;
59829
+ }
59830
+ }
59831
+ return { vertices, walls };
59832
+ }
59833
+ function buildingMapFromRobotMaps(name, maps, floorHeightM = 3) {
59834
+ const levels = [...maps ?? []].sort((a, b) => a.floorNo - b.floorNo).map((map) => {
59835
+ const vertices = [];
59836
+ if (map.baseX || map.baseY) {
59837
+ vertices.push({
59838
+ x: map.baseX ?? 0,
59839
+ y: map.baseY ?? 0,
59840
+ z: 0,
59841
+ name: `L${map.floorNo}_charger`,
59842
+ isCharger: true,
59843
+ theta: map.baseTheta ?? 0
59844
+ });
59845
+ }
59846
+ for (const area of map.areaList ?? []) {
59847
+ vertices.push({
59848
+ x: area.x,
59849
+ y: area.y,
59850
+ z: 0,
59851
+ name: area.areaName,
59852
+ isCleaningZone: true
59853
+ });
59854
+ }
59855
+ return {
59856
+ name: map.mapName?.trim() || `L${map.floorNo}`,
59857
+ elevationM: (map.floorNo - 1) * floorHeightM,
59858
+ drawing: { fileKey: map.backgroundFileKey },
59859
+ scaleMPerPx: map.resolution && map.resolution > 0 ? map.resolution : DEFAULT_SCALE_M_PER_PX,
59860
+ originXM: map.originX ?? 0,
59861
+ originYM: map.originY ?? 0,
59862
+ vertices,
59863
+ walls: [],
59864
+ floors: [],
59865
+ doors: [],
59866
+ measurements: []
59867
+ };
59868
+ });
59869
+ return { name, levels, lifts: [] };
59870
+ }
59871
+
59575
59872
  // src/services/robot-fleet.service.ts
59576
59873
  import { BadRequestError as BadRequestError134, NotFoundError as NotFoundError43 } from "@7365admin1/node-server-utils";
59577
59874
 
@@ -59829,6 +60126,129 @@ function useRobotFleetService(deps) {
59829
60126
  };
59830
60127
  }
59831
60128
 
60129
+ // src/services/ounitech.adapter.ts
60130
+ var OUNITECH_BRAND = "ounitech";
60131
+ var OUNITECH_CAPABILITIES = {
60132
+ // The tracking stream carries x, y, theta.
60133
+ readLocation: true,
60134
+ // `/portal/robots/return-to-base`.
60135
+ sendToDock: true,
60136
+ // `start-full-cleaning`, `start-area-cleaning`, `start-spot-cleaning` — RMF's
60137
+ // "start a process (such as clean Zone_A)", and their areas are RMF's zones.
60138
+ startProcess: true,
60139
+ // Everything below is FALSE on purpose, and each one is a question already put
60140
+ // to the vendor. Their app can do most of it; the API cannot.
60141
+ navigateToPoint: false,
60142
+ // app: "Move to Target Point in standby mode"
60143
+ relocalize: false,
60144
+ // app: "Set Origin"
60145
+ pauseResume: false,
60146
+ // handlebar + app: Pause/Resume, long-press cancel
60147
+ pauseProcess: false,
60148
+ // app: "End work"
60149
+ battery: false,
60150
+ // nothing in the API carries it
60151
+ pathAndEta: false,
60152
+ // nothing in the API carries it
60153
+ processCompletion: false,
60154
+ // no task id is returned, so completion is inferred
60155
+ floorReporting: false,
60156
+ // the tracking stream has no floorNo
60157
+ multiFloor: false
60158
+ // their app does cross-floor; the API does not expose it
60159
+ };
60160
+ function createOunitechAdapter(credentials, options) {
60161
+ const cloud = useRobotCloudService(credentials, options);
60162
+ function toAdapterRobot(raw) {
60163
+ return {
60164
+ robotSn: raw.robotSn,
60165
+ name: raw.robotName,
60166
+ online: raw.onlineFlag !== false,
60167
+ mode: robotModeFromList(raw),
60168
+ statusName: raw.statusName,
60169
+ // Null, not 0 or a guess. A screen must be able to tell "we do not know"
60170
+ // from "empty", and `floorKnown: false` is what stops a robot being drawn
60171
+ // on a storey nobody confirmed.
60172
+ batteryPercent: null,
60173
+ floorNo: null,
60174
+ floorKnown: false,
60175
+ position: null
60176
+ };
60177
+ }
60178
+ return {
60179
+ brand: OUNITECH_BRAND,
60180
+ displayName: "Ounitech (Q3-G / Q3-W)",
60181
+ capabilities: OUNITECH_CAPABILITIES,
60182
+ async listRobots() {
60183
+ const fleet = await cloud.getRobots();
60184
+ return fleet.map(toAdapterRobot);
60185
+ },
60186
+ async getRobot(robotSn) {
60187
+ const fleet = await cloud.getRobots();
60188
+ const match = fleet.find((r) => r.robotSn === robotSn);
60189
+ if (!match) {
60190
+ return {
60191
+ robotSn,
60192
+ online: false,
60193
+ mode: "UNAVAILABLE",
60194
+ statusName: "Not found in the robot cloud",
60195
+ batteryPercent: null,
60196
+ floorNo: null,
60197
+ floorKnown: false,
60198
+ position: null
60199
+ };
60200
+ }
60201
+ return toAdapterRobot(match);
60202
+ },
60203
+ async sendCommand(robotSn, command) {
60204
+ const options2 = {
60205
+ cleanTimes: 1,
60206
+ modeFlag: 1,
60207
+ subModeFlag: 0,
60208
+ levelFlag: 1
60209
+ };
60210
+ switch (command.kind) {
60211
+ case "full":
60212
+ await cloud.startFullCleaning(robotSn, options2);
60213
+ return;
60214
+ case "area":
60215
+ await cloud.startAreaCleaning(robotSn, command.areas, options2);
60216
+ return;
60217
+ case "spot":
60218
+ await cloud.startSpotCleaning(robotSn, command.points, options2);
60219
+ return;
60220
+ case "return":
60221
+ await cloud.returnToBase(robotSn);
60222
+ return;
60223
+ default:
60224
+ throw new Error(
60225
+ `Ounitech robots do not support "${command.kind}" through the API yet.`
60226
+ );
60227
+ }
60228
+ },
60229
+ async getMaps(robotSn) {
60230
+ return cloud.getMaps(robotSn);
60231
+ },
60232
+ async getPlans(robotSn) {
60233
+ return cloud.getPlans(robotSn);
60234
+ }
60235
+ };
60236
+ }
60237
+ function registerOunitechBrand() {
60238
+ const unconfigured = () => {
60239
+ throw new Error(
60240
+ "No Ounitech credentials for this robot. Add the app key and token on the robot's record."
60241
+ );
60242
+ };
60243
+ registerRobotAdapter({
60244
+ brand: OUNITECH_BRAND,
60245
+ displayName: "Ounitech (Q3-G / Q3-W)",
60246
+ capabilities: OUNITECH_CAPABILITIES,
60247
+ listRobots: async () => unconfigured(),
60248
+ getRobot: async () => unconfigured()
60249
+ });
60250
+ }
60251
+
59832
60252
  // src/controllers/robot-fleet.controller.ts
59833
60253
  import Joi76 from "joi";
59834
60254
  import { BadRequestError as BadRequestError135, logger as logger106 } from "@7365admin1/node-server-utils";
@@ -93743,6 +94163,7 @@ export {
93743
94163
  ConsoleAuditAction,
93744
94164
  ConsoleAuditTarget,
93745
94165
  DEFAULT_RING_SECONDS,
94166
+ DEFAULT_SCALE_M_PER_PX,
93746
94167
  DEFAULT_SITE_TIMEZONE,
93747
94168
  DEVICE_STATUS,
93748
94169
  DOBStatus,
@@ -93881,6 +94302,8 @@ export {
93881
94302
  ORG_OWNER_REPAIR_CLAIM_TAKEN_REASON,
93882
94303
  ORG_OWNER_REPAIR_DEFAULT,
93883
94304
  ORG_OWNER_REPAIR_OFF_REASON,
94305
+ OUNITECH_BRAND,
94306
+ OUNITECH_CAPABILITIES,
93884
94307
  OrgNature,
93885
94308
  OvernightParkingRequestSort,
93886
94309
  OvernightParkingRequestStatus,
@@ -93908,6 +94331,7 @@ export {
93908
94331
  REALTIME_MAX_FANOUT,
93909
94332
  REGISTERED_VEHICLE_CATEGORIES,
93910
94333
  ROBOT_APP_SCOPES,
94334
+ ROBOT_CAPABILITIES,
93911
94335
  ROBOT_CLOUD_DOC_CONFIRMATIONS,
93912
94336
  ROBOT_CLOUD_PATHS,
93913
94337
  ROBOT_MODULE,
@@ -93959,6 +94383,8 @@ export {
93959
94383
  VerificationType,
93960
94384
  VisitorSort,
93961
94385
  VisitorStatus,
94386
+ WALL_HEIGHT_M,
94387
+ WALL_THICKNESS_M,
93962
94388
  addressSchema,
93963
94389
  allowedFieldsSite,
93964
94390
  allowedNatures,
@@ -93970,6 +94396,8 @@ export {
93970
94396
  attendanceSchema,
93971
94397
  attendanceSettingsSchema,
93972
94398
  buildSelfServiceEmailContext,
94399
+ buildingMapFromRobotMaps,
94400
+ buildingSolids,
93973
94401
  building_level_namespace_collection,
93974
94402
  building_units_namespace_collection,
93975
94403
  buildings_namespace_collection,
@@ -93996,13 +94424,16 @@ export {
93996
94424
  chatSchema,
93997
94425
  claimReadsAsStale,
93998
94426
  clampPtzSpeed,
94427
+ clearRobotAdapters,
93999
94428
  clockDriftSeconds,
94429
+ commandSupported,
94000
94430
  consoleRoleAllows,
94001
94431
  consoleRoleAllowsAll,
94002
94432
  consoleSpellings,
94003
94433
  console_audit_namespace_collection,
94004
94434
  createLiftSimulator,
94005
94435
  createManpowerRemarksDaily,
94436
+ createOunitechAdapter,
94006
94437
  createSseParser,
94007
94438
  customerSchema,
94008
94439
  customerSitePropertyFields,
@@ -94073,6 +94504,9 @@ export {
94073
94504
  isTermsCurrent,
94074
94505
  isTrackingStale,
94075
94506
  isValidTimezone,
94507
+ levelPixelToMetres,
94508
+ levelScale,
94509
+ levelSolids,
94076
94510
  listStaffUserIds,
94077
94511
  liveStatusFor,
94078
94512
  manpowerDesignationsSchema,
@@ -94084,6 +94518,7 @@ export {
94084
94518
  maskNric,
94085
94519
  memberModulesHandlers,
94086
94520
  metresToPixel,
94521
+ missingForTier,
94087
94522
  moduleDeniedForCaller,
94088
94523
  moduleGateEnforceKeys,
94089
94524
  moduleGateMode,
@@ -94138,6 +94573,9 @@ export {
94138
94573
  recordConsoleAction,
94139
94574
  refuseServiceProviderInviteAction,
94140
94575
  registerCameraTransport,
94576
+ registerOunitechBrand,
94577
+ registerRobotAdapter,
94578
+ registeredRobotBrands,
94141
94579
  relayForRecorder,
94142
94580
  remarksSchema,
94143
94581
  renderPagePdf,
@@ -94155,6 +94593,7 @@ export {
94155
94593
  resolveSiteTimezone,
94156
94594
  resolveStaffRole,
94157
94595
  robotActionsFor,
94596
+ robotAdapterFor,
94158
94597
  robotCloudBody,
94159
94598
  robotCloudSign,
94160
94599
  robotCloudSigningString,
@@ -94163,8 +94602,10 @@ export {
94163
94602
  robotModeFromList,
94164
94603
  robotSchema,
94165
94604
  robotScopeForApp,
94605
+ robotTier,
94166
94606
  robotTypesVisibleTo,
94167
94607
  rtspUrl,
94608
+ scaleFromMeasurement,
94168
94609
  schema,
94169
94610
  schemaAppSlugNotification,
94170
94611
  schemaApprovedBy,
@@ -94336,7 +94777,9 @@ export {
94336
94777
  stripFacialImageMetadata,
94337
94778
  subscriptionPlanSchema,
94338
94779
  summariseBulkCameraPlan,
94780
+ supports,
94339
94781
  trackingBackoffMs,
94782
+ uncontrollableFleetWarning,
94340
94783
  updateRemarksStatusEod,
94341
94784
  updateRemarksisAcknowledged,
94342
94785
  updateSiteSchema,
@@ -94580,8 +95023,10 @@ export {
94580
95023
  vehicleSchema,
94581
95024
  vehicleViewOf,
94582
95025
  vehicles_namespace_collection,
95026
+ vertexDistancePx,
94583
95027
  visitors_namespace_collection,
94584
95028
  wallConfig,
95029
+ wallsFromOccupancyGrid,
94585
95030
  withLiveLink,
94586
95031
  workOrderSchema,
94587
95032
  work_orders2_namespace_collection,