@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.cjs +172 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -4
- package/dist/index.d.ts +25 -4
- package/dist/index.js +172 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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 `
|
|
1233
|
-
*
|
|
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
|
-
/**
|
|
1239
|
-
|
|
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 `
|
|
1233
|
-
*
|
|
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
|
-
/**
|
|
1239
|
-
|
|
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.
|
|
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
|
-
|
|
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().
|
|
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().
|
|
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().
|
|
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().
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
|
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;
|