@apocaliss92/nodedreame 1.11.2 → 1.11.4

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.d.cts CHANGED
@@ -1229,14 +1229,26 @@ interface VacuumMap {
1229
1229
  *
1230
1230
  * Coordinate transform: every overlay coordinate is mm in the world frame.
1231
1231
  * `dimensions.left` / `.top` are the world-mm of pixel (0,0) and `.gridSize` is
1232
- * mm-per-pixel, so `px = (worldX - left) / gridSize` (then × `scale`). Row 0 is
1233
- * the top edge — no Y flip.
1232
+ * mm-per-pixel, so `xPx = (worldX - left) / gridSize` (then × `scale`).
1233
+ *
1234
+ * Y IS FLIPPED. Dreame's grid has world-Y increasing DOWNWARD relative to how
1235
+ * the robot/dock/app present the floor, so rendering rows top-down produced a
1236
+ * VERTICALLY MIRRORED map vs the Dreamehome app / Home Assistant (live-confirmed
1237
+ * on r2538z). We reflect in cell space — `yPx = ((height - 1) - cellY) × scale` —
1238
+ * applied identically to the run-length base layers ({@link paintRun}) and the
1239
+ * world-frame overlays ({@link worldToPx}) so they stay pixel-aligned, and the
1240
+ * robot heading's Y component is negated ({@link drawRobot}). Glyph labels are
1241
+ * drawn upright (only their anchor is flipped).
1234
1242
  *
1235
1243
  * Pure: only `pngjs` + arithmetic. No casts.
1236
1244
  */
1237
1245
 
1238
- /** A named map palette. Mirrors the HA integration's `color_scheme` choices. */
1239
- type MapColorScheme = 'dreame-light' | 'dreame-dark' | 'mijia-light' | 'tasshack';
1246
+ /**
1247
+ * A named map palette. The first four mirror the HA integration's `color_scheme`
1248
+ * choices; `flat` / `dark-neon` / `materico` are node-dreame additions for
1249
+ * distinct visual styles.
1250
+ */
1251
+ type MapColorScheme = 'dreame-light' | 'dreame-dark' | 'mijia-light' | 'tasshack' | 'flat' | 'dark-neon' | 'materico';
1240
1252
  interface RenderVacuumPngOptions {
1241
1253
  /** Integer upscale factor (nearest-neighbour). Default 1. */
1242
1254
  scale?: number;
@@ -1256,6 +1268,15 @@ interface RenderVacuumPngOptions {
1256
1268
  showObstacles?: boolean;
1257
1269
  /** Draw the segment id as a tiny label at each room centroid. Default false. */
1258
1270
  showSegmentLabels?: boolean;
1271
+ /** Draw each room's NAME (falls back to `Room <id>`) at its centroid. Default
1272
+ * false. Mutually useful with `showSegmentLabels` (names take the centroid). */
1273
+ showSegmentNames?: boolean;
1274
+ /** Tint the cleaned-area overlay (`cleanedArea.cleaned`) when present. Default false. */
1275
+ showCleanedArea?: boolean;
1276
+ /** Outline low-clearance "sneak under furniture" zones. Default true. */
1277
+ showFurniture?: boolean;
1278
+ /** Colour AI obstacle markers by their `type` (else a single accent). Default true. */
1279
+ colorObstaclesByType?: boolean;
1259
1280
  }
1260
1281
  declare function renderVacuumPng(map: VacuumMap, opts?: RenderVacuumPngOptions): Buffer;
1261
1282
 
package/dist/index.d.ts CHANGED
@@ -1229,14 +1229,26 @@ interface VacuumMap {
1229
1229
  *
1230
1230
  * Coordinate transform: every overlay coordinate is mm in the world frame.
1231
1231
  * `dimensions.left` / `.top` are the world-mm of pixel (0,0) and `.gridSize` is
1232
- * mm-per-pixel, so `px = (worldX - left) / gridSize` (then × `scale`). Row 0 is
1233
- * the top edge — no Y flip.
1232
+ * mm-per-pixel, so `xPx = (worldX - left) / gridSize` (then × `scale`).
1233
+ *
1234
+ * Y IS FLIPPED. Dreame's grid has world-Y increasing DOWNWARD relative to how
1235
+ * the robot/dock/app present the floor, so rendering rows top-down produced a
1236
+ * VERTICALLY MIRRORED map vs the Dreamehome app / Home Assistant (live-confirmed
1237
+ * on r2538z). We reflect in cell space — `yPx = ((height - 1) - cellY) × scale` —
1238
+ * applied identically to the run-length base layers ({@link paintRun}) and the
1239
+ * world-frame overlays ({@link worldToPx}) so they stay pixel-aligned, and the
1240
+ * robot heading's Y component is negated ({@link drawRobot}). Glyph labels are
1241
+ * drawn upright (only their anchor is flipped).
1234
1242
  *
1235
1243
  * Pure: only `pngjs` + arithmetic. No casts.
1236
1244
  */
1237
1245
 
1238
- /** A named map palette. Mirrors the HA integration's `color_scheme` choices. */
1239
- type MapColorScheme = 'dreame-light' | 'dreame-dark' | 'mijia-light' | 'tasshack';
1246
+ /**
1247
+ * A named map palette. The first four mirror the HA integration's `color_scheme`
1248
+ * choices; `flat` / `dark-neon` / `materico` are node-dreame additions for
1249
+ * distinct visual styles.
1250
+ */
1251
+ type MapColorScheme = 'dreame-light' | 'dreame-dark' | 'mijia-light' | 'tasshack' | 'flat' | 'dark-neon' | 'materico';
1240
1252
  interface RenderVacuumPngOptions {
1241
1253
  /** Integer upscale factor (nearest-neighbour). Default 1. */
1242
1254
  scale?: number;
@@ -1256,6 +1268,15 @@ interface RenderVacuumPngOptions {
1256
1268
  showObstacles?: boolean;
1257
1269
  /** Draw the segment id as a tiny label at each room centroid. Default false. */
1258
1270
  showSegmentLabels?: boolean;
1271
+ /** Draw each room's NAME (falls back to `Room <id>`) at its centroid. Default
1272
+ * false. Mutually useful with `showSegmentLabels` (names take the centroid). */
1273
+ showSegmentNames?: boolean;
1274
+ /** Tint the cleaned-area overlay (`cleanedArea.cleaned`) when present. Default false. */
1275
+ showCleanedArea?: boolean;
1276
+ /** Outline low-clearance "sneak under furniture" zones. Default true. */
1277
+ showFurniture?: boolean;
1278
+ /** Colour AI obstacle markers by their `type` (else a single accent). Default true. */
1279
+ colorObstaclesByType?: boolean;
1259
1280
  }
1260
1281
  declare function renderVacuumPng(map: VacuumMap, opts?: RenderVacuumPngOptions): Buffer;
1261
1282
 
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/support/version.ts
2
2
  var LIBRARY_NAME = "nodedreame";
3
- var LIBRARY_VERSION = "1.11.2";
3
+ var LIBRARY_VERSION = "1.11.4";
4
4
 
5
5
  // src/transport/errors.ts
6
6
  var DreameError = class extends Error {
@@ -58,8 +58,11 @@ function buildRlcHeader(region, lang, country) {
58
58
  function randomMqttClientId() {
59
59
  return "p_" + randomBytes(8).toString("hex");
60
60
  }
61
+ var MAX_REQUEST_ID = 16777215;
62
+ var nextRequestId = Math.floor(Math.random() * 100) + 1;
61
63
  function randomRequestId() {
62
- return Math.floor(Math.random() * 2147483647) + 1;
64
+ nextRequestId = nextRequestId >= MAX_REQUEST_ID ? 1 : nextRequestId + 1;
65
+ return nextRequestId;
63
66
  }
64
67
 
65
68
  // src/auth/config.ts
@@ -258,7 +261,7 @@ var OAuthTokenResponseSchema = z.object({
258
261
  error: z.string().optional(),
259
262
  error_description: z.string().optional(),
260
263
  code: z.number().optional(),
261
- msg: z.string().optional()
264
+ msg: z.string().nullish()
262
265
  }).passthrough();
263
266
  var RawDeviceSchema = z.object({
264
267
  did: z.union([z.string(), z.number()]).optional(),
@@ -273,7 +276,7 @@ var RawDeviceSchema = z.object({
273
276
  }).passthrough();
274
277
  var DeviceListResponseSchema = z.object({
275
278
  code: z.number().optional(),
276
- msg: z.string().optional(),
279
+ msg: z.string().nullish(),
277
280
  data: z.object({
278
281
  page: z.object({ records: z.array(RawDeviceSchema).optional() }).partial().optional(),
279
282
  records: z.array(RawDeviceSchema).optional()
@@ -293,12 +296,12 @@ var CachedPropEntrySchema = z.object({
293
296
  }).passthrough();
294
297
  var CachedPropsResponseSchema = z.object({
295
298
  code: z.number().optional(),
296
- msg: z.string().optional(),
299
+ msg: z.string().nullish(),
297
300
  data: z.array(CachedPropEntrySchema).optional()
298
301
  }).passthrough();
299
302
  var SendCommandResponseSchema = z.object({
300
303
  code: z.number().optional(),
301
- msg: z.string().optional(),
304
+ msg: z.string().nullish(),
302
305
  data: z.object({ result: z.unknown().optional() }).passthrough().optional(),
303
306
  result: z.unknown().optional()
304
307
  }).passthrough();
@@ -2895,6 +2898,66 @@ var SCHEMES = {
2895
2898
  virtualWall: [195, 35, 35, 230],
2896
2899
  obstacle: [225, 145, 25, 255],
2897
2900
  label: [35, 42, 54, 255]
2901
+ },
2902
+ // Flat-design: soft neutral floor, bright low-saturation segment fills, thin
2903
+ // mid-grey walls, a single vivid accent for the path.
2904
+ flat: {
2905
+ floor: [236, 239, 241, 255],
2906
+ wall: [120, 132, 145, 255],
2907
+ carpet: [205, 185, 160, 200],
2908
+ segSat: 0.28,
2909
+ segVal: 0.99,
2910
+ path: [255, 138, 101, 240],
2911
+ robotBody: [38, 50, 56, 255],
2912
+ robotHeading: [255, 255, 255, 255],
2913
+ charger: [38, 166, 154, 255],
2914
+ noGoFill: [239, 83, 80, 60],
2915
+ noGoBorder: [229, 57, 53, 210],
2916
+ noMopFill: [66, 165, 245, 55],
2917
+ noMopBorder: [30, 136, 229, 200],
2918
+ virtualWall: [229, 57, 53, 225],
2919
+ obstacle: [255, 167, 38, 255],
2920
+ label: [55, 71, 79, 255]
2921
+ },
2922
+ // Dark-neon: near-black floor with high-saturation glowing segment fills, a
2923
+ // bright cyan path and magenta robot.
2924
+ "dark-neon": {
2925
+ floor: [16, 18, 27, 255],
2926
+ wall: [5, 6, 10, 255],
2927
+ carpet: [60, 50, 80, 200],
2928
+ segSat: 0.85,
2929
+ segVal: 0.95,
2930
+ path: [0, 230, 255, 240],
2931
+ robotBody: [255, 45, 170, 255],
2932
+ robotHeading: [255, 255, 255, 255],
2933
+ charger: [0, 255, 150, 255],
2934
+ noGoFill: [255, 40, 90, 80],
2935
+ noGoBorder: [255, 40, 90, 235],
2936
+ noMopFill: [40, 130, 255, 75],
2937
+ noMopBorder: [40, 160, 255, 225],
2938
+ virtualWall: [255, 40, 90, 235],
2939
+ obstacle: [255, 220, 0, 255],
2940
+ label: [230, 240, 255, 255]
2941
+ },
2942
+ // Materico (Material-design): light grey surface, Material-palette segment
2943
+ // fills, indigo/teal accents.
2944
+ materico: {
2945
+ floor: [245, 245, 245, 255],
2946
+ wall: [97, 97, 97, 255],
2947
+ carpet: [188, 170, 144, 205],
2948
+ segSat: 0.55,
2949
+ segVal: 0.93,
2950
+ path: [63, 81, 181, 235],
2951
+ robotBody: [48, 63, 159, 255],
2952
+ robotHeading: [255, 255, 255, 255],
2953
+ charger: [0, 150, 136, 255],
2954
+ noGoFill: [244, 67, 54, 65],
2955
+ noGoBorder: [211, 47, 47, 215],
2956
+ noMopFill: [33, 150, 243, 60],
2957
+ noMopBorder: [25, 118, 210, 205],
2958
+ virtualWall: [211, 47, 47, 225],
2959
+ obstacle: [255, 152, 0, 255],
2960
+ label: [33, 33, 33, 255]
2898
2961
  }
2899
2962
  };
2900
2963
  function hsvToRgba(h, s, v) {
@@ -2937,10 +3000,13 @@ function renderVacuumPng(map, opts = {}) {
2937
3000
  const h = Math.max(1, map.dimensions.height * scale);
2938
3001
  const png = new PNG({ width: w, height: h });
2939
3002
  png.data.fill(0);
3003
+ const dim = map.dimensions;
2940
3004
  for (const layer of map.layers) {
2941
- paintLayer(png, layer, scale, pal);
3005
+ paintLayer(png, layer, dim.height, scale, pal);
3006
+ }
3007
+ if (opts.showCleanedArea === true && map.cleanedArea !== null) {
3008
+ paintCleanedArea(png, map.cleanedArea, dim, scale, pal.path);
2942
3009
  }
2943
- const dim = map.dimensions;
2944
3010
  if (opts.showNoGo !== false) {
2945
3011
  for (const area of map.restrictedAreas) {
2946
3012
  const fill = area.kind === "noMop" ? pal.noMopFill : pal.noGoFill;
@@ -2963,10 +3029,17 @@ function renderVacuumPng(map, opts = {}) {
2963
3029
  drawPolyline(png, path.points, dim, scale, pal.path, Math.max(1, scale));
2964
3030
  }
2965
3031
  }
3032
+ if (opts.showFurniture !== false) {
3033
+ for (const zone of map.lowLyingAreas) {
3034
+ paintFurnitureZone(png, zone.points, dim, scale);
3035
+ }
3036
+ }
2966
3037
  if (opts.showObstacles !== false) {
3038
+ const byType = opts.colorObstaclesByType !== false;
2967
3039
  for (const ob of map.obstacles) {
2968
3040
  const p = worldToPx(ob.x, ob.y, dim, scale);
2969
- drawDisk(png, p.x, p.y, Math.max(2, scale * 2), pal.obstacle);
3041
+ const color = byType ? obstacleColor(ob.type) : pal.obstacle;
3042
+ drawDiamond(png, p.x, p.y, Math.max(2, scale * 2), color);
2970
3043
  }
2971
3044
  }
2972
3045
  if (opts.showCharger !== false && map.dock !== null) {
@@ -2975,31 +3048,33 @@ function renderVacuumPng(map, opts = {}) {
2975
3048
  if (opts.showRobot !== false && map.robot !== null) {
2976
3049
  drawRobot(png, map.robot, dim, scale, pal);
2977
3050
  }
2978
- if (opts.showSegmentLabels === true) {
3051
+ if (opts.showSegmentNames === true || opts.showSegmentLabels === true) {
2979
3052
  for (const seg of map.segments) {
2980
3053
  const p = worldToPx(seg.centroid.x, seg.centroid.y, dim, scale);
2981
- drawLabel(png, p.x, p.y, String(seg.id), pal.label, Math.max(1, scale));
3054
+ const text = opts.showSegmentNames === true ? labelText(seg.name, seg.id) : String(seg.id);
3055
+ drawLabel(png, p.x, p.y, text, pal.label, Math.max(1, scale));
2982
3056
  }
2983
3057
  }
2984
3058
  return PNG.sync.write(png);
2985
3059
  }
2986
3060
  function worldToPx(worldX, worldY, dim, scale) {
3061
+ const cellY = (worldY - dim.top) / dim.gridSize;
2987
3062
  return {
2988
3063
  x: Math.round((worldX - dim.left) / dim.gridSize * scale),
2989
- y: Math.round((worldY - dim.top) / dim.gridSize * scale)
3064
+ y: Math.round((dim.height - 1 - cellY) * scale)
2990
3065
  };
2991
3066
  }
2992
- function paintLayer(png, layer, scale, pal) {
3067
+ function paintLayer(png, layer, dimHeight, scale, pal) {
2993
3068
  const color = layer.type === "segment" ? segmentColor(layer.segmentId ?? 0, pal) : layer.type === "wall" ? pal.wall : layer.type === "carpet" ? pal.carpet : pal.floor;
2994
3069
  for (const run of layer.runs) {
2995
- paintRun(png, run, color, scale);
3070
+ paintRun(png, run, dimHeight, color, scale);
2996
3071
  }
2997
3072
  }
2998
- function paintRun(png, run, color, scale) {
3073
+ function paintRun(png, run, dimHeight, color, scale) {
2999
3074
  const [xPx, yPx, len] = run;
3075
+ const baseY = (dimHeight - 1 - yPx) * scale;
3000
3076
  for (let i = 0; i < len; i += 1) {
3001
3077
  const baseX = (xPx + i) * scale;
3002
- const baseY = yPx * scale;
3003
3078
  for (let dy = 0; dy < scale; dy += 1) {
3004
3079
  for (let dx = 0; dx < scale; dx += 1) {
3005
3080
  setPixel(png, baseX + dx, baseY + dy, color);
@@ -3105,7 +3180,7 @@ function drawRobot(png, robot, dim, scale, pal) {
3105
3180
  drawDisk(png, p.x, p.y, r, pal.robotBody);
3106
3181
  const rad = robot.angle * Math.PI / 180;
3107
3182
  const hx = Math.round(p.x + Math.cos(rad) * r);
3108
- const hy = Math.round(p.y + Math.sin(rad) * r);
3183
+ const hy = Math.round(p.y - Math.sin(rad) * r);
3109
3184
  drawLine(png, p.x, p.y, hx, hy, pal.robotHeading, Math.max(1, Math.trunc(scale / 2) + 1));
3110
3185
  }
3111
3186
  function drawCharger(png, dock, dim, scale, pal) {
@@ -3113,6 +3188,58 @@ function drawCharger(png, dock, dim, scale, pal) {
3113
3188
  const r = Math.max(2, scale * 2);
3114
3189
  fillRect(png, p.x - r, p.y - r, p.x + r, p.y + r, pal.charger);
3115
3190
  }
3191
+ function drawDiamond(png, cx, cy, r, color) {
3192
+ for (let dy = -r; dy <= r; dy += 1) {
3193
+ const w = r - Math.abs(dy);
3194
+ for (let dx = -w; dx <= w; dx += 1) {
3195
+ setPixel(png, cx + dx, cy + dy, color);
3196
+ }
3197
+ }
3198
+ }
3199
+ function obstacleColor(type) {
3200
+ const hue = Math.abs(Math.trunc(type)) * 47 % 360;
3201
+ return hsvToRgba(hue, 0.85, 0.95);
3202
+ }
3203
+ var FURNITURE_LINE = [150, 110, 200, 235];
3204
+ function paintFurnitureZone(png, points, dim, scale) {
3205
+ if (points.length < 2) return;
3206
+ for (let i = 0; i < points.length; i += 1) {
3207
+ const a = points[i];
3208
+ const b = points[(i + 1) % points.length];
3209
+ if (a === void 0 || b === void 0) continue;
3210
+ const pa = worldToPx(a.x, a.y, dim, scale);
3211
+ const pb = worldToPx(b.x, b.y, dim, scale);
3212
+ drawLine(png, pa.x, pa.y, pb.x, pb.y, FURNITURE_LINE, Math.max(1, scale));
3213
+ }
3214
+ }
3215
+ function paintCleanedArea(png, overlay, parentDim, scale, baseColor) {
3216
+ const tint = [baseColor[0], baseColor[1], baseColor[2], 55];
3217
+ const inner = overlay.dimensions;
3218
+ const block = Math.max(1, Math.round(inner.gridSize / parentDim.gridSize * scale));
3219
+ for (const [xPx, yPx, len] of overlay.cleaned) {
3220
+ for (let i = 0; i < len; i += 1) {
3221
+ const worldX = inner.left + (xPx + i) * inner.gridSize;
3222
+ const worldY = inner.top + yPx * inner.gridSize;
3223
+ const p = worldToPx(worldX, worldY, parentDim, scale);
3224
+ for (let dy = 0; dy < block; dy += 1) {
3225
+ for (let dx = 0; dx < block; dx += 1) {
3226
+ setPixel(png, p.x + dx, p.y + dy, tint);
3227
+ }
3228
+ }
3229
+ }
3230
+ }
3231
+ }
3232
+ function labelText(name, id) {
3233
+ const raw = (name ?? "").trim();
3234
+ const base = raw.length > 0 ? raw : `ROOM ${id}`;
3235
+ const upper = base.toUpperCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
3236
+ let out = "";
3237
+ for (const ch of upper) {
3238
+ if (GLYPHS[ch] !== void 0) out += ch;
3239
+ }
3240
+ const trimmed = out.trim().slice(0, 14);
3241
+ return trimmed.length > 0 ? trimmed : `ROOM ${id}`;
3242
+ }
3116
3243
  var GLYPHS = {
3117
3244
  "0": [7, 5, 5, 5, 7],
3118
3245
  "1": [2, 6, 2, 2, 7],
@@ -3124,7 +3251,34 @@ var GLYPHS = {
3124
3251
  "7": [7, 1, 2, 2, 2],
3125
3252
  "8": [7, 5, 7, 5, 7],
3126
3253
  "9": [7, 5, 7, 1, 7],
3127
- "-": [0, 0, 7, 0, 0]
3254
+ "-": [0, 0, 7, 0, 0],
3255
+ " ": [0, 0, 0, 0, 0],
3256
+ A: [2, 5, 7, 5, 5],
3257
+ B: [6, 5, 6, 5, 6],
3258
+ C: [3, 4, 4, 4, 3],
3259
+ D: [6, 5, 5, 5, 6],
3260
+ E: [7, 4, 6, 4, 7],
3261
+ F: [7, 4, 6, 4, 4],
3262
+ G: [3, 4, 5, 5, 3],
3263
+ H: [5, 5, 7, 5, 5],
3264
+ I: [7, 2, 2, 2, 7],
3265
+ J: [1, 1, 1, 5, 2],
3266
+ K: [5, 5, 6, 5, 5],
3267
+ L: [4, 4, 4, 4, 7],
3268
+ M: [5, 7, 7, 5, 5],
3269
+ N: [5, 7, 7, 7, 5],
3270
+ O: [2, 5, 5, 5, 2],
3271
+ P: [6, 5, 6, 4, 4],
3272
+ Q: [2, 5, 5, 6, 3],
3273
+ R: [6, 5, 6, 5, 5],
3274
+ S: [3, 4, 2, 1, 6],
3275
+ T: [7, 2, 2, 2, 2],
3276
+ U: [5, 5, 5, 5, 7],
3277
+ V: [5, 5, 5, 5, 2],
3278
+ W: [5, 5, 7, 7, 5],
3279
+ X: [5, 5, 2, 5, 5],
3280
+ Y: [5, 5, 2, 2, 2],
3281
+ Z: [7, 1, 2, 4, 7]
3128
3282
  };
3129
3283
  function drawLabel(png, cx, cy, text, color, px) {
3130
3284
  const glyphW = 3 * px;