@apocaliss92/nodedreame 1.11.4 → 1.11.6

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/dist/index.cjs CHANGED
@@ -75,8 +75,10 @@ __export(index_exports, {
75
75
  getVacuumCapabilities: () => getVacuumCapabilities,
76
76
  isDreameConsumableKey: () => isDreameConsumableKey,
77
77
  mowerConsumableIndex: () => mowerConsumableIndex,
78
+ mowerFaultSeverity: () => mowerFaultSeverity,
78
79
  parseMowerConsumables: () => parseMowerConsumables,
79
80
  parseMowerHeartbeat: () => parseMowerHeartbeat,
81
+ parseMowingProgress: () => parseMowingProgress,
80
82
  renderMowerSvg: () => renderMowerSvg,
81
83
  renderVacuumPng: () => renderVacuumPng,
82
84
  resolveCapabilities: () => resolveCapabilities,
@@ -86,7 +88,7 @@ module.exports = __toCommonJS(index_exports);
86
88
 
87
89
  // src/support/version.ts
88
90
  var LIBRARY_NAME = "nodedreame";
89
- var LIBRARY_VERSION = "1.11.4";
91
+ var LIBRARY_VERSION = "1.11.6";
90
92
 
91
93
  // src/transport/errors.ts
92
94
  var DreameError = class extends Error {
@@ -2857,18 +2859,27 @@ function decodeVacuumMap(input, opts = {}) {
2857
2859
  const pixelGrid = inflated.subarray(pixelStart, pixelEnd);
2858
2860
  const canDecodePixels = header.frameType === "I" && pixelGrid.length === header.width * header.height;
2859
2861
  const layers = canDecodePixels ? decodePixelGridFsm1(pixelGrid, header.width, header.height) : [];
2860
- const segments = canDecodePixels ? collectSegments(layers, dimensions, tail) : [];
2861
2862
  const paths = parsePathTr(tail.tr ?? "");
2862
2863
  const obstacles = parseObstacles(tail.ai_obstacle ?? []);
2863
2864
  let geometry = parseTailGeometry(tail);
2864
- if (!isGeometryComplete(geometry) && typeof tail.rism === "string" && tail.rism.length > 0) {
2865
+ let segTail = tail;
2866
+ const outerHasNames = Object.values(tail.seg_inf ?? {}).some(
2867
+ (m) => typeof m?.name === "string" && m.name.length > 0
2868
+ );
2869
+ if ((!isGeometryComplete(geometry) || !outerHasNames) && typeof tail.rism === "string" && tail.rism.length > 0) {
2865
2870
  try {
2866
2871
  const innerInflated = unwrapEnvelope(tail.rism);
2867
2872
  const { tail: innerTail } = parseFrame(innerInflated);
2868
- geometry = coalesceGeometry(geometry, parseTailGeometry(innerTail));
2873
+ if (!isGeometryComplete(geometry)) {
2874
+ geometry = coalesceGeometry(geometry, parseTailGeometry(innerTail));
2875
+ }
2876
+ if (!outerHasNames && innerTail.seg_inf) {
2877
+ segTail = mergeSegInfNames(tail, innerTail.seg_inf);
2878
+ }
2869
2879
  } catch {
2870
2880
  }
2871
2881
  }
2882
+ const segments = canDecodePixels ? collectSegments(layers, dimensions, segTail) : [];
2872
2883
  const cleanedArea = typeof tail.decmap === "string" ? parseCleanedAreaOverlay(tail.decmap) : null;
2873
2884
  return {
2874
2885
  mapId: header.mapId,
@@ -2892,6 +2903,18 @@ function applyVacuumPFrame(prev, pframe, opts = {}) {
2892
2903
  const merged = typeof prev === "string" || typeof pframe === "string" ? mergePFrameEnvelope(prev, pframe, opts.prev, opts.pframe) : mergePFrame(prev, pframe);
2893
2904
  return { buffer: merged, data: decodeVacuumMap(merged) };
2894
2905
  }
2906
+ function mergeSegInfNames(tail, innerSegInf) {
2907
+ const outer = tail.seg_inf ?? {};
2908
+ const ids = /* @__PURE__ */ new Set([...Object.keys(outer), ...Object.keys(innerSegInf)]);
2909
+ const merged = {};
2910
+ for (const id of ids) {
2911
+ const o = outer[id] ?? {};
2912
+ const inner = innerSegInf[id] ?? {};
2913
+ const name = typeof o.name === "string" && o.name.length > 0 ? o.name : inner.name;
2914
+ merged[id] = { ...inner, ...o, ...name !== void 0 ? { name } : {} };
2915
+ }
2916
+ return { ...tail, seg_inf: merged };
2917
+ }
2895
2918
  function mergeDimensions(header, tail) {
2896
2919
  const left = tail.origin?.[0] ?? header.left;
2897
2920
  const top = tail.origin?.[1] ?? header.top;
@@ -2913,78 +2936,6 @@ function pose(x, y, angle, absent) {
2913
2936
  // src/models/vacuum/map/render.ts
2914
2937
  var import_pngjs = require("pngjs");
2915
2938
  var SCHEMES = {
2916
- "dreame-light": {
2917
- floor: [210, 222, 235, 255],
2918
- wall: [60, 60, 60, 255],
2919
- carpet: [180, 150, 120, 200],
2920
- segSat: 0.45,
2921
- segVal: 0.95,
2922
- path: [60, 110, 180, 235],
2923
- robotBody: [40, 70, 110, 255],
2924
- robotHeading: [255, 255, 255, 255],
2925
- charger: [40, 160, 80, 255],
2926
- noGoFill: [220, 60, 60, 70],
2927
- noGoBorder: [200, 40, 40, 220],
2928
- noMopFill: [60, 120, 220, 60],
2929
- noMopBorder: [40, 90, 200, 200],
2930
- virtualWall: [200, 40, 40, 230],
2931
- obstacle: [230, 150, 30, 255],
2932
- label: [40, 40, 40, 255]
2933
- },
2934
- "dreame-dark": {
2935
- floor: [40, 46, 56, 255],
2936
- wall: [15, 15, 18, 255],
2937
- carpet: [80, 66, 52, 200],
2938
- segSat: 0.5,
2939
- segVal: 0.62,
2940
- path: [120, 170, 230, 235],
2941
- robotBody: [120, 170, 230, 255],
2942
- robotHeading: [20, 24, 30, 255],
2943
- charger: [60, 200, 110, 255],
2944
- noGoFill: [220, 70, 70, 80],
2945
- noGoBorder: [230, 70, 70, 220],
2946
- noMopFill: [70, 130, 230, 70],
2947
- noMopBorder: [80, 140, 235, 210],
2948
- virtualWall: [230, 70, 70, 235],
2949
- obstacle: [240, 180, 60, 255],
2950
- label: [225, 230, 240, 255]
2951
- },
2952
- "mijia-light": {
2953
- floor: [225, 232, 240, 255],
2954
- wall: [70, 78, 92, 255],
2955
- carpet: [188, 162, 132, 200],
2956
- segSat: 0.35,
2957
- segVal: 0.98,
2958
- path: [80, 130, 200, 230],
2959
- robotBody: [30, 100, 200, 255],
2960
- robotHeading: [255, 255, 255, 255],
2961
- charger: [40, 170, 90, 255],
2962
- noGoFill: [225, 70, 70, 70],
2963
- noGoBorder: [205, 45, 45, 220],
2964
- noMopFill: [70, 130, 225, 60],
2965
- noMopBorder: [50, 100, 205, 200],
2966
- virtualWall: [205, 45, 45, 230],
2967
- obstacle: [235, 160, 35, 255],
2968
- label: [45, 52, 64, 255]
2969
- },
2970
- tasshack: {
2971
- floor: [200, 210, 225, 255],
2972
- wall: [80, 80, 95, 255],
2973
- carpet: [176, 148, 118, 205],
2974
- segSat: 0.5,
2975
- segVal: 0.9,
2976
- path: [50, 100, 170, 235],
2977
- robotBody: [35, 60, 100, 255],
2978
- robotHeading: [255, 255, 255, 255],
2979
- charger: [35, 150, 75, 255],
2980
- noGoFill: [215, 55, 55, 75],
2981
- noGoBorder: [195, 35, 35, 220],
2982
- noMopFill: [55, 115, 215, 65],
2983
- noMopBorder: [35, 85, 195, 205],
2984
- virtualWall: [195, 35, 35, 230],
2985
- obstacle: [225, 145, 25, 255],
2986
- label: [35, 42, 54, 255]
2987
- },
2988
2939
  // Flat-design: soft neutral floor, bright low-saturation segment fills, thin
2989
2940
  // mid-grey walls, a single vivid accent for the path.
2990
2941
  flat: {
@@ -3081,7 +3032,7 @@ function segmentColor(id, pal) {
3081
3032
  }
3082
3033
  function renderVacuumPng(map, opts = {}) {
3083
3034
  const scale = Math.max(1, Math.trunc(opts.scale ?? 1));
3084
- const pal = SCHEMES[opts.colorScheme ?? "dreame-light"];
3035
+ const pal = SCHEMES[opts.colorScheme ?? "flat"] ?? SCHEMES.flat;
3085
3036
  const w = Math.max(1, map.dimensions.width * scale);
3086
3037
  const h = Math.max(1, map.dimensions.height * scale);
3087
3038
  const png = new import_pngjs.PNG({ width: w, height: h });
@@ -3124,8 +3075,12 @@ function renderVacuumPng(map, opts = {}) {
3124
3075
  const byType = opts.colorObstaclesByType !== false;
3125
3076
  for (const ob of map.obstacles) {
3126
3077
  const p = worldToPx(ob.x, ob.y, dim, scale);
3127
- const color = byType ? obstacleColor(ob.type) : pal.obstacle;
3128
- drawDiamond(png, p.x, p.y, Math.max(2, scale * 2), color);
3078
+ const r = Math.max(2, scale * 2);
3079
+ if (byType) {
3080
+ drawObstacleShape(png, p.x, p.y, r, ob.type, obstacleColor(ob.type));
3081
+ } else {
3082
+ drawDiamond(png, p.x, p.y, r, pal.obstacle);
3083
+ }
3129
3084
  }
3130
3085
  }
3131
3086
  if (opts.showCharger !== false && map.dock !== null) {
@@ -3286,6 +3241,32 @@ function obstacleColor(type) {
3286
3241
  const hue = Math.abs(Math.trunc(type)) * 47 % 360;
3287
3242
  return hsvToRgba(hue, 0.85, 0.95);
3288
3243
  }
3244
+ function drawSquare(png, cx, cy, r, color) {
3245
+ fillRect(png, cx - r, cy - r, cx + r, cy + r, color);
3246
+ }
3247
+ function drawTriangle(png, cx, cy, r, color) {
3248
+ for (let dy = -r; dy <= r; dy += 1) {
3249
+ const w = Math.round((dy + r) / (2 * r) * r);
3250
+ for (let dx = -w; dx <= w; dx += 1) {
3251
+ setPixel(png, cx + dx, cy + dy, color);
3252
+ }
3253
+ }
3254
+ }
3255
+ function drawObstacleShape(png, cx, cy, r, type, color) {
3256
+ switch (Math.abs(Math.trunc(type)) % 4) {
3257
+ case 0:
3258
+ drawDiamond(png, cx, cy, r, color);
3259
+ return;
3260
+ case 1:
3261
+ drawSquare(png, cx, cy, r, color);
3262
+ return;
3263
+ case 2:
3264
+ drawTriangle(png, cx, cy, r, color);
3265
+ return;
3266
+ default:
3267
+ drawDisk(png, cx, cy, r, color);
3268
+ }
3269
+ }
3289
3270
  var FURNITURE_LINE = [150, 110, 200, 235];
3290
3271
  function paintFurnitureZone(png, points, dim, scale) {
3291
3272
  if (points.length < 2) return;
@@ -4270,6 +4251,12 @@ var MowerFault = /* @__PURE__ */ ((MowerFault2) => {
4270
4251
  MowerFault2[MowerFault2["TopCoverOpen"] = 73] = "TopCoverOpen";
4271
4252
  return MowerFault2;
4272
4253
  })(MowerFault || {});
4254
+ function mowerFaultSeverity(code) {
4255
+ if (code === null) return "info";
4256
+ if (code >= 1 && code <= 30 || code === 37 || code === 73) return "error";
4257
+ if (code >= 31 && code <= 36 || code >= 38 && code <= 45) return "warning";
4258
+ return "info";
4259
+ }
4273
4260
 
4274
4261
  // src/models/mower/decode.ts
4275
4262
  function isRecord2(v) {
@@ -4480,6 +4467,37 @@ function parseMowerHeartbeat(value) {
4480
4467
  const taskSubState = mainState === HEARTBEAT_MAIN_STATE_MOWING ? MOWER_TASK_SUBSTATES[subStateRaw - HEARTBEAT_SUBSTATE_BASE] ?? null : null;
4481
4468
  return { rawBattery, mainState, subStateRaw, taskSubState };
4482
4469
  }
4470
+ var POSE_SENTINEL = 206;
4471
+ function poseBytes(value) {
4472
+ if (!Array.isArray(value)) return null;
4473
+ const bytes = [];
4474
+ for (const b of value) {
4475
+ if (typeof b !== "number") return null;
4476
+ bytes.push(b);
4477
+ }
4478
+ return bytes;
4479
+ }
4480
+ function parseMowingProgress(value) {
4481
+ const bytes = poseBytes(value);
4482
+ if (bytes === null || bytes.length < 11) return null;
4483
+ const last = bytes[bytes.length - 1];
4484
+ const head = bytes[0];
4485
+ let offset = null;
4486
+ if (head !== POSE_SENTINEL && last === POSE_SENTINEL) {
4487
+ offset = 0;
4488
+ } else if (head === POSE_SENTINEL && last === POSE_SENTINEL && bytes.length >= 33) {
4489
+ offset = 22;
4490
+ }
4491
+ if (offset === null || offset + 10 > bytes.length) return null;
4492
+ const rawPercent = (bytes[offset + 2] ?? 0) | (bytes[offset + 3] ?? 0) << 8;
4493
+ const total = (bytes[offset + 4] ?? 0) | (bytes[offset + 5] ?? 0) << 8 | (bytes[offset + 6] ?? 0) << 16;
4494
+ const finish = (bytes[offset + 7] ?? 0) | (bytes[offset + 8] ?? 0) << 8 | (bytes[offset + 9] ?? 0) << 16;
4495
+ return {
4496
+ progressPercent: rawPercent ? Math.min(100, rawPercent / 100) : 0,
4497
+ currentAreaSqm: finish / 100,
4498
+ totalAreaSqm: total / 100
4499
+ };
4500
+ }
4483
4501
 
4484
4502
  // src/models/mower/capabilities.ts
4485
4503
  var FALLBACK2 = {
@@ -5179,6 +5197,25 @@ var MowerDevice = class _MowerDevice extends BaseDevice {
5179
5197
  get coverageTargetPct() {
5180
5198
  return this.task?.coverageTarget ?? null;
5181
5199
  }
5200
+ /**
5201
+ * Live mowing PROGRESS decoded from the POSE_COVERAGE (1:4) task block —
5202
+ * `{progressPercent, currentAreaSqm, totalAreaSqm}` — or null when 1:4 carries
5203
+ * no task block (idle / pose-only frame). This is the byte-accurate progress
5204
+ * the Dreamehome app shows (vs {@link coverageTargetPct}, the task TARGET).
5205
+ */
5206
+ get mowingProgress() {
5207
+ return parseMowingProgress(
5208
+ this.getProperty(MOWER_PROP.POSE_COVERAGE.siid, MOWER_PROP.POSE_COVERAGE.piid)?.value
5209
+ );
5210
+ }
5211
+ /** Mowing completion percent (0..100) from {@link mowingProgress}, or null. */
5212
+ get mowingProgressPct() {
5213
+ return this.mowingProgress?.progressPercent ?? null;
5214
+ }
5215
+ /** Severity of the current DEVICE_CODE (2:2) — info / warning / error. */
5216
+ get faultSeverity() {
5217
+ return mowerFaultSeverity(this.faultRaw);
5218
+ }
5182
5219
  /** Parsed per-zone control status (2:56), or null. */
5183
5220
  get controlStatus() {
5184
5221
  return parseControlStatus(
@@ -5358,6 +5395,7 @@ var MowerDevice = class _MowerDevice extends BaseDevice {
5358
5395
  /** Props worth seeding on start() / polling — exported for the facade. */
5359
5396
  static DEFAULT_PROPS = [
5360
5397
  MOWER_PROP.HEARTBEAT,
5398
+ MOWER_PROP.POSE_COVERAGE,
5361
5399
  MOWER_PROP.STATUS,
5362
5400
  MOWER_PROP.DEVICE_CODE,
5363
5401
  MOWER_PROP.BATTERY,
@@ -6017,8 +6055,10 @@ function createClientDumper(client, options) {
6017
6055
  getVacuumCapabilities,
6018
6056
  isDreameConsumableKey,
6019
6057
  mowerConsumableIndex,
6058
+ mowerFaultSeverity,
6020
6059
  parseMowerConsumables,
6021
6060
  parseMowerHeartbeat,
6061
+ parseMowingProgress,
6022
6062
  renderMowerSvg,
6023
6063
  renderVacuumPng,
6024
6064
  resolveCapabilities,