UNPKG

matterbridge-roborock-vacuum-plugin

Version:
153 lines 6.62 kB
const MM_PER_PIXEL = 50; const BLOCK_TYPE = { CHARGER_LOCATION: 1, IMAGE: 2, ROBOT_POSITION: 8, CURRENTLY_CLEANED_BLOCKS: 11, }; export class LegacyMapParser { parse(buffer) { if (buffer.length < 20 || buffer[0] !== 0x72 || buffer[1] !== 0x72) return {}; const headerLength = buffer.readUInt16LE(0x02); const dataLength = buffer.readUInt32LE(0x04); const dataEnd = headerLength + dataLength; const result = {}; let dataPosition = headerLength; while (dataPosition < dataEnd) { if (dataPosition + 8 > buffer.length) break; const type = buffer.readUInt16LE(dataPosition); const blockHLength = buffer.readUInt16LE(dataPosition + 2); const blockDataLength = buffer.readUInt32LE(dataPosition + 4); if (dataPosition + blockHLength + blockDataLength > buffer.length) break; switch (type) { case BLOCK_TYPE.ROBOT_POSITION: result.robotPosition = this.parsePositionBlock(buffer, dataPosition, blockHLength, blockDataLength); break; case BLOCK_TYPE.CHARGER_LOCATION: result.chargerPosition = this.parsePositionBlock(buffer, dataPosition, blockHLength, blockDataLength); break; case BLOCK_TYPE.IMAGE: result.image = this.parseImageBlock(buffer, dataPosition, blockHLength, blockDataLength); break; case BLOCK_TYPE.CURRENTLY_CLEANED_BLOCKS: result.currentlyCleanedBlocks = this.parseCleanedBlocks(buffer, dataPosition); break; default: break; } dataPosition += blockHLength + blockDataLength; } return result; } resolveCurrentRoom(data, namedRooms) { if (!data.robotPosition || !data.image) return undefined; const { x, y } = data.robotPosition; const { position: { top, left }, dimensions: { width, height }, pixels: { segments }, } = data.image; const pxAbs = Math.round(x / MM_PER_PIXEL); const pyAbs = Math.round(y / MM_PER_PIXEL); const pxRel = pxAbs - left; const pyRel = height - 1 - (pyAbs - top); if (pxRel < 0 || pxRel >= width || pyRel < 0 || pyRel >= height) return undefined; const targetIndex = pyRel * width + pxRel; for (const packed of segments) { if ((packed & 0x1fffff) === targetIndex) { const segmentId = packed >> 21; const name = namedRooms?.find((r) => r.id === segmentId)?.name ?? ''; return { segmentId, name }; } } return this.findNearestSegment(data.image.segments.list, x, y, namedRooms); } parsePositionBlock(buffer, dataPosition, hlength, length) { const payloadStart = dataPosition + hlength; const x = buffer.readInt32LE(payloadStart); const y = buffer.readInt32LE(payloadStart + 4); const angle = length >= 12 ? buffer.readInt32LE(payloadStart + 8) : 0; return { x, y, angle }; } parseImageBlock(buffer, dataPosition, hlength, length) { const segmentCount = hlength > 24 ? buffer.readUInt32LE(dataPosition + 0x08) : 0; const top = buffer.readInt32LE(dataPosition + 0x0c); const left = buffer.readInt32LE(dataPosition + 0x10); const height = buffer.readInt32LE(dataPosition + 0x14); const width = buffer.readInt32LE(dataPosition + 0x18); const pixelStart = dataPosition + hlength; const floor = []; const obstacle = []; const segments = []; const segmentBoundingBoxes = new Map(); const segmentIdsSeen = new Set(); for (let i = 0; i < length; i++) { const byte = buffer.readUInt8(pixelStart + i); const pixelType = byte & 0x07; // bits 2:0 if (pixelType === 1) { obstacle.push(i); continue; } if (pixelType === 0) continue; floor.push(i); const segmentId = (byte & 0xf8) >> 3; // bits 7:3 if (segmentId === 0) continue; segmentIdsSeen.add(segmentId); segments.push(i | (segmentId << 21)); this.updateBoundingBox(segmentBoundingBoxes, segmentId, i % width, Math.floor(i / width)); } const list = [...segmentIdsSeen].map((segmentId) => { const bb = segmentBoundingBoxes.get(segmentId); const centerX = bb ? Math.round(((bb.minX + bb.maxX) / 2 + left) * MM_PER_PIXEL) : 0; const centerY = bb ? Math.round(((bb.minY + bb.maxY) / 2 + top) * MM_PER_PIXEL) : 0; return { id: segmentId, name: '', center: [centerX, centerY] }; }); return { position: { top, left }, dimensions: { width, height }, pixels: { floor, obstacle, segments }, segments: { count: segmentCount, list }, }; } parseCleanedBlocks(buffer, dataPosition) { const count = buffer.readUInt16LE(dataPosition + 0x08); const blocks = []; for (let i = 0; i < count; i++) { blocks.push(buffer.readUInt8(dataPosition + 0x0c + i)); } return blocks; } updateBoundingBox(map, segmentId, px, py) { const existing = map.get(segmentId); if (!existing) { map.set(segmentId, { minX: px, maxX: px, minY: py, maxY: py }); return; } existing.minX = Math.min(existing.minX, px); existing.maxX = Math.max(existing.maxX, px); existing.minY = Math.min(existing.minY, py); existing.maxY = Math.max(existing.maxY, py); } findNearestSegment(list, x, y, namedRooms) { if (list.length === 0) return undefined; let closest; let closestDistance = Infinity; for (const segment of list) { const [centerX, centerY] = segment.center; const distance = Math.hypot(centerX - x, centerY - y); if (distance < closestDistance) { closestDistance = distance; closest = segment; } } if (!closest) return undefined; const name = namedRooms?.find((r) => r.id === closest.id)?.name ?? closest.name; return { segmentId: closest.id, name }; } } //# sourceMappingURL=mapParser.js.map