@7365admin1/core 3.82.0 → 3.83.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 +29 -0
- package/dist/index.d.ts +213 -1
- package/dist/index.js +281 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +267 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- 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,
|
|
@@ -59829,6 +59953,129 @@ function useRobotFleetService(deps) {
|
|
|
59829
59953
|
};
|
|
59830
59954
|
}
|
|
59831
59955
|
|
|
59956
|
+
// src/services/ounitech.adapter.ts
|
|
59957
|
+
var OUNITECH_BRAND = "ounitech";
|
|
59958
|
+
var OUNITECH_CAPABILITIES = {
|
|
59959
|
+
// The tracking stream carries x, y, theta.
|
|
59960
|
+
readLocation: true,
|
|
59961
|
+
// `/portal/robots/return-to-base`.
|
|
59962
|
+
sendToDock: true,
|
|
59963
|
+
// `start-full-cleaning`, `start-area-cleaning`, `start-spot-cleaning` — RMF's
|
|
59964
|
+
// "start a process (such as clean Zone_A)", and their areas are RMF's zones.
|
|
59965
|
+
startProcess: true,
|
|
59966
|
+
// Everything below is FALSE on purpose, and each one is a question already put
|
|
59967
|
+
// to the vendor. Their app can do most of it; the API cannot.
|
|
59968
|
+
navigateToPoint: false,
|
|
59969
|
+
// app: "Move to Target Point in standby mode"
|
|
59970
|
+
relocalize: false,
|
|
59971
|
+
// app: "Set Origin"
|
|
59972
|
+
pauseResume: false,
|
|
59973
|
+
// handlebar + app: Pause/Resume, long-press cancel
|
|
59974
|
+
pauseProcess: false,
|
|
59975
|
+
// app: "End work"
|
|
59976
|
+
battery: false,
|
|
59977
|
+
// nothing in the API carries it
|
|
59978
|
+
pathAndEta: false,
|
|
59979
|
+
// nothing in the API carries it
|
|
59980
|
+
processCompletion: false,
|
|
59981
|
+
// no task id is returned, so completion is inferred
|
|
59982
|
+
floorReporting: false,
|
|
59983
|
+
// the tracking stream has no floorNo
|
|
59984
|
+
multiFloor: false
|
|
59985
|
+
// their app does cross-floor; the API does not expose it
|
|
59986
|
+
};
|
|
59987
|
+
function createOunitechAdapter(credentials, options) {
|
|
59988
|
+
const cloud = useRobotCloudService(credentials, options);
|
|
59989
|
+
function toAdapterRobot(raw) {
|
|
59990
|
+
return {
|
|
59991
|
+
robotSn: raw.robotSn,
|
|
59992
|
+
name: raw.robotName,
|
|
59993
|
+
online: raw.onlineFlag !== false,
|
|
59994
|
+
mode: robotModeFromList(raw),
|
|
59995
|
+
statusName: raw.statusName,
|
|
59996
|
+
// Null, not 0 or a guess. A screen must be able to tell "we do not know"
|
|
59997
|
+
// from "empty", and `floorKnown: false` is what stops a robot being drawn
|
|
59998
|
+
// on a storey nobody confirmed.
|
|
59999
|
+
batteryPercent: null,
|
|
60000
|
+
floorNo: null,
|
|
60001
|
+
floorKnown: false,
|
|
60002
|
+
position: null
|
|
60003
|
+
};
|
|
60004
|
+
}
|
|
60005
|
+
return {
|
|
60006
|
+
brand: OUNITECH_BRAND,
|
|
60007
|
+
displayName: "Ounitech (Q3-G / Q3-W)",
|
|
60008
|
+
capabilities: OUNITECH_CAPABILITIES,
|
|
60009
|
+
async listRobots() {
|
|
60010
|
+
const fleet = await cloud.getRobots();
|
|
60011
|
+
return fleet.map(toAdapterRobot);
|
|
60012
|
+
},
|
|
60013
|
+
async getRobot(robotSn) {
|
|
60014
|
+
const fleet = await cloud.getRobots();
|
|
60015
|
+
const match = fleet.find((r) => r.robotSn === robotSn);
|
|
60016
|
+
if (!match) {
|
|
60017
|
+
return {
|
|
60018
|
+
robotSn,
|
|
60019
|
+
online: false,
|
|
60020
|
+
mode: "UNAVAILABLE",
|
|
60021
|
+
statusName: "Not found in the robot cloud",
|
|
60022
|
+
batteryPercent: null,
|
|
60023
|
+
floorNo: null,
|
|
60024
|
+
floorKnown: false,
|
|
60025
|
+
position: null
|
|
60026
|
+
};
|
|
60027
|
+
}
|
|
60028
|
+
return toAdapterRobot(match);
|
|
60029
|
+
},
|
|
60030
|
+
async sendCommand(robotSn, command) {
|
|
60031
|
+
const options2 = {
|
|
60032
|
+
cleanTimes: 1,
|
|
60033
|
+
modeFlag: 1,
|
|
60034
|
+
subModeFlag: 0,
|
|
60035
|
+
levelFlag: 1
|
|
60036
|
+
};
|
|
60037
|
+
switch (command.kind) {
|
|
60038
|
+
case "full":
|
|
60039
|
+
await cloud.startFullCleaning(robotSn, options2);
|
|
60040
|
+
return;
|
|
60041
|
+
case "area":
|
|
60042
|
+
await cloud.startAreaCleaning(robotSn, command.areas, options2);
|
|
60043
|
+
return;
|
|
60044
|
+
case "spot":
|
|
60045
|
+
await cloud.startSpotCleaning(robotSn, command.points, options2);
|
|
60046
|
+
return;
|
|
60047
|
+
case "return":
|
|
60048
|
+
await cloud.returnToBase(robotSn);
|
|
60049
|
+
return;
|
|
60050
|
+
default:
|
|
60051
|
+
throw new Error(
|
|
60052
|
+
`Ounitech robots do not support "${command.kind}" through the API yet.`
|
|
60053
|
+
);
|
|
60054
|
+
}
|
|
60055
|
+
},
|
|
60056
|
+
async getMaps(robotSn) {
|
|
60057
|
+
return cloud.getMaps(robotSn);
|
|
60058
|
+
},
|
|
60059
|
+
async getPlans(robotSn) {
|
|
60060
|
+
return cloud.getPlans(robotSn);
|
|
60061
|
+
}
|
|
60062
|
+
};
|
|
60063
|
+
}
|
|
60064
|
+
function registerOunitechBrand() {
|
|
60065
|
+
const unconfigured = () => {
|
|
60066
|
+
throw new Error(
|
|
60067
|
+
"No Ounitech credentials for this robot. Add the app key and token on the robot's record."
|
|
60068
|
+
);
|
|
60069
|
+
};
|
|
60070
|
+
registerRobotAdapter({
|
|
60071
|
+
brand: OUNITECH_BRAND,
|
|
60072
|
+
displayName: "Ounitech (Q3-G / Q3-W)",
|
|
60073
|
+
capabilities: OUNITECH_CAPABILITIES,
|
|
60074
|
+
listRobots: async () => unconfigured(),
|
|
60075
|
+
getRobot: async () => unconfigured()
|
|
60076
|
+
});
|
|
60077
|
+
}
|
|
60078
|
+
|
|
59832
60079
|
// src/controllers/robot-fleet.controller.ts
|
|
59833
60080
|
import Joi76 from "joi";
|
|
59834
60081
|
import { BadRequestError as BadRequestError135, logger as logger106 } from "@7365admin1/node-server-utils";
|
|
@@ -93881,6 +94128,8 @@ export {
|
|
|
93881
94128
|
ORG_OWNER_REPAIR_CLAIM_TAKEN_REASON,
|
|
93882
94129
|
ORG_OWNER_REPAIR_DEFAULT,
|
|
93883
94130
|
ORG_OWNER_REPAIR_OFF_REASON,
|
|
94131
|
+
OUNITECH_BRAND,
|
|
94132
|
+
OUNITECH_CAPABILITIES,
|
|
93884
94133
|
OrgNature,
|
|
93885
94134
|
OvernightParkingRequestSort,
|
|
93886
94135
|
OvernightParkingRequestStatus,
|
|
@@ -93908,6 +94157,7 @@ export {
|
|
|
93908
94157
|
REALTIME_MAX_FANOUT,
|
|
93909
94158
|
REGISTERED_VEHICLE_CATEGORIES,
|
|
93910
94159
|
ROBOT_APP_SCOPES,
|
|
94160
|
+
ROBOT_CAPABILITIES,
|
|
93911
94161
|
ROBOT_CLOUD_DOC_CONFIRMATIONS,
|
|
93912
94162
|
ROBOT_CLOUD_PATHS,
|
|
93913
94163
|
ROBOT_MODULE,
|
|
@@ -93996,13 +94246,16 @@ export {
|
|
|
93996
94246
|
chatSchema,
|
|
93997
94247
|
claimReadsAsStale,
|
|
93998
94248
|
clampPtzSpeed,
|
|
94249
|
+
clearRobotAdapters,
|
|
93999
94250
|
clockDriftSeconds,
|
|
94251
|
+
commandSupported,
|
|
94000
94252
|
consoleRoleAllows,
|
|
94001
94253
|
consoleRoleAllowsAll,
|
|
94002
94254
|
consoleSpellings,
|
|
94003
94255
|
console_audit_namespace_collection,
|
|
94004
94256
|
createLiftSimulator,
|
|
94005
94257
|
createManpowerRemarksDaily,
|
|
94258
|
+
createOunitechAdapter,
|
|
94006
94259
|
createSseParser,
|
|
94007
94260
|
customerSchema,
|
|
94008
94261
|
customerSitePropertyFields,
|
|
@@ -94084,6 +94337,7 @@ export {
|
|
|
94084
94337
|
maskNric,
|
|
94085
94338
|
memberModulesHandlers,
|
|
94086
94339
|
metresToPixel,
|
|
94340
|
+
missingForTier,
|
|
94087
94341
|
moduleDeniedForCaller,
|
|
94088
94342
|
moduleGateEnforceKeys,
|
|
94089
94343
|
moduleGateMode,
|
|
@@ -94138,6 +94392,9 @@ export {
|
|
|
94138
94392
|
recordConsoleAction,
|
|
94139
94393
|
refuseServiceProviderInviteAction,
|
|
94140
94394
|
registerCameraTransport,
|
|
94395
|
+
registerOunitechBrand,
|
|
94396
|
+
registerRobotAdapter,
|
|
94397
|
+
registeredRobotBrands,
|
|
94141
94398
|
relayForRecorder,
|
|
94142
94399
|
remarksSchema,
|
|
94143
94400
|
renderPagePdf,
|
|
@@ -94155,6 +94412,7 @@ export {
|
|
|
94155
94412
|
resolveSiteTimezone,
|
|
94156
94413
|
resolveStaffRole,
|
|
94157
94414
|
robotActionsFor,
|
|
94415
|
+
robotAdapterFor,
|
|
94158
94416
|
robotCloudBody,
|
|
94159
94417
|
robotCloudSign,
|
|
94160
94418
|
robotCloudSigningString,
|
|
@@ -94163,6 +94421,7 @@ export {
|
|
|
94163
94421
|
robotModeFromList,
|
|
94164
94422
|
robotSchema,
|
|
94165
94423
|
robotScopeForApp,
|
|
94424
|
+
robotTier,
|
|
94166
94425
|
robotTypesVisibleTo,
|
|
94167
94426
|
rtspUrl,
|
|
94168
94427
|
schema,
|
|
@@ -94336,7 +94595,9 @@ export {
|
|
|
94336
94595
|
stripFacialImageMetadata,
|
|
94337
94596
|
subscriptionPlanSchema,
|
|
94338
94597
|
summariseBulkCameraPlan,
|
|
94598
|
+
supports,
|
|
94339
94599
|
trackingBackoffMs,
|
|
94600
|
+
uncontrollableFleetWarning,
|
|
94340
94601
|
updateRemarksStatusEod,
|
|
94341
94602
|
updateRemarksisAcknowledged,
|
|
94342
94603
|
updateSiteSchema,
|