@7365admin1/core 3.133.1 → 3.134.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 +6 -0
- package/dist/index.d.ts +113 -4
- package/dist/index.js +328 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +319 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/robot-vendor-errors.e2e.test.mjs +3 -2
- package/test/ounitech-api-2025-10.test.mjs +140 -2
- package/test/robot-vendor-errors.test.mjs +7 -0
package/package.json
CHANGED
|
@@ -144,8 +144,9 @@ describe("robot vendor failures are handled answers; diagnose names the step", {
|
|
|
144
144
|
mode = "ok";
|
|
145
145
|
const ok = await h.api(`/robots/${id.robot}/diagnose`, { sid: staffSid });
|
|
146
146
|
assert.equal(ok.body.failedStep, null, JSON.stringify(ok.body));
|
|
147
|
-
// credentials, sign-in, get robot, maps, then the
|
|
148
|
-
|
|
147
|
+
// credentials, sign-in, get robot, maps, then the 16 read-only 2025.10 calls (all answered;
|
|
148
|
+
// this mock's map has no numeric mapId, so the 4 per-map layer reads are skipped).
|
|
149
|
+
assert.equal(ok.body.steps.length, 20, JSON.stringify(ok.body.steps.map((s) => s.step)));
|
|
149
150
|
assert.ok(ok.body.steps.every((s) => s.ok), JSON.stringify(ok.body.steps.filter((s) => !s.ok)));
|
|
150
151
|
assert.match(ok.body.steps[2].detail, /model Q3, battery 88%, floor 1/);
|
|
151
152
|
assert.ok(ok.body.clock?.vendorDate, "the vendor's clock is reported");
|
|
@@ -21,6 +21,14 @@ import {
|
|
|
21
21
|
ounitechTime,
|
|
22
22
|
pngSize,
|
|
23
23
|
clearOunitechCaches,
|
|
24
|
+
ounitechShapes,
|
|
25
|
+
ounitechTrack,
|
|
26
|
+
ounitechStatusLog,
|
|
27
|
+
ounitechAppAction,
|
|
28
|
+
ounitechSettings,
|
|
29
|
+
ounitechObstacleImage,
|
|
30
|
+
ounitechMapVersion,
|
|
31
|
+
ounitechFloorMapHistory,
|
|
24
32
|
} from "./.build/services/ounitech.adapter.mjs";
|
|
25
33
|
|
|
26
34
|
/* --------------------------------------------------------------------------
|
|
@@ -121,6 +129,28 @@ const ALERTS = [
|
|
|
121
129
|
{ deviceFlag: 1, errCode: "621 - 右轮驱动器重置( 4)(Resolved)", errMsg: "621 - 右轮驱动器重置( 4)(Resolved)", createTime: "2025-10-20 14:23:45" },
|
|
122
130
|
];
|
|
123
131
|
|
|
132
|
+
/* The ten read-only map-layer / log / settings replies, in the document's shapes (2025.10.22 §3083-3688, §6989-7204, §1478). */
|
|
133
|
+
const WALLS = [{ wallId: 216395, wallName: "大门口", pointList: [[-1, -5], [1.5, -2.25], [9, 9]] }, { wallId: 216396, wallName: "", pointList: [[0, 0]] }];
|
|
134
|
+
const SAFE = [{ zoneId: 1, zoneName: "test", pointList: [[0.1, 0.1], [0.5, 0.5], [0.9, 0.1]] }];
|
|
135
|
+
const SLOW = [{ zoneId: 6, zoneName: "", pointList: [[-1, -5], [0, -5], [0, -4]] }];
|
|
136
|
+
const PNG_B64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==";
|
|
137
|
+
const TEACH = { since: "2025-03-04 11:29:10", offset: "2025-03-04 11:29:10", trackList: [{ x: 1, y: 1, theta: 0, mapid: 1 }, { x: 2, y: -1, theta: 0, mapid: 1 }, { x: 5, y: 5, theta: 0, mapid: 99 }] };
|
|
138
|
+
const VERSIONS = [{ versionId: 11206, versionNo: 1, mapId: 1, latestFlag: true, partitionFileKey: "/z.png", mapFileKey: "", backgroundFileKey: "/b.png", creatorFlag: 0, creatorId: 0, createTime: "2025-05-28 19:06:48" }];
|
|
139
|
+
const FLOOR_MAPS = [
|
|
140
|
+
{ mapId: 2139, mapName: "欧亚学院 2F", sceneId: 94, backgroundFileKey: "/a.png", totalArea: 6106.217, floorNo: 2, latestFlag: true, enableFlag: true, createTime: "2024-08-20 16:26:45", updateTime: "2025-03-12 19:20:16" },
|
|
141
|
+
{ mapId: 2071, mapName: "5楼会议室", sceneId: 94, totalArea: 22.585, floorNo: 2, latestFlag: false, enableFlag: false, createTime: "2024-07-26 14:22:18", updateTime: "2024-07-26 18:37:42" },
|
|
142
|
+
];
|
|
143
|
+
const ROBOT_LOG = [
|
|
144
|
+
{ taskId: 1822883, robotId: 590, robotName: "2A7F25DE96E56", sceneId: 603, sceneName: "2A7F25DE96E56", typeFlag: "9", timeFrom: "2025-10-22 17:48:39", typeName: "充电完成" },
|
|
145
|
+
{ taskId: 1822707, robotId: 590, sceneId: 603, typeFlag: "16", timeFrom: "2025-10-22 17:01:06", typeName: "断点充电中" },
|
|
146
|
+
{ taskId: 1822460, robotId: 590, sceneId: 603, typeFlag: "2", timeFrom: "2025-10-22 15:55:21", typeName: "清洁中" },
|
|
147
|
+
];
|
|
148
|
+
const USER_LOG = [
|
|
149
|
+
{ robotId: 318, actionId: 181630, actionName: "原地停止", createTime: "2025-10-20 14:22:32", userName: "梁家茂", userPhone: "17875645433" },
|
|
150
|
+
{ robotId: 318, actionId: 181629, actionName: "下发清洁", createTime: "2025-10-20 14:22:07", userName: "梁家茂", userPhone: "17875645433" },
|
|
151
|
+
];
|
|
152
|
+
const SETTING = { fanLevel: 0, soundLevel: 80, enablePlanFlag: true, wifiName: "SiteWifi", wifiPwd: "wifi-secret-123", minCleanBatteryPerc: 50, language: "zh", timeZone: 8, clean_speed_mode: "standard", garbage_threshold: 15 };
|
|
153
|
+
|
|
124
154
|
let server;
|
|
125
155
|
let base;
|
|
126
156
|
const seen = [];
|
|
@@ -200,6 +230,26 @@ before(async () => {
|
|
|
200
230
|
return ok({ liftlist: [{ liftid: 45, liftno: 3701, liftname: "Lift A", floorno: 13, onlineflag: 1, enableflag: true, simno: "868327073829804" }] });
|
|
201
231
|
case ROBOT_CLOUD_PATHS.mapList:
|
|
202
232
|
return ok({ mapList: [{ floorNo: 1 }] });
|
|
233
|
+
case ROBOT_CLOUD_PATHS.wallList:
|
|
234
|
+
return ok({ wallList: body.mapId === 1 ? WALLS : [] });
|
|
235
|
+
case ROBOT_CLOUD_PATHS.safeZoneList:
|
|
236
|
+
return ok({ safeZoneList: body.mapId === 1 ? SAFE : [] });
|
|
237
|
+
case ROBOT_CLOUD_PATHS.slowZoneList:
|
|
238
|
+
return ok({ slowZoneList: body.mapId === 1 ? SLOW : [] });
|
|
239
|
+
case ROBOT_CLOUD_PATHS.obstacleLayer:
|
|
240
|
+
return ok({ version: 123123123123, image: PNG_B64 });
|
|
241
|
+
case ROBOT_CLOUD_PATHS.teachTrack:
|
|
242
|
+
return ok(TEACH);
|
|
243
|
+
case ROBOT_CLOUD_PATHS.mapVersions:
|
|
244
|
+
return ok({ total: 1, versionList: VERSIONS });
|
|
245
|
+
case ROBOT_CLOUD_PATHS.floorMapHistory:
|
|
246
|
+
return ok({ total: 2, mapList: FLOOR_MAPS });
|
|
247
|
+
case ROBOT_CLOUD_PATHS.robotLog:
|
|
248
|
+
return ok({ total: 342, logList: ROBOT_LOG });
|
|
249
|
+
case ROBOT_CLOUD_PATHS.userLog:
|
|
250
|
+
return ok({ actionList: USER_LOG, total: 38 });
|
|
251
|
+
case ROBOT_CLOUD_PATHS.cleaningSetting:
|
|
252
|
+
return ok(SETTING);
|
|
203
253
|
default:
|
|
204
254
|
return text(res, 404, "not found");
|
|
205
255
|
}
|
|
@@ -267,11 +317,22 @@ test("status 7 costs one extra read, get-robot-state-v2.0 by robotId, to name th
|
|
|
267
317
|
assert.deepEqual(ounitechFault([]), { code: "error", message: "The robot reports an error" });
|
|
268
318
|
});
|
|
269
319
|
|
|
270
|
-
test("maps: get-floor-maps by the robot's sceneId, latest only, walls left out", async () => {
|
|
320
|
+
test("maps: get-floor-maps by the robot's sceneId, latest only, raw walls left out (drawn as zones)", async () => {
|
|
271
321
|
clearOunitechCaches();
|
|
272
322
|
seen.length = 0;
|
|
273
323
|
const maps = await adapter().getMaps("F8FCA9A1B1C4C");
|
|
274
|
-
assert.deepEqual(seen.map((s) => s.path), [
|
|
324
|
+
assert.deepEqual(seen.map((s) => s.path), [
|
|
325
|
+
ROBOT_CLOUD_PATHS.homeRobotList,
|
|
326
|
+
ROBOT_CLOUD_PATHS.floorMaps,
|
|
327
|
+
ROBOT_CLOUD_PATHS.filePreview,
|
|
328
|
+
ROBOT_CLOUD_PATHS.wallList,
|
|
329
|
+
ROBOT_CLOUD_PATHS.safeZoneList,
|
|
330
|
+
ROBOT_CLOUD_PATHS.slowZoneList,
|
|
331
|
+
ROBOT_CLOUD_PATHS.teachTrack,
|
|
332
|
+
]);
|
|
333
|
+
assert.equal(seen[3].body.mapId, 1);
|
|
334
|
+
assert.equal(seen[6].body.robotId, 424);
|
|
335
|
+
assert.match(seen[6].body.offset, /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/);
|
|
275
336
|
assert.equal(seen[1].body.sceneId, 438);
|
|
276
337
|
assert.equal(seen[1].body.enableFlag, 1);
|
|
277
338
|
assert.equal(maps.length, 1);
|
|
@@ -564,3 +625,80 @@ test("records: 'no station bound (e=59)' means no base station - no error shown"
|
|
|
564
625
|
const r = await adapter().getRecords("OFFLINE1");
|
|
565
626
|
assert.deepEqual(r.station, { items: [] });
|
|
566
627
|
});
|
|
628
|
+
|
|
629
|
+
test("map layers: walls, safe and slow zones and the taught route drawn on the robot's map (get-wall/safezone/slowzone-list, get-teach-track)", async () => {
|
|
630
|
+
clearOunitechCaches();
|
|
631
|
+
const [m] = await adapter().getMaps("F8FCA9A1B1C4C");
|
|
632
|
+
// Map: 200 x 100 px, origin (-1, -5), 0.05 m/px. (-1,-5) = bottom-left corner; (1.5,-2.25) = 25 %, 45 %; (9,9) past the edge -> clamped.
|
|
633
|
+
assert.deepEqual(m.zones[0], { kind: "wall", name: "大门口", points: [{ x: 0, y: 100 }, { x: 25, y: 45 }, { x: 100, y: 0 }] });
|
|
634
|
+
assert.equal(m.zones.filter((z) => z.kind === "wall").length, 1, "a one-point wall is not drawn");
|
|
635
|
+
assert.deepEqual(m.zones.find((z) => z.kind === "safe").name, "test");
|
|
636
|
+
assert.deepEqual(m.zones.find((z) => z.kind === "slow"), { kind: "slow", name: "Slow zone 1", points: [{ x: 0, y: 100 }, { x: 10, y: 100 }, { x: 10, y: 80 }] });
|
|
637
|
+
// Taught route: only points on this map (mapid 1); (1,1) -> 20 %, (1+5)/0.05=120 px up of 100 -> clamped top; (2,-1) -> 30 %, 20 %.
|
|
638
|
+
assert.deepEqual(m.taughtRoute, [{ x: 20, y: 0 }, { x: 30, y: 20 }]);
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
test("records: the ten slow reads - status log, app actions, settings (no Wi-Fi), obstacles, map versions, floor map history, zones", async () => {
|
|
642
|
+
clearOunitechCaches();
|
|
643
|
+
seen.length = 0;
|
|
644
|
+
const a = adapter();
|
|
645
|
+
const r = await a.getRecords("F8FCA9A1B1C4C");
|
|
646
|
+
assert.equal(seen.some((x) => COMMAND_PATHS.has(x.path)), false, "never a command");
|
|
647
|
+
assert.deepEqual(r.statusLog.items.map((x) => x.status), ["Charged", "Charging (mid-job)", "Cleaning"]);
|
|
648
|
+
assert.equal(r.statusLog.items[0].at, "2025-10-22T09:48:39.000Z");
|
|
649
|
+
const log = seen.find((x) => x.path === ROBOT_CLOUD_PATHS.robotLog).body;
|
|
650
|
+
assert.deepEqual([log.robotId, log.sceneId, log.pageSize], ["424", "438", 50], "robot log takes ids as strings (document)");
|
|
651
|
+
assert.deepEqual(r.appActions.items, [
|
|
652
|
+
{ at: "2025-10-20T06:22:32.000Z", action: "Stopped in place", by: "梁家茂" },
|
|
653
|
+
{ at: "2025-10-20T06:22:07.000Z", action: "Started a cleaning job", by: "梁家茂" },
|
|
654
|
+
]);
|
|
655
|
+
const json = JSON.stringify(r);
|
|
656
|
+
assert.equal(json.includes("17875645433"), false, "app user's phone number left out");
|
|
657
|
+
assert.equal(json.includes("wifi-secret-123") || json.includes("SiteWifi"), false, "Wi-Fi name/password never passed on");
|
|
658
|
+
assert.ok(r.settings.items.some((x) => x.label === "Sound level" && x.value === "80 %"));
|
|
659
|
+
assert.ok(r.settings.items.some((x) => x.label === "Robot language" && x.value === "Chinese"));
|
|
660
|
+
assert.ok(r.settings.items.some((x) => x.label === "Robot time zone" && x.value === "UTC+8"));
|
|
661
|
+
assert.ok(r.settings.items.some((x) => x.group === "Other settings" && x.label === "garbage_threshold"));
|
|
662
|
+
assert.equal(r.obstacles.items[0].image, `data:image/png;base64,${PNG_B64}`);
|
|
663
|
+
assert.deepEqual(r.mapVersions.items, [{ version: 1, latest: true, createdAt: "2025-05-28T11:06:48.000Z" }]);
|
|
664
|
+
assert.equal(seen.find((x) => x.path === ROBOT_CLOUD_PATHS.mapVersions).body.mapId, 1);
|
|
665
|
+
assert.deepEqual(r.mapHistory.items.map((x) => [x.name, x.areaM2, x.latest, x.inUse]), [["欧亚学院 2F", 6106.2, true, true], ["5楼会议室", 22.6, false, false]]);
|
|
666
|
+
assert.deepEqual(seen.find((x) => x.path === ROBOT_CLOUD_PATHS.floorMapHistory).body.floorNo, 3);
|
|
667
|
+
assert.deepEqual(r.mapLayers.items[0].zones.map((z) => z.kind), ["wall", "safe", "slow"]);
|
|
668
|
+
assert.ok(r.tasks && r.alerts, "the fast records are still there");
|
|
669
|
+
const calls = seen.length;
|
|
670
|
+
await a.getRecords("F8FCA9A1B1C4C");
|
|
671
|
+
assert.equal(seen.length, calls, "both caches serve the second read");
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
test("the ten reads, pure mapping from the document's replies", () => {
|
|
675
|
+
assert.deepEqual(ounitechStatusLog({ typeFlag: "15", timeFrom: "2025-10-22 16:58:56", typeName: "断点回充" }), { at: "2025-10-22T08:58:56.000Z", status: "Returning to charge (mid-job)" });
|
|
676
|
+
assert.equal(ounitechStatusLog({ typeFlag: "77", typeName: "x" }).status, "x");
|
|
677
|
+
assert.equal(ounitechAppAction({ actionName: "示教路径控制" }).action, "Controlled a taught route");
|
|
678
|
+
assert.equal(ounitechAppAction({ actionName: "新动作" }).action, "App action: 新动作", "untranslated text is labelled, never bare Chinese");
|
|
679
|
+
assert.deepEqual(ounitechSettings({ wifiName: "a", wifiPwd: "b", ok: true, msg: null, enableWorkSound: true }), [
|
|
680
|
+
{ key: "enableWorkSound", group: "Sound", label: "Sounds while working", value: "On", attention: false },
|
|
681
|
+
]);
|
|
682
|
+
assert.equal(ounitechObstacleImage({ image: "" }), null);
|
|
683
|
+
assert.equal(ounitechObstacleImage({ image: "/9j/abc" }).image, "data:image/jpeg;base64,/9j/abc");
|
|
684
|
+
assert.equal(ounitechObstacleImage({ image: "A".repeat(1_500_001) }).tooLarge, true);
|
|
685
|
+
assert.deepEqual(ounitechMapVersion({ versionNo: 3, latestFlag: false, createTime: "2025-05-28 19:06:48" }), { version: 3, latest: false, createdAt: "2025-05-28T11:06:48.000Z" });
|
|
686
|
+
assert.equal(ounitechFloorMapHistory({ mapId: 5 }).name, "Map 5");
|
|
687
|
+
const frame = { width: 200, height: 100, resolution: 0.05, originX: -1, originY: -5 };
|
|
688
|
+
assert.deepEqual(ounitechShapes(frame, "safe", [{ zoneName: "", pointList: [[0.1, 0.1], [0.5, 0.5]] }])[0].name, "Safe zone 1");
|
|
689
|
+
assert.deepEqual(ounitechShapes(null, "wall", WALLS), [], "no frame, nothing drawn");
|
|
690
|
+
assert.equal(ounitechTrack({ ...frame, mapId: "1" }, Array.from({ length: 2000 }, () => ({ x: 0, y: 0, mapid: 1 }))).length, 500, "long routes thinned to 500 points");
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
test("diagnose: the ten reads each answered, no Wi-Fi password, no phone number", async () => {
|
|
694
|
+
clearOunitechCaches();
|
|
695
|
+
const steps = await adapter().diagnose("F8FCA9A1B1C4C");
|
|
696
|
+
for (const name of ["virtual walls", "safe zones", "slow zones", "map versions", "floor map history", "obstacle layer", "taught route", "robot status log", "app action log", "robot settings"]) {
|
|
697
|
+
const st = steps.find((x) => x.step.startsWith(name));
|
|
698
|
+
assert.ok(st, `step ${name}`);
|
|
699
|
+
assert.equal(st.code, 0, `${name}: ${st.message}`);
|
|
700
|
+
}
|
|
701
|
+
const json = JSON.stringify(steps);
|
|
702
|
+
assert.equal(json.includes("wifi-secret-123") || json.includes("17875645433"), false);
|
|
703
|
+
assert.equal(steps.find((x) => x.step.startsWith("obstacle layer")).data.imageChars, PNG_B64.length, "image size only, not the picture");
|
|
704
|
+
});
|
|
@@ -233,6 +233,13 @@ test("diagnose: every step passes on a healthy account, with the vendor's clock"
|
|
|
233
233
|
["power schedule (halt-plan/get)", true],
|
|
234
234
|
["base station (get-station-detail-state)", true],
|
|
235
235
|
["lifts (get-lift-list)", true],
|
|
236
|
+
// This account's map has no numeric mapId, so the per-map layer reads (walls, zones, versions) are skipped.
|
|
237
|
+
["floor map history (v2405/get-floor-maplist)", true],
|
|
238
|
+
["obstacle layer (get-obstacle-layer)", true],
|
|
239
|
+
["taught route (get-teach-track)", true],
|
|
240
|
+
["robot status log, last 7 days (get-robot-log-list)", true],
|
|
241
|
+
["app action log, last 7 days (get-user-logs)", true],
|
|
242
|
+
["robot settings (get-cleaning-setting)", true],
|
|
236
243
|
["current job (robot list taskState)", true],
|
|
237
244
|
]);
|
|
238
245
|
assert.equal(seen.slice(from).some((b) => /cleanTimes|areaList|pointList|actionNo/.test(b)), false, "no command body was sent");
|