@apocaliss92/nodedreame 1.11.5 → 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.5";
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;
@@ -3052,8 +3075,12 @@ function renderVacuumPng(map, opts = {}) {
3052
3075
  const byType = opts.colorObstaclesByType !== false;
3053
3076
  for (const ob of map.obstacles) {
3054
3077
  const p = worldToPx(ob.x, ob.y, dim, scale);
3055
- const color = byType ? obstacleColor(ob.type) : pal.obstacle;
3056
- 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
+ }
3057
3084
  }
3058
3085
  }
3059
3086
  if (opts.showCharger !== false && map.dock !== null) {
@@ -3214,6 +3241,32 @@ function obstacleColor(type) {
3214
3241
  const hue = Math.abs(Math.trunc(type)) * 47 % 360;
3215
3242
  return hsvToRgba(hue, 0.85, 0.95);
3216
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
+ }
3217
3270
  var FURNITURE_LINE = [150, 110, 200, 235];
3218
3271
  function paintFurnitureZone(png, points, dim, scale) {
3219
3272
  if (points.length < 2) return;
@@ -4198,6 +4251,12 @@ var MowerFault = /* @__PURE__ */ ((MowerFault2) => {
4198
4251
  MowerFault2[MowerFault2["TopCoverOpen"] = 73] = "TopCoverOpen";
4199
4252
  return MowerFault2;
4200
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
+ }
4201
4260
 
4202
4261
  // src/models/mower/decode.ts
4203
4262
  function isRecord2(v) {
@@ -4408,6 +4467,37 @@ function parseMowerHeartbeat(value) {
4408
4467
  const taskSubState = mainState === HEARTBEAT_MAIN_STATE_MOWING ? MOWER_TASK_SUBSTATES[subStateRaw - HEARTBEAT_SUBSTATE_BASE] ?? null : null;
4409
4468
  return { rawBattery, mainState, subStateRaw, taskSubState };
4410
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
+ }
4411
4501
 
4412
4502
  // src/models/mower/capabilities.ts
4413
4503
  var FALLBACK2 = {
@@ -5107,6 +5197,25 @@ var MowerDevice = class _MowerDevice extends BaseDevice {
5107
5197
  get coverageTargetPct() {
5108
5198
  return this.task?.coverageTarget ?? null;
5109
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
+ }
5110
5219
  /** Parsed per-zone control status (2:56), or null. */
5111
5220
  get controlStatus() {
5112
5221
  return parseControlStatus(
@@ -5286,6 +5395,7 @@ var MowerDevice = class _MowerDevice extends BaseDevice {
5286
5395
  /** Props worth seeding on start() / polling — exported for the facade. */
5287
5396
  static DEFAULT_PROPS = [
5288
5397
  MOWER_PROP.HEARTBEAT,
5398
+ MOWER_PROP.POSE_COVERAGE,
5289
5399
  MOWER_PROP.STATUS,
5290
5400
  MOWER_PROP.DEVICE_CODE,
5291
5401
  MOWER_PROP.BATTERY,
@@ -5945,8 +6055,10 @@ function createClientDumper(client, options) {
5945
6055
  getVacuumCapabilities,
5946
6056
  isDreameConsumableKey,
5947
6057
  mowerConsumableIndex,
6058
+ mowerFaultSeverity,
5948
6059
  parseMowerConsumables,
5949
6060
  parseMowerHeartbeat,
6061
+ parseMowingProgress,
5950
6062
  renderMowerSvg,
5951
6063
  renderVacuumPng,
5952
6064
  resolveCapabilities,