@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/CHANGELOG.md +53 -0
- package/dist/index.d.ts +462 -1
- package/dist/index.js +476 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +451 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/building-map.test.mjs +305 -0
- package/test/robot-adapter.test.mjs +179 -0
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
|
|
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 (
|
|
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" &&
|
|
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" &&
|
|
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 (!
|
|
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 =
|
|
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,
|