@7365admin1/core 3.84.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/CHANGELOG.md +44 -0
- package/dist/index.d.ts +236 -86
- package/dist/index.js +674 -173
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +662 -170
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/demo-adapter.test.mjs +164 -0
- package/test/robot-adapter.test.mjs +83 -32
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,
|
|
@@ -248,6 +250,7 @@ __export(src_exports, {
|
|
|
248
250
|
ROBOT_CAPABILITIES: () => ROBOT_CAPABILITIES,
|
|
249
251
|
ROBOT_CLOUD_DOC_CONFIRMATIONS: () => ROBOT_CLOUD_DOC_CONFIRMATIONS,
|
|
250
252
|
ROBOT_CLOUD_PATHS: () => ROBOT_CLOUD_PATHS,
|
|
253
|
+
ROBOT_CLOUD_TIMEOUT_MS: () => ROBOT_CLOUD_TIMEOUT_MS,
|
|
251
254
|
ROBOT_MODULE: () => ROBOT_MODULE,
|
|
252
255
|
ROBOT_PERMISSIONS: () => ROBOT_PERMISSIONS,
|
|
253
256
|
ROBOT_STATUS_NAMES: () => ROBOT_STATUS_NAMES,
|
|
@@ -299,6 +302,7 @@ __export(src_exports, {
|
|
|
299
302
|
VisitorStatus: () => VisitorStatus,
|
|
300
303
|
WALL_HEIGHT_M: () => WALL_HEIGHT_M,
|
|
301
304
|
WALL_THICKNESS_M: () => WALL_THICKNESS_M,
|
|
305
|
+
adapterForRobot: () => adapterForRobot,
|
|
302
306
|
addressSchema: () => addressSchema,
|
|
303
307
|
allowedFieldsSite: () => allowedFieldsSite,
|
|
304
308
|
allowedNatures: () => allowedNatures,
|
|
@@ -338,13 +342,14 @@ __export(src_exports, {
|
|
|
338
342
|
chatSchema: () => chatSchema,
|
|
339
343
|
claimReadsAsStale: () => claimReadsAsStale,
|
|
340
344
|
clampPtzSpeed: () => clampPtzSpeed,
|
|
341
|
-
|
|
345
|
+
clearRobotBrands: () => clearRobotBrands,
|
|
342
346
|
clockDriftSeconds: () => clockDriftSeconds,
|
|
343
347
|
commandSupported: () => commandSupported,
|
|
344
348
|
consoleRoleAllows: () => consoleRoleAllows,
|
|
345
349
|
consoleRoleAllowsAll: () => consoleRoleAllowsAll,
|
|
346
350
|
consoleSpellings: () => consoleSpellings,
|
|
347
351
|
console_audit_namespace_collection: () => console_audit_namespace_collection,
|
|
352
|
+
createDemoAdapter: () => createDemoAdapter,
|
|
348
353
|
createLiftSimulator: () => createLiftSimulator,
|
|
349
354
|
createManpowerRemarksDaily: () => createManpowerRemarksDaily,
|
|
350
355
|
createOunitechAdapter: () => createOunitechAdapter,
|
|
@@ -368,6 +373,7 @@ __export(src_exports, {
|
|
|
368
373
|
disconnectedState: () => disconnectedState,
|
|
369
374
|
emitNotificationCreated: () => emitNotificationCreated,
|
|
370
375
|
emptyPersonalEmergencyChain: () => emptyPersonalEmergencyChain,
|
|
376
|
+
encodeGreyscalePng: () => encodeGreyscalePng,
|
|
371
377
|
encodeHidPacsCard: () => encodeHidPacsCard,
|
|
372
378
|
entitledSiteScope: () => entitledSiteScope,
|
|
373
379
|
events_namespace_collection: () => events_namespace_collection,
|
|
@@ -386,6 +392,7 @@ __export(src_exports, {
|
|
|
386
392
|
getIO: () => getIO,
|
|
387
393
|
getSessionIdFromRequest: () => getSessionIdFromRequest,
|
|
388
394
|
grabWithSubStreamFallback: () => grabWithSubStreamFallback,
|
|
395
|
+
greyscalePngDataUri: () => greyscalePngDataUri,
|
|
389
396
|
guests_namespace_collection: () => guests_namespace_collection,
|
|
390
397
|
hasAnyCapability: () => hasAnyCapability,
|
|
391
398
|
hasAnyPermission: () => hasAnyPermission,
|
|
@@ -487,8 +494,9 @@ __export(src_exports, {
|
|
|
487
494
|
recordConsoleAction: () => recordConsoleAction,
|
|
488
495
|
refuseServiceProviderInviteAction: () => refuseServiceProviderInviteAction,
|
|
489
496
|
registerCameraTransport: () => registerCameraTransport,
|
|
497
|
+
registerDemoBrand: () => registerDemoBrand,
|
|
490
498
|
registerOunitechBrand: () => registerOunitechBrand,
|
|
491
|
-
|
|
499
|
+
registerRobotBrand: () => registerRobotBrand,
|
|
492
500
|
registeredRobotBrands: () => registeredRobotBrands,
|
|
493
501
|
relayForRecorder: () => relayForRecorder,
|
|
494
502
|
remarksSchema: () => remarksSchema,
|
|
@@ -507,7 +515,7 @@ __export(src_exports, {
|
|
|
507
515
|
resolveSiteTimezone: () => resolveSiteTimezone,
|
|
508
516
|
resolveStaffRole: () => resolveStaffRole,
|
|
509
517
|
robotActionsFor: () => robotActionsFor,
|
|
510
|
-
|
|
518
|
+
robotBrandFor: () => robotBrandFor,
|
|
511
519
|
robotCloudBody: () => robotCloudBody,
|
|
512
520
|
robotCloudSign: () => robotCloudSign,
|
|
513
521
|
robotCloudSigningString: () => robotCloudSigningString,
|
|
@@ -686,6 +694,7 @@ __export(src_exports, {
|
|
|
686
694
|
snapshotEndpoint: () => snapshotEndpoint,
|
|
687
695
|
snapshotRefusalReason: () => snapshotRefusalReason,
|
|
688
696
|
staffMayBypass: () => staffMayBypass,
|
|
697
|
+
startRobotTracking: () => startRobotTracking,
|
|
689
698
|
startTodaysBookForSite: () => startTodaysBookForSite,
|
|
690
699
|
stringifyHidJson: () => stringifyHidJson,
|
|
691
700
|
stripFacialImageMetadata: () => stripFacialImageMetadata,
|
|
@@ -20388,8 +20397,8 @@ function useDahuaService() {
|
|
|
20388
20397
|
}
|
|
20389
20398
|
}
|
|
20390
20399
|
}
|
|
20391
|
-
async function handleBuffer(
|
|
20392
|
-
partialBuffer = Buffer.concat([partialBuffer,
|
|
20400
|
+
async function handleBuffer(chunk2, onDetected2) {
|
|
20401
|
+
partialBuffer = Buffer.concat([partialBuffer, chunk2]);
|
|
20393
20402
|
while (true) {
|
|
20394
20403
|
const boundaryIndex = partialBuffer.indexOf(Buffer.from(boundary));
|
|
20395
20404
|
if (boundaryIndex === -1)
|
|
@@ -20640,12 +20649,12 @@ function useDahuaService() {
|
|
|
20640
20649
|
resolve("aborted");
|
|
20641
20650
|
};
|
|
20642
20651
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
20643
|
-
response.res.on("data", (
|
|
20652
|
+
response.res.on("data", (chunk2) => {
|
|
20644
20653
|
if (signal.aborted) {
|
|
20645
20654
|
response.res.destroy();
|
|
20646
20655
|
return;
|
|
20647
20656
|
}
|
|
20648
|
-
bufferQueue.enqueue(
|
|
20657
|
+
bufferQueue.enqueue(chunk2);
|
|
20649
20658
|
});
|
|
20650
20659
|
response.res.on("end", () => {
|
|
20651
20660
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -23249,10 +23258,10 @@ function buildAdapter(params) {
|
|
|
23249
23258
|
return;
|
|
23250
23259
|
let buffered = "";
|
|
23251
23260
|
const decoder = new TextDecoder();
|
|
23252
|
-
for await (const
|
|
23261
|
+
for await (const chunk2 of body) {
|
|
23253
23262
|
if (params2.signal.aborted)
|
|
23254
23263
|
return;
|
|
23255
|
-
buffered += decoder.decode(
|
|
23264
|
+
buffered += decoder.decode(chunk2, { stream: true });
|
|
23256
23265
|
let at2 = buffered.indexOf(marker);
|
|
23257
23266
|
while (at2 >= 0) {
|
|
23258
23267
|
const next = buffered.indexOf(marker, at2 + marker.length);
|
|
@@ -23564,16 +23573,16 @@ function useCameraViewService() {
|
|
|
23564
23573
|
() => finish(new Error("camera-timeout")),
|
|
23565
23574
|
CAMERA_RTSP_TIMEOUT_MS
|
|
23566
23575
|
);
|
|
23567
|
-
child.stdout.on("data", (
|
|
23568
|
-
bytes +=
|
|
23576
|
+
child.stdout.on("data", (chunk2) => {
|
|
23577
|
+
bytes += chunk2.length;
|
|
23569
23578
|
if (bytes > CAMERA_SNAPSHOT_MAX_BYTES) {
|
|
23570
23579
|
finish(new Error("camera-picture-too-large"));
|
|
23571
23580
|
return;
|
|
23572
23581
|
}
|
|
23573
|
-
chunks.push(
|
|
23582
|
+
chunks.push(chunk2);
|
|
23574
23583
|
});
|
|
23575
|
-
child.stderr?.on("data", (
|
|
23576
|
-
const text2 = carry +
|
|
23584
|
+
child.stderr?.on("data", (chunk2) => {
|
|
23585
|
+
const text2 = carry + chunk2.toString("latin1");
|
|
23577
23586
|
if (/401\s*unauthorized|\bunauthorized\b/i.test(text2))
|
|
23578
23587
|
rejected = true;
|
|
23579
23588
|
carry = text2.slice(-32);
|
|
@@ -32869,8 +32878,8 @@ function useOccurrenceBookRepo() {
|
|
|
32869
32878
|
const chunkSize = 500;
|
|
32870
32879
|
let totalInserted = 0;
|
|
32871
32880
|
for (let i = 0; i < payloads.length; i += chunkSize) {
|
|
32872
|
-
const
|
|
32873
|
-
const operations =
|
|
32881
|
+
const chunk2 = payloads.slice(i, i + chunkSize);
|
|
32882
|
+
const operations = chunk2.map((item) => ({
|
|
32874
32883
|
insertOne: {
|
|
32875
32884
|
document: {
|
|
32876
32885
|
...item,
|
|
@@ -52165,9 +52174,9 @@ function useVisitorTransactionService() {
|
|
|
52165
52174
|
expectedCheckIn
|
|
52166
52175
|
} = value;
|
|
52167
52176
|
for (let i = 0; i < value.members.length; i += chunkSize) {
|
|
52168
|
-
const
|
|
52177
|
+
const chunk2 = value.members.slice(i, i + chunkSize);
|
|
52169
52178
|
await Promise.all(
|
|
52170
|
-
|
|
52179
|
+
chunk2.map(async (member) => {
|
|
52171
52180
|
const clonedMember = structuredClone(member);
|
|
52172
52181
|
const { visitorPass, passKeys, nric: nric2, name: name2, contact: contact2 } = clonedMember;
|
|
52173
52182
|
if (/^(guest|walk-in|contractor|delivery)$/.test(type2 ?? "invalid") && site2) {
|
|
@@ -52409,9 +52418,9 @@ function useVisitorTransactionService() {
|
|
|
52409
52418
|
unitName
|
|
52410
52419
|
} = value;
|
|
52411
52420
|
for (let i = 0; i < value.members.length; i += chunkSize) {
|
|
52412
|
-
const
|
|
52421
|
+
const chunk2 = value.members.slice(i, i + chunkSize);
|
|
52413
52422
|
await Promise.all(
|
|
52414
|
-
|
|
52423
|
+
chunk2.map(async (member) => {
|
|
52415
52424
|
const clonedMember = structuredClone(member);
|
|
52416
52425
|
const { visitorPass, passKeys, nric: nric2, name: name2, contact: contact2 } = clonedMember;
|
|
52417
52426
|
if (/^(guest|walk-in|contractor|delivery)$/.test(type2 ?? "invalid") && site2) {
|
|
@@ -56868,8 +56877,8 @@ function useAccessManagementSvc() {
|
|
|
56868
56877
|
return new Promise((resolve, reject) => {
|
|
56869
56878
|
const fileStream = import_stream.Readable.from(bufferFile);
|
|
56870
56879
|
let fileBuffer = Buffer.alloc(0);
|
|
56871
|
-
fileStream.on("data", (
|
|
56872
|
-
fileBuffer = Buffer.concat([fileBuffer,
|
|
56880
|
+
fileStream.on("data", (chunk2) => {
|
|
56881
|
+
fileBuffer = Buffer.concat([fileBuffer, chunk2]);
|
|
56873
56882
|
});
|
|
56874
56883
|
fileStream.on("end", () => {
|
|
56875
56884
|
try {
|
|
@@ -59708,10 +59717,10 @@ function parseRobotCommand(body) {
|
|
|
59708
59717
|
function createSseParser() {
|
|
59709
59718
|
let buffer = "";
|
|
59710
59719
|
let dataLines = [];
|
|
59711
|
-
function push(
|
|
59712
|
-
if (!
|
|
59720
|
+
function push(chunk2) {
|
|
59721
|
+
if (!chunk2)
|
|
59713
59722
|
return { events: [], sawActivity: false };
|
|
59714
|
-
buffer +=
|
|
59723
|
+
buffer += chunk2;
|
|
59715
59724
|
const events = [];
|
|
59716
59725
|
buffer = buffer.replace(/\r\n|\r/g, "\n");
|
|
59717
59726
|
const lines = buffer.split("\n");
|
|
@@ -59977,26 +59986,44 @@ function commandSupported(capabilities, kind) {
|
|
|
59977
59986
|
return false;
|
|
59978
59987
|
return supports(capabilities, needed);
|
|
59979
59988
|
}
|
|
59980
|
-
var
|
|
59981
|
-
function
|
|
59982
|
-
if (!
|
|
59983
|
-
throw new Error("A robot
|
|
59984
|
-
|
|
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);
|
|
59985
59997
|
}
|
|
59986
|
-
function
|
|
59998
|
+
function robotBrandFor(brand) {
|
|
59987
59999
|
if (typeof brand !== "string" || !brand)
|
|
59988
60000
|
return null;
|
|
59989
|
-
return
|
|
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 });
|
|
59990
60017
|
}
|
|
59991
60018
|
function registeredRobotBrands() {
|
|
59992
|
-
return [...
|
|
59993
|
-
brand:
|
|
59994
|
-
displayName:
|
|
59995
|
-
tier: robotTier(
|
|
60019
|
+
return [...brands.values()].map((b) => ({
|
|
60020
|
+
brand: b.brand,
|
|
60021
|
+
displayName: b.displayName,
|
|
60022
|
+
tier: robotTier(b.capabilities)
|
|
59996
60023
|
}));
|
|
59997
60024
|
}
|
|
59998
|
-
function
|
|
59999
|
-
|
|
60025
|
+
function clearRobotBrands() {
|
|
60026
|
+
brands.clear();
|
|
60000
60027
|
}
|
|
60001
60028
|
|
|
60002
60029
|
// src/utils/lift-contract.util.ts
|
|
@@ -60452,122 +60479,6 @@ function useRobotCloudService(credentials, options = {}) {
|
|
|
60452
60479
|
};
|
|
60453
60480
|
}
|
|
60454
60481
|
|
|
60455
|
-
// src/services/robot-fleet.service.ts
|
|
60456
|
-
function credentialsOf(robot) {
|
|
60457
|
-
const appid = (robot.appKey ?? "").trim();
|
|
60458
|
-
const secret = (robot.appToken ?? "").trim();
|
|
60459
|
-
if (!appid || !secret) {
|
|
60460
|
-
throw new import_node_server_utils157.BadRequestError(
|
|
60461
|
-
`Robot "${robot.name}" has no cloud credentials saved.`
|
|
60462
|
-
);
|
|
60463
|
-
}
|
|
60464
|
-
return { appid, secret };
|
|
60465
|
-
}
|
|
60466
|
-
function useRobotFleetService(deps) {
|
|
60467
|
-
const cloudFor = deps?.cloudFor ?? useRobotCloudService;
|
|
60468
|
-
function clientFor(robot) {
|
|
60469
|
-
return cloudFor(credentialsOf(robot));
|
|
60470
|
-
}
|
|
60471
|
-
async function resolveRobotSn(robot) {
|
|
60472
|
-
const stored = (robot.robotSn ?? "").trim();
|
|
60473
|
-
if (stored)
|
|
60474
|
-
return stored;
|
|
60475
|
-
const fleet = await clientFor(robot).getRobots();
|
|
60476
|
-
if (fleet.length === 0) {
|
|
60477
|
-
throw new import_node_server_utils157.NotFoundError(
|
|
60478
|
-
`Robot "${robot.name}": the cloud reports no robots for these credentials.`
|
|
60479
|
-
);
|
|
60480
|
-
}
|
|
60481
|
-
if (fleet.length > 1) {
|
|
60482
|
-
throw new import_node_server_utils157.BadRequestError(
|
|
60483
|
-
`Robot "${robot.name}": these credentials cover ${fleet.length} robots, so the serial cannot be determined automatically. Set it explicitly.`
|
|
60484
|
-
);
|
|
60485
|
-
}
|
|
60486
|
-
const robotSn = fleet[0].robotSn;
|
|
60487
|
-
if (!robotSn) {
|
|
60488
|
-
throw new import_node_server_utils157.BadRequestError(
|
|
60489
|
-
`Robot "${robot.name}": the cloud returned a robot with no serial.`
|
|
60490
|
-
);
|
|
60491
|
-
}
|
|
60492
|
-
const id = robot._id ? String(robot._id) : "";
|
|
60493
|
-
if (id && deps?.saveRobotSn) {
|
|
60494
|
-
await deps.saveRobotSn(id, robotSn).catch(() => void 0);
|
|
60495
|
-
}
|
|
60496
|
-
return robotSn;
|
|
60497
|
-
}
|
|
60498
|
-
async function getFleetRobot(robot) {
|
|
60499
|
-
const robotSn = await resolveRobotSn(robot);
|
|
60500
|
-
const fleet = await clientFor(robot).getRobots();
|
|
60501
|
-
const match = fleet.find(
|
|
60502
|
-
(r) => r.robotSn === robotSn
|
|
60503
|
-
);
|
|
60504
|
-
if (!match) {
|
|
60505
|
-
return {
|
|
60506
|
-
id: robot._id ? String(robot._id) : "",
|
|
60507
|
-
name: robot.name,
|
|
60508
|
-
robotSn,
|
|
60509
|
-
statusFlag: null,
|
|
60510
|
-
statusName: "Not found in the robot cloud",
|
|
60511
|
-
mode: "UNAVAILABLE",
|
|
60512
|
-
online: false,
|
|
60513
|
-
dispatchable: false
|
|
60514
|
-
};
|
|
60515
|
-
}
|
|
60516
|
-
const mode = robotModeFromList(match);
|
|
60517
|
-
return {
|
|
60518
|
-
id: robot._id ? String(robot._id) : "",
|
|
60519
|
-
name: robot.name,
|
|
60520
|
-
robotSn,
|
|
60521
|
-
statusFlag: typeof match.statusFlag === "number" ? match.statusFlag : null,
|
|
60522
|
-
statusName: match.statusName ?? "",
|
|
60523
|
-
mode,
|
|
60524
|
-
online: match.onlineFlag !== false,
|
|
60525
|
-
dispatchable: mode === "IDLE" || mode === "CHARGED"
|
|
60526
|
-
};
|
|
60527
|
-
}
|
|
60528
|
-
async function getMaps(robot) {
|
|
60529
|
-
const robotSn = await resolveRobotSn(robot);
|
|
60530
|
-
return clientFor(robot).getMaps(robotSn);
|
|
60531
|
-
}
|
|
60532
|
-
async function getMapImages(robot, fileKeys) {
|
|
60533
|
-
const robotSn = await resolveRobotSn(robot);
|
|
60534
|
-
return clientFor(robot).getFileUrls(robotSn, fileKeys);
|
|
60535
|
-
}
|
|
60536
|
-
async function dispatch(robot, command) {
|
|
60537
|
-
const robotSn = await resolveRobotSn(robot);
|
|
60538
|
-
const cloud = clientFor(robot);
|
|
60539
|
-
if (command.kind === "return") {
|
|
60540
|
-
return cloud.returnToBase(robotSn);
|
|
60541
|
-
}
|
|
60542
|
-
const state = await getFleetRobot(robot);
|
|
60543
|
-
if (!state.dispatchable) {
|
|
60544
|
-
throw new import_node_server_utils157.BadRequestError(
|
|
60545
|
-
`Robot "${robot.name}" is ${state.statusName || state.mode} and cannot take a new job.`
|
|
60546
|
-
);
|
|
60547
|
-
}
|
|
60548
|
-
switch (command.kind) {
|
|
60549
|
-
case "full":
|
|
60550
|
-
return cloud.startFullCleaning(robotSn, command.options);
|
|
60551
|
-
case "area":
|
|
60552
|
-
return cloud.startAreaCleaning(robotSn, command.areas, command.options);
|
|
60553
|
-
case "spot":
|
|
60554
|
-
return cloud.startSpotCleaning(robotSn, command.points, command.options);
|
|
60555
|
-
}
|
|
60556
|
-
}
|
|
60557
|
-
async function getPlans(robot) {
|
|
60558
|
-
const robotSn = await resolveRobotSn(robot);
|
|
60559
|
-
return clientFor(robot).getPlans(robotSn);
|
|
60560
|
-
}
|
|
60561
|
-
return {
|
|
60562
|
-
resolveRobotSn,
|
|
60563
|
-
getFleetRobot,
|
|
60564
|
-
getMaps,
|
|
60565
|
-
getMapImages,
|
|
60566
|
-
dispatch,
|
|
60567
|
-
getPlans
|
|
60568
|
-
};
|
|
60569
|
-
}
|
|
60570
|
-
|
|
60571
60482
|
// src/services/ounitech.adapter.ts
|
|
60572
60483
|
var OUNITECH_BRAND = "ounitech";
|
|
60573
60484
|
var OUNITECH_CAPABILITIES = {
|
|
@@ -60671,24 +60582,550 @@ function createOunitechAdapter(credentials, options) {
|
|
|
60671
60582
|
async getMaps(robotSn) {
|
|
60672
60583
|
return cloud.getMaps(robotSn);
|
|
60673
60584
|
},
|
|
60585
|
+
async getMapImages(robotSn, fileKeys) {
|
|
60586
|
+
return cloud.getFileUrls(robotSn, fileKeys);
|
|
60587
|
+
},
|
|
60674
60588
|
async getPlans(robotSn) {
|
|
60675
60589
|
return cloud.getPlans(robotSn);
|
|
60676
60590
|
}
|
|
60677
60591
|
};
|
|
60678
60592
|
}
|
|
60679
60593
|
function registerOunitechBrand() {
|
|
60680
|
-
|
|
60681
|
-
throw new Error(
|
|
60682
|
-
"No Ounitech credentials for this robot. Add the app key and token on the robot's record."
|
|
60683
|
-
);
|
|
60684
|
-
};
|
|
60685
|
-
registerRobotAdapter({
|
|
60594
|
+
registerRobotBrand({
|
|
60686
60595
|
brand: OUNITECH_BRAND,
|
|
60687
60596
|
displayName: "Ounitech (Q3-G / Q3-W)",
|
|
60688
60597
|
capabilities: OUNITECH_CAPABILITIES,
|
|
60689
|
-
|
|
60690
|
-
|
|
60598
|
+
create: (credentials) => createOunitechAdapter(credentials)
|
|
60599
|
+
});
|
|
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
|
+
}
|
|
60991
|
+
|
|
60992
|
+
// src/services/robot-tracking.service.ts
|
|
60993
|
+
var import_axios4 = __toESM(require("axios"));
|
|
60994
|
+
async function openWithAxios(url, body) {
|
|
60995
|
+
const response = await import_axios4.default.post(url, body, {
|
|
60996
|
+
responseType: "stream",
|
|
60997
|
+
// No read timeout: this connection is SUPPOSED to stay open and mostly
|
|
60998
|
+
// silent. A request timeout here would sever a healthy stream on a quiet
|
|
60999
|
+
// robot. The idle watchdog is what decides a stream is dead, because it can
|
|
61000
|
+
// tell silence from a closed socket.
|
|
61001
|
+
timeout: 0,
|
|
61002
|
+
headers: { "Content-Type": "application/json", Accept: "text/event-stream" }
|
|
60691
61003
|
});
|
|
61004
|
+
return response.data;
|
|
61005
|
+
}
|
|
61006
|
+
function startRobotTracking(options) {
|
|
61007
|
+
const {
|
|
61008
|
+
credentials,
|
|
61009
|
+
robotSn,
|
|
61010
|
+
onEvent,
|
|
61011
|
+
baseUrl = ROBOT_CLOUD_BASE_URL,
|
|
61012
|
+
idleTimeoutMs = TRACKING_IDLE_TIMEOUT_MS,
|
|
61013
|
+
openStream = openWithAxios,
|
|
61014
|
+
now = Date.now
|
|
61015
|
+
} = options;
|
|
61016
|
+
if (!baseUrl)
|
|
61017
|
+
throw new Error("Robot cloud: ROBOT_CLOUD_BASE_URL is not configured.");
|
|
61018
|
+
let stopped = false;
|
|
61019
|
+
let attempt = 0;
|
|
61020
|
+
let current = null;
|
|
61021
|
+
let watchdog = null;
|
|
61022
|
+
let retry = null;
|
|
61023
|
+
let lastActivityAt = now();
|
|
61024
|
+
let everConnected = false;
|
|
61025
|
+
const parser = createSseParser();
|
|
61026
|
+
function clearTimers() {
|
|
61027
|
+
if (watchdog) {
|
|
61028
|
+
clearInterval(watchdog);
|
|
61029
|
+
watchdog = null;
|
|
61030
|
+
}
|
|
61031
|
+
if (retry) {
|
|
61032
|
+
clearTimeout(retry);
|
|
61033
|
+
retry = null;
|
|
61034
|
+
}
|
|
61035
|
+
}
|
|
61036
|
+
function dropUpstream() {
|
|
61037
|
+
if (!current)
|
|
61038
|
+
return;
|
|
61039
|
+
try {
|
|
61040
|
+
current.destroy();
|
|
61041
|
+
} catch {
|
|
61042
|
+
}
|
|
61043
|
+
current = null;
|
|
61044
|
+
}
|
|
61045
|
+
function lose(reason) {
|
|
61046
|
+
if (stopped)
|
|
61047
|
+
return;
|
|
61048
|
+
onEvent({ type: "lost", reason, at: now() });
|
|
61049
|
+
dropUpstream();
|
|
61050
|
+
scheduleReconnect();
|
|
61051
|
+
}
|
|
61052
|
+
function scheduleReconnect() {
|
|
61053
|
+
if (stopped)
|
|
61054
|
+
return;
|
|
61055
|
+
clearTimers();
|
|
61056
|
+
const delay2 = trackingBackoffMs(attempt++);
|
|
61057
|
+
retry = setTimeout(connect2, delay2);
|
|
61058
|
+
}
|
|
61059
|
+
async function connect2() {
|
|
61060
|
+
if (stopped)
|
|
61061
|
+
return;
|
|
61062
|
+
const body = robotCloudBody(
|
|
61063
|
+
credentials.appid,
|
|
61064
|
+
credentials.secret,
|
|
61065
|
+
ROBOT_CLOUD_PATHS.tracking,
|
|
61066
|
+
{ robotSn }
|
|
61067
|
+
);
|
|
61068
|
+
let stream;
|
|
61069
|
+
try {
|
|
61070
|
+
stream = await openStream(
|
|
61071
|
+
`${baseUrl.replace(/\/+$/, "")}${ROBOT_CLOUD_PATHS.tracking}`,
|
|
61072
|
+
body
|
|
61073
|
+
);
|
|
61074
|
+
} catch (error) {
|
|
61075
|
+
lose(error?.message ?? "Could not open the robot's tracking stream.");
|
|
61076
|
+
return;
|
|
61077
|
+
}
|
|
61078
|
+
if (stopped) {
|
|
61079
|
+
try {
|
|
61080
|
+
stream.destroy();
|
|
61081
|
+
} catch {
|
|
61082
|
+
}
|
|
61083
|
+
return;
|
|
61084
|
+
}
|
|
61085
|
+
parser.reset();
|
|
61086
|
+
current = stream;
|
|
61087
|
+
lastActivityAt = now();
|
|
61088
|
+
if (everConnected)
|
|
61089
|
+
onEvent({ type: "reconnected", at: now() });
|
|
61090
|
+
everConnected = true;
|
|
61091
|
+
attempt = 0;
|
|
61092
|
+
stream.on("data", (chunk2) => {
|
|
61093
|
+
if (stopped)
|
|
61094
|
+
return;
|
|
61095
|
+
const { events, sawActivity } = parser.push(chunk2.toString("utf8"));
|
|
61096
|
+
if (sawActivity)
|
|
61097
|
+
lastActivityAt = now();
|
|
61098
|
+
for (const event of events) {
|
|
61099
|
+
const pose = decodeTrackingFrame(event.data);
|
|
61100
|
+
if (pose)
|
|
61101
|
+
onEvent({ type: "pose", pose, at: now() });
|
|
61102
|
+
}
|
|
61103
|
+
});
|
|
61104
|
+
stream.on("error", (error) => lose(error?.message ?? "The tracking stream failed."));
|
|
61105
|
+
stream.on("end", () => lose("The robot cloud closed the tracking stream."));
|
|
61106
|
+
stream.on("close", () => {
|
|
61107
|
+
if (!stopped && current === stream)
|
|
61108
|
+
lose("The tracking stream closed.");
|
|
61109
|
+
});
|
|
61110
|
+
clearTimers();
|
|
61111
|
+
watchdog = setInterval(() => {
|
|
61112
|
+
if (stopped)
|
|
61113
|
+
return;
|
|
61114
|
+
if (now() - lastActivityAt >= idleTimeoutMs) {
|
|
61115
|
+
lose("The robot stopped reporting.");
|
|
61116
|
+
}
|
|
61117
|
+
}, Math.max(1e3, Math.floor(idleTimeoutMs / 3)));
|
|
61118
|
+
}
|
|
61119
|
+
void connect2();
|
|
61120
|
+
return {
|
|
61121
|
+
stop() {
|
|
61122
|
+
if (stopped)
|
|
61123
|
+
return;
|
|
61124
|
+
stopped = true;
|
|
61125
|
+
clearTimers();
|
|
61126
|
+
dropUpstream();
|
|
61127
|
+
}
|
|
61128
|
+
};
|
|
60692
61129
|
}
|
|
60693
61130
|
|
|
60694
61131
|
// src/controllers/robot-fleet.controller.ts
|
|
@@ -60769,7 +61206,62 @@ function useRobotFleetController(deps) {
|
|
|
60769
61206
|
fail(next, error);
|
|
60770
61207
|
}
|
|
60771
61208
|
}
|
|
60772
|
-
|
|
61209
|
+
async function trackRobot(req, res, next) {
|
|
61210
|
+
let robot;
|
|
61211
|
+
let robotSn;
|
|
61212
|
+
try {
|
|
61213
|
+
robot = await entitleRobot(req, req.params.id);
|
|
61214
|
+
robotSn = await getFleet().resolveRobotSn(robot);
|
|
61215
|
+
} catch (error) {
|
|
61216
|
+
fail(next, error);
|
|
61217
|
+
return;
|
|
61218
|
+
}
|
|
61219
|
+
res.writeHead(200, {
|
|
61220
|
+
"Content-Type": "text/event-stream",
|
|
61221
|
+
"Cache-Control": "no-cache, no-transform",
|
|
61222
|
+
Connection: "keep-alive",
|
|
61223
|
+
// Nginx buffers proxied responses by default, which holds an SSE stream
|
|
61224
|
+
// until the buffer fills - the stream appears dead for minutes.
|
|
61225
|
+
"X-Accel-Buffering": "no"
|
|
61226
|
+
});
|
|
61227
|
+
res.flushHeaders?.();
|
|
61228
|
+
const send2 = (event) => {
|
|
61229
|
+
if (res.writableEnded)
|
|
61230
|
+
return;
|
|
61231
|
+
res.write(`data: ${JSON.stringify(event)}
|
|
61232
|
+
|
|
61233
|
+
`);
|
|
61234
|
+
};
|
|
61235
|
+
const handle = startRobotTracking({
|
|
61236
|
+
credentials: {
|
|
61237
|
+
appid: (robot.appKey ?? "").trim(),
|
|
61238
|
+
secret: (robot.appToken ?? "").trim()
|
|
61239
|
+
},
|
|
61240
|
+
robotSn,
|
|
61241
|
+
onEvent: send2
|
|
61242
|
+
});
|
|
61243
|
+
const keepAlive = setInterval(() => {
|
|
61244
|
+
if (!res.writableEnded)
|
|
61245
|
+
res.write(": keep-alive\n\n");
|
|
61246
|
+
}, 15e3);
|
|
61247
|
+
const close = () => {
|
|
61248
|
+
clearInterval(keepAlive);
|
|
61249
|
+
handle.stop();
|
|
61250
|
+
if (!res.writableEnded)
|
|
61251
|
+
res.end();
|
|
61252
|
+
};
|
|
61253
|
+
req.on("close", close);
|
|
61254
|
+
req.on("aborted", close);
|
|
61255
|
+
res.on("error", close);
|
|
61256
|
+
}
|
|
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 };
|
|
60773
61265
|
}
|
|
60774
61266
|
|
|
60775
61267
|
// src/models/patrol-question.model.ts
|
|
@@ -81086,7 +81578,7 @@ var MManpowerMonitoring = class {
|
|
|
81086
81578
|
var import_node_server_utils248 = require("@7365admin1/node-server-utils");
|
|
81087
81579
|
|
|
81088
81580
|
// src/utils/hrmlabs-attendance.util.ts
|
|
81089
|
-
var
|
|
81581
|
+
var import_axios5 = __toESM(require("axios"));
|
|
81090
81582
|
var import_moment_timezone7 = __toESM(require("moment-timezone"));
|
|
81091
81583
|
var default_early_checkIn = 4;
|
|
81092
81584
|
async function hrmLabsAuthentication({
|
|
@@ -81095,7 +81587,7 @@ async function hrmLabsAuthentication({
|
|
|
81095
81587
|
password,
|
|
81096
81588
|
domain
|
|
81097
81589
|
}) {
|
|
81098
|
-
const res = await
|
|
81590
|
+
const res = await import_axios5.default.post(
|
|
81099
81591
|
authUrl,
|
|
81100
81592
|
{
|
|
81101
81593
|
grant_type: "password",
|
|
@@ -81125,7 +81617,7 @@ async function fetchAttendanceData({
|
|
|
81125
81617
|
search
|
|
81126
81618
|
}) {
|
|
81127
81619
|
try {
|
|
81128
|
-
const res = await
|
|
81620
|
+
const res = await import_axios5.default.post(
|
|
81129
81621
|
attendanceUrl,
|
|
81130
81622
|
{
|
|
81131
81623
|
start: startDate,
|
|
@@ -81325,7 +81817,7 @@ async function fetchSites({ siteUrl, token }) {
|
|
|
81325
81817
|
do {
|
|
81326
81818
|
try {
|
|
81327
81819
|
attempt++;
|
|
81328
|
-
const res = await
|
|
81820
|
+
const res = await import_axios5.default.get(siteUrl, {
|
|
81329
81821
|
headers: { Authorization: `Bearer ${token}` },
|
|
81330
81822
|
timeout: 15e3
|
|
81331
81823
|
});
|
|
@@ -84923,7 +85415,7 @@ async function manpowerEvents(io) {
|
|
|
84923
85415
|
}
|
|
84924
85416
|
|
|
84925
85417
|
// src/services/reddot-payment.service.ts
|
|
84926
|
-
var
|
|
85418
|
+
var import_axios6 = __toESM(require("axios"));
|
|
84927
85419
|
|
|
84928
85420
|
// src/utils/payment-signature.util.ts
|
|
84929
85421
|
var crypto9 = __toESM(require("crypto"));
|
|
@@ -85557,7 +86049,7 @@ var useRedDotPaymentSvc = () => {
|
|
|
85557
86049
|
);
|
|
85558
86050
|
}
|
|
85559
86051
|
const url = process.env.MERCHANT_PAYMENT;
|
|
85560
|
-
const response = await
|
|
86052
|
+
const response = await import_axios6.default.post(url, dataString, { headers });
|
|
85561
86053
|
const result = response.data;
|
|
85562
86054
|
return result;
|
|
85563
86055
|
} catch (error) {
|
|
@@ -85591,7 +86083,7 @@ var useRedDotPaymentSvc = () => {
|
|
|
85591
86083
|
);
|
|
85592
86084
|
}
|
|
85593
86085
|
const url = process.env.MERCHANT_ENQUIRY;
|
|
85594
|
-
const response = await
|
|
86086
|
+
const response = await import_axios6.default.post(url, dataString, { headers });
|
|
85595
86087
|
const result = response.data;
|
|
85596
86088
|
const transactionId = result.transaction_id;
|
|
85597
86089
|
const amount = result.request_amount || 0;
|
|
@@ -94130,6 +94622,8 @@ function usePersonalEmergencyChainController() {
|
|
|
94130
94622
|
DEFAULT_RING_SECONDS,
|
|
94131
94623
|
DEFAULT_SCALE_M_PER_PX,
|
|
94132
94624
|
DEFAULT_SITE_TIMEZONE,
|
|
94625
|
+
DEMO_BRAND,
|
|
94626
|
+
DEMO_CAPABILITIES,
|
|
94133
94627
|
DEVICE_STATUS,
|
|
94134
94628
|
DOBStatus,
|
|
94135
94629
|
DUPLICATE_TERMS_VERSION_MESSAGE,
|
|
@@ -94299,6 +94793,7 @@ function usePersonalEmergencyChainController() {
|
|
|
94299
94793
|
ROBOT_CAPABILITIES,
|
|
94300
94794
|
ROBOT_CLOUD_DOC_CONFIRMATIONS,
|
|
94301
94795
|
ROBOT_CLOUD_PATHS,
|
|
94796
|
+
ROBOT_CLOUD_TIMEOUT_MS,
|
|
94302
94797
|
ROBOT_MODULE,
|
|
94303
94798
|
ROBOT_PERMISSIONS,
|
|
94304
94799
|
ROBOT_STATUS_NAMES,
|
|
@@ -94350,6 +94845,7 @@ function usePersonalEmergencyChainController() {
|
|
|
94350
94845
|
VisitorStatus,
|
|
94351
94846
|
WALL_HEIGHT_M,
|
|
94352
94847
|
WALL_THICKNESS_M,
|
|
94848
|
+
adapterForRobot,
|
|
94353
94849
|
addressSchema,
|
|
94354
94850
|
allowedFieldsSite,
|
|
94355
94851
|
allowedNatures,
|
|
@@ -94389,13 +94885,14 @@ function usePersonalEmergencyChainController() {
|
|
|
94389
94885
|
chatSchema,
|
|
94390
94886
|
claimReadsAsStale,
|
|
94391
94887
|
clampPtzSpeed,
|
|
94392
|
-
|
|
94888
|
+
clearRobotBrands,
|
|
94393
94889
|
clockDriftSeconds,
|
|
94394
94890
|
commandSupported,
|
|
94395
94891
|
consoleRoleAllows,
|
|
94396
94892
|
consoleRoleAllowsAll,
|
|
94397
94893
|
consoleSpellings,
|
|
94398
94894
|
console_audit_namespace_collection,
|
|
94895
|
+
createDemoAdapter,
|
|
94399
94896
|
createLiftSimulator,
|
|
94400
94897
|
createManpowerRemarksDaily,
|
|
94401
94898
|
createOunitechAdapter,
|
|
@@ -94419,6 +94916,7 @@ function usePersonalEmergencyChainController() {
|
|
|
94419
94916
|
disconnectedState,
|
|
94420
94917
|
emitNotificationCreated,
|
|
94421
94918
|
emptyPersonalEmergencyChain,
|
|
94919
|
+
encodeGreyscalePng,
|
|
94422
94920
|
encodeHidPacsCard,
|
|
94423
94921
|
entitledSiteScope,
|
|
94424
94922
|
events_namespace_collection,
|
|
@@ -94437,6 +94935,7 @@ function usePersonalEmergencyChainController() {
|
|
|
94437
94935
|
getIO,
|
|
94438
94936
|
getSessionIdFromRequest,
|
|
94439
94937
|
grabWithSubStreamFallback,
|
|
94938
|
+
greyscalePngDataUri,
|
|
94440
94939
|
guests_namespace_collection,
|
|
94441
94940
|
hasAnyCapability,
|
|
94442
94941
|
hasAnyPermission,
|
|
@@ -94538,8 +95037,9 @@ function usePersonalEmergencyChainController() {
|
|
|
94538
95037
|
recordConsoleAction,
|
|
94539
95038
|
refuseServiceProviderInviteAction,
|
|
94540
95039
|
registerCameraTransport,
|
|
95040
|
+
registerDemoBrand,
|
|
94541
95041
|
registerOunitechBrand,
|
|
94542
|
-
|
|
95042
|
+
registerRobotBrand,
|
|
94543
95043
|
registeredRobotBrands,
|
|
94544
95044
|
relayForRecorder,
|
|
94545
95045
|
remarksSchema,
|
|
@@ -94558,7 +95058,7 @@ function usePersonalEmergencyChainController() {
|
|
|
94558
95058
|
resolveSiteTimezone,
|
|
94559
95059
|
resolveStaffRole,
|
|
94560
95060
|
robotActionsFor,
|
|
94561
|
-
|
|
95061
|
+
robotBrandFor,
|
|
94562
95062
|
robotCloudBody,
|
|
94563
95063
|
robotCloudSign,
|
|
94564
95064
|
robotCloudSigningString,
|
|
@@ -94737,6 +95237,7 @@ function usePersonalEmergencyChainController() {
|
|
|
94737
95237
|
snapshotEndpoint,
|
|
94738
95238
|
snapshotRefusalReason,
|
|
94739
95239
|
staffMayBypass,
|
|
95240
|
+
startRobotTracking,
|
|
94740
95241
|
startTodaysBookForSite,
|
|
94741
95242
|
stringifyHidJson,
|
|
94742
95243
|
stripFacialImageMetadata,
|