@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.mjs
CHANGED
|
@@ -19668,8 +19668,8 @@ function useDahuaService() {
|
|
|
19668
19668
|
}
|
|
19669
19669
|
}
|
|
19670
19670
|
}
|
|
19671
|
-
async function handleBuffer(
|
|
19672
|
-
partialBuffer = Buffer.concat([partialBuffer,
|
|
19671
|
+
async function handleBuffer(chunk2, onDetected2) {
|
|
19672
|
+
partialBuffer = Buffer.concat([partialBuffer, chunk2]);
|
|
19673
19673
|
while (true) {
|
|
19674
19674
|
const boundaryIndex = partialBuffer.indexOf(Buffer.from(boundary));
|
|
19675
19675
|
if (boundaryIndex === -1)
|
|
@@ -19920,12 +19920,12 @@ function useDahuaService() {
|
|
|
19920
19920
|
resolve("aborted");
|
|
19921
19921
|
};
|
|
19922
19922
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
19923
|
-
response.res.on("data", (
|
|
19923
|
+
response.res.on("data", (chunk2) => {
|
|
19924
19924
|
if (signal.aborted) {
|
|
19925
19925
|
response.res.destroy();
|
|
19926
19926
|
return;
|
|
19927
19927
|
}
|
|
19928
|
-
bufferQueue.enqueue(
|
|
19928
|
+
bufferQueue.enqueue(chunk2);
|
|
19929
19929
|
});
|
|
19930
19930
|
response.res.on("end", () => {
|
|
19931
19931
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -22529,10 +22529,10 @@ function buildAdapter(params) {
|
|
|
22529
22529
|
return;
|
|
22530
22530
|
let buffered = "";
|
|
22531
22531
|
const decoder = new TextDecoder();
|
|
22532
|
-
for await (const
|
|
22532
|
+
for await (const chunk2 of body) {
|
|
22533
22533
|
if (params2.signal.aborted)
|
|
22534
22534
|
return;
|
|
22535
|
-
buffered += decoder.decode(
|
|
22535
|
+
buffered += decoder.decode(chunk2, { stream: true });
|
|
22536
22536
|
let at2 = buffered.indexOf(marker);
|
|
22537
22537
|
while (at2 >= 0) {
|
|
22538
22538
|
const next = buffered.indexOf(marker, at2 + marker.length);
|
|
@@ -22844,16 +22844,16 @@ function useCameraViewService() {
|
|
|
22844
22844
|
() => finish(new Error("camera-timeout")),
|
|
22845
22845
|
CAMERA_RTSP_TIMEOUT_MS
|
|
22846
22846
|
);
|
|
22847
|
-
child.stdout.on("data", (
|
|
22848
|
-
bytes +=
|
|
22847
|
+
child.stdout.on("data", (chunk2) => {
|
|
22848
|
+
bytes += chunk2.length;
|
|
22849
22849
|
if (bytes > CAMERA_SNAPSHOT_MAX_BYTES) {
|
|
22850
22850
|
finish(new Error("camera-picture-too-large"));
|
|
22851
22851
|
return;
|
|
22852
22852
|
}
|
|
22853
|
-
chunks.push(
|
|
22853
|
+
chunks.push(chunk2);
|
|
22854
22854
|
});
|
|
22855
|
-
child.stderr?.on("data", (
|
|
22856
|
-
const text2 = carry +
|
|
22855
|
+
child.stderr?.on("data", (chunk2) => {
|
|
22856
|
+
const text2 = carry + chunk2.toString("latin1");
|
|
22857
22857
|
if (/401\s*unauthorized|\bunauthorized\b/i.test(text2))
|
|
22858
22858
|
rejected = true;
|
|
22859
22859
|
carry = text2.slice(-32);
|
|
@@ -32276,8 +32276,8 @@ function useOccurrenceBookRepo() {
|
|
|
32276
32276
|
const chunkSize = 500;
|
|
32277
32277
|
let totalInserted = 0;
|
|
32278
32278
|
for (let i = 0; i < payloads.length; i += chunkSize) {
|
|
32279
|
-
const
|
|
32280
|
-
const operations =
|
|
32279
|
+
const chunk2 = payloads.slice(i, i + chunkSize);
|
|
32280
|
+
const operations = chunk2.map((item) => ({
|
|
32281
32281
|
insertOne: {
|
|
32282
32282
|
document: {
|
|
32283
32283
|
...item,
|
|
@@ -51702,9 +51702,9 @@ function useVisitorTransactionService() {
|
|
|
51702
51702
|
expectedCheckIn
|
|
51703
51703
|
} = value;
|
|
51704
51704
|
for (let i = 0; i < value.members.length; i += chunkSize) {
|
|
51705
|
-
const
|
|
51705
|
+
const chunk2 = value.members.slice(i, i + chunkSize);
|
|
51706
51706
|
await Promise.all(
|
|
51707
|
-
|
|
51707
|
+
chunk2.map(async (member) => {
|
|
51708
51708
|
const clonedMember = structuredClone(member);
|
|
51709
51709
|
const { visitorPass, passKeys, nric: nric2, name: name2, contact: contact2 } = clonedMember;
|
|
51710
51710
|
if (/^(guest|walk-in|contractor|delivery)$/.test(type2 ?? "invalid") && site2) {
|
|
@@ -51946,9 +51946,9 @@ function useVisitorTransactionService() {
|
|
|
51946
51946
|
unitName
|
|
51947
51947
|
} = value;
|
|
51948
51948
|
for (let i = 0; i < value.members.length; i += chunkSize) {
|
|
51949
|
-
const
|
|
51949
|
+
const chunk2 = value.members.slice(i, i + chunkSize);
|
|
51950
51950
|
await Promise.all(
|
|
51951
|
-
|
|
51951
|
+
chunk2.map(async (member) => {
|
|
51952
51952
|
const clonedMember = structuredClone(member);
|
|
51953
51953
|
const { visitorPass, passKeys, nric: nric2, name: name2, contact: contact2 } = clonedMember;
|
|
51954
51954
|
if (/^(guest|walk-in|contractor|delivery)$/.test(type2 ?? "invalid") && site2) {
|
|
@@ -56409,8 +56409,8 @@ function useAccessManagementSvc() {
|
|
|
56409
56409
|
return new Promise((resolve, reject) => {
|
|
56410
56410
|
const fileStream = Readable.from(bufferFile);
|
|
56411
56411
|
let fileBuffer = Buffer.alloc(0);
|
|
56412
|
-
fileStream.on("data", (
|
|
56413
|
-
fileBuffer = Buffer.concat([fileBuffer,
|
|
56412
|
+
fileStream.on("data", (chunk2) => {
|
|
56413
|
+
fileBuffer = Buffer.concat([fileBuffer, chunk2]);
|
|
56414
56414
|
});
|
|
56415
56415
|
fileStream.on("end", () => {
|
|
56416
56416
|
try {
|
|
@@ -59266,10 +59266,10 @@ function parseRobotCommand(body) {
|
|
|
59266
59266
|
function createSseParser() {
|
|
59267
59267
|
let buffer = "";
|
|
59268
59268
|
let dataLines = [];
|
|
59269
|
-
function push(
|
|
59270
|
-
if (!
|
|
59269
|
+
function push(chunk2) {
|
|
59270
|
+
if (!chunk2)
|
|
59271
59271
|
return { events: [], sawActivity: false };
|
|
59272
|
-
buffer +=
|
|
59272
|
+
buffer += chunk2;
|
|
59273
59273
|
const events = [];
|
|
59274
59274
|
buffer = buffer.replace(/\r\n|\r/g, "\n");
|
|
59275
59275
|
const lines = buffer.split("\n");
|
|
@@ -59535,26 +59535,44 @@ function commandSupported(capabilities, kind) {
|
|
|
59535
59535
|
return false;
|
|
59536
59536
|
return supports(capabilities, needed);
|
|
59537
59537
|
}
|
|
59538
|
-
var
|
|
59539
|
-
function
|
|
59540
|
-
if (!
|
|
59541
|
-
throw new Error("A robot
|
|
59542
|
-
|
|
59538
|
+
var brands = /* @__PURE__ */ new Map();
|
|
59539
|
+
function registerRobotBrand(brand) {
|
|
59540
|
+
if (!brand?.brand)
|
|
59541
|
+
throw new Error("A robot brand must name itself.");
|
|
59542
|
+
if (typeof brand.create !== "function") {
|
|
59543
|
+
throw new Error(`Robot brand "${brand.brand}" has no way to build an adapter.`);
|
|
59544
|
+
}
|
|
59545
|
+
brands.set(brand.brand, brand);
|
|
59543
59546
|
}
|
|
59544
|
-
function
|
|
59547
|
+
function robotBrandFor(brand) {
|
|
59545
59548
|
if (typeof brand !== "string" || !brand)
|
|
59546
59549
|
return null;
|
|
59547
|
-
return
|
|
59550
|
+
return brands.get(brand) ?? null;
|
|
59551
|
+
}
|
|
59552
|
+
function adapterForRobot(robot) {
|
|
59553
|
+
const brandName = (robot?.brand ?? "").trim();
|
|
59554
|
+
const brand = robotBrandFor(brandName);
|
|
59555
|
+
if (!brand) {
|
|
59556
|
+
throw new Error(
|
|
59557
|
+
brandName ? `No robot adapter is registered for the brand "${brandName}".` : `Robot "${robot?.name ?? "unknown"}" does not say which brand it is.`
|
|
59558
|
+
);
|
|
59559
|
+
}
|
|
59560
|
+
const appid = (robot.appKey ?? "").trim();
|
|
59561
|
+
const secret = (robot.appToken ?? "").trim();
|
|
59562
|
+
if (!appid || !secret) {
|
|
59563
|
+
throw new Error(`Robot "${robot?.name ?? brandName}" has no cloud credentials saved.`);
|
|
59564
|
+
}
|
|
59565
|
+
return brand.create({ appid, secret });
|
|
59548
59566
|
}
|
|
59549
59567
|
function registeredRobotBrands() {
|
|
59550
|
-
return [...
|
|
59551
|
-
brand:
|
|
59552
|
-
displayName:
|
|
59553
|
-
tier: robotTier(
|
|
59568
|
+
return [...brands.values()].map((b) => ({
|
|
59569
|
+
brand: b.brand,
|
|
59570
|
+
displayName: b.displayName,
|
|
59571
|
+
tier: robotTier(b.capabilities)
|
|
59554
59572
|
}));
|
|
59555
59573
|
}
|
|
59556
|
-
function
|
|
59557
|
-
|
|
59574
|
+
function clearRobotBrands() {
|
|
59575
|
+
brands.clear();
|
|
59558
59576
|
}
|
|
59559
59577
|
|
|
59560
59578
|
// src/utils/lift-contract.util.ts
|
|
@@ -60010,122 +60028,6 @@ function useRobotCloudService(credentials, options = {}) {
|
|
|
60010
60028
|
};
|
|
60011
60029
|
}
|
|
60012
60030
|
|
|
60013
|
-
// src/services/robot-fleet.service.ts
|
|
60014
|
-
function credentialsOf(robot) {
|
|
60015
|
-
const appid = (robot.appKey ?? "").trim();
|
|
60016
|
-
const secret = (robot.appToken ?? "").trim();
|
|
60017
|
-
if (!appid || !secret) {
|
|
60018
|
-
throw new BadRequestError134(
|
|
60019
|
-
`Robot "${robot.name}" has no cloud credentials saved.`
|
|
60020
|
-
);
|
|
60021
|
-
}
|
|
60022
|
-
return { appid, secret };
|
|
60023
|
-
}
|
|
60024
|
-
function useRobotFleetService(deps) {
|
|
60025
|
-
const cloudFor = deps?.cloudFor ?? useRobotCloudService;
|
|
60026
|
-
function clientFor(robot) {
|
|
60027
|
-
return cloudFor(credentialsOf(robot));
|
|
60028
|
-
}
|
|
60029
|
-
async function resolveRobotSn(robot) {
|
|
60030
|
-
const stored = (robot.robotSn ?? "").trim();
|
|
60031
|
-
if (stored)
|
|
60032
|
-
return stored;
|
|
60033
|
-
const fleet = await clientFor(robot).getRobots();
|
|
60034
|
-
if (fleet.length === 0) {
|
|
60035
|
-
throw new NotFoundError43(
|
|
60036
|
-
`Robot "${robot.name}": the cloud reports no robots for these credentials.`
|
|
60037
|
-
);
|
|
60038
|
-
}
|
|
60039
|
-
if (fleet.length > 1) {
|
|
60040
|
-
throw new BadRequestError134(
|
|
60041
|
-
`Robot "${robot.name}": these credentials cover ${fleet.length} robots, so the serial cannot be determined automatically. Set it explicitly.`
|
|
60042
|
-
);
|
|
60043
|
-
}
|
|
60044
|
-
const robotSn = fleet[0].robotSn;
|
|
60045
|
-
if (!robotSn) {
|
|
60046
|
-
throw new BadRequestError134(
|
|
60047
|
-
`Robot "${robot.name}": the cloud returned a robot with no serial.`
|
|
60048
|
-
);
|
|
60049
|
-
}
|
|
60050
|
-
const id = robot._id ? String(robot._id) : "";
|
|
60051
|
-
if (id && deps?.saveRobotSn) {
|
|
60052
|
-
await deps.saveRobotSn(id, robotSn).catch(() => void 0);
|
|
60053
|
-
}
|
|
60054
|
-
return robotSn;
|
|
60055
|
-
}
|
|
60056
|
-
async function getFleetRobot(robot) {
|
|
60057
|
-
const robotSn = await resolveRobotSn(robot);
|
|
60058
|
-
const fleet = await clientFor(robot).getRobots();
|
|
60059
|
-
const match = fleet.find(
|
|
60060
|
-
(r) => r.robotSn === robotSn
|
|
60061
|
-
);
|
|
60062
|
-
if (!match) {
|
|
60063
|
-
return {
|
|
60064
|
-
id: robot._id ? String(robot._id) : "",
|
|
60065
|
-
name: robot.name,
|
|
60066
|
-
robotSn,
|
|
60067
|
-
statusFlag: null,
|
|
60068
|
-
statusName: "Not found in the robot cloud",
|
|
60069
|
-
mode: "UNAVAILABLE",
|
|
60070
|
-
online: false,
|
|
60071
|
-
dispatchable: false
|
|
60072
|
-
};
|
|
60073
|
-
}
|
|
60074
|
-
const mode = robotModeFromList(match);
|
|
60075
|
-
return {
|
|
60076
|
-
id: robot._id ? String(robot._id) : "",
|
|
60077
|
-
name: robot.name,
|
|
60078
|
-
robotSn,
|
|
60079
|
-
statusFlag: typeof match.statusFlag === "number" ? match.statusFlag : null,
|
|
60080
|
-
statusName: match.statusName ?? "",
|
|
60081
|
-
mode,
|
|
60082
|
-
online: match.onlineFlag !== false,
|
|
60083
|
-
dispatchable: mode === "IDLE" || mode === "CHARGED"
|
|
60084
|
-
};
|
|
60085
|
-
}
|
|
60086
|
-
async function getMaps(robot) {
|
|
60087
|
-
const robotSn = await resolveRobotSn(robot);
|
|
60088
|
-
return clientFor(robot).getMaps(robotSn);
|
|
60089
|
-
}
|
|
60090
|
-
async function getMapImages(robot, fileKeys) {
|
|
60091
|
-
const robotSn = await resolveRobotSn(robot);
|
|
60092
|
-
return clientFor(robot).getFileUrls(robotSn, fileKeys);
|
|
60093
|
-
}
|
|
60094
|
-
async function dispatch(robot, command) {
|
|
60095
|
-
const robotSn = await resolveRobotSn(robot);
|
|
60096
|
-
const cloud = clientFor(robot);
|
|
60097
|
-
if (command.kind === "return") {
|
|
60098
|
-
return cloud.returnToBase(robotSn);
|
|
60099
|
-
}
|
|
60100
|
-
const state = await getFleetRobot(robot);
|
|
60101
|
-
if (!state.dispatchable) {
|
|
60102
|
-
throw new BadRequestError134(
|
|
60103
|
-
`Robot "${robot.name}" is ${state.statusName || state.mode} and cannot take a new job.`
|
|
60104
|
-
);
|
|
60105
|
-
}
|
|
60106
|
-
switch (command.kind) {
|
|
60107
|
-
case "full":
|
|
60108
|
-
return cloud.startFullCleaning(robotSn, command.options);
|
|
60109
|
-
case "area":
|
|
60110
|
-
return cloud.startAreaCleaning(robotSn, command.areas, command.options);
|
|
60111
|
-
case "spot":
|
|
60112
|
-
return cloud.startSpotCleaning(robotSn, command.points, command.options);
|
|
60113
|
-
}
|
|
60114
|
-
}
|
|
60115
|
-
async function getPlans(robot) {
|
|
60116
|
-
const robotSn = await resolveRobotSn(robot);
|
|
60117
|
-
return clientFor(robot).getPlans(robotSn);
|
|
60118
|
-
}
|
|
60119
|
-
return {
|
|
60120
|
-
resolveRobotSn,
|
|
60121
|
-
getFleetRobot,
|
|
60122
|
-
getMaps,
|
|
60123
|
-
getMapImages,
|
|
60124
|
-
dispatch,
|
|
60125
|
-
getPlans
|
|
60126
|
-
};
|
|
60127
|
-
}
|
|
60128
|
-
|
|
60129
60031
|
// src/services/ounitech.adapter.ts
|
|
60130
60032
|
var OUNITECH_BRAND = "ounitech";
|
|
60131
60033
|
var OUNITECH_CAPABILITIES = {
|
|
@@ -60229,24 +60131,550 @@ function createOunitechAdapter(credentials, options) {
|
|
|
60229
60131
|
async getMaps(robotSn) {
|
|
60230
60132
|
return cloud.getMaps(robotSn);
|
|
60231
60133
|
},
|
|
60134
|
+
async getMapImages(robotSn, fileKeys) {
|
|
60135
|
+
return cloud.getFileUrls(robotSn, fileKeys);
|
|
60136
|
+
},
|
|
60232
60137
|
async getPlans(robotSn) {
|
|
60233
60138
|
return cloud.getPlans(robotSn);
|
|
60234
60139
|
}
|
|
60235
60140
|
};
|
|
60236
60141
|
}
|
|
60237
60142
|
function registerOunitechBrand() {
|
|
60238
|
-
|
|
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({
|
|
60143
|
+
registerRobotBrand({
|
|
60244
60144
|
brand: OUNITECH_BRAND,
|
|
60245
60145
|
displayName: "Ounitech (Q3-G / Q3-W)",
|
|
60246
60146
|
capabilities: OUNITECH_CAPABILITIES,
|
|
60247
|
-
|
|
60248
|
-
|
|
60147
|
+
create: (credentials) => createOunitechAdapter(credentials)
|
|
60148
|
+
});
|
|
60149
|
+
}
|
|
60150
|
+
registerOunitechBrand();
|
|
60151
|
+
|
|
60152
|
+
// src/utils/png-encoder.util.ts
|
|
60153
|
+
import { deflateSync } from "zlib";
|
|
60154
|
+
function crc32(buffer) {
|
|
60155
|
+
let crc = ~0;
|
|
60156
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
60157
|
+
crc ^= buffer[i];
|
|
60158
|
+
for (let bit = 0; bit < 8; bit++) {
|
|
60159
|
+
crc = crc & 1 ? crc >>> 1 ^ 3988292384 : crc >>> 1;
|
|
60160
|
+
}
|
|
60161
|
+
}
|
|
60162
|
+
return ~crc >>> 0;
|
|
60163
|
+
}
|
|
60164
|
+
function chunk(type, data) {
|
|
60165
|
+
const length = Buffer.alloc(4);
|
|
60166
|
+
length.writeUInt32BE(data.length, 0);
|
|
60167
|
+
const typed = Buffer.concat([Buffer.from(type, "ascii"), data]);
|
|
60168
|
+
const crc = Buffer.alloc(4);
|
|
60169
|
+
crc.writeUInt32BE(crc32(typed), 0);
|
|
60170
|
+
return Buffer.concat([length, typed, crc]);
|
|
60171
|
+
}
|
|
60172
|
+
function encodeGreyscalePng(pixels, width, height) {
|
|
60173
|
+
if (width <= 0 || height <= 0)
|
|
60174
|
+
throw new Error("A PNG needs a positive size.");
|
|
60175
|
+
if (pixels.length < width * height) {
|
|
60176
|
+
throw new Error("A PNG needs one byte per pixel.");
|
|
60177
|
+
}
|
|
60178
|
+
const raw = Buffer.alloc((width + 1) * height);
|
|
60179
|
+
for (let y = 0; y < height; y++) {
|
|
60180
|
+
raw[y * (width + 1)] = 0;
|
|
60181
|
+
for (let x = 0; x < width; x++) {
|
|
60182
|
+
raw[y * (width + 1) + 1 + x] = pixels[y * width + x];
|
|
60183
|
+
}
|
|
60184
|
+
}
|
|
60185
|
+
const ihdr = Buffer.alloc(13);
|
|
60186
|
+
ihdr.writeUInt32BE(width, 0);
|
|
60187
|
+
ihdr.writeUInt32BE(height, 4);
|
|
60188
|
+
ihdr[8] = 8;
|
|
60189
|
+
ihdr[9] = 0;
|
|
60190
|
+
ihdr[10] = 0;
|
|
60191
|
+
ihdr[11] = 0;
|
|
60192
|
+
ihdr[12] = 0;
|
|
60193
|
+
return Buffer.concat([
|
|
60194
|
+
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
|
|
60195
|
+
chunk("IHDR", ihdr),
|
|
60196
|
+
chunk("IDAT", deflateSync(raw)),
|
|
60197
|
+
chunk("IEND", Buffer.alloc(0))
|
|
60198
|
+
]);
|
|
60199
|
+
}
|
|
60200
|
+
function greyscalePngDataUri(pixels, width, height) {
|
|
60201
|
+
return `data:image/png;base64,${encodeGreyscalePng(pixels, width, height).toString("base64")}`;
|
|
60202
|
+
}
|
|
60203
|
+
|
|
60204
|
+
// src/services/demo.adapter.ts
|
|
60205
|
+
var DEMO_BRAND = "demo";
|
|
60206
|
+
var DEMO_CAPABILITIES = {
|
|
60207
|
+
readLocation: true,
|
|
60208
|
+
navigateToPoint: true,
|
|
60209
|
+
pauseResume: true,
|
|
60210
|
+
pathAndEta: true,
|
|
60211
|
+
battery: true,
|
|
60212
|
+
sendToDock: true,
|
|
60213
|
+
relocalize: true,
|
|
60214
|
+
startProcess: true,
|
|
60215
|
+
pauseProcess: true,
|
|
60216
|
+
processCompletion: true,
|
|
60217
|
+
floorReporting: true,
|
|
60218
|
+
multiFloor: true
|
|
60219
|
+
};
|
|
60220
|
+
var SCALE = 0.05;
|
|
60221
|
+
var PLAN_W = 260;
|
|
60222
|
+
var PLAN_H = 180;
|
|
60223
|
+
var DEMO_ROBOTS = [
|
|
60224
|
+
{ robotSn: "DEMO-0001", name: "Demo Cleaner 01", model: "Demo S1", floorNo: 1, phase: 0 },
|
|
60225
|
+
{ robotSn: "DEMO-0002", name: "Demo Cleaner 02", model: "Demo S1", floorNo: 1, phase: 0.37 },
|
|
60226
|
+
{ robotSn: "DEMO-0003", name: "Demo Scrubber 03", model: "Demo X2", floorNo: 2, phase: 0.68 }
|
|
60227
|
+
];
|
|
60228
|
+
var commanded = /* @__PURE__ */ new Map();
|
|
60229
|
+
function demoPlanPixels(floorNo) {
|
|
60230
|
+
const px = new Uint8Array(PLAN_W * PLAN_H).fill(255);
|
|
60231
|
+
const set = (x, y) => {
|
|
60232
|
+
if (x >= 0 && x < PLAN_W && y >= 0 && y < PLAN_H)
|
|
60233
|
+
px[y * PLAN_W + x] = 0;
|
|
60234
|
+
};
|
|
60235
|
+
for (let x = 0; x < PLAN_W; x++) {
|
|
60236
|
+
for (const t of [0, 1]) {
|
|
60237
|
+
set(x, t);
|
|
60238
|
+
set(x, PLAN_H - 1 - t);
|
|
60239
|
+
}
|
|
60240
|
+
}
|
|
60241
|
+
for (let y = 0; y < PLAN_H; y++) {
|
|
60242
|
+
for (const t of [0, 1]) {
|
|
60243
|
+
set(t, y);
|
|
60244
|
+
set(PLAN_W - 1 - t, y);
|
|
60245
|
+
}
|
|
60246
|
+
}
|
|
60247
|
+
const wallX = Math.floor(PLAN_W * (floorNo === 1 ? 0.45 : 0.6));
|
|
60248
|
+
for (let y = 2; y < PLAN_H - 2; y++) {
|
|
60249
|
+
if (y > PLAN_H * 0.45 && y < PLAN_H * 0.6)
|
|
60250
|
+
continue;
|
|
60251
|
+
set(wallX, y);
|
|
60252
|
+
set(wallX + 1, y);
|
|
60253
|
+
}
|
|
60254
|
+
if (floorNo === 2) {
|
|
60255
|
+
const wallY = Math.floor(PLAN_H * 0.5);
|
|
60256
|
+
for (let x = 2; x < wallX; x++) {
|
|
60257
|
+
if (x > PLAN_W * 0.15 && x < PLAN_W * 0.25)
|
|
60258
|
+
continue;
|
|
60259
|
+
set(x, wallY);
|
|
60260
|
+
set(x, wallY + 1);
|
|
60261
|
+
}
|
|
60262
|
+
}
|
|
60263
|
+
return px;
|
|
60264
|
+
}
|
|
60265
|
+
function demoPosition(robot, now) {
|
|
60266
|
+
const t = (now / 9e4 + robot.phase) % 1;
|
|
60267
|
+
const angle = t * Math.PI * 2;
|
|
60268
|
+
const cx = PLAN_W * 0.5 * SCALE;
|
|
60269
|
+
const cy = PLAN_H * 0.5 * SCALE;
|
|
60270
|
+
const rx = PLAN_W * 0.28 * SCALE;
|
|
60271
|
+
const ry = PLAN_H * 0.28 * SCALE;
|
|
60272
|
+
return {
|
|
60273
|
+
x: cx + Math.cos(angle) * rx,
|
|
60274
|
+
y: cy + Math.sin(angle) * ry,
|
|
60275
|
+
// Facing along the direction of travel, which is the tangent.
|
|
60276
|
+
theta: angle + Math.PI / 2
|
|
60277
|
+
};
|
|
60278
|
+
}
|
|
60279
|
+
function demoBattery(robot, now) {
|
|
60280
|
+
const t = (now / 36e5 + robot.phase) % 1;
|
|
60281
|
+
return Math.round(t < 0.8 ? 100 - t / 0.8 * 75 : 25 + (t - 0.8) / 0.2 * 75);
|
|
60282
|
+
}
|
|
60283
|
+
function demoState(robot, now) {
|
|
60284
|
+
const order = commanded.get(robot.robotSn);
|
|
60285
|
+
const active = order && order.until > now ? order.mode : null;
|
|
60286
|
+
const battery = demoBattery(robot, now);
|
|
60287
|
+
const mode = battery <= 25 ? "CHARGING_CYCLE" : active ?? "IDLE";
|
|
60288
|
+
return {
|
|
60289
|
+
robotSn: robot.robotSn,
|
|
60290
|
+
name: robot.name,
|
|
60291
|
+
model: robot.model,
|
|
60292
|
+
online: true,
|
|
60293
|
+
mode,
|
|
60294
|
+
statusName: mode === "WORKING" ? "Cleaning" : mode === "CHARGING_CYCLE" ? "Charging" : "Idle",
|
|
60295
|
+
batteryPercent: battery,
|
|
60296
|
+
floorNo: robot.floorNo,
|
|
60297
|
+
// The demo DOES know its floor - unlike Ounitech today - so the map can
|
|
60298
|
+
// draw it. That contrast is part of what the demo shows.
|
|
60299
|
+
floorKnown: true,
|
|
60300
|
+
position: demoPosition(robot, now)
|
|
60301
|
+
};
|
|
60302
|
+
}
|
|
60303
|
+
function createDemoAdapter(options) {
|
|
60304
|
+
const now = options?.now ?? Date.now;
|
|
60305
|
+
return {
|
|
60306
|
+
brand: DEMO_BRAND,
|
|
60307
|
+
displayName: "Demo robots (simulated)",
|
|
60308
|
+
capabilities: DEMO_CAPABILITIES,
|
|
60309
|
+
async listRobots() {
|
|
60310
|
+
const at2 = now();
|
|
60311
|
+
return DEMO_ROBOTS.map((r) => demoState(r, at2));
|
|
60312
|
+
},
|
|
60313
|
+
async getRobot(robotSn) {
|
|
60314
|
+
const robot = DEMO_ROBOTS.find((r) => r.robotSn === robotSn);
|
|
60315
|
+
return robot ? demoState(robot, now()) : null;
|
|
60316
|
+
},
|
|
60317
|
+
async sendCommand(robotSn, command) {
|
|
60318
|
+
const robot = DEMO_ROBOTS.find((r) => r.robotSn === robotSn);
|
|
60319
|
+
if (!robot)
|
|
60320
|
+
throw new Error(`No demo robot with serial ${robotSn}.`);
|
|
60321
|
+
const at2 = now();
|
|
60322
|
+
switch (command.kind) {
|
|
60323
|
+
case "full":
|
|
60324
|
+
case "area":
|
|
60325
|
+
case "spot":
|
|
60326
|
+
commanded.set(robotSn, { mode: "WORKING", until: at2 + 10 * 6e4 });
|
|
60327
|
+
return;
|
|
60328
|
+
case "return":
|
|
60329
|
+
commanded.set(robotSn, { mode: "CHARGING_CYCLE", until: at2 + 5 * 6e4 });
|
|
60330
|
+
return;
|
|
60331
|
+
case "pause":
|
|
60332
|
+
commanded.set(robotSn, { mode: "PAUSED", until: at2 + 10 * 6e4 });
|
|
60333
|
+
return;
|
|
60334
|
+
case "resume":
|
|
60335
|
+
commanded.set(robotSn, { mode: "WORKING", until: at2 + 10 * 6e4 });
|
|
60336
|
+
return;
|
|
60337
|
+
case "cancel":
|
|
60338
|
+
commanded.delete(robotSn);
|
|
60339
|
+
return;
|
|
60340
|
+
case "navigate":
|
|
60341
|
+
case "relocalize":
|
|
60342
|
+
commanded.set(robotSn, { mode: "WORKING", until: at2 + 6e4 });
|
|
60343
|
+
return;
|
|
60344
|
+
}
|
|
60345
|
+
},
|
|
60346
|
+
async getMaps() {
|
|
60347
|
+
return DEMO_ROBOTS.map((r) => r.floorNo).filter((floorNo, i, all) => all.indexOf(floorNo) === i).sort((a, b) => a - b).map((floorNo) => ({
|
|
60348
|
+
floorNo,
|
|
60349
|
+
mapName: `Demo Building L${floorNo}`,
|
|
60350
|
+
backgroundFileKey: `demo/L${floorNo}.png`,
|
|
60351
|
+
partitionFileKey: `demo/L${floorNo}.png`,
|
|
60352
|
+
originX: 0,
|
|
60353
|
+
originY: 0,
|
|
60354
|
+
resolution: SCALE,
|
|
60355
|
+
totalArea: Math.round(PLAN_W * PLAN_H * SCALE * SCALE),
|
|
60356
|
+
baseX: 12,
|
|
60357
|
+
baseY: 3,
|
|
60358
|
+
baseTheta: 0,
|
|
60359
|
+
createTime: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
60360
|
+
areaList: [
|
|
60361
|
+
{ areaName: `L${floorNo}-LOBBY`, rgba: "FF0B0B83", x: 60, y: 60 },
|
|
60362
|
+
{ areaName: `L${floorNo}-OFFICE`, rgba: "B7CAEFFF", x: 190, y: 60 },
|
|
60363
|
+
{ areaName: `L${floorNo}-CORRIDOR`, rgba: "BA144BFF", x: 120, y: 140 }
|
|
60364
|
+
]
|
|
60365
|
+
}));
|
|
60366
|
+
},
|
|
60367
|
+
/**
|
|
60368
|
+
* Data URIs rather than signed links.
|
|
60369
|
+
*
|
|
60370
|
+
* A demo must work with no storage account and no network, and a `data:`
|
|
60371
|
+
* URI is same-origin so the canvas is not tainted — which matters, because
|
|
60372
|
+
* the layout is built by reading these pixels back.
|
|
60373
|
+
*/
|
|
60374
|
+
async getMapImages(_robotSn, fileKeys) {
|
|
60375
|
+
return fileKeys.map((fileKey) => {
|
|
60376
|
+
const floorNo = fileKey.includes("L2") ? 2 : 1;
|
|
60377
|
+
return {
|
|
60378
|
+
fileKey,
|
|
60379
|
+
url: greyscalePngDataUri(demoPlanPixels(floorNo), PLAN_W, PLAN_H)
|
|
60380
|
+
};
|
|
60381
|
+
});
|
|
60382
|
+
},
|
|
60383
|
+
async getPlans() {
|
|
60384
|
+
return [
|
|
60385
|
+
{
|
|
60386
|
+
planId: 1,
|
|
60387
|
+
dateList: [1, 2, 3, 4, 5],
|
|
60388
|
+
startTime: "08:00",
|
|
60389
|
+
endTime: "10:00",
|
|
60390
|
+
enableFlag: true,
|
|
60391
|
+
jobList: [
|
|
60392
|
+
{
|
|
60393
|
+
floorNo: 1,
|
|
60394
|
+
rangeFlag: 3,
|
|
60395
|
+
cleanTimes: 1,
|
|
60396
|
+
pointList: [],
|
|
60397
|
+
colorList: [{ r: 255, g: 11, b: 11, a: 131 }],
|
|
60398
|
+
modeFlag: 1,
|
|
60399
|
+
subModeFlag: 0,
|
|
60400
|
+
levelFlag: 1
|
|
60401
|
+
}
|
|
60402
|
+
]
|
|
60403
|
+
}
|
|
60404
|
+
];
|
|
60405
|
+
}
|
|
60406
|
+
};
|
|
60407
|
+
}
|
|
60408
|
+
function registerDemoBrand() {
|
|
60409
|
+
registerRobotBrand({
|
|
60410
|
+
brand: DEMO_BRAND,
|
|
60411
|
+
displayName: "Demo robots (simulated)",
|
|
60412
|
+
capabilities: DEMO_CAPABILITIES,
|
|
60413
|
+
create: () => createDemoAdapter()
|
|
60414
|
+
});
|
|
60415
|
+
}
|
|
60416
|
+
registerDemoBrand();
|
|
60417
|
+
|
|
60418
|
+
// src/services/robot-fleet.service.ts
|
|
60419
|
+
function useRobotFleetService(deps) {
|
|
60420
|
+
const adapterFor = deps?.adapterFor ?? ((robot) => adapterForRobot(robot));
|
|
60421
|
+
function capabilitiesOf(robot) {
|
|
60422
|
+
return robotBrandFor(robot.brand ?? "")?.capabilities ?? {};
|
|
60423
|
+
}
|
|
60424
|
+
async function resolveRobotSn(robot) {
|
|
60425
|
+
const stored = (robot.robotSn ?? "").trim();
|
|
60426
|
+
if (stored)
|
|
60427
|
+
return stored;
|
|
60428
|
+
const fleet = await adapterFor(robot).listRobots();
|
|
60429
|
+
if (fleet.length === 0) {
|
|
60430
|
+
throw new NotFoundError43(
|
|
60431
|
+
`Robot "${robot.name}": the robot cloud reports no robots for these credentials.`
|
|
60432
|
+
);
|
|
60433
|
+
}
|
|
60434
|
+
if (fleet.length > 1) {
|
|
60435
|
+
throw new BadRequestError134(
|
|
60436
|
+
`Robot "${robot.name}": these credentials cover ${fleet.length} robots, so the serial cannot be determined automatically. Set it explicitly.`
|
|
60437
|
+
);
|
|
60438
|
+
}
|
|
60439
|
+
const robotSn = fleet[0].robotSn;
|
|
60440
|
+
if (!robotSn) {
|
|
60441
|
+
throw new BadRequestError134(`Robot "${robot.name}": the cloud returned a robot with no serial.`);
|
|
60442
|
+
}
|
|
60443
|
+
const id = robot._id ? String(robot._id) : "";
|
|
60444
|
+
if (id && deps?.saveRobotSn) {
|
|
60445
|
+
await deps.saveRobotSn(id, robotSn).catch(() => void 0);
|
|
60446
|
+
}
|
|
60447
|
+
return robotSn;
|
|
60448
|
+
}
|
|
60449
|
+
async function getFleetRobot(robot) {
|
|
60450
|
+
const adapter = adapterFor(robot);
|
|
60451
|
+
const robotSn = await resolveRobotSn(robot);
|
|
60452
|
+
const found = await adapter.getRobot(robotSn);
|
|
60453
|
+
const id = robot._id ? String(robot._id) : "";
|
|
60454
|
+
const brand = adapter.brand;
|
|
60455
|
+
if (!found) {
|
|
60456
|
+
return {
|
|
60457
|
+
id,
|
|
60458
|
+
name: robot.name,
|
|
60459
|
+
brand,
|
|
60460
|
+
robotSn,
|
|
60461
|
+
statusName: "Not found in the robot cloud",
|
|
60462
|
+
mode: "UNAVAILABLE",
|
|
60463
|
+
online: false,
|
|
60464
|
+
dispatchable: false,
|
|
60465
|
+
batteryPercent: null,
|
|
60466
|
+
floorNo: null,
|
|
60467
|
+
floorKnown: false,
|
|
60468
|
+
position: null
|
|
60469
|
+
};
|
|
60470
|
+
}
|
|
60471
|
+
const free = found.mode === "IDLE" || found.mode === "CHARGED";
|
|
60472
|
+
return {
|
|
60473
|
+
id,
|
|
60474
|
+
name: robot.name,
|
|
60475
|
+
brand,
|
|
60476
|
+
robotSn,
|
|
60477
|
+
statusName: found.statusName ?? "",
|
|
60478
|
+
mode: found.mode,
|
|
60479
|
+
online: found.online,
|
|
60480
|
+
dispatchable: found.online && free && commandSupported(adapter.capabilities, "full"),
|
|
60481
|
+
batteryPercent: found.batteryPercent ?? null,
|
|
60482
|
+
floorNo: found.floorNo ?? null,
|
|
60483
|
+
floorKnown: found.floorKnown === true,
|
|
60484
|
+
position: found.position ?? null
|
|
60485
|
+
};
|
|
60486
|
+
}
|
|
60487
|
+
async function getMaps(robot) {
|
|
60488
|
+
const adapter = adapterFor(robot);
|
|
60489
|
+
if (!adapter.getMaps)
|
|
60490
|
+
return [];
|
|
60491
|
+
return adapter.getMaps(await resolveRobotSn(robot));
|
|
60492
|
+
}
|
|
60493
|
+
async function getMapImages(robot, fileKeys) {
|
|
60494
|
+
const adapter = adapterFor(robot);
|
|
60495
|
+
if (!adapter.getMapImages || !fileKeys.length)
|
|
60496
|
+
return [];
|
|
60497
|
+
return adapter.getMapImages(await resolveRobotSn(robot), fileKeys);
|
|
60498
|
+
}
|
|
60499
|
+
async function getPlans(robot) {
|
|
60500
|
+
const adapter = adapterFor(robot);
|
|
60501
|
+
if (!adapter.getPlans)
|
|
60502
|
+
return [];
|
|
60503
|
+
return adapter.getPlans(await resolveRobotSn(robot));
|
|
60504
|
+
}
|
|
60505
|
+
async function dispatch(robot, command) {
|
|
60506
|
+
const adapter = adapterFor(robot);
|
|
60507
|
+
if (!adapter.sendCommand) {
|
|
60508
|
+
throw new BadRequestError134(
|
|
60509
|
+
`${adapter.displayName} robots cannot be commanded through their API.`
|
|
60510
|
+
);
|
|
60511
|
+
}
|
|
60512
|
+
if (!commandSupported(adapter.capabilities, command.kind)) {
|
|
60513
|
+
throw new BadRequestError134(
|
|
60514
|
+
`${adapter.displayName} does not support "${command.kind}" through its API yet.`
|
|
60515
|
+
);
|
|
60516
|
+
}
|
|
60517
|
+
const robotSn = await resolveRobotSn(robot);
|
|
60518
|
+
if (command.kind === "return") {
|
|
60519
|
+
return adapter.sendCommand(robotSn, command);
|
|
60520
|
+
}
|
|
60521
|
+
const state = await getFleetRobot(robot);
|
|
60522
|
+
if (!state.dispatchable) {
|
|
60523
|
+
throw new BadRequestError134(
|
|
60524
|
+
`Robot "${robot.name}" is ${state.statusName || state.mode} and cannot take a new job.`
|
|
60525
|
+
);
|
|
60526
|
+
}
|
|
60527
|
+
return adapter.sendCommand(robotSn, command);
|
|
60528
|
+
}
|
|
60529
|
+
return {
|
|
60530
|
+
adapterFor,
|
|
60531
|
+
capabilitiesOf,
|
|
60532
|
+
resolveRobotSn,
|
|
60533
|
+
getFleetRobot,
|
|
60534
|
+
getMaps,
|
|
60535
|
+
getMapImages,
|
|
60536
|
+
getPlans,
|
|
60537
|
+
dispatch
|
|
60538
|
+
};
|
|
60539
|
+
}
|
|
60540
|
+
|
|
60541
|
+
// src/services/robot-tracking.service.ts
|
|
60542
|
+
import axios4 from "axios";
|
|
60543
|
+
async function openWithAxios(url, body) {
|
|
60544
|
+
const response = await axios4.post(url, body, {
|
|
60545
|
+
responseType: "stream",
|
|
60546
|
+
// No read timeout: this connection is SUPPOSED to stay open and mostly
|
|
60547
|
+
// silent. A request timeout here would sever a healthy stream on a quiet
|
|
60548
|
+
// robot. The idle watchdog is what decides a stream is dead, because it can
|
|
60549
|
+
// tell silence from a closed socket.
|
|
60550
|
+
timeout: 0,
|
|
60551
|
+
headers: { "Content-Type": "application/json", Accept: "text/event-stream" }
|
|
60249
60552
|
});
|
|
60553
|
+
return response.data;
|
|
60554
|
+
}
|
|
60555
|
+
function startRobotTracking(options) {
|
|
60556
|
+
const {
|
|
60557
|
+
credentials,
|
|
60558
|
+
robotSn,
|
|
60559
|
+
onEvent,
|
|
60560
|
+
baseUrl = ROBOT_CLOUD_BASE_URL,
|
|
60561
|
+
idleTimeoutMs = TRACKING_IDLE_TIMEOUT_MS,
|
|
60562
|
+
openStream = openWithAxios,
|
|
60563
|
+
now = Date.now
|
|
60564
|
+
} = options;
|
|
60565
|
+
if (!baseUrl)
|
|
60566
|
+
throw new Error("Robot cloud: ROBOT_CLOUD_BASE_URL is not configured.");
|
|
60567
|
+
let stopped = false;
|
|
60568
|
+
let attempt = 0;
|
|
60569
|
+
let current = null;
|
|
60570
|
+
let watchdog = null;
|
|
60571
|
+
let retry = null;
|
|
60572
|
+
let lastActivityAt = now();
|
|
60573
|
+
let everConnected = false;
|
|
60574
|
+
const parser = createSseParser();
|
|
60575
|
+
function clearTimers() {
|
|
60576
|
+
if (watchdog) {
|
|
60577
|
+
clearInterval(watchdog);
|
|
60578
|
+
watchdog = null;
|
|
60579
|
+
}
|
|
60580
|
+
if (retry) {
|
|
60581
|
+
clearTimeout(retry);
|
|
60582
|
+
retry = null;
|
|
60583
|
+
}
|
|
60584
|
+
}
|
|
60585
|
+
function dropUpstream() {
|
|
60586
|
+
if (!current)
|
|
60587
|
+
return;
|
|
60588
|
+
try {
|
|
60589
|
+
current.destroy();
|
|
60590
|
+
} catch {
|
|
60591
|
+
}
|
|
60592
|
+
current = null;
|
|
60593
|
+
}
|
|
60594
|
+
function lose(reason) {
|
|
60595
|
+
if (stopped)
|
|
60596
|
+
return;
|
|
60597
|
+
onEvent({ type: "lost", reason, at: now() });
|
|
60598
|
+
dropUpstream();
|
|
60599
|
+
scheduleReconnect();
|
|
60600
|
+
}
|
|
60601
|
+
function scheduleReconnect() {
|
|
60602
|
+
if (stopped)
|
|
60603
|
+
return;
|
|
60604
|
+
clearTimers();
|
|
60605
|
+
const delay2 = trackingBackoffMs(attempt++);
|
|
60606
|
+
retry = setTimeout(connect2, delay2);
|
|
60607
|
+
}
|
|
60608
|
+
async function connect2() {
|
|
60609
|
+
if (stopped)
|
|
60610
|
+
return;
|
|
60611
|
+
const body = robotCloudBody(
|
|
60612
|
+
credentials.appid,
|
|
60613
|
+
credentials.secret,
|
|
60614
|
+
ROBOT_CLOUD_PATHS.tracking,
|
|
60615
|
+
{ robotSn }
|
|
60616
|
+
);
|
|
60617
|
+
let stream;
|
|
60618
|
+
try {
|
|
60619
|
+
stream = await openStream(
|
|
60620
|
+
`${baseUrl.replace(/\/+$/, "")}${ROBOT_CLOUD_PATHS.tracking}`,
|
|
60621
|
+
body
|
|
60622
|
+
);
|
|
60623
|
+
} catch (error) {
|
|
60624
|
+
lose(error?.message ?? "Could not open the robot's tracking stream.");
|
|
60625
|
+
return;
|
|
60626
|
+
}
|
|
60627
|
+
if (stopped) {
|
|
60628
|
+
try {
|
|
60629
|
+
stream.destroy();
|
|
60630
|
+
} catch {
|
|
60631
|
+
}
|
|
60632
|
+
return;
|
|
60633
|
+
}
|
|
60634
|
+
parser.reset();
|
|
60635
|
+
current = stream;
|
|
60636
|
+
lastActivityAt = now();
|
|
60637
|
+
if (everConnected)
|
|
60638
|
+
onEvent({ type: "reconnected", at: now() });
|
|
60639
|
+
everConnected = true;
|
|
60640
|
+
attempt = 0;
|
|
60641
|
+
stream.on("data", (chunk2) => {
|
|
60642
|
+
if (stopped)
|
|
60643
|
+
return;
|
|
60644
|
+
const { events, sawActivity } = parser.push(chunk2.toString("utf8"));
|
|
60645
|
+
if (sawActivity)
|
|
60646
|
+
lastActivityAt = now();
|
|
60647
|
+
for (const event of events) {
|
|
60648
|
+
const pose = decodeTrackingFrame(event.data);
|
|
60649
|
+
if (pose)
|
|
60650
|
+
onEvent({ type: "pose", pose, at: now() });
|
|
60651
|
+
}
|
|
60652
|
+
});
|
|
60653
|
+
stream.on("error", (error) => lose(error?.message ?? "The tracking stream failed."));
|
|
60654
|
+
stream.on("end", () => lose("The robot cloud closed the tracking stream."));
|
|
60655
|
+
stream.on("close", () => {
|
|
60656
|
+
if (!stopped && current === stream)
|
|
60657
|
+
lose("The tracking stream closed.");
|
|
60658
|
+
});
|
|
60659
|
+
clearTimers();
|
|
60660
|
+
watchdog = setInterval(() => {
|
|
60661
|
+
if (stopped)
|
|
60662
|
+
return;
|
|
60663
|
+
if (now() - lastActivityAt >= idleTimeoutMs) {
|
|
60664
|
+
lose("The robot stopped reporting.");
|
|
60665
|
+
}
|
|
60666
|
+
}, Math.max(1e3, Math.floor(idleTimeoutMs / 3)));
|
|
60667
|
+
}
|
|
60668
|
+
void connect2();
|
|
60669
|
+
return {
|
|
60670
|
+
stop() {
|
|
60671
|
+
if (stopped)
|
|
60672
|
+
return;
|
|
60673
|
+
stopped = true;
|
|
60674
|
+
clearTimers();
|
|
60675
|
+
dropUpstream();
|
|
60676
|
+
}
|
|
60677
|
+
};
|
|
60250
60678
|
}
|
|
60251
60679
|
|
|
60252
60680
|
// src/controllers/robot-fleet.controller.ts
|
|
@@ -60327,7 +60755,62 @@ function useRobotFleetController(deps) {
|
|
|
60327
60755
|
fail(next, error);
|
|
60328
60756
|
}
|
|
60329
60757
|
}
|
|
60330
|
-
|
|
60758
|
+
async function trackRobot(req, res, next) {
|
|
60759
|
+
let robot;
|
|
60760
|
+
let robotSn;
|
|
60761
|
+
try {
|
|
60762
|
+
robot = await entitleRobot(req, req.params.id);
|
|
60763
|
+
robotSn = await getFleet().resolveRobotSn(robot);
|
|
60764
|
+
} catch (error) {
|
|
60765
|
+
fail(next, error);
|
|
60766
|
+
return;
|
|
60767
|
+
}
|
|
60768
|
+
res.writeHead(200, {
|
|
60769
|
+
"Content-Type": "text/event-stream",
|
|
60770
|
+
"Cache-Control": "no-cache, no-transform",
|
|
60771
|
+
Connection: "keep-alive",
|
|
60772
|
+
// Nginx buffers proxied responses by default, which holds an SSE stream
|
|
60773
|
+
// until the buffer fills - the stream appears dead for minutes.
|
|
60774
|
+
"X-Accel-Buffering": "no"
|
|
60775
|
+
});
|
|
60776
|
+
res.flushHeaders?.();
|
|
60777
|
+
const send2 = (event) => {
|
|
60778
|
+
if (res.writableEnded)
|
|
60779
|
+
return;
|
|
60780
|
+
res.write(`data: ${JSON.stringify(event)}
|
|
60781
|
+
|
|
60782
|
+
`);
|
|
60783
|
+
};
|
|
60784
|
+
const handle = startRobotTracking({
|
|
60785
|
+
credentials: {
|
|
60786
|
+
appid: (robot.appKey ?? "").trim(),
|
|
60787
|
+
secret: (robot.appToken ?? "").trim()
|
|
60788
|
+
},
|
|
60789
|
+
robotSn,
|
|
60790
|
+
onEvent: send2
|
|
60791
|
+
});
|
|
60792
|
+
const keepAlive = setInterval(() => {
|
|
60793
|
+
if (!res.writableEnded)
|
|
60794
|
+
res.write(": keep-alive\n\n");
|
|
60795
|
+
}, 15e3);
|
|
60796
|
+
const close = () => {
|
|
60797
|
+
clearInterval(keepAlive);
|
|
60798
|
+
handle.stop();
|
|
60799
|
+
if (!res.writableEnded)
|
|
60800
|
+
res.end();
|
|
60801
|
+
};
|
|
60802
|
+
req.on("close", close);
|
|
60803
|
+
req.on("aborted", close);
|
|
60804
|
+
res.on("error", close);
|
|
60805
|
+
}
|
|
60806
|
+
async function getBrands(_req, res, next) {
|
|
60807
|
+
try {
|
|
60808
|
+
res.json({ items: registeredRobotBrands() });
|
|
60809
|
+
} catch (error) {
|
|
60810
|
+
fail(next, error);
|
|
60811
|
+
}
|
|
60812
|
+
}
|
|
60813
|
+
return { getState, getMaps, getMapImages, getPlans, sendCommand: sendCommand2, trackRobot, getBrands };
|
|
60331
60814
|
}
|
|
60332
60815
|
|
|
60333
60816
|
// src/models/patrol-question.model.ts
|
|
@@ -80953,7 +81436,7 @@ import {
|
|
|
80953
81436
|
} from "@7365admin1/node-server-utils";
|
|
80954
81437
|
|
|
80955
81438
|
// src/utils/hrmlabs-attendance.util.ts
|
|
80956
|
-
import
|
|
81439
|
+
import axios5 from "axios";
|
|
80957
81440
|
import moment8 from "moment-timezone";
|
|
80958
81441
|
var default_early_checkIn = 4;
|
|
80959
81442
|
async function hrmLabsAuthentication({
|
|
@@ -80962,7 +81445,7 @@ async function hrmLabsAuthentication({
|
|
|
80962
81445
|
password,
|
|
80963
81446
|
domain
|
|
80964
81447
|
}) {
|
|
80965
|
-
const res = await
|
|
81448
|
+
const res = await axios5.post(
|
|
80966
81449
|
authUrl,
|
|
80967
81450
|
{
|
|
80968
81451
|
grant_type: "password",
|
|
@@ -80992,7 +81475,7 @@ async function fetchAttendanceData({
|
|
|
80992
81475
|
search
|
|
80993
81476
|
}) {
|
|
80994
81477
|
try {
|
|
80995
|
-
const res = await
|
|
81478
|
+
const res = await axios5.post(
|
|
80996
81479
|
attendanceUrl,
|
|
80997
81480
|
{
|
|
80998
81481
|
start: startDate,
|
|
@@ -81192,7 +81675,7 @@ async function fetchSites({ siteUrl, token }) {
|
|
|
81192
81675
|
do {
|
|
81193
81676
|
try {
|
|
81194
81677
|
attempt++;
|
|
81195
|
-
const res = await
|
|
81678
|
+
const res = await axios5.get(siteUrl, {
|
|
81196
81679
|
headers: { Authorization: `Bearer ${token}` },
|
|
81197
81680
|
timeout: 15e3
|
|
81198
81681
|
});
|
|
@@ -84840,7 +85323,7 @@ async function manpowerEvents(io) {
|
|
|
84840
85323
|
}
|
|
84841
85324
|
|
|
84842
85325
|
// src/services/reddot-payment.service.ts
|
|
84843
|
-
import
|
|
85326
|
+
import axios6 from "axios";
|
|
84844
85327
|
|
|
84845
85328
|
// src/utils/payment-signature.util.ts
|
|
84846
85329
|
import * as crypto9 from "crypto";
|
|
@@ -85482,7 +85965,7 @@ var useRedDotPaymentSvc = () => {
|
|
|
85482
85965
|
);
|
|
85483
85966
|
}
|
|
85484
85967
|
const url = process.env.MERCHANT_PAYMENT;
|
|
85485
|
-
const response = await
|
|
85968
|
+
const response = await axios6.post(url, dataString, { headers });
|
|
85486
85969
|
const result = response.data;
|
|
85487
85970
|
return result;
|
|
85488
85971
|
} catch (error) {
|
|
@@ -85516,7 +85999,7 @@ var useRedDotPaymentSvc = () => {
|
|
|
85516
85999
|
);
|
|
85517
86000
|
}
|
|
85518
86001
|
const url = process.env.MERCHANT_ENQUIRY;
|
|
85519
|
-
const response = await
|
|
86002
|
+
const response = await axios6.post(url, dataString, { headers });
|
|
85520
86003
|
const result = response.data;
|
|
85521
86004
|
const transactionId = result.transaction_id;
|
|
85522
86005
|
const amount = result.request_amount || 0;
|
|
@@ -94165,6 +94648,8 @@ export {
|
|
|
94165
94648
|
DEFAULT_RING_SECONDS,
|
|
94166
94649
|
DEFAULT_SCALE_M_PER_PX,
|
|
94167
94650
|
DEFAULT_SITE_TIMEZONE,
|
|
94651
|
+
DEMO_BRAND,
|
|
94652
|
+
DEMO_CAPABILITIES,
|
|
94168
94653
|
DEVICE_STATUS,
|
|
94169
94654
|
DOBStatus,
|
|
94170
94655
|
DUPLICATE_TERMS_VERSION_MESSAGE,
|
|
@@ -94334,6 +94819,7 @@ export {
|
|
|
94334
94819
|
ROBOT_CAPABILITIES,
|
|
94335
94820
|
ROBOT_CLOUD_DOC_CONFIRMATIONS,
|
|
94336
94821
|
ROBOT_CLOUD_PATHS,
|
|
94822
|
+
ROBOT_CLOUD_TIMEOUT_MS,
|
|
94337
94823
|
ROBOT_MODULE,
|
|
94338
94824
|
ROBOT_PERMISSIONS,
|
|
94339
94825
|
ROBOT_STATUS_NAMES,
|
|
@@ -94385,6 +94871,7 @@ export {
|
|
|
94385
94871
|
VisitorStatus,
|
|
94386
94872
|
WALL_HEIGHT_M,
|
|
94387
94873
|
WALL_THICKNESS_M,
|
|
94874
|
+
adapterForRobot,
|
|
94388
94875
|
addressSchema,
|
|
94389
94876
|
allowedFieldsSite,
|
|
94390
94877
|
allowedNatures,
|
|
@@ -94424,13 +94911,14 @@ export {
|
|
|
94424
94911
|
chatSchema,
|
|
94425
94912
|
claimReadsAsStale,
|
|
94426
94913
|
clampPtzSpeed,
|
|
94427
|
-
|
|
94914
|
+
clearRobotBrands,
|
|
94428
94915
|
clockDriftSeconds,
|
|
94429
94916
|
commandSupported,
|
|
94430
94917
|
consoleRoleAllows,
|
|
94431
94918
|
consoleRoleAllowsAll,
|
|
94432
94919
|
consoleSpellings,
|
|
94433
94920
|
console_audit_namespace_collection,
|
|
94921
|
+
createDemoAdapter,
|
|
94434
94922
|
createLiftSimulator,
|
|
94435
94923
|
createManpowerRemarksDaily,
|
|
94436
94924
|
createOunitechAdapter,
|
|
@@ -94454,6 +94942,7 @@ export {
|
|
|
94454
94942
|
disconnectedState,
|
|
94455
94943
|
emitNotificationCreated,
|
|
94456
94944
|
emptyPersonalEmergencyChain,
|
|
94945
|
+
encodeGreyscalePng,
|
|
94457
94946
|
encodeHidPacsCard,
|
|
94458
94947
|
entitledSiteScope,
|
|
94459
94948
|
events_namespace_collection,
|
|
@@ -94472,6 +94961,7 @@ export {
|
|
|
94472
94961
|
getIO,
|
|
94473
94962
|
getSessionIdFromRequest,
|
|
94474
94963
|
grabWithSubStreamFallback,
|
|
94964
|
+
greyscalePngDataUri,
|
|
94475
94965
|
guests_namespace_collection,
|
|
94476
94966
|
hasAnyCapability,
|
|
94477
94967
|
hasAnyPermission,
|
|
@@ -94573,8 +95063,9 @@ export {
|
|
|
94573
95063
|
recordConsoleAction,
|
|
94574
95064
|
refuseServiceProviderInviteAction,
|
|
94575
95065
|
registerCameraTransport,
|
|
95066
|
+
registerDemoBrand,
|
|
94576
95067
|
registerOunitechBrand,
|
|
94577
|
-
|
|
95068
|
+
registerRobotBrand,
|
|
94578
95069
|
registeredRobotBrands,
|
|
94579
95070
|
relayForRecorder,
|
|
94580
95071
|
remarksSchema,
|
|
@@ -94593,7 +95084,7 @@ export {
|
|
|
94593
95084
|
resolveSiteTimezone,
|
|
94594
95085
|
resolveStaffRole,
|
|
94595
95086
|
robotActionsFor,
|
|
94596
|
-
|
|
95087
|
+
robotBrandFor,
|
|
94597
95088
|
robotCloudBody,
|
|
94598
95089
|
robotCloudSign,
|
|
94599
95090
|
robotCloudSigningString,
|
|
@@ -94772,6 +95263,7 @@ export {
|
|
|
94772
95263
|
snapshotEndpoint,
|
|
94773
95264
|
snapshotRefusalReason,
|
|
94774
95265
|
staffMayBypass,
|
|
95266
|
+
startRobotTracking,
|
|
94775
95267
|
startTodaysBookForSite,
|
|
94776
95268
|
stringifyHidJson,
|
|
94777
95269
|
stripFacialImageMetadata,
|