@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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@7365admin1/core",
3
3
  "license": "MIT",
4
- "version": "3.133.1",
4
+ "version": "3.134.0",
5
5
  "author": "7365admin1",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -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 11 read-only 2025.10 calls (all answered).
148
- assert.equal(ok.body.steps.length, 15, JSON.stringify(ok.body.steps.map((s) => s.step)));
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), [ROBOT_CLOUD_PATHS.homeRobotList, ROBOT_CLOUD_PATHS.floorMaps, ROBOT_CLOUD_PATHS.filePreview]);
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");