@opendisplay/opendisplay 1.0.1 → 1.1.0

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.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/device.ts","../src/encoding/images.ts","../src/exceptions.ts","../src/encoding/bitplanes.ts","../src/encoding/compression.ts","../src/models/enums.ts","../src/protocol/constants.ts","../src/protocol/commands.ts","../src/protocol/responses.ts","../src/models/config.ts","../src/protocol/config-parser.ts","../src/protocol/config-serializer.ts","../src/transport/notification-queue.ts","../src/transport/connection.ts","../src/discovery.ts","../src/models/advertisement.ts","../src/index.ts"],"sourcesContent":["/**\n * Main OpenDisplay BLE device class.\n */\n\nimport { ColorScheme, DitherMode } from '@opendisplay/epaper-dithering';\nimport { prepareImageForUpload } from './encoding/images';\nimport { compressImageData } from './encoding/compression';\nimport { BLEConnectionError, BLETimeoutError, ProtocolError } from './exceptions';\nimport type { DeviceCapabilities } from './models/capabilities';\nimport type { GlobalConfig } from './models/config';\nimport { RefreshMode } from './models/enums';\nimport type { FirmwareVersion } from './models/firmware';\nimport {\n CHUNK_SIZE,\n MAX_COMPRESSED_SIZE,\n CommandCode,\n} from './protocol/constants';\nimport {\n buildReadConfigCommand,\n buildReadFwVersionCommand,\n buildRebootCommand,\n buildDirectWriteStartCompressed,\n buildDirectWriteStartUncompressed,\n buildDirectWriteDataCommand,\n buildDirectWriteEndCommand,\n buildWriteConfigCommand,\n} from './protocol/commands';\nimport {\n validateAckResponse,\n parseFirmwareVersion,\n stripCommandEcho,\n checkResponseType,\n} from './protocol/responses';\nimport { parseConfigResponse } from './protocol/config-parser';\nimport { serializeConfig } from './protocol/config-serializer';\nimport { BLEConnection, type BLEConnectionOptions } from './transport/connection';\n\n/**\n * OpenDisplay BLE e-paper device.\n *\n * Main API for communicating with OpenDisplay BLE tags.\n *\n * @example\n * ```typescript\n * // Auto-interrogate on first connect\n * const device = new OpenDisplayDevice();\n * await device.connect();\n * await device.uploadImage(imageData);\n * await device.disconnect();\n *\n * // Skip interrogation with cached config\n * const device = new OpenDisplayDevice({ config: cachedConfig });\n * await device.connect();\n *\n * // Skip interrogation with minimal capabilities\n * const caps: DeviceCapabilities = { width: 296, height: 128, colorScheme: ColorScheme.BWR };\n * const device = new OpenDisplayDevice({ capabilities: caps });\n * ```\n */\nexport class OpenDisplayDevice {\n // BLE operation timeouts (milliseconds)\n static readonly TIMEOUT_FIRST_CHUNK = 10000; // First chunk may take longer\n static readonly TIMEOUT_CHUNK = 2000; // Subsequent chunks\n static readonly TIMEOUT_ACK = 5000; // Command acknowledgments\n static readonly TIMEOUT_REFRESH = 90000; // Display refresh (firmware spec: up to 60s)\n\n private connection: BLEConnection | null = null;\n private _config: GlobalConfig | null = null;\n private _capabilities: DeviceCapabilities | null = null;\n private _fwVersion: FirmwareVersion | null = null;\n\n /**\n * Initialize OpenDisplay device.\n *\n * @param options - Device initialization options\n */\n constructor(\n private options: {\n config?: GlobalConfig;\n capabilities?: DeviceCapabilities;\n device?: BluetoothDevice;\n namePrefix?: string;\n } = {}\n ) {\n this._config = options.config ?? null;\n this._capabilities = options.capabilities ?? null;\n }\n\n /**\n * Get full device configuration (if interrogated).\n */\n get config(): GlobalConfig | null {\n return this._config;\n }\n\n /**\n * Get device capabilities (width, height, color scheme, rotation).\n */\n get capabilities(): DeviceCapabilities | null {\n return this._capabilities;\n }\n\n /**\n * Get display width in pixels.\n */\n get width(): number {\n return this.ensureCapabilities().width;\n }\n\n /**\n * Get display height in pixels.\n */\n get height(): number {\n return this.ensureCapabilities().height;\n }\n\n /**\n * Get display color scheme.\n */\n get colorScheme(): ColorScheme {\n return this.ensureCapabilities().colorScheme;\n }\n\n /**\n * Get display rotation in degrees.\n */\n get rotation(): number {\n return this.ensureCapabilities().rotation ?? 0;\n }\n\n /**\n * Check if currently connected to a device.\n */\n get isConnected(): boolean {\n return this.connection?.isConnected ?? false;\n }\n\n /**\n * Connect to an OpenDisplay device and optionally interrogate.\n *\n * @param connectionOptions - Optional connection parameters\n * @throws {BLEConnectionError} If connection fails\n */\n async connect(connectionOptions?: BLEConnectionOptions): Promise<void> {\n // Create connection\n this.connection = new BLEConnection();\n\n // Merge provided options with constructor options\n const mergedOptions: BLEConnectionOptions = {\n ...connectionOptions,\n device: this.options.device ?? connectionOptions?.device,\n namePrefix: this.options.namePrefix ?? connectionOptions?.namePrefix,\n };\n\n await this.connection.connect(mergedOptions);\n\n // Auto-interrogate if no config or capabilities provided\n if (!this._config && !this._capabilities) {\n console.log('No config provided, auto-interrogating device');\n await this.interrogate();\n }\n\n // Extract capabilities from config if available\n if (this._config && !this._capabilities) {\n this._capabilities = this.extractCapabilitiesFromConfig();\n }\n }\n\n /**\n * Disconnect from the device.\n */\n async disconnect(): Promise<void> {\n if (this.connection) {\n await this.connection.disconnect();\n this.connection = null;\n }\n }\n\n /**\n * Read device configuration from device.\n *\n * @returns GlobalConfig with complete device configuration\n * @throws {ProtocolError} If interrogation fails\n */\n async interrogate(): Promise<GlobalConfig> {\n this.ensureConnected();\n\n console.log('Interrogating device');\n\n // Send read config command\n const cmd = buildReadConfigCommand();\n await this.connection!.writeCommand(cmd);\n\n // Read first chunk\n const response = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_FIRST_CHUNK\n );\n const chunkData = stripCommandEcho(response, CommandCode.READ_CONFIG);\n\n // Parse first chunk header\n const view = new DataView(chunkData.buffer, chunkData.byteOffset);\n const totalLength = view.getUint16(2, true); // little-endian\n const tlvData: Uint8Array[] = [chunkData.subarray(4)];\n let currentLength = chunkData.length - 4;\n\n console.debug(`First chunk: ${chunkData.length} bytes, total length: ${totalLength}`);\n\n // Read remaining chunks\n while (currentLength < totalLength) {\n const nextResponse = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_CHUNK\n );\n const nextChunkData = stripCommandEcho(nextResponse, CommandCode.READ_CONFIG);\n\n // Skip chunk number field (2 bytes) and append data\n tlvData.push(nextChunkData.subarray(2));\n currentLength += nextChunkData.length - 2;\n\n console.debug(`Received chunk, total: ${currentLength}/${totalLength} bytes`);\n }\n\n console.log(`Received complete TLV data: ${currentLength} bytes`);\n\n // Concatenate all chunks\n const completeData = new Uint8Array(currentLength);\n let offset = 0;\n for (const chunk of tlvData) {\n completeData.set(chunk, offset);\n offset += chunk.length;\n }\n\n // Parse complete config response (handles wrapper strip)\n this._config = parseConfigResponse(completeData);\n this._capabilities = this.extractCapabilitiesFromConfig();\n\n console.log(\n `Interrogated device: ${this.width}x${this.height}, ` +\n `${ColorScheme[this.colorScheme]}, rotation=${this.rotation}°`\n );\n\n return this._config;\n }\n\n /**\n * Read firmware version from device.\n *\n * @returns FirmwareVersion with major, minor, and sha fields\n */\n async readFirmwareVersion(): Promise<FirmwareVersion> {\n this.ensureConnected();\n\n console.log('Reading firmware version');\n\n // Send read firmware version command\n const cmd = buildReadFwVersionCommand();\n await this.connection!.writeCommand(cmd);\n\n // Read response\n const response = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_ACK\n );\n\n // Parse version (includes SHA hash)\n this._fwVersion = parseFirmwareVersion(response);\n\n console.log(\n `Firmware version: ${this._fwVersion.major}.${this._fwVersion.minor} ` +\n `(SHA: ${this._fwVersion.sha.substring(0, 8)}...)`\n );\n\n return this._fwVersion;\n }\n\n /**\n * Reboot the device.\n *\n * Sends a reboot command to the device, which will cause an immediate\n * system reset. The device will NOT send an ACK response - it simply\n * resets after a 100ms delay.\n *\n * Warning: The BLE connection will be forcibly terminated when the device\n * resets. This is expected behavior. The device will restart and begin\n * advertising again after the reset completes (typically within a few seconds).\n *\n * @throws {BLEConnectionError} If command cannot be sent\n */\n async reboot(): Promise<void> {\n this.ensureConnected();\n\n console.log('Sending reboot command to device');\n\n // Build and send reboot command\n const cmd = buildRebootCommand();\n await this.connection!.writeCommand(cmd);\n\n // Device will reset immediately - no ACK expected\n console.log('Reboot command sent - device will reset (connection will drop)');\n }\n\n /**\n * Write configuration to device.\n *\n * Serializes the GlobalConfig to TLV binary format and writes it\n * to the device using the WRITE_CONFIG (0x0041) command with\n * automatic chunking for large configs.\n *\n * @param config - GlobalConfig to write to device\n * @throws {Error} If config serialization fails or exceeds size limit\n * @throws {BLEConnectionError} If write fails\n * @throws {ProtocolError} If device returns error response\n *\n * @example\n * ```typescript\n * // Read current config\n * const config = device.config;\n *\n * // Modify config\n * config.displays[0].rotation = 180;\n *\n * // Write back to device\n * await device.writeConfig(config);\n *\n * // Reboot to apply changes\n * await device.reboot();\n * ```\n */\n async writeConfig(config: GlobalConfig): Promise<void> {\n this.ensureConnected();\n\n console.log('Writing config to device');\n\n // Validate critical packets are present\n if (!config.system) {\n console.warn('Config missing system packet - device may not boot correctly');\n }\n if (!config.displays || config.displays.length === 0) {\n throw new Error('Config must have at least one display');\n }\n\n // Warn about optional but important packets\n const missingPackets: string[] = [];\n if (!config.manufacturer) {\n missingPackets.push('manufacturer');\n }\n if (!config.power) {\n missingPackets.push('power');\n }\n\n if (missingPackets.length > 0) {\n console.warn(\n `Config missing optional packets: ${missingPackets.join(', ')}. ` +\n 'Device may lose these settings.'\n );\n }\n\n // Serialize config to binary\n const configData = serializeConfig(config);\n\n console.log(\n `Serialized config: ${configData.length} bytes ` +\n `(chunking ${configData.length > 200 ? 'required' : 'not needed'})`\n );\n\n // Build command with chunking\n const [firstCmd, chunkCmds] = buildWriteConfigCommand(configData);\n\n // Send first command\n console.debug(`Sending first config chunk (${firstCmd.length} bytes)`);\n await this.connection!.writeCommand(firstCmd);\n\n // Wait for ACK\n let response = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_ACK\n );\n validateAckResponse(response, CommandCode.WRITE_CONFIG);\n\n // Send remaining chunks if needed\n for (let i = 0; i < chunkCmds.length; i++) {\n const chunkCmd = chunkCmds[i];\n console.debug(\n `Sending config chunk ${i + 1}/${chunkCmds.length} (${chunkCmd.length} bytes)`\n );\n await this.connection!.writeCommand(chunkCmd);\n\n // Wait for ACK after each chunk\n response = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_ACK\n );\n validateAckResponse(response, CommandCode.WRITE_CONFIG_CHUNK);\n }\n\n console.log('Config written successfully');\n }\n\n /**\n * Upload image to device display.\n *\n * Automatically handles:\n * - Image resizing to display dimensions\n * - Dithering based on color scheme\n * - Encoding to device format\n * - Compression\n * - Direct write protocol\n *\n * @param imageData - Image as ImageData (from canvas or OffscreenCanvas)\n * @param options - Upload options\n * @throws {Error} If device not interrogated/configured\n * @throws {ProtocolError} If upload fails\n *\n * @example\n * ```typescript\n * const canvas = document.getElementById('myCanvas') as HTMLCanvasElement;\n * const ctx = canvas.getContext('2d')!;\n * const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);\n *\n * await device.uploadImage(imageData, {\n * refreshMode: RefreshMode.FULL,\n * ditherMode: DitherMode.BURKES,\n * compress: true\n * });\n * ```\n */\n async uploadImage(\n imageData: ImageData,\n options: {\n refreshMode?: RefreshMode;\n ditherMode?: DitherMode;\n compress?: boolean;\n } = {}\n ): Promise<void> {\n this.ensureConnected();\n this.ensureCapabilities();\n\n const refreshMode = options.refreshMode ?? RefreshMode.FULL;\n const ditherMode = options.ditherMode ?? DitherMode.BURKES;\n const compress = options.compress ?? true;\n\n console.log(\n `Uploading image (${this.width}x${this.height}, ${ColorScheme[this.colorScheme]})`\n );\n\n // Prepare image (resize, dither, encode)\n const encodedData = prepareImageForUpload(\n imageData,\n this.width,\n this.height,\n this.colorScheme,\n ditherMode\n );\n\n // Choose protocol based on compression and size\n let compressedData: Uint8Array | null = null;\n if (compress) {\n compressedData = compressImageData(encodedData, 6);\n\n if (compressedData.length < MAX_COMPRESSED_SIZE) {\n console.log(`Using compressed upload protocol (size: ${compressedData.length} bytes)`);\n await this.executeUpload({\n imageData: encodedData,\n refreshMode,\n useCompression: true,\n compressedData,\n uncompressedSize: encodedData.length,\n });\n } else {\n console.log(\n `Compressed size exceeds ${MAX_COMPRESSED_SIZE} bytes, using uncompressed protocol`\n );\n await this.executeUpload({ imageData: encodedData, refreshMode });\n }\n } else {\n console.log('Compression disabled, using uncompressed protocol');\n await this.executeUpload({ imageData: encodedData, refreshMode });\n }\n\n console.log('Image upload complete');\n }\n\n /**\n * Execute image upload using compressed or uncompressed protocol.\n */\n private async executeUpload(params: {\n imageData: Uint8Array;\n refreshMode: RefreshMode;\n useCompression?: boolean;\n compressedData?: Uint8Array;\n uncompressedSize?: number;\n }): Promise<void> {\n const {\n imageData,\n refreshMode,\n useCompression = false,\n compressedData,\n uncompressedSize,\n } = params;\n\n // 1. Send START command (different for each protocol)\n let startCmd: Uint8Array;\n let remainingCompressed: Uint8Array | null = null;\n\n if (useCompression && compressedData && uncompressedSize) {\n [startCmd, remainingCompressed] = buildDirectWriteStartCompressed(\n uncompressedSize,\n compressedData\n );\n } else {\n startCmd = buildDirectWriteStartUncompressed();\n }\n\n await this.connection!.writeCommand(startCmd);\n\n // 2. Wait for START ACK (identical for both protocols)\n let response = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_ACK\n );\n validateAckResponse(response, CommandCode.DIRECT_WRITE_START);\n\n // 3. Send data chunks\n let autoCompleted = false;\n if (useCompression && remainingCompressed && remainingCompressed.length > 0) {\n // Compressed upload: send remaining compressed data as chunks\n autoCompleted = await this.sendDataChunks(remainingCompressed);\n } else if (!useCompression) {\n // Uncompressed upload: send raw image data as chunks\n autoCompleted = await this.sendDataChunks(imageData);\n }\n\n // 4. Send END command if needed (identical for both protocols)\n if (!autoCompleted) {\n const endCmd = buildDirectWriteEndCommand(refreshMode);\n await this.connection!.writeCommand(endCmd);\n\n // Wait for END ACK (90s timeout for display refresh)\n response = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_REFRESH\n );\n validateAckResponse(response, CommandCode.DIRECT_WRITE_END);\n }\n }\n\n /**\n * Send image data chunks with ACK handling.\n *\n * Sends image data in chunks via 0x0071 DATA commands. Handles:\n * - Timeout recovery when firmware starts display refresh\n * - Auto-completion detection (firmware sends 0x0072 END early)\n * - Progress logging\n *\n * @param imageData - Data to send in chunks\n * @returns True if device auto-completed (sent 0x0072 END early), false if all chunks sent normally\n * @throws {ProtocolError} If unexpected response received\n * @throws {BLETimeoutError} If no response within timeout\n */\n private async sendDataChunks(imageData: Uint8Array): Promise<boolean> {\n let bytesSent = 0;\n let chunksSent = 0;\n\n while (bytesSent < imageData.length) {\n // Get next chunk\n const chunkStart = bytesSent;\n const chunkEnd = Math.min(chunkStart + CHUNK_SIZE, imageData.length);\n const chunkData = imageData.subarray(chunkStart, chunkEnd);\n\n // Send DATA command\n const dataCmd = buildDirectWriteDataCommand(chunkData);\n await this.connection!.writeCommand(dataCmd);\n\n bytesSent += chunkData.length;\n chunksSent++;\n\n // Wait for response after every chunk (PIPELINE_CHUNKS=1)\n let response: Uint8Array;\n try {\n response = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_ACK\n );\n } catch (error) {\n if (error instanceof BLETimeoutError) {\n // Timeout on response - firmware might be doing display refresh\n console.log(\n `No response after chunk ${chunksSent} ` +\n `(${((bytesSent / imageData.length) * 100).toFixed(1)}%), ` +\n 'waiting for device refresh...'\n );\n\n // Wait up to 90 seconds for the END response\n response = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_REFRESH\n );\n } else {\n throw error;\n }\n }\n\n // Check what response we got (firmware can send 0x0072 on ANY chunk, not just last!)\n const [command, isAck] = checkResponseType(response);\n\n if (command === CommandCode.DIRECT_WRITE_DATA) {\n // Normal DATA ACK (0x0071) - continue sending chunks\n // Continue to next iteration\n } else if (command === CommandCode.DIRECT_WRITE_END) {\n // Firmware auto-triggered END (0x0072) after receiving all data\n console.log(\n `Received END response after chunk ${chunksSent} - device auto-completed`\n );\n // Note: 0x0072 is sent AFTER display refresh completes\n // So we're already done - no need to send our own 0x0072 END command!\n return true; // Auto-completed\n } else {\n // Unexpected response\n throw new ProtocolError(\n `Unexpected response: ${CommandCode[command]} (0x${command.toString(16).padStart(4, '0')})`\n );\n }\n\n // Log progress every 50 chunks to reduce spam\n if (chunksSent % 50 === 0 || bytesSent >= imageData.length) {\n console.debug(\n `Sent ${bytesSent}/${imageData.length} bytes ` +\n `(${((bytesSent / imageData.length) * 100).toFixed(1)}%)`\n );\n }\n }\n\n console.debug(`All data chunks sent (${chunksSent} chunks total)`);\n return false; // Normal completion, caller should send END\n }\n\n /**\n * Extract DeviceCapabilities from GlobalConfig.\n */\n private extractCapabilitiesFromConfig(): DeviceCapabilities {\n if (!this._config) {\n throw new Error('No config available');\n }\n\n if (!this._config.displays || this._config.displays.length === 0) {\n throw new Error('Config has no display information');\n }\n\n const display = this._config.displays[0]; // Primary display\n\n return {\n width: display.pixelWidth,\n height: display.pixelHeight,\n colorScheme: display.colorScheme as ColorScheme,\n rotation: display.rotation,\n };\n }\n\n /**\n * Ensure device capabilities are available.\n */\n private ensureCapabilities(): DeviceCapabilities {\n if (!this._capabilities) {\n throw new Error(\n 'Device capabilities unknown - interrogate first or provide config/capabilities'\n );\n }\n return this._capabilities;\n }\n\n /**\n * Ensure device is connected.\n */\n private ensureConnected(): void {\n if (!this.connection || !this.isConnected) {\n throw new BLEConnectionError('Not connected to device');\n }\n }\n}","/**\n * Image encoding for e-paper displays.\n */\n\nimport {\n ColorScheme,\n DitherMode,\n ditherImage,\n type PaletteImageBuffer,\n} from '@opendisplay/epaper-dithering';\nimport { ImageEncodingError } from '../exceptions';\nimport { encodeBitplanes } from './bitplanes';\n\n/**\n * Encode palette image to display format based on color scheme.\n *\n * @param paletteImage - Dithered palette image\n * @param colorScheme - Display color scheme\n * @returns Encoded image bytes\n * @throws {ImageEncodingError} For unsupported color schemes\n */\nexport function encodeImage(\n paletteImage: PaletteImageBuffer,\n colorScheme: ColorScheme\n): Uint8Array {\n switch (colorScheme) {\n case ColorScheme.MONO:\n return encode1bpp(paletteImage);\n\n case ColorScheme.BWR:\n case ColorScheme.BWY:\n throw new ImageEncodingError(\n `Color scheme ${ColorScheme[colorScheme]} requires bitplane encoding, ` +\n 'use encodeBitplanes() instead'\n );\n\n case ColorScheme.BWRY:\n return encode2bpp(paletteImage);\n\n case ColorScheme.BWGBRY:\n // 6-color Spectra 6 display uses 4bpp with special firmware mapping\n return encode4bpp(paletteImage, true);\n\n case ColorScheme.GRAYSCALE_4:\n return encode2bpp(paletteImage);\n\n default:\n throw new ImageEncodingError(\n `Unsupported color scheme: ${ColorScheme[colorScheme]}`\n );\n }\n}\n\n/**\n * Encode image to 1-bit-per-pixel format (monochrome).\n *\n * Format: 8 pixels per byte, MSB first\n * Palette index 0 = black (0), index 1 = white (1)\n *\n * @param paletteImage - Palette image\n * @returns Encoded bytes\n */\nexport function encode1bpp(paletteImage: PaletteImageBuffer): Uint8Array {\n const { width, height, indices: pixels } = paletteImage;\n\n // Calculate output size (round up to byte boundary)\n const bytesPerRow = Math.ceil(width / 8);\n const output = new Uint8Array(bytesPerRow * height);\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const byteIdx = y * bytesPerRow + Math.floor(x / 8);\n const bitIdx = 7 - (x % 8); // MSB first\n\n if (pixels[y * width + x] > 0) {\n // Non-zero palette index = white\n output[byteIdx] |= 1 << bitIdx;\n }\n }\n }\n\n return output;\n}\n\n/**\n * Encode image to 2-bits-per-pixel format (4 colors).\n *\n * Format: 4 pixels per byte, MSB first\n * Each 2-bit value maps to palette index (0-3)\n *\n * @param paletteImage - Palette image\n * @returns Encoded bytes\n */\nexport function encode2bpp(paletteImage: PaletteImageBuffer): Uint8Array {\n const { width, height, indices: pixels } = paletteImage;\n\n // Calculate output size (round up to 4-pixel boundary)\n const bytesPerRow = Math.ceil(width / 4);\n const output = new Uint8Array(bytesPerRow * height);\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const byteIdx = y * bytesPerRow + Math.floor(x / 4);\n const pixelInByte = x % 4;\n const bitShift = (3 - pixelInByte) * 2; // MSB first\n\n const paletteIdx = pixels[y * width + x] & 0x03; // 2-bit value\n output[byteIdx] |= paletteIdx << bitShift;\n }\n }\n\n return output;\n}\n\n/**\n * Encode image to 4-bits-per-pixel format (16 colors).\n *\n * Format: 2 pixels per byte, MSB first\n * Each 4-bit value maps to palette index (0-15)\n *\n * @param paletteImage - Palette image\n * @param bwgbryMapping - If true, remap palette indices for BWGBRY firmware\n * (0→0, 1→1, 2→2, 3→3, 4→5, 5→6)\n * @returns Encoded bytes\n */\nexport function encode4bpp(\n paletteImage: PaletteImageBuffer,\n bwgbryMapping: boolean = false\n): Uint8Array {\n const { width, height, indices: pixels } = paletteImage;\n\n // BWGBRY firmware color mapping (Spectra 6 display)\n // Palette indices to firmware values: 0→0, 1→1, 2→2, 3→3, 4→5, 5→6\n const BWGBRY_MAP: Record<number, number> = { 0: 0, 1: 1, 2: 2, 3: 3, 4: 5, 5: 6 };\n\n // Calculate output size (round up to 2-pixel boundary)\n const bytesPerRow = Math.ceil(width / 2);\n const output = new Uint8Array(bytesPerRow * height);\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const byteIdx = y * bytesPerRow + Math.floor(x / 2);\n const pixelInByte = x % 2;\n\n let paletteIdx = pixels[y * width + x] & 0x0f; // 4-bit value\n\n // Apply BWGBRY mapping if needed\n if (bwgbryMapping && paletteIdx in BWGBRY_MAP) {\n paletteIdx = BWGBRY_MAP[paletteIdx];\n }\n\n const bitShift = (1 - pixelInByte) * 4; // MSB first\n output[byteIdx] |= paletteIdx << bitShift;\n }\n }\n\n return output;\n}\n\n/**\n * Prepare image for upload: resize, dither, and encode.\n *\n * @param imageData - Input image as ImageData\n * @param targetWidth - Target display width\n * @param targetHeight - Target display height\n * @param colorScheme - Display color scheme\n * @param ditherMode - Dithering algorithm to use\n * @returns Encoded image data ready for upload\n */\nexport function prepareImageForUpload(\n imageData: ImageData,\n targetWidth: number,\n targetHeight: number,\n colorScheme: ColorScheme,\n ditherMode: DitherMode = DitherMode.BURKES\n): Uint8Array {\n // Resize if needed\n let resizedImageData = imageData;\n if (imageData.width !== targetWidth || imageData.height !== targetHeight) {\n console.warn(\n `Resizing image from ${imageData.width}x${imageData.height} to ${targetWidth}x${targetHeight}`\n );\n resizedImageData = resizeImageData(imageData, targetWidth, targetHeight);\n }\n\n // Apply dithering\n const paletteImage = ditherImage(resizedImageData, colorScheme, ditherMode);\n\n // Encode based on color scheme\n if (colorScheme === ColorScheme.BWR || colorScheme === ColorScheme.BWY) {\n // For BWR/BWY, encode to bitplanes and concatenate\n const [plane1, plane2] = encodeBitplanes(paletteImage, colorScheme);\n const result = new Uint8Array(plane1.length + plane2.length);\n result.set(plane1, 0);\n result.set(plane2, plane1.length);\n return result;\n } else {\n return encodeImage(paletteImage, colorScheme);\n }\n}\n\n/**\n * Resize ImageData to target dimensions using canvas.\n *\n * @param imageData - Source image data\n * @param targetWidth - Target width\n * @param targetHeight - Target height\n * @returns Resized image data\n */\nfunction resizeImageData(\n imageData: ImageData,\n targetWidth: number,\n targetHeight: number\n): ImageData {\n // Create offscreen canvas for resizing\n const canvas = new OffscreenCanvas(imageData.width, imageData.height);\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n throw new ImageEncodingError('Failed to get canvas context');\n }\n\n // Draw source image\n ctx.putImageData(imageData, 0, 0);\n\n // Create target canvas\n const targetCanvas = new OffscreenCanvas(targetWidth, targetHeight);\n const targetCtx = targetCanvas.getContext('2d');\n if (!targetCtx) {\n throw new ImageEncodingError('Failed to get target canvas context');\n }\n\n // Resize (browser handles interpolation)\n targetCtx.drawImage(canvas, 0, 0, targetWidth, targetHeight);\n\n return targetCtx.getImageData(0, 0, targetWidth, targetHeight);\n}","/**\n * Exception classes for OpenDisplay library.\n */\n\nexport class OpenDisplayError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'OpenDisplayError';\n }\n}\n\nexport class BLEConnectionError extends OpenDisplayError {\n constructor(message: string) {\n super(message);\n this.name = 'BLEConnectionError';\n }\n}\n\nexport class BLETimeoutError extends OpenDisplayError {\n constructor(message: string) {\n super(message);\n this.name = 'BLETimeoutError';\n }\n}\n\nexport class ProtocolError extends OpenDisplayError {\n constructor(message: string) {\n super(message);\n this.name = 'ProtocolError';\n }\n}\n\nexport class ConfigParseError extends ProtocolError {\n constructor(message: string) {\n super(message);\n this.name = 'ConfigParseError';\n }\n}\n\nexport class InvalidResponseError extends ProtocolError {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidResponseError';\n }\n}\n\nexport class ImageEncodingError extends OpenDisplayError {\n constructor(message: string) {\n super(message);\n this.name = 'ImageEncodingError';\n }\n}","/**\n * Bitplane encoding for multi-color e-paper displays.\n */\n\nimport { ColorScheme, PaletteImageBuffer } from '@opendisplay/epaper-dithering';\nimport { ImageEncodingError } from '../exceptions';\n\n/**\n * Encode image to bitplane format for BWR/BWY displays.\n *\n * BWR/BWY displays use two bitplanes:\n * - Plane 1 (BW): Black/White layer\n * - Plane 2 (R/Y): Red/Yellow accent color layer\n *\n * Palette mapping:\n * - Index 0 = Black -> BW=0, R/Y=0\n * - Index 1 = White -> BW=1, R/Y=0\n * - Index 2 = Red/Yellow -> BW=0, R/Y=1\n *\n * @param paletteImage - Dithered palette image from epaper-dithering\n * @param colorScheme - Must be BWR or BWY\n * @returns Tuple of [plane1Bytes, plane2Bytes]\n * @throws {ImageEncodingError} If color scheme is not BWR or BWY\n */\nexport function encodeBitplanes(\n paletteImage: PaletteImageBuffer,\n colorScheme: ColorScheme\n): [Uint8Array, Uint8Array] {\n if (colorScheme !== ColorScheme.BWR && colorScheme !== ColorScheme.BWY) {\n throw new ImageEncodingError(\n `Bitplane encoding only supports BWR/BWY, got ${ColorScheme[colorScheme]}`\n );\n }\n\n const { width, height, indices: pixels } = paletteImage;\n\n // Calculate output size (1bpp, 8 pixels per byte)\n const bytesPerRow = Math.ceil(width / 8);\n const plane1 = new Uint8Array(bytesPerRow * height); // BW plane\n const plane2 = new Uint8Array(bytesPerRow * height); // R/Y plane\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const byteIdx = y * bytesPerRow + Math.floor(x / 8);\n const bitIdx = 7 - (x % 8); // MSB first\n\n const paletteIdx = pixels[y * width + x];\n\n if (paletteIdx === 1) {\n // White - set BW plane\n plane1[byteIdx] |= 1 << bitIdx;\n } else if (paletteIdx === 2) {\n // Red/Yellow - set R/Y plane\n plane2[byteIdx] |= 1 << bitIdx;\n }\n // else: paletteIdx === 0 (black) - both planes stay 0\n }\n }\n\n return [plane1, plane2];\n}","/**\n * Image compression for BLE transfer using pako (zlib).\n */\n\nimport pako from 'pako';\n\n/**\n * Compress image data using zlib.\n *\n * @param data - Raw image data\n * @param level - Compression level (0-9, default: 6)\n * - 0 = no compression\n * - 1 = fastest\n * - 6 = default balance\n * - 9 = best compression\n * @returns Compressed data\n */\nexport function compressImageData(\n data: Uint8Array,\n level: number = 6\n): Uint8Array {\n if (level === 0) {\n return data;\n }\n\n const compressed = pako.deflate(data, { level: level as pako.DeflateOptions['level'] });\n\n const ratio = data.length > 0 ? (compressed.length / data.length) * 100 : 0;\n console.debug(\n `Compressed ${data.length} bytes -> ${compressed.length} bytes (${ratio.toFixed(1)}%)`\n );\n\n return compressed;\n}\n\n/**\n * Decompress zlib-compressed image data.\n *\n * @param data - Compressed data\n * @returns Decompressed data\n * @throws {Error} If decompression fails\n */\nexport function decompressImageData(data: Uint8Array): Uint8Array {\n return pako.inflate(data);\n}","/**\n * Enums for OpenDisplay device configuration.\n */\n\n/**\n * Display refresh modes.\n */\nexport enum RefreshMode {\n FULL = 0,\n FAST = 1,\n PARTIAL = 2,\n PARTIAL2 = 3,\n}\n\n/**\n * Microcontroller IC types.\n */\nexport enum ICType {\n NRF52840 = 1,\n ESP32_S3 = 2,\n ESP32_C3 = 3,\n ESP32_C6 = 4,\n}\n\n/**\n * Power source types.\n */\nexport enum PowerMode {\n BATTERY = 1,\n USB = 2,\n SOLAR = 3,\n}\n\n/**\n * Data bus types for sensors.\n */\nexport enum BusType {\n I2C = 0,\n SPI = 1,\n}\n\n/**\n * Display rotation angles in degrees.\n */\nexport enum Rotation {\n ROTATE_0 = 0,\n ROTATE_90 = 90,\n ROTATE_180 = 180,\n ROTATE_270 = 270,\n}","/**\n * BLE protocol constants for OpenDisplay devices.\n */\n\nexport const SERVICE_UUID = '00002446-0000-1000-8000-00805f9b34fb';\nexport const MANUFACTURER_ID = 0x2446;\nexport const RESPONSE_HIGH_BIT_FLAG = 0x8000;\n\n// Chunking constants\nexport const CHUNK_SIZE = 230; // Maximum data bytes per chunk\nexport const CONFIG_CHUNK_SIZE = 200; // Maximum config chunk size\nexport const PIPELINE_CHUNKS = 1; // Wait for ACK after each chunk\n\n// Upload protocol constants\nexport const MAX_COMPRESSED_SIZE = 50 * 1024; // 50KB firmware buffer limit\nexport const MAX_START_PAYLOAD = 200; // Maximum bytes in START command\n\n/**\n * BLE command codes for OpenDisplay protocol.\n */\nexport enum CommandCode {\n // Configuration commands\n READ_CONFIG = 0x0040,\n WRITE_CONFIG = 0x0041,\n WRITE_CONFIG_CHUNK = 0x0042,\n\n // Firmware commands\n READ_FW_VERSION = 0x0043,\n REBOOT = 0x000f,\n\n // Image upload commands (direct write mode)\n DIRECT_WRITE_START = 0x0070,\n DIRECT_WRITE_DATA = 0x0071,\n DIRECT_WRITE_END = 0x0072,\n}","/**\n * BLE protocol command builders for OpenDisplay devices.\n */\n\nimport {\n CommandCode,\n CHUNK_SIZE,\n CONFIG_CHUNK_SIZE,\n MAX_START_PAYLOAD,\n} from './constants';\n\n/**\n * Build command to read device TLV configuration.\n *\n * @returns Command bytes: 0x0040 (2 bytes, big-endian)\n */\nexport function buildReadConfigCommand(): Uint8Array {\n const buffer = new ArrayBuffer(2);\n const view = new DataView(buffer);\n view.setUint16(0, CommandCode.READ_CONFIG, false); // false = big-endian\n return new Uint8Array(buffer);\n}\n\n/**\n * Build command to read firmware version.\n *\n * @returns Command bytes: 0x0043 (2 bytes, big-endian)\n */\nexport function buildReadFwVersionCommand(): Uint8Array {\n const buffer = new ArrayBuffer(2);\n const view = new DataView(buffer);\n view.setUint16(0, CommandCode.READ_FW_VERSION, false);\n return new Uint8Array(buffer);\n}\n\n/**\n * Build command to reboot device.\n *\n * The device will perform an immediate system reset and will NOT send\n * an ACK response. The BLE connection will drop when the device resets.\n *\n * @returns Command bytes: 0x000F (2 bytes, big-endian)\n */\nexport function buildRebootCommand(): Uint8Array {\n const buffer = new ArrayBuffer(2);\n const view = new DataView(buffer);\n view.setUint16(0, CommandCode.REBOOT, false);\n return new Uint8Array(buffer);\n}\n\n/**\n * Build START command for compressed upload with chunking.\n *\n * To prevent BLE MTU issues, the START command is limited to MAX_START_PAYLOAD\n * bytes. For large compressed payloads, this returns:\n * - START command with header + first chunk of compressed data\n * - Remaining compressed data (to be sent via DATA chunks)\n *\n * @param uncompressedSize - Original uncompressed image size in bytes\n * @param compressedData - Complete compressed image data\n * @returns Tuple of [startCommand, remainingData]:\n * - startCommand: 0x0070 + uncompressed_size (4 bytes LE) + first chunk\n * - remainingData: Compressed data not included in START (empty if all fits)\n *\n * Format of START command:\n * [cmd:2][uncompressed_size:4][compressed_data:up to 194 bytes]\n * - cmd: 0x0070 (big-endian)\n * - uncompressed_size: Original size before compression (little-endian uint32)\n * - compressed_data: First chunk of compressed data\n */\nexport function buildDirectWriteStartCompressed(\n uncompressedSize: number,\n compressedData: Uint8Array\n): [Uint8Array, Uint8Array] {\n // Calculate max compressed data that fits in START\n // MAX_START_PAYLOAD = 200 total bytes\n // Header uses: 2 (cmd) + 4 (size) = 6 bytes\n // Remaining for compressed data: 200 - 6 = 194 bytes\n const maxDataInStart = MAX_START_PAYLOAD - 6; // 194 bytes\n\n const headerSize = 6;\n const totalSize =\n compressedData.length <= maxDataInStart\n ? headerSize + compressedData.length\n : MAX_START_PAYLOAD;\n\n const buffer = new ArrayBuffer(totalSize);\n const view = new DataView(buffer);\n\n // Command code (big-endian)\n view.setUint16(0, CommandCode.DIRECT_WRITE_START, false);\n\n // Uncompressed size (little-endian)\n view.setUint32(2, uncompressedSize, true);\n\n // Compressed data\n const startCommand = new Uint8Array(buffer);\n const dataLength = Math.min(compressedData.length, maxDataInStart);\n startCommand.set(compressedData.subarray(0, dataLength), 6);\n\n // Remaining data\n const remainingData =\n compressedData.length <= maxDataInStart\n ? new Uint8Array(0)\n : compressedData.subarray(maxDataInStart);\n\n return [startCommand, remainingData];\n}\n\n/**\n * Build START command for uncompressed upload protocol.\n *\n * This protocol sends NO data in START - all data follows via 0x0071 chunks.\n *\n * @returns Command bytes: 0x0070 (just the command, no data!)\n *\n * Format:\n * [cmd:2]\n * - cmd: 0x0070 (big-endian)\n * - NO size, NO data - everything sent via 0x0071 DATA chunks\n */\nexport function buildDirectWriteStartUncompressed(): Uint8Array {\n const buffer = new ArrayBuffer(2);\n const view = new DataView(buffer);\n view.setUint16(0, CommandCode.DIRECT_WRITE_START, false);\n return new Uint8Array(buffer);\n}\n\n/**\n * Build command to send image data chunk.\n *\n * @param chunkData - Image data chunk (max CHUNK_SIZE bytes)\n * @returns Command bytes: 0x0071 + chunk_data\n *\n * Format:\n * [cmd:2][data:230]\n * - cmd: 0x0071 (big-endian)\n * - data: Image data chunk\n */\nexport function buildDirectWriteDataCommand(chunkData: Uint8Array): Uint8Array {\n if (chunkData.length > CHUNK_SIZE) {\n throw new Error(\n `Chunk size ${chunkData.length} exceeds maximum ${CHUNK_SIZE}`\n );\n }\n\n const buffer = new ArrayBuffer(2 + chunkData.length);\n const view = new DataView(buffer);\n view.setUint16(0, CommandCode.DIRECT_WRITE_DATA, false);\n\n const result = new Uint8Array(buffer);\n result.set(chunkData, 2);\n\n return result;\n}\n\n/**\n * Build command to end image transfer and refresh display.\n *\n * @param refreshMode - Display refresh mode (0 = FULL, 1 = FAST/PARTIAL)\n * @returns Command bytes: 0x0072 + refresh_mode\n *\n * Format:\n * [cmd:2][refresh:1]\n * - cmd: 0x0072 (big-endian)\n * - refresh: Refresh mode (0=full, 1=fast)\n */\nexport function buildDirectWriteEndCommand(refreshMode: number = 0): Uint8Array {\n const buffer = new ArrayBuffer(3);\n const view = new DataView(buffer);\n view.setUint16(0, CommandCode.DIRECT_WRITE_END, false);\n view.setUint8(2, refreshMode);\n return new Uint8Array(buffer);\n}\n\n/**\n * Build WRITE_CONFIG command with chunking support.\n *\n * Protocol:\n * - Single chunk (≤200 bytes): [0x00][0x41][config_data]\n * - Multi-chunk (>200 bytes):\n * - First: [0x00][0x41][total_size:2LE][first_198_bytes]\n * - Rest: [0x00][0x42][chunk_data] (up to 200 bytes each)\n *\n * @param configData - Complete serialized config data\n * @returns Tuple of [firstCommand, remainingChunks]:\n * - firstCommand: 0x0041 command with first chunk\n * - remainingChunks: Array of 0x0042 commands for subsequent chunks\n */\nexport function buildWriteConfigCommand(\n configData: Uint8Array\n): [Uint8Array, Uint8Array[]] {\n const configLen = configData.length;\n\n // Single chunk mode (≤200 bytes)\n if (configLen <= CONFIG_CHUNK_SIZE) {\n const buffer = new ArrayBuffer(2 + configLen);\n const view = new DataView(buffer);\n view.setUint16(0, CommandCode.WRITE_CONFIG, false);\n\n const result = new Uint8Array(buffer);\n result.set(configData, 2);\n\n return [result, []];\n }\n\n // Multi-chunk mode (>200 bytes)\n // First chunk: [cmd][total_size:2LE][first_198_bytes]\n const firstChunkDataSize = CONFIG_CHUNK_SIZE - 2; // 198 bytes\n const firstBuffer = new ArrayBuffer(2 + 2 + firstChunkDataSize);\n const firstView = new DataView(firstBuffer);\n\n firstView.setUint16(0, CommandCode.WRITE_CONFIG, false);\n firstView.setUint16(2, configLen, true); // total size, little-endian\n\n const firstCommand = new Uint8Array(firstBuffer);\n firstCommand.set(configData.subarray(0, firstChunkDataSize), 4);\n\n // Remaining chunks: [cmd][chunk_data] (up to 200 bytes each)\n const chunks: Uint8Array[] = [];\n let offset = firstChunkDataSize;\n\n while (offset < configLen) {\n const chunkSize = Math.min(CONFIG_CHUNK_SIZE, configLen - offset);\n const buffer = new ArrayBuffer(2 + chunkSize);\n const view = new DataView(buffer);\n view.setUint16(0, CommandCode.WRITE_CONFIG_CHUNK, false);\n\n const chunk = new Uint8Array(buffer);\n chunk.set(configData.subarray(offset, offset + chunkSize), 2);\n chunks.push(chunk);\n\n offset += chunkSize;\n }\n\n return [firstCommand, chunks];\n}","/**\n * BLE response validation and parsing.\n */\n\nimport { InvalidResponseError } from '../exceptions';\nimport { FirmwareVersion } from '../models/firmware';\nimport { CommandCode, RESPONSE_HIGH_BIT_FLAG } from './constants';\n\n/**\n * Extract 2-byte big-endian command code from response data.\n *\n * @param data - Response data from device\n * @param offset - Byte offset to read from (default: 0)\n * @returns Command code as integer\n */\nexport function unpackCommandCode(data: Uint8Array, offset: number = 0): number {\n const view = new DataView(data.buffer, data.byteOffset + offset, 2);\n return view.getUint16(0, false); // false = big-endian\n}\n\n/**\n * Strip command echo from response data.\n *\n * Firmware echoes commands in responses, sometimes with high bit set.\n * This function removes the 2-byte echo if present.\n *\n * @param data - Response data from device\n * @param expectedCmd - Expected command echo\n * @returns Data with echo stripped (if present), otherwise original data\n */\nexport function stripCommandEcho(\n data: Uint8Array,\n expectedCmd: CommandCode\n): Uint8Array {\n if (data.length >= 2) {\n const echo = unpackCommandCode(data);\n if (echo === expectedCmd || echo === (expectedCmd | RESPONSE_HIGH_BIT_FLAG)) {\n return data.subarray(2);\n }\n }\n return data;\n}\n\n/**\n * Check response type and whether it's an ACK.\n *\n * @param response - Raw response data from device\n * @returns Tuple of [commandCode, isAck]\n * - commandCode: The command code (without high bit)\n * - isAck: True if response has high bit set (RESPONSE_HIGH_BIT_FLAG)\n */\nexport function checkResponseType(\n response: Uint8Array\n): [CommandCode, boolean] {\n const code = unpackCommandCode(response);\n const isAck = Boolean(code & RESPONSE_HIGH_BIT_FLAG);\n const command = (code & ~RESPONSE_HIGH_BIT_FLAG) as CommandCode;\n return [command, isAck];\n}\n\n/**\n * Validate ACK response from device.\n *\n * ACK responses echo the command code (sometimes with high bit set).\n *\n * @param data - Raw response data\n * @param expectedCommand - Command code that was sent\n * @throws {InvalidResponseError} If response invalid or doesn't match command\n */\nexport function validateAckResponse(\n data: Uint8Array,\n expectedCommand: number\n): void {\n if (data.length < 2) {\n throw new InvalidResponseError(\n `ACK too short: ${data.length} bytes (need at least 2)`\n );\n }\n\n const responseCode = unpackCommandCode(data);\n\n // Response can be exact echo or with high bit set (RESPONSE_HIGH_BIT_FLAG | cmd)\n const validResponses = new Set([\n expectedCommand,\n expectedCommand | RESPONSE_HIGH_BIT_FLAG,\n ]);\n\n if (!validResponses.has(responseCode)) {\n throw new InvalidResponseError(\n `ACK mismatch: expected 0x${expectedCommand.toString(16).padStart(4, '0')}, ` +\n `got 0x${responseCode.toString(16).padStart(4, '0')}`\n );\n }\n}\n\n/**\n * Parse firmware version response.\n *\n * Format: [echo:2][major:1][minor:1][shaLength:1][sha:variable]\n *\n * @param data - Raw firmware version response\n * @returns FirmwareVersion object with major, minor, and sha fields\n * @throws {InvalidResponseError} If response format invalid\n */\nexport function parseFirmwareVersion(data: Uint8Array): FirmwareVersion {\n if (data.length < 5) {\n throw new InvalidResponseError(\n `Firmware version response too short: ${data.length} bytes (need at least 5)`\n );\n }\n\n // Validate echo\n const echo = unpackCommandCode(data);\n if (\n echo !== 0x0043 &&\n echo !== (0x0043 | RESPONSE_HIGH_BIT_FLAG)\n ) {\n throw new InvalidResponseError(\n `Firmware version echo mismatch: expected 0x0043, ` +\n `got 0x${echo.toString(16).padStart(4, '0')}`\n );\n }\n\n const major = data[2];\n const minor = data[3];\n const shaLength = data[4];\n\n // SHA hash is always present in firmware responses\n if (shaLength === 0) {\n throw new InvalidResponseError(\n 'Firmware version missing SHA hash (shaLength is 0)'\n );\n }\n\n // Validate sufficient bytes for SHA\n const expectedTotalLength = 5 + shaLength;\n if (data.length < expectedTotalLength) {\n throw new InvalidResponseError(\n `Firmware version response incomplete: expected ${expectedTotalLength} bytes ` +\n `(5 header + ${shaLength} SHA), got ${data.length}`\n );\n }\n\n // Extract SHA bytes and decode as ASCII string\n const shaBytes = data.subarray(5, 5 + shaLength);\n const textDecoder = new TextDecoder('ascii');\n let sha: string;\n\n try {\n sha = textDecoder.decode(shaBytes);\n } catch (e) {\n throw new InvalidResponseError(\n `Invalid SHA hash encoding (expected ASCII): ${e}`\n );\n }\n\n return {\n major,\n minor,\n sha,\n };\n}","/**\n * TLV configuration data structures.\n *\n * These interfaces map directly to the firmware's TLV packet structures.\n * Reference: OpenDisplayFirmware/src/structs.h\n */\n\nimport { ColorScheme } from '@opendisplay/epaper-dithering';\nimport { BusType, ICType, PowerMode, Rotation } from './enums.js';\n\n/**\n * System configuration (TLV packet type 0x01).\n *\n * Size: 22 bytes (packed struct from firmware)\n */\nexport interface SystemConfig {\n /** uint16 - IC type identifier */\n icType: number;\n /** uint8 bitfield - Supported communication modes */\n communicationModes: number;\n /** uint8 bitfield - Device capability flags */\n deviceFlags: number;\n /** uint8 - External power pin (0xFF = none) */\n pwrPin: number;\n /** 17 bytes - Reserved for future use */\n reserved: Uint8Array;\n}\n\n/**\n * Helper functions for SystemConfig\n */\nexport namespace SystemConfig {\n export const SIZE = 22;\n\n /**\n * Check if device has external power management pin (DEVICE_FLAG_PWR_PIN).\n */\n export function hasPwrPin(config: SystemConfig): boolean {\n return !!(config.deviceFlags & 0x01);\n }\n\n /**\n * Check if xiaoinit() should be called after config load - nRF52840 only (DEVICE_FLAG_XIAOINIT).\n */\n export function needsXiaoinit(config: SystemConfig): boolean {\n return !!(config.deviceFlags & 0x02);\n }\n\n /**\n * Get IC type as enum, or raw int if unknown.\n */\n export function icTypeEnum(config: SystemConfig): ICType | number {\n if (Object.values(ICType).includes(config.icType)) {\n return config.icType as ICType;\n }\n return config.icType;\n }\n\n /**\n * Parse SystemConfig from TLV packet data.\n */\n export function fromBytes(data: Uint8Array): SystemConfig {\n if (data.length < SIZE) {\n throw new Error(`Invalid SystemConfig size: ${data.length} < ${SIZE}`);\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return {\n icType: view.getUint16(0, true), // little-endian\n communicationModes: view.getUint8(2),\n deviceFlags: view.getUint8(3),\n pwrPin: view.getUint8(4),\n reserved: data.slice(5, 22),\n };\n }\n}\n\n/**\n * Manufacturer data (TLV packet type 0x02).\n *\n * Size: 22 bytes (packed struct from firmware)\n */\nexport interface ManufacturerData {\n /** uint16 - Manufacturer identifier */\n manufacturerId: number;\n /** uint8 - Board type */\n boardType: number;\n /** uint8 - Board revision */\n boardRevision: number;\n /** 18 bytes - Reserved for future use */\n reserved: Uint8Array;\n}\n\n/**\n * Helper functions for ManufacturerData\n */\nexport namespace ManufacturerData {\n export const SIZE = 22;\n\n /**\n * Parse ManufacturerData from TLV packet data.\n */\n export function fromBytes(data: Uint8Array): ManufacturerData {\n if (data.length < SIZE) {\n throw new Error(`Invalid ManufacturerData size: ${data.length} < ${SIZE}`);\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return {\n manufacturerId: view.getUint16(0, true), // little-endian\n boardType: view.getUint8(2),\n boardRevision: view.getUint8(3),\n reserved: data.slice(4, 22),\n };\n }\n}\n\n/**\n * Power configuration (TLV packet type 0x04).\n *\n * Size: 30 bytes (packed struct from firmware - corrected from 32)\n */\nexport interface PowerOption {\n /** uint8 - Power mode identifier */\n powerMode: number;\n /** 3 bytes (24-bit value) - Battery capacity in mAh */\n batteryCapacityMah: number;\n /** uint16 - Sleep timeout in milliseconds */\n sleepTimeoutMs: number;\n /** uint8 - TX power level */\n txPower: number;\n /** uint8 bitfield - Sleep configuration flags */\n sleepFlags: number;\n /** uint8 - Battery sense pin (0xFF = none) */\n batterySensePin: number;\n /** uint8 - Battery sense enable pin (0xFF = none) */\n batterySenseEnablePin: number;\n /** uint8 bitfield - Battery sense flags */\n batterySenseFlags: number;\n /** uint8 - Capacity estimator type */\n capacityEstimator: number;\n /** uint16 - Voltage scaling factor */\n voltageScalingFactor: number;\n /** uint32 - Deep sleep current in microamps */\n deepSleepCurrentUa: number;\n /** uint16 - Deep sleep time in seconds */\n deepSleepTimeSeconds: number;\n /** 10 bytes - Reserved for future use */\n reserved: Uint8Array;\n}\n\n/**\n * Helper functions for PowerOption\n */\nexport namespace PowerOption {\n export const SIZE = 30;\n\n /**\n * Get battery capacity in mAh (already converted from 3-byte array).\n */\n export function batteryMah(config: PowerOption): number {\n return config.batteryCapacityMah;\n }\n\n /**\n * Get power mode as enum, or raw int if unknown.\n */\n export function powerModeEnum(config: PowerOption): PowerMode | number {\n if (Object.values(PowerMode).includes(config.powerMode)) {\n return config.powerMode as PowerMode;\n }\n return config.powerMode;\n }\n\n /**\n * Parse PowerOption from TLV packet data.\n */\n export function fromBytes(data: Uint8Array): PowerOption {\n if (data.length < SIZE) {\n throw new Error(`Invalid PowerOption size: ${data.length} < ${SIZE}`);\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n // Parse 3-byte battery capacity (little-endian)\n const batteryMah = data[1] | (data[2] << 8) | (data[3] << 16);\n\n return {\n powerMode: view.getUint8(0),\n batteryCapacityMah: batteryMah,\n sleepTimeoutMs: view.getUint16(4, true), // little-endian\n txPower: view.getInt8(6),\n sleepFlags: view.getUint8(7),\n batterySensePin: view.getUint8(8),\n batterySenseEnablePin: view.getUint8(9),\n batterySenseFlags: view.getUint8(10),\n capacityEstimator: view.getUint8(11),\n voltageScalingFactor: view.getUint16(12, true), // little-endian\n deepSleepCurrentUa: view.getUint32(14, true), // little-endian\n deepSleepTimeSeconds: view.getUint16(18, true), // little-endian\n reserved: data.slice(20, 30),\n };\n }\n}\n\n/**\n * Display configuration (TLV packet type 0x20, repeatable max 4).\n *\n * Size: 46 bytes (packed struct from firmware - corrected from 66)\n */\nexport interface DisplayConfig {\n /** uint8 - Display instance number (0-3) */\n instanceNumber: number;\n /** uint8 - Display technology type */\n displayTechnology: number;\n /** uint16 - Panel IC type */\n panelIcType: number;\n /** uint16 - Display width in pixels */\n pixelWidth: number;\n /** uint16 - Display height in pixels */\n pixelHeight: number;\n /** uint16 - Active area width in millimeters */\n activeWidthMm: number;\n /** uint16 - Active area height in millimeters */\n activeHeightMm: number;\n /** uint16 - Tag type (legacy) */\n tagType: number;\n /** uint8 - Display rotation in degrees */\n rotation: number;\n /** uint8 - Reset pin (0xFF = none) */\n resetPin: number;\n /** uint8 - Busy pin (0xFF = none) */\n busyPin: number;\n /** uint8 - Data/Command pin (0xFF = none) */\n dcPin: number;\n /** uint8 - Chip select pin (0xFF = none) */\n csPin: number;\n /** uint8 - Data pin */\n dataPin: number;\n /** uint8 - Partial update support level */\n partialUpdateSupport: number;\n /** uint8 - Color scheme identifier */\n colorScheme: number;\n /** uint8 bitfield - Supported transmission modes */\n transmissionModes: number;\n /** uint8 - Clock pin */\n clkPin: number;\n /** 7 bytes - Reserved pin slots */\n reservedPins: Uint8Array;\n /** 15 bytes - Reserved for future use */\n reserved: Uint8Array;\n}\n\n/**\n * Helper functions for DisplayConfig\n */\nexport namespace DisplayConfig {\n export const SIZE = 46;\n\n /**\n * Check if display supports raw image transmission (TRANSMISSION_MODE_RAW).\n */\n export function supportsRaw(config: DisplayConfig): boolean {\n return !!(config.transmissionModes & 0x01);\n }\n\n /**\n * Check if display supports ZIP compressed transmission (TRANSMISSION_MODE_ZIP).\n */\n export function supportsZip(config: DisplayConfig): boolean {\n return !!(config.transmissionModes & 0x02);\n }\n\n /**\n * Check if display supports Group 5 compression (TRANSMISSION_MODE_G5).\n */\n export function supportsG5(config: DisplayConfig): boolean {\n return !!(config.transmissionModes & 0x04);\n }\n\n /**\n * Check if display supports direct write mode - bufferless (TRANSMISSION_MODE_DIRECT_WRITE).\n */\n export function supportsDirectWrite(config: DisplayConfig): boolean {\n return !!(config.transmissionModes & 0x08);\n }\n\n /**\n * Check if display should clear screen at bootup (TRANSMISSION_MODE_CLEAR_ON_BOOT).\n */\n export function clearOnBoot(config: DisplayConfig): boolean {\n return !!(config.transmissionModes & 0x80);\n }\n\n /**\n * Get color scheme as enum, or raw int if unknown.\n */\n export function colorSchemeEnum(config: DisplayConfig): ColorScheme | number {\n if (Object.values(ColorScheme).includes(config.colorScheme)) {\n return config.colorScheme as ColorScheme;\n }\n return config.colorScheme;\n }\n\n /**\n * Get rotation as enum, or raw int if unknown.\n */\n export function rotationEnum(config: DisplayConfig): Rotation | number {\n if (Object.values(Rotation).includes(config.rotation)) {\n return config.rotation as Rotation;\n }\n return config.rotation;\n }\n\n /**\n * Parse DisplayConfig from TLV packet data.\n */\n export function fromBytes(data: Uint8Array): DisplayConfig {\n if (data.length < SIZE) {\n throw new Error(`Invalid DisplayConfig size: ${data.length} < ${SIZE}`);\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return {\n instanceNumber: view.getUint8(0),\n displayTechnology: view.getUint8(1),\n panelIcType: view.getUint16(2, true), // little-endian\n pixelWidth: view.getUint16(4, true), // little-endian\n pixelHeight: view.getUint16(6, true), // little-endian\n activeWidthMm: view.getUint16(8, true), // little-endian\n activeHeightMm: view.getUint16(10, true), // little-endian\n tagType: view.getUint16(12, true), // little-endian\n rotation: view.getUint8(14),\n resetPin: view.getUint8(15),\n busyPin: view.getUint8(16),\n dcPin: view.getUint8(17),\n csPin: view.getUint8(18),\n dataPin: view.getUint8(19),\n partialUpdateSupport: view.getUint8(20),\n colorScheme: view.getUint8(21),\n transmissionModes: view.getUint8(22),\n clkPin: view.getUint8(23),\n reservedPins: data.slice(24, 31), // 7 pins\n reserved: data.slice(31, 46), // 15 bytes\n };\n }\n}\n\n/**\n * LED configuration (TLV packet type 0x21, repeatable max 4).\n *\n * Size: 22 bytes (packed struct from firmware)\n */\nexport interface LedConfig {\n /** uint8 - LED instance number */\n instanceNumber: number;\n /** uint8 - LED type */\n ledType: number;\n /** uint8 - Red channel pin */\n led1R: number;\n /** uint8 - Green channel pin */\n led2G: number;\n /** uint8 - Blue channel pin */\n led3B: number;\n /** uint8 - 4th channel pin */\n led4: number;\n /** uint8 bitfield - LED configuration flags */\n ledFlags: number;\n /** 15 bytes - Reserved for future use */\n reserved: Uint8Array;\n}\n\n/**\n * Helper functions for LedConfig\n */\nexport namespace LedConfig {\n export const SIZE = 22;\n\n /**\n * Parse LedConfig from TLV packet data.\n */\n export function fromBytes(data: Uint8Array): LedConfig {\n if (data.length < SIZE) {\n throw new Error(`Invalid LedConfig size: ${data.length} < ${SIZE}`);\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return {\n instanceNumber: view.getUint8(0),\n ledType: view.getUint8(1),\n led1R: view.getUint8(2),\n led2G: view.getUint8(3),\n led3B: view.getUint8(4),\n led4: view.getUint8(5),\n ledFlags: view.getUint8(6),\n reserved: data.slice(7, 22),\n };\n }\n}\n\n/**\n * Sensor configuration (TLV packet type 0x23, repeatable max 4).\n *\n * Size: 30 bytes (packed struct from firmware)\n */\nexport interface SensorData {\n /** uint8 - Sensor instance number */\n instanceNumber: number;\n /** uint16 - Sensor type identifier */\n sensorType: number;\n /** uint8 - Data bus ID */\n busId: number;\n /** 26 bytes - Reserved for future use */\n reserved: Uint8Array;\n}\n\n/**\n * Helper functions for SensorData\n */\nexport namespace SensorData {\n export const SIZE = 30;\n\n /**\n * Parse SensorData from TLV packet data.\n */\n export function fromBytes(data: Uint8Array): SensorData {\n if (data.length < SIZE) {\n throw new Error(`Invalid SensorData size: ${data.length} < ${SIZE}`);\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return {\n instanceNumber: view.getUint8(0),\n sensorType: view.getUint16(1, true), // little-endian\n busId: view.getUint8(3),\n reserved: data.slice(4, 30),\n };\n }\n}\n\n/**\n * Data bus configuration (TLV packet type 0x24, repeatable max 4).\n *\n * Size: 30 bytes (packed struct from firmware - corrected from 28)\n */\nexport interface DataBus {\n /** uint8 - Bus instance number */\n instanceNumber: number;\n /** uint8 - Bus type identifier */\n busType: number;\n /** uint8 - Pin 1 (SCL for I2C) */\n pin1: number;\n /** uint8 - Pin 2 (SDA for I2C) */\n pin2: number;\n /** uint8 - Pin 3 */\n pin3: number;\n /** uint8 - Pin 4 */\n pin4: number;\n /** uint8 - Pin 5 */\n pin5: number;\n /** uint8 - Pin 6 */\n pin6: number;\n /** uint8 - Pin 7 */\n pin7: number;\n /** uint32 - Bus speed in Hz */\n busSpeedHz: number;\n /** uint8 bitfield - Bus configuration flags */\n busFlags: number;\n /** uint8 bitfield - Pullup resistors configuration */\n pullups: number;\n /** uint8 bitfield - Pulldown resistors configuration */\n pulldowns: number;\n /** 14 bytes - Reserved for future use */\n reserved: Uint8Array;\n}\n\n/**\n * Helper functions for DataBus\n */\nexport namespace DataBus {\n export const SIZE = 30;\n\n /**\n * Get bus type as enum, or raw int if unknown.\n */\n export function busTypeEnum(config: DataBus): BusType | number {\n if (Object.values(BusType).includes(config.busType)) {\n return config.busType as BusType;\n }\n return config.busType;\n }\n\n /**\n * Parse DataBus from TLV packet data.\n */\n export function fromBytes(data: Uint8Array): DataBus {\n if (data.length < SIZE) {\n throw new Error(`Invalid DataBus size: ${data.length} < ${SIZE}`);\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return {\n instanceNumber: view.getUint8(0),\n busType: view.getUint8(1),\n pin1: view.getUint8(2),\n pin2: view.getUint8(3),\n pin3: view.getUint8(4),\n pin4: view.getUint8(5),\n pin5: view.getUint8(6),\n pin6: view.getUint8(7),\n pin7: view.getUint8(8),\n busSpeedHz: view.getUint32(9, true), // little-endian\n busFlags: view.getUint8(13),\n pullups: view.getUint8(14),\n pulldowns: view.getUint8(15),\n reserved: data.slice(16, 30),\n };\n }\n}\n\n/**\n * Binary inputs configuration (TLV packet type 0x25, repeatable max 4).\n *\n * Size: 30 bytes (packed struct from firmware - corrected from 29)\n */\nexport interface BinaryInputs {\n /** uint8 - Input instance number */\n instanceNumber: number;\n /** uint8 - Input type */\n inputType: number;\n /** uint8 - Display representation type */\n displayAs: number;\n /** 8 bytes - Reserved pin slots */\n reservedPins: Uint8Array;\n /** uint8 bitfield - Input configuration flags */\n inputFlags: number;\n /** uint8 bitfield - Invert flags */\n invert: number;\n /** uint8 bitfield - Pullup resistors configuration */\n pullups: number;\n /** uint8 bitfield - Pulldown resistors configuration */\n pulldowns: number;\n /** 15 bytes - Reserved for future use */\n reserved: Uint8Array;\n}\n\n/**\n * Helper functions for BinaryInputs\n */\nexport namespace BinaryInputs {\n export const SIZE = 30;\n\n /**\n * Parse BinaryInputs from TLV packet data.\n */\n export function fromBytes(data: Uint8Array): BinaryInputs {\n if (data.length < SIZE) {\n throw new Error(`Invalid BinaryInputs size: ${data.length} < ${SIZE}`);\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return {\n instanceNumber: view.getUint8(0),\n inputType: view.getUint8(1),\n displayAs: view.getUint8(2),\n reservedPins: data.slice(3, 11), // 8 pins\n inputFlags: view.getUint8(11),\n invert: view.getUint8(12),\n pullups: view.getUint8(13),\n pulldowns: view.getUint8(14),\n reserved: data.slice(15, 30),\n };\n }\n}\n\n/**\n * Complete device configuration parsed from TLV data.\n *\n * Corresponds to GlobalConfig struct in firmware.\n */\nexport interface GlobalConfig {\n /** System configuration (single instance) */\n system?: SystemConfig;\n /** Manufacturer data (single instance) */\n manufacturer?: ManufacturerData;\n /** Power configuration (single instance) */\n power?: PowerOption;\n\n /** Display configurations (max 4) */\n displays: DisplayConfig[];\n /** LED configurations (max 4) */\n leds: LedConfig[];\n /** Sensor configurations (max 4) */\n sensors: SensorData[];\n /** Data bus configurations (max 4) */\n dataBuses: DataBus[];\n /** Binary input configurations (max 4) */\n binaryInputs: BinaryInputs[];\n\n /** Configuration version from device */\n version: number;\n /** Minor version (not stored in device) */\n minorVersion: number;\n /** Whether config was successfully loaded */\n loaded: boolean;\n}","/**\n * TLV configuration parser for OpenDisplay devices.\n */\n\nimport { ConfigParseError } from '../exceptions.js';\nimport {\n BinaryInputs,\n DataBus,\n DisplayConfig,\n GlobalConfig,\n LedConfig,\n ManufacturerData,\n PowerOption,\n SensorData,\n SystemConfig,\n} from '../models/config.js';\n\n// TLV packet type IDs\nexport const PACKET_TYPE_SYSTEM = 0x01;\nexport const PACKET_TYPE_MANUFACTURER = 0x02;\nexport const PACKET_TYPE_POWER = 0x04;\nexport const PACKET_TYPE_DISPLAY = 0x20;\nexport const PACKET_TYPE_LED = 0x21;\nexport const PACKET_TYPE_SENSOR = 0x23;\nexport const PACKET_TYPE_DATABUS = 0x24;\nexport const PACKET_TYPE_BINARY_INPUT = 0x25;\n\n/**\n * Parse complete TLV config response from device.\n *\n * Firmware sends config data with a wrapper: [length:2][version:1][packets...][crc:2]\n * This function strips the wrapper and passes clean packet data to the TLV parser.\n *\n * @param rawData - Complete TLV data assembled from all BLE chunks\n * @returns Parsed GlobalConfig\n * @throws {ConfigParseError} If data is too short or invalid\n */\nexport function parseConfigResponse(rawData: Uint8Array): GlobalConfig {\n if (rawData.length < 5) {\n // Min: 2 (length) + 1 (version) + 0 (packets) + 2 (crc)\n throw new ConfigParseError(\n `Config data too short: ${rawData.length} bytes (need at least 5)`\n );\n }\n\n // Parse TLV wrapper header\n const view = new DataView(rawData.buffer, rawData.byteOffset, rawData.byteLength);\n const configLength = view.getUint16(0, true); // little-endian\n const configVersion = view.getUint8(2);\n\n console.debug(\n `TLV wrapper: length=${configLength} bytes, version=${configVersion}`\n );\n\n // Extract packet data (skip 3-byte header, ignore 2-byte CRC at end)\n let packetData: Uint8Array;\n if (rawData.length > 5) {\n packetData = rawData.slice(3, -2); // Skip header, ignore CRC\n } else {\n packetData = rawData.slice(3); // Skip header only\n }\n\n console.debug(`Packet data after wrapper strip: ${packetData.length} bytes`);\n\n // Parse TLV packets\n return parseTlvConfig(packetData, configVersion);\n}\n\n/**\n * Parse complete TLV configuration from device response.\n *\n * BLE format: [TLV packets...] (raw TLV data, no header)\n *\n * Each TLV packet: [packet_number:1][packet_type:1][data:fixed_size]\n *\n * @param data - Raw TLV data from device (after echo bytes stripped)\n * @param version - Config version from wrapper (default 1 if called directly)\n * @returns GlobalConfig with all parsed configuration\n * @throws {ConfigParseError} If parsing fails\n */\nexport function parseTlvConfig(data: Uint8Array, version: number = 1): GlobalConfig {\n if (data.length < 2) {\n throw new ConfigParseError(\n `TLV data too short: ${data.length} bytes (need at least 2)`\n );\n }\n\n console.debug(`Parsing TLV config, ${data.length} bytes`);\n\n // Parse TLV packets (OEPL format: [packet_number:1][packet_id:1][fixed_data])\n let offset = 0;\n const packets = new Map<string, Uint8Array>();\n\n while (offset < data.length - 1) {\n if (offset + 2 > data.length) {\n break; // Not enough data for packet header\n }\n\n const packetNumber = data[offset];\n const packetType = data[offset + 1];\n offset += 2;\n\n // Determine packet size based on type\n const packetSize = getPacketSize(packetType);\n if (packetSize === null) {\n console.warn(\n `Unknown packet type 0x${packetType.toString(16).padStart(2, '0')} at offset ${offset - 2}, skipping`\n );\n break;\n }\n\n // Extract packet data\n if (offset + packetSize > data.length) {\n throw new ConfigParseError(\n `Packet type 0x${packetType.toString(16).padStart(2, '0')} truncated: ` +\n `need ${packetSize} bytes, have ${data.length - offset}`\n );\n }\n\n const packetData = data.slice(offset, offset + packetSize);\n offset += packetSize;\n\n // Store packet (use type and number as key)\n const key = `${packetType}-${packetNumber}`;\n packets.set(key, packetData);\n\n console.debug(\n `Parsed packet: type=0x${packetType.toString(16).padStart(2, '0')}, num=${packetNumber}, size=${packetSize}`\n );\n }\n\n // Parse packets in a single pass\n // Note: Firmware uses global sequential numbering across all packet types\n let system: SystemConfig | undefined;\n let manufacturer: ManufacturerData | undefined;\n let power: PowerOption | undefined;\n const displays: DisplayConfig[] = [];\n const leds: LedConfig[] = [];\n const sensors: SensorData[] = [];\n const dataBuses: DataBus[] = [];\n const binaryInputs: BinaryInputs[] = [];\n\n for (const [key, packetData] of packets) {\n const [packetTypeStr] = key.split('-');\n const packetType = parseInt(packetTypeStr, 10);\n\n switch (packetType) {\n case PACKET_TYPE_SYSTEM:\n system = parseSystemConfig(packetData);\n break;\n case PACKET_TYPE_MANUFACTURER:\n manufacturer = parseManufacturerData(packetData);\n break;\n case PACKET_TYPE_POWER:\n power = parsePowerOption(packetData);\n break;\n case PACKET_TYPE_DISPLAY:\n displays.push(parseDisplayConfig(packetData));\n break;\n case PACKET_TYPE_LED:\n leds.push(parseLedConfig(packetData));\n break;\n case PACKET_TYPE_SENSOR:\n sensors.push(parseSensorData(packetData));\n break;\n case PACKET_TYPE_DATABUS:\n dataBuses.push(parseDataBus(packetData));\n break;\n case PACKET_TYPE_BINARY_INPUT:\n binaryInputs.push(parseBinaryInputs(packetData));\n break;\n }\n }\n\n return {\n system,\n manufacturer,\n power,\n displays,\n leds,\n sensors,\n dataBuses,\n binaryInputs,\n version, // From firmware wrapper\n minorVersion: 1, // Not stored in device (only single version byte exists)\n loaded: true,\n };\n}\n\n/**\n * Get expected size for a packet type.\n *\n * @param packetType - TLV packet type ID\n * @returns Expected packet size in bytes, or null if unknown type\n */\nfunction getPacketSize(packetType: number): number | null {\n const sizes: Record<number, number> = {\n [PACKET_TYPE_SYSTEM]: 22,\n [PACKET_TYPE_MANUFACTURER]: 22,\n [PACKET_TYPE_POWER]: 30, // Fixed: was 32\n [PACKET_TYPE_DISPLAY]: 46, // Fixed: was 66\n [PACKET_TYPE_LED]: 22,\n [PACKET_TYPE_SENSOR]: 30,\n [PACKET_TYPE_DATABUS]: 30, // Fixed: was 28\n [PACKET_TYPE_BINARY_INPUT]: 30, // Fixed: was 29\n };\n return sizes[packetType] ?? null;\n}\n\n/**\n * Parse SystemConfig packet (0x01, 22 bytes).\n */\nfunction parseSystemConfig(data: Uint8Array): SystemConfig {\n if (data.length < 22) {\n throw new ConfigParseError(\n `SystemConfig too short: ${data.length} bytes (need 22)`\n );\n }\n\n return SystemConfig.fromBytes(data);\n}\n\n/**\n * Parse ManufacturerData packet (0x02, 22 bytes).\n */\nfunction parseManufacturerData(data: Uint8Array): ManufacturerData {\n if (data.length < 22) {\n throw new ConfigParseError(\n `ManufacturerData too short: ${data.length} bytes (need 22)`\n );\n }\n\n return ManufacturerData.fromBytes(data);\n}\n\n/**\n * Parse PowerOption packet (0x04, 30 bytes).\n */\nfunction parsePowerOption(data: Uint8Array): PowerOption {\n if (data.length < 30) {\n throw new ConfigParseError(\n `PowerOption too short: ${data.length} bytes (need 30)`\n );\n }\n\n return PowerOption.fromBytes(data);\n}\n\n/**\n * Parse DisplayConfig packet (0x20, 46 bytes).\n */\nfunction parseDisplayConfig(data: Uint8Array): DisplayConfig {\n if (data.length < 46) {\n throw new ConfigParseError(\n `DisplayConfig too short: ${data.length} bytes (need 46)`\n );\n }\n\n return DisplayConfig.fromBytes(data);\n}\n\n/**\n * Parse LedConfig packet (0x21, 22 bytes).\n */\nfunction parseLedConfig(data: Uint8Array): LedConfig {\n if (data.length < 22) {\n throw new ConfigParseError(\n `LedConfig too short: ${data.length} bytes (need 22)`\n );\n }\n\n return LedConfig.fromBytes(data);\n}\n\n/**\n * Parse SensorData packet (0x23, 30 bytes).\n */\nfunction parseSensorData(data: Uint8Array): SensorData {\n if (data.length < 30) {\n throw new ConfigParseError(\n `SensorData too short: ${data.length} bytes (need 30)`\n );\n }\n\n return SensorData.fromBytes(data);\n}\n\n/**\n * Parse DataBus packet (0x24, 30 bytes).\n */\nfunction parseDataBus(data: Uint8Array): DataBus {\n if (data.length < 30) {\n throw new ConfigParseError(\n `DataBus too short: ${data.length} bytes (need 30)`\n );\n }\n\n return DataBus.fromBytes(data);\n}\n\n/**\n * Parse BinaryInputs packet (0x25, 30 bytes).\n */\nfunction parseBinaryInputs(data: Uint8Array): BinaryInputs {\n if (data.length < 30) {\n throw new ConfigParseError(\n `BinaryInputs too short: ${data.length} bytes (need 30)`\n );\n }\n\n return BinaryInputs.fromBytes(data);\n}","/**\n * TLV configuration serializer for OpenDisplay devices.\n */\n\nimport type {\n BinaryInputs,\n DataBus,\n DisplayConfig,\n GlobalConfig,\n LedConfig,\n ManufacturerData,\n PowerOption,\n SensorData,\n SystemConfig,\n} from '../models/config';\n\n// Packet type IDs (same as config-parser.ts)\nconst PACKET_TYPE_SYSTEM = 0x01;\nconst PACKET_TYPE_MANUFACTURER = 0x02;\nconst PACKET_TYPE_POWER = 0x04;\nconst PACKET_TYPE_DISPLAY = 0x20;\nconst PACKET_TYPE_LED = 0x21;\nconst PACKET_TYPE_SENSOR = 0x23;\nconst PACKET_TYPE_DATABUS = 0x24;\nconst PACKET_TYPE_BINARY_INPUT = 0x25;\n\n/**\n * Calculate CRC32 and return lower 16 bits.\n *\n * Uses standard CRC32 algorithm (same as zlib/firmware) but only returns\n * the lower 16 bits for backwards compatibility with firmware.\n *\n * @param data - Config data to calculate CRC over\n * @returns Lower 16 bits of CRC32 value\n */\nexport function calculateConfigCrc(data: Uint8Array): number {\n let crc = 0xffffffff;\n\n for (const byte of data) {\n crc ^= byte;\n for (let i = 0; i < 8; i++) {\n if (crc & 1) {\n crc = (crc >>> 1) ^ 0xedb88320;\n } else {\n crc = crc >>> 1;\n }\n }\n }\n\n const crc32 = (~crc) >>> 0;\n return crc32 & 0xffff; // Return lower 16 bits only\n}\n\n/**\n * Serialize SystemConfig to 22 bytes.\n */\nexport function serializeSystemConfig(config: SystemConfig): Uint8Array {\n const buffer = new ArrayBuffer(22);\n const view = new DataView(buffer);\n const result = new Uint8Array(buffer);\n\n view.setUint16(0, config.icType, true);\n view.setUint8(2, config.communicationModes);\n view.setUint8(3, config.deviceFlags);\n view.setUint8(4, config.pwrPin);\n\n // Reserved bytes (17 bytes)\n const reserved = config.reserved || new Uint8Array(17);\n result.set(reserved.subarray(0, 17), 5);\n\n return result;\n}\n\n/**\n * Serialize ManufacturerData to 22 bytes.\n */\nexport function serializeManufacturerData(\n config: ManufacturerData\n): Uint8Array {\n const buffer = new ArrayBuffer(22);\n const view = new DataView(buffer);\n const result = new Uint8Array(buffer);\n\n view.setUint16(0, config.manufacturerId, true);\n view.setUint8(2, config.boardType);\n view.setUint8(3, config.boardRevision);\n\n // Reserved bytes (18 bytes)\n const reserved = config.reserved || new Uint8Array(18);\n result.set(reserved.subarray(0, 18), 4);\n\n return result;\n}\n\n/**\n * Serialize PowerOption to 30 bytes.\n */\nexport function serializePowerOption(config: PowerOption): Uint8Array {\n const buffer = new ArrayBuffer(30);\n const view = new DataView(buffer);\n const result = new Uint8Array(buffer);\n\n view.setUint8(0, config.powerMode);\n\n // 3-byte battery capacity (little-endian)\n view.setUint8(1, config.batteryCapacityMah & 0xff);\n view.setUint8(2, (config.batteryCapacityMah >> 8) & 0xff);\n view.setUint8(3, (config.batteryCapacityMah >> 16) & 0xff);\n\n view.setUint16(4, config.sleepTimeoutMs, true);\n view.setInt8(6, config.txPower);\n view.setUint8(7, config.sleepFlags);\n view.setUint8(8, config.batterySensePin);\n view.setUint8(9, config.batterySenseEnablePin);\n view.setUint8(10, config.batterySenseFlags);\n view.setUint8(11, config.capacityEstimator);\n view.setUint16(12, config.voltageScalingFactor, true);\n view.setUint32(14, config.deepSleepCurrentUa, true);\n view.setUint16(18, config.deepSleepTimeSeconds, true);\n\n // Reserved bytes (10 bytes)\n const reserved = config.reserved || new Uint8Array(10);\n result.set(reserved.subarray(0, 10), 20);\n\n return result;\n}\n\n/**\n * Serialize DisplayConfig to 46 bytes.\n */\nexport function serializeDisplayConfig(config: DisplayConfig): Uint8Array {\n const buffer = new ArrayBuffer(46);\n const view = new DataView(buffer);\n const result = new Uint8Array(buffer);\n\n view.setUint8(0, config.instanceNumber);\n view.setUint8(1, config.displayTechnology);\n view.setUint16(2, config.panelIcType, true);\n view.setUint16(4, config.pixelWidth, true);\n view.setUint16(6, config.pixelHeight, true);\n view.setUint16(8, config.activeWidthMm, true);\n view.setUint16(10, config.activeHeightMm, true);\n view.setUint16(12, config.tagType, true);\n view.setUint8(14, config.rotation);\n view.setUint8(15, config.resetPin);\n view.setUint8(16, config.busyPin);\n view.setUint8(17, config.dcPin);\n view.setUint8(18, config.csPin);\n view.setUint8(19, config.dataPin);\n view.setUint8(20, config.partialUpdateSupport);\n view.setUint8(21, config.colorScheme);\n view.setUint8(22, config.transmissionModes);\n view.setUint8(23, config.clkPin);\n\n // Reserved pins (7 bytes)\n const reservedPins = config.reservedPins || new Uint8Array(7).fill(0xff);\n result.set(reservedPins.subarray(0, 7), 24);\n\n // Reserved bytes (15 bytes)\n const reserved = config.reserved || new Uint8Array(15);\n result.set(reserved.subarray(0, 15), 31);\n\n return result;\n}\n\n/**\n * Serialize LedConfig to 22 bytes.\n */\nexport function serializeLedConfig(config: LedConfig): Uint8Array {\n const buffer = new ArrayBuffer(22);\n const view = new DataView(buffer);\n const result = new Uint8Array(buffer);\n\n view.setUint8(0, config.instanceNumber);\n view.setUint8(1, config.ledType);\n view.setUint8(2, config.led1R);\n view.setUint8(3, config.led2G);\n view.setUint8(4, config.led3B);\n view.setUint8(5, config.led4);\n view.setUint8(6, config.ledFlags);\n\n // Reserved bytes (15 bytes)\n const reserved = config.reserved || new Uint8Array(15);\n result.set(reserved.subarray(0, 15), 7);\n\n return result;\n}\n\n/**\n * Serialize SensorData to 30 bytes.\n */\nexport function serializeSensorData(config: SensorData): Uint8Array {\n const buffer = new ArrayBuffer(30);\n const view = new DataView(buffer);\n const result = new Uint8Array(buffer);\n\n view.setUint8(0, config.instanceNumber);\n view.setUint16(1, config.sensorType, true);\n view.setUint8(3, config.busId);\n\n // Reserved bytes (26 bytes)\n const reserved = config.reserved || new Uint8Array(26);\n result.set(reserved.subarray(0, 26), 4);\n\n return result;\n}\n\n/**\n * Serialize DataBus to 30 bytes.\n */\nexport function serializeDataBus(config: DataBus): Uint8Array {\n const buffer = new ArrayBuffer(30);\n const view = new DataView(buffer);\n const result = new Uint8Array(buffer);\n\n view.setUint8(0, config.instanceNumber);\n view.setUint8(1, config.busType);\n view.setUint8(2, config.pin1);\n view.setUint8(3, config.pin2);\n view.setUint8(4, config.pin3);\n view.setUint8(5, config.pin4);\n view.setUint8(6, config.pin5);\n view.setUint8(7, config.pin6);\n view.setUint8(8, config.pin7);\n view.setUint32(9, config.busSpeedHz, true);\n view.setUint8(13, config.busFlags);\n view.setUint8(14, config.pullups);\n view.setUint8(15, config.pulldowns);\n\n // Reserved bytes (14 bytes)\n const reserved = config.reserved || new Uint8Array(14);\n result.set(reserved.subarray(0, 14), 16);\n\n return result;\n}\n\n/**\n * Serialize BinaryInputs to 30 bytes.\n */\nexport function serializeBinaryInputs(config: BinaryInputs): Uint8Array {\n const buffer = new ArrayBuffer(30);\n const view = new DataView(buffer);\n const result = new Uint8Array(buffer);\n\n view.setUint8(0, config.instanceNumber);\n view.setUint8(1, config.inputType);\n view.setUint8(2, config.displayAs);\n\n // Reserved pins (8 bytes)\n const reservedPins = config.reservedPins || new Uint8Array(8);\n result.set(reservedPins.subarray(0, 8), 3);\n\n view.setUint8(11, config.inputFlags);\n view.setUint8(12, config.invert);\n view.setUint8(13, config.pullups);\n view.setUint8(14, config.pulldowns);\n\n // Reserved bytes (15 bytes)\n const reserved = config.reserved || new Uint8Array(15);\n result.set(reserved.subarray(0, 15), 15);\n\n return result;\n}\n\n/**\n * Serialize complete GlobalConfig to TLV binary format.\n *\n * Format:\n * [2 bytes: padding/reserved]\n * [1 byte: version]\n * [TLV packets...]\n * [2 bytes: CRC16 (lower 16 bits of CRC32)]\n *\n * TLV Packet Format:\n * [1 byte: packet_number] // 0-3 for repeatable types\n * [1 byte: packet_type] // 0x01, 0x02, 0x04, 0x20-0x25\n * [N bytes: fixed-size data]\n *\n * @param config - GlobalConfig to serialize\n * @returns Complete config data ready to send to device\n * @throws {Error} If config exceeds maximum size (4096 bytes)\n */\nexport function serializeConfig(config: GlobalConfig): Uint8Array {\n // Build packet data in chunks\n const chunks: Uint8Array[] = [];\n\n // Start with 2 bytes padding and 1 byte version\n const header = new Uint8Array([0x00, 0x00, config.version]);\n chunks.push(header);\n\n // Serialize single-instance packets\n if (config.system) {\n chunks.push(new Uint8Array([0, PACKET_TYPE_SYSTEM]));\n chunks.push(serializeSystemConfig(config.system));\n }\n\n if (config.manufacturer) {\n chunks.push(new Uint8Array([0, PACKET_TYPE_MANUFACTURER]));\n chunks.push(serializeManufacturerData(config.manufacturer));\n }\n\n if (config.power) {\n chunks.push(new Uint8Array([0, PACKET_TYPE_POWER]));\n chunks.push(serializePowerOption(config.power));\n }\n\n // Serialize repeatable packets (max 4 instances each)\n for (let i = 0; i < Math.min(config.displays.length, 4); i++) {\n chunks.push(new Uint8Array([i, PACKET_TYPE_DISPLAY]));\n chunks.push(serializeDisplayConfig(config.displays[i]));\n }\n\n for (let i = 0; i < Math.min(config.leds.length, 4); i++) {\n chunks.push(new Uint8Array([i, PACKET_TYPE_LED]));\n chunks.push(serializeLedConfig(config.leds[i]));\n }\n\n for (let i = 0; i < Math.min(config.sensors.length, 4); i++) {\n chunks.push(new Uint8Array([i, PACKET_TYPE_SENSOR]));\n chunks.push(serializeSensorData(config.sensors[i]));\n }\n\n for (let i = 0; i < Math.min(config.dataBuses.length, 4); i++) {\n chunks.push(new Uint8Array([i, PACKET_TYPE_DATABUS]));\n chunks.push(serializeDataBus(config.dataBuses[i]));\n }\n\n for (let i = 0; i < Math.min(config.binaryInputs.length, 4); i++) {\n chunks.push(new Uint8Array([i, PACKET_TYPE_BINARY_INPUT]));\n chunks.push(serializeBinaryInputs(config.binaryInputs[i]));\n }\n\n // Calculate total size\n const totalDataSize = chunks.reduce((sum, chunk) => sum + chunk.length, 0);\n\n // Validate size (max 4096 bytes including CRC)\n if (totalDataSize + 2 > 4096) {\n throw new Error(\n `Config size ${totalDataSize + 2} bytes exceeds maximum 4096 bytes`\n );\n }\n\n // Concatenate all chunks\n const packetData = new Uint8Array(totalDataSize);\n let offset = 0;\n for (const chunk of chunks) {\n packetData.set(chunk, offset);\n offset += chunk.length;\n }\n\n // Calculate CRC over packet data (excluding CRC itself)\n const crc16 = calculateConfigCrc(packetData);\n\n // Append CRC as 2 bytes little-endian\n const result = new Uint8Array(totalDataSize + 2);\n result.set(packetData, 0);\n\n const crcView = new DataView(result.buffer, totalDataSize, 2);\n crcView.setUint16(0, crc16, true);\n\n return result;\n}","/**\n * Notification queue for BLE responses.\n *\n * Web Bluetooth delivers notifications asynchronously via events.\n * This queue buffers notifications and provides a Promise-based interface\n * for consuming them, with timeout support.\n */\n\nimport { BLETimeoutError } from '../exceptions';\n\ninterface PendingResolver {\n resolve: (data: Uint8Array) => void;\n reject: (error: Error) => void;\n timeoutId: number;\n}\n\n/**\n * Queue for managing BLE notification responses.\n *\n * Handles the asynchronous nature of Web Bluetooth notifications by:\n * - Buffering notifications that arrive before being requested\n * - Queuing requests that wait for future notifications\n * - Providing timeout support for all operations\n */\nexport class NotificationQueue {\n private queue: Uint8Array[] = [];\n private pendingResolvers: PendingResolver[] = [];\n\n /**\n * Add a notification to the queue.\n *\n * If there are pending consumers waiting, immediately resolve the oldest one.\n * Otherwise, buffer the notification for future consumption.\n *\n * @param data - Notification data received from BLE characteristic\n */\n enqueue(data: Uint8Array): void {\n if (this.pendingResolvers.length > 0) {\n // Immediately resolve a waiting consumer\n const pending = this.pendingResolvers.shift()!;\n clearTimeout(pending.timeoutId);\n pending.resolve(data);\n } else {\n // Buffer for later\n this.queue.push(data);\n }\n }\n\n /**\n * Get the next notification from the queue.\n *\n * If a notification is already buffered, return it immediately.\n * Otherwise, wait for the next notification or timeout.\n *\n * @param timeoutMs - Maximum time to wait in milliseconds\n * @returns Promise that resolves with notification data\n * @throws {BLETimeoutError} If timeout expires before notification arrives\n */\n async dequeue(timeoutMs: number): Promise<Uint8Array> {\n if (this.queue.length > 0) {\n return this.queue.shift()!;\n }\n\n return new Promise<Uint8Array>((resolve, reject) => {\n const timeoutId = window.setTimeout(() => {\n const index = this.pendingResolvers.findIndex(\n (p) => p.resolve === resolve\n );\n if (index !== -1) {\n this.pendingResolvers.splice(index, 1);\n reject(\n new BLETimeoutError(\n `No response received within ${timeoutMs}ms timeout`\n )\n );\n }\n }, timeoutMs);\n\n this.pendingResolvers.push({ resolve, reject, timeoutId });\n });\n }\n\n /**\n * Clear the queue and reject all pending requests.\n *\n * Called when the connection is closed or reset.\n *\n * @param reason - Reason for clearing (default: \"Connection closed\")\n */\n clear(reason: string = 'Connection closed'): void {\n this.queue = [];\n\n for (const pending of this.pendingResolvers) {\n clearTimeout(pending.timeoutId);\n pending.reject(new Error(reason));\n }\n\n this.pendingResolvers = [];\n }\n\n /**\n * Get the number of buffered notifications.\n */\n get size(): number {\n return this.queue.length;\n }\n\n /**\n * Get the number of pending consumers waiting for notifications.\n */\n get pendingCount(): number {\n return this.pendingResolvers.length;\n }\n}","/**\n * Web Bluetooth connection wrapper for OpenDisplay devices.\n *\n * Provides a clean interface for BLE operations:\n * - Device discovery and connection\n * - Command transmission\n * - Response reception with queuing\n * - Automatic reconnection handling\n */\n\nimport { BLEConnectionError, BLETimeoutError } from '../exceptions';\nimport { SERVICE_UUID, MANUFACTURER_ID } from '../protocol/constants';\nimport { NotificationQueue } from './notification-queue';\n\n/**\n * Connection options for BLE device.\n */\nexport interface BLEConnectionOptions {\n /**\n * Name prefix to filter devices (e.g., \"OpenDisplay\")\n * If not provided, user will see all devices with the OpenDisplay service\n */\n namePrefix?: string;\n\n /**\n * Optional BluetoothDevice to connect to directly\n */\n device?: BluetoothDevice;\n}\n\n/**\n * BLE connection manager for OpenDisplay devices.\n *\n * Handles all low-level Web Bluetooth operations and provides a\n * command/response interface for the protocol layer.\n */\nexport class BLEConnection {\n private device: BluetoothDevice | null = null;\n private gattServer: BluetoothRemoteGATTServer | null = null;\n private characteristic: BluetoothRemoteGATTCharacteristic | null = null;\n private notificationQueue = new NotificationQueue();\n private disconnectHandler: ((event: Event) => void) | null = null;\n\n /**\n * Check if currently connected to a device.\n */\n get isConnected(): boolean {\n return this.gattServer?.connected ?? false;\n }\n\n /**\n * Get the connected device name, if available.\n */\n get deviceName(): string | undefined {\n return this.device?.name;\n }\n\n /**\n * Connect to an OpenDisplay device.\n *\n * @param options - Connection options (device or namePrefix filter)\n * @throws {BLEConnectionError} If connection fails\n * @throws {Error} If Web Bluetooth is not supported\n */\n async connect(options: BLEConnectionOptions = {}): Promise<void> {\n if (!navigator.bluetooth) {\n throw new Error(\n 'Web Bluetooth API not supported in this browser. ' +\n 'Try Chrome, Edge, or Opera on desktop/Android.'\n );\n }\n\n try {\n // Get or request device\n if (options.device) {\n this.device = options.device;\n } else {\n // Request device with filters\n const filters: BluetoothLEScanFilter[] = [];\n\n // Create filter with service UUID and optional name prefix\n if (options.namePrefix) {\n filters.push({\n services: [SERVICE_UUID],\n namePrefix: options.namePrefix,\n });\n } else {\n filters.push({\n services: [SERVICE_UUID],\n });\n }\n\n // Add manufacturer data filter\n filters.push({\n manufacturerData: [\n {\n companyIdentifier: MANUFACTURER_ID,\n },\n ],\n });\n\n this.device = await navigator.bluetooth.requestDevice({\n filters,\n optionalServices: [SERVICE_UUID],\n });\n }\n\n // Connect to GATT server\n if (!this.device.gatt) {\n throw new BLEConnectionError('Device does not support GATT');\n }\n\n this.gattServer = await this.device.gatt.connect();\n\n // Get service and characteristic\n const service = await this.gattServer.getPrimaryService(SERVICE_UUID);\n const characteristics = await service.getCharacteristics();\n\n if (characteristics.length === 0) {\n throw new BLEConnectionError(\n 'No characteristics found in OpenDisplay service'\n );\n }\n\n // Use the first characteristic (OpenDisplay uses single characteristic)\n this.characteristic = characteristics[0];\n\n // Start notifications\n await this.characteristic.startNotifications();\n\n // Bind notification handler\n this.characteristic.addEventListener(\n 'characteristicvaluechanged',\n this.handleNotification.bind(this)\n );\n\n // Listen for disconnection\n this.disconnectHandler = this.handleDisconnect.bind(this);\n this.device.addEventListener(\n 'gattserverdisconnected',\n this.disconnectHandler\n );\n\n console.log(\n `Connected to ${this.device.name || 'OpenDisplay device'}`\n );\n } catch (error) {\n this.cleanup();\n if (error instanceof Error) {\n throw new BLEConnectionError(\n `Failed to connect: ${error.message}`\n );\n }\n throw new BLEConnectionError('Failed to connect to device');\n }\n }\n\n /**\n * Disconnect from the device.\n */\n async disconnect(): Promise<void> {\n try {\n // Stop notifications if characteristic is available\n if (this.characteristic) {\n try {\n await this.characteristic.stopNotifications();\n } catch {\n // Ignore errors during cleanup\n }\n }\n\n // Disconnect GATT server\n if (this.gattServer?.connected) {\n this.gattServer.disconnect();\n }\n } finally {\n this.cleanup();\n }\n }\n\n /**\n * Write a command to the device.\n *\n * @param data - Command data to send\n * @throws {BLEConnectionError} If not connected or write fails\n */\n async writeCommand(data: Uint8Array): Promise<void> {\n if (!this.isConnected || !this.characteristic) {\n throw new BLEConnectionError('Not connected to device');\n }\n\n try {\n await this.characteristic.writeValueWithResponse(data as BufferSource);\n } catch (error) {\n if (error instanceof Error) {\n throw new BLEConnectionError(\n `Failed to write command: ${error.message}`\n );\n }\n throw new BLEConnectionError('Failed to write command');\n }\n }\n\n /**\n * Read the next response from the device.\n *\n * @param timeoutMs - Maximum time to wait for response\n * @returns Promise that resolves with response data\n * @throws {BLETimeoutError} If timeout expires\n * @throws {BLEConnectionError} If not connected\n */\n async readResponse(timeoutMs: number): Promise<Uint8Array> {\n if (!this.isConnected) {\n throw new BLEConnectionError('Not connected to device');\n }\n\n return this.notificationQueue.dequeue(timeoutMs);\n }\n\n /**\n * Handle incoming BLE notifications.\n *\n * @param event - Characteristic value changed event\n */\n private handleNotification(event: Event): void {\n const characteristic = event.target as BluetoothRemoteGATTCharacteristic;\n if (!characteristic.value) {\n return;\n }\n\n const data = new Uint8Array(characteristic.value.buffer);\n this.notificationQueue.enqueue(data);\n }\n\n /**\n * Handle device disconnection.\n */\n private handleDisconnect(): void {\n console.log('Device disconnected');\n this.cleanup();\n }\n\n /**\n * Clean up resources and reset state.\n */\n private cleanup(): void {\n // Clear notification queue\n this.notificationQueue.clear();\n\n // Remove event listeners\n if (this.characteristic) {\n this.characteristic.removeEventListener(\n 'characteristicvaluechanged',\n this.handleNotification.bind(this)\n );\n this.characteristic = null;\n }\n\n if (this.device && this.disconnectHandler) {\n this.device.removeEventListener(\n 'gattserverdisconnected',\n this.disconnectHandler\n );\n this.disconnectHandler = null;\n }\n\n this.gattServer = null;\n this.device = null;\n }\n}","/**\n * BLE device discovery for OpenDisplay devices.\n *\n * Note: Web Bluetooth API does not provide general scanning like Python's Bleak.\n * Instead, we use requestDevice() which shows a browser picker dialog.\n */\n\nimport { MANUFACTURER_ID, SERVICE_UUID } from './protocol/constants';\n\n/**\n * Discover OpenDisplay BLE devices using browser picker.\n *\n * Shows the browser's device picker dialog filtered to OpenDisplay devices.\n * User selects a device, and we return it for connection.\n *\n * @param namePrefix - Optional name prefix filter (e.g., \"OpenDisplay\")\n * @returns Selected BluetoothDevice\n * @throws {Error} If Web Bluetooth not supported or user cancels\n *\n * @example\n * ```typescript\n * const device = await discoverDevices();\n * // or with name filter:\n * const device = await discoverDevices('OpenDisplay');\n * ```\n */\nexport async function discoverDevices(\n namePrefix?: string\n): Promise<BluetoothDevice> {\n if (!navigator.bluetooth) {\n throw new Error(\n 'Web Bluetooth API not supported in this browser. ' +\n 'Try Chrome, Edge, or Opera on desktop/Android.'\n );\n }\n\n const filters: BluetoothLEScanFilter[] = [];\n\n // Create filter with service UUID and optional name prefix\n if (namePrefix) {\n filters.push({\n services: [SERVICE_UUID],\n namePrefix,\n });\n } else {\n filters.push({\n services: [SERVICE_UUID],\n });\n }\n\n // Add manufacturer data filter\n filters.push({\n manufacturerData: [\n {\n companyIdentifier: MANUFACTURER_ID,\n },\n ],\n });\n\n try {\n const device = await navigator.bluetooth.requestDevice({\n filters,\n optionalServices: [SERVICE_UUID],\n });\n\n console.log(`Selected device: ${device.name || 'Unknown'}`);\n return device;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Device selection failed: ${error.message}`);\n }\n throw new Error('Device selection cancelled or failed');\n }\n}","/**\n * BLE advertisement data structures.\n */\n\n/**\n * Parsed BLE advertisement manufacturer data.\n *\n * Advertisement format (11 bytes, manufacturer ID already stripped by Web Bluetooth):\n *\n * - [0-6]: Fixed protocol bytes\n * - [7-8]: Battery voltage in millivolts (little-endian uint16)\n * - [9]: Chip temperature in Celsius (signed int8)\n * - [10]: Loop counter (uint8, increments each advertisement)\n *\n * Note: Web Bluetooth provides manufacturer data without the manufacturer ID prefix.\n */\nexport interface AdvertisementData {\n /** Battery voltage in millivolts */\n batteryMv: number;\n\n /** Chip temperature in Celsius */\n temperatureC: number;\n\n /** Incrementing counter for each advertisement */\n loopCounter: number;\n}\n\n/**\n * Parse BLE advertisement manufacturer data.\n *\n * Note: The manufacturer ID (0x2446) is already stripped by Web Bluetooth\n * and provided as the key in manufacturerData.\n *\n * @param data - Raw manufacturer data (11 bytes, without the manufacturer ID prefix)\n * @returns AdvertisementData with parsed values\n * @throws {Error} If data is too short\n */\nexport function parseAdvertisement(data: Uint8Array): AdvertisementData {\n if (data.length < 11) {\n throw new Error(\n `Advertisement data too short: ${data.length} bytes (need 11)`\n );\n }\n\n const view = new DataView(data.buffer, data.byteOffset);\n\n // Parse sensor data\n // Bytes 0-6 are fixed protocol bytes (ignored)\n const batteryMv = view.getUint16(7, true); // uint16, little-endian\n const temperatureC = view.getInt8(9); // int8, signed\n const loopCounter = data[10]; // uint8\n\n return {\n batteryMv,\n temperatureC,\n loopCounter,\n };\n}","/**\n * @opendisplay/opendisplay - TypeScript library for OpenDisplay BLE e-paper displays\n *\n * Main entry point exporting the public API.\n */\n\n// Core device API\nexport { OpenDisplayDevice } from './device';\nexport { discoverDevices } from './discovery';\n\n// Models and types\nexport * from './models/enums';\nexport * from './models/config';\nexport * from './models/capabilities';\nexport * from './models/firmware';\nexport * from './models/advertisement';\n\n// Exceptions\nexport * from './exceptions';\n\n// Re-export from epaper-dithering for convenience\nexport { ColorScheme, DitherMode } from '@opendisplay/epaper-dithering';"],"mappings":"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","names":["ColorScheme","DitherMode","ColorScheme","ColorScheme","RefreshMode","ICType","PowerMode","BusType","Rotation","CommandCode","ColorScheme","SystemConfig","ManufacturerData","PowerOption","batteryMah","DisplayConfig","ColorScheme","LedConfig","SensorData","DataBus","BinaryInputs","PACKET_TYPE_SYSTEM","PACKET_TYPE_MANUFACTURER","PACKET_TYPE_POWER","PACKET_TYPE_DISPLAY","PACKET_TYPE_LED","PACKET_TYPE_SENSOR","PACKET_TYPE_DATABUS","PACKET_TYPE_BINARY_INPUT","ColorScheme","DitherMode","ColorScheme","DitherMode"]}
1
+ {"version":3,"sources":["../src/device.ts","../src/encoding/images.ts","../src/exceptions.ts","../src/encoding/bitplanes.ts","../src/encoding/compression.ts","../src/models/enums.ts","../src/protocol/constants.ts","../src/protocol/commands.ts","../src/protocol/responses.ts","../src/models/config.ts","../src/protocol/config-parser.ts","../src/protocol/config-serializer.ts","../src/transport/notification-queue.ts","../src/transport/connection.ts","../src/discovery.ts","../src/models/advertisement.ts","../src/index.ts"],"sourcesContent":["/**\n * Main OpenDisplay BLE device class.\n */\n\nimport { ColorScheme, DitherMode } from '@opendisplay/epaper-dithering';\nimport { prepareImageForUpload } from './encoding/images';\nimport { compressImageData } from './encoding/compression';\nimport { BLEConnectionError, BLETimeoutError, ProtocolError } from './exceptions';\nimport type { DeviceCapabilities } from './models/capabilities';\nimport type { GlobalConfig } from './models/config';\nimport { RefreshMode } from './models/enums';\nimport type { FirmwareVersion } from './models/firmware';\nimport {\n CHUNK_SIZE,\n MAX_COMPRESSED_SIZE,\n CommandCode,\n} from './protocol/constants';\nimport {\n buildReadConfigCommand,\n buildReadFwVersionCommand,\n buildRebootCommand,\n buildDirectWriteStartCompressed,\n buildDirectWriteStartUncompressed,\n buildDirectWriteDataCommand,\n buildDirectWriteEndCommand,\n buildWriteConfigCommand,\n} from './protocol/commands';\nimport {\n validateAckResponse,\n parseFirmwareVersion,\n stripCommandEcho,\n checkResponseType,\n} from './protocol/responses';\nimport { parseConfigResponse } from './protocol/config-parser';\nimport { serializeConfig } from './protocol/config-serializer';\nimport { BLEConnection, type BLEConnectionOptions } from './transport/connection';\n\n/**\n * OpenDisplay BLE e-paper device.\n *\n * Main API for communicating with OpenDisplay BLE tags.\n *\n * @example\n * ```typescript\n * // Auto-interrogate on first connect\n * const device = new OpenDisplayDevice();\n * await device.connect();\n * await device.uploadImage(imageData);\n * await device.disconnect();\n *\n * // Skip interrogation with cached config\n * const device = new OpenDisplayDevice({ config: cachedConfig });\n * await device.connect();\n *\n * // Skip interrogation with minimal capabilities\n * const caps: DeviceCapabilities = { width: 296, height: 128, colorScheme: ColorScheme.BWR };\n * const device = new OpenDisplayDevice({ capabilities: caps });\n * ```\n */\nexport class OpenDisplayDevice {\n // BLE operation timeouts (milliseconds)\n static readonly TIMEOUT_FIRST_CHUNK = 10000; // First chunk may take longer\n static readonly TIMEOUT_CHUNK = 2000; // Subsequent chunks\n static readonly TIMEOUT_ACK = 5000; // Command acknowledgments\n static readonly TIMEOUT_REFRESH = 90000; // Display refresh (firmware spec: up to 60s)\n\n private connection: BLEConnection | null = null;\n private _config: GlobalConfig | null = null;\n private _capabilities: DeviceCapabilities | null = null;\n private _fwVersion: FirmwareVersion | null = null;\n\n /**\n * Initialize OpenDisplay device.\n *\n * @param options - Device initialization options\n */\n constructor(\n private options: {\n config?: GlobalConfig;\n capabilities?: DeviceCapabilities;\n device?: BluetoothDevice;\n namePrefix?: string;\n } = {}\n ) {\n this._config = options.config ?? null;\n this._capabilities = options.capabilities ?? null;\n }\n\n /**\n * Get full device configuration (if interrogated).\n */\n get config(): GlobalConfig | null {\n return this._config;\n }\n\n /**\n * Get device capabilities (width, height, color scheme, rotation).\n */\n get capabilities(): DeviceCapabilities | null {\n return this._capabilities;\n }\n\n /**\n * Get display width in pixels.\n */\n get width(): number {\n return this.ensureCapabilities().width;\n }\n\n /**\n * Get display height in pixels.\n */\n get height(): number {\n return this.ensureCapabilities().height;\n }\n\n /**\n * Get display color scheme.\n */\n get colorScheme(): ColorScheme {\n return this.ensureCapabilities().colorScheme;\n }\n\n /**\n * Get display rotation in degrees.\n */\n get rotation(): number {\n return this.ensureCapabilities().rotation ?? 0;\n }\n\n /**\n * Check if currently connected to a device.\n */\n get isConnected(): boolean {\n return this.connection?.isConnected ?? false;\n }\n\n /**\n * Connect to an OpenDisplay device and optionally interrogate.\n *\n * @param connectionOptions - Optional connection parameters\n * @throws {BLEConnectionError} If connection fails\n */\n async connect(connectionOptions?: BLEConnectionOptions): Promise<void> {\n // Create connection\n this.connection = new BLEConnection();\n\n // Merge provided options with constructor options\n const mergedOptions: BLEConnectionOptions = {\n ...connectionOptions,\n device: this.options.device ?? connectionOptions?.device,\n namePrefix: this.options.namePrefix ?? connectionOptions?.namePrefix,\n };\n\n await this.connection.connect(mergedOptions);\n\n // Auto-interrogate if no config or capabilities provided\n if (!this._config && !this._capabilities) {\n console.log('No config provided, auto-interrogating device');\n await this.interrogate();\n }\n\n // Extract capabilities from config if available\n if (this._config && !this._capabilities) {\n this._capabilities = this.extractCapabilitiesFromConfig();\n }\n }\n\n /**\n * Disconnect from the device.\n */\n async disconnect(): Promise<void> {\n if (this.connection) {\n await this.connection.disconnect();\n this.connection = null;\n }\n }\n\n /**\n * Read device configuration from device.\n *\n * @returns GlobalConfig with complete device configuration\n * @throws {ProtocolError} If interrogation fails\n */\n async interrogate(): Promise<GlobalConfig> {\n this.ensureConnected();\n\n console.log('Interrogating device');\n\n // Send read config command\n const cmd = buildReadConfigCommand();\n await this.connection!.writeCommand(cmd);\n\n // Read first chunk\n const response = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_FIRST_CHUNK\n );\n const chunkData = stripCommandEcho(response, CommandCode.READ_CONFIG);\n\n // Parse first chunk header\n const view = new DataView(chunkData.buffer, chunkData.byteOffset);\n const totalLength = view.getUint16(2, true); // little-endian\n const tlvData: Uint8Array[] = [chunkData.subarray(4)];\n let currentLength = chunkData.length - 4;\n\n console.debug(`First chunk: ${chunkData.length} bytes, total length: ${totalLength}`);\n\n // Read remaining chunks\n while (currentLength < totalLength) {\n const nextResponse = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_CHUNK\n );\n const nextChunkData = stripCommandEcho(nextResponse, CommandCode.READ_CONFIG);\n\n // Skip chunk number field (2 bytes) and append data\n tlvData.push(nextChunkData.subarray(2));\n currentLength += nextChunkData.length - 2;\n\n console.debug(`Received chunk, total: ${currentLength}/${totalLength} bytes`);\n }\n\n console.log(`Received complete TLV data: ${currentLength} bytes`);\n\n // Concatenate all chunks\n const completeData = new Uint8Array(currentLength);\n let offset = 0;\n for (const chunk of tlvData) {\n completeData.set(chunk, offset);\n offset += chunk.length;\n }\n\n // Parse complete config response (handles wrapper strip)\n this._config = parseConfigResponse(completeData);\n this._capabilities = this.extractCapabilitiesFromConfig();\n\n console.log(\n `Interrogated device: ${this.width}x${this.height}, ` +\n `${ColorScheme[this.colorScheme]}, rotation=${this.rotation}°`\n );\n\n return this._config;\n }\n\n /**\n * Read firmware version from device.\n *\n * @returns FirmwareVersion with major, minor, and sha fields\n */\n async readFirmwareVersion(): Promise<FirmwareVersion> {\n this.ensureConnected();\n\n console.log('Reading firmware version');\n\n // Send read firmware version command\n const cmd = buildReadFwVersionCommand();\n await this.connection!.writeCommand(cmd);\n\n // Read response\n const response = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_ACK\n );\n\n // Parse version (includes SHA hash)\n this._fwVersion = parseFirmwareVersion(response);\n\n console.log(\n `Firmware version: ${this._fwVersion.major}.${this._fwVersion.minor} ` +\n `(SHA: ${this._fwVersion.sha.substring(0, 8)}...)`\n );\n\n return this._fwVersion;\n }\n\n /**\n * Reboot the device.\n *\n * Sends a reboot command to the device, which will cause an immediate\n * system reset. The device will NOT send an ACK response - it simply\n * resets after a 100ms delay.\n *\n * Warning: The BLE connection will be forcibly terminated when the device\n * resets. This is expected behavior. The device will restart and begin\n * advertising again after the reset completes (typically within a few seconds).\n *\n * @throws {BLEConnectionError} If command cannot be sent\n */\n async reboot(): Promise<void> {\n this.ensureConnected();\n\n console.log('Sending reboot command to device');\n\n // Build and send reboot command\n const cmd = buildRebootCommand();\n await this.connection!.writeCommand(cmd);\n\n // Device will reset immediately - no ACK expected\n console.log('Reboot command sent - device will reset (connection will drop)');\n }\n\n /**\n * Write configuration to device.\n *\n * Serializes the GlobalConfig to TLV binary format and writes it\n * to the device using the WRITE_CONFIG (0x0041) command with\n * automatic chunking for large configs.\n *\n * @param config - GlobalConfig to write to device\n * @throws {Error} If config serialization fails or exceeds size limit\n * @throws {BLEConnectionError} If write fails\n * @throws {ProtocolError} If device returns error response\n *\n * @example\n * ```typescript\n * // Read current config\n * const config = device.config;\n *\n * // Modify config\n * config.displays[0].rotation = 180;\n *\n * // Write back to device\n * await device.writeConfig(config);\n *\n * // Reboot to apply changes\n * await device.reboot();\n * ```\n */\n async writeConfig(config: GlobalConfig): Promise<void> {\n this.ensureConnected();\n\n console.log('Writing config to device');\n\n // Validate critical packets are present\n if (!config.system) {\n console.warn('Config missing system packet - device may not boot correctly');\n }\n if (!config.displays || config.displays.length === 0) {\n throw new Error('Config must have at least one display');\n }\n\n // Warn about optional but important packets\n const missingPackets: string[] = [];\n if (!config.manufacturer) {\n missingPackets.push('manufacturer');\n }\n if (!config.power) {\n missingPackets.push('power');\n }\n\n if (missingPackets.length > 0) {\n console.warn(\n `Config missing optional packets: ${missingPackets.join(', ')}. ` +\n 'Device may lose these settings.'\n );\n }\n\n // Serialize config to binary\n const configData = serializeConfig(config);\n\n console.log(\n `Serialized config: ${configData.length} bytes ` +\n `(chunking ${configData.length > 200 ? 'required' : 'not needed'})`\n );\n\n // Build command with chunking\n const [firstCmd, chunkCmds] = buildWriteConfigCommand(configData);\n\n // Send first command\n console.debug(`Sending first config chunk (${firstCmd.length} bytes)`);\n await this.connection!.writeCommand(firstCmd);\n\n // Wait for ACK\n let response = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_ACK\n );\n validateAckResponse(response, CommandCode.WRITE_CONFIG);\n\n // Send remaining chunks if needed\n for (let i = 0; i < chunkCmds.length; i++) {\n const chunkCmd = chunkCmds[i];\n console.debug(\n `Sending config chunk ${i + 1}/${chunkCmds.length} (${chunkCmd.length} bytes)`\n );\n await this.connection!.writeCommand(chunkCmd);\n\n // Wait for ACK after each chunk\n response = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_ACK\n );\n validateAckResponse(response, CommandCode.WRITE_CONFIG_CHUNK);\n }\n\n console.log('Config written successfully');\n }\n\n /**\n * Upload image to device display.\n *\n * Automatically handles:\n * - Image resizing to display dimensions\n * - Dithering based on color scheme\n * - Encoding to device format\n * - Compression\n * - Direct write protocol\n *\n * @param imageData - Image as ImageData (from canvas or OffscreenCanvas)\n * @param options - Upload options\n * @throws {Error} If device not interrogated/configured\n * @throws {ProtocolError} If upload fails\n *\n * @example\n * ```typescript\n * const canvas = document.getElementById('myCanvas') as HTMLCanvasElement;\n * const ctx = canvas.getContext('2d')!;\n * const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);\n *\n * await device.uploadImage(imageData, {\n * refreshMode: RefreshMode.FULL,\n * ditherMode: DitherMode.BURKES,\n * compress: true,\n * onProgress: (current, total, stage) => {\n * console.log(`${stage}: ${current}/${total} (${Math.floor(current/total*100)}%)`);\n * }\n * });\n * ```\n */\n async uploadImage(\n imageData: ImageData,\n options: {\n refreshMode?: RefreshMode;\n ditherMode?: DitherMode;\n compress?: boolean;\n onProgress?: (current: number, total: number, stage: string) => void;\n onStatusChange?: (message: string) => void;\n } = {}\n ): Promise<void> {\n this.ensureConnected();\n this.ensureCapabilities();\n\n const refreshMode = options.refreshMode ?? RefreshMode.FULL;\n const ditherMode = options.ditherMode ?? DitherMode.BURKES;\n const compress = options.compress ?? true;\n const onProgress = options.onProgress;\n const onStatusChange = options.onStatusChange;\n\n console.log(\n `Uploading image (${this.width}x${this.height}, ${ColorScheme[this.colorScheme]})`\n );\n\n // Prepare image (resize, dither, encode)\n onStatusChange?.('Preparing image...');\n const encodedData = prepareImageForUpload(\n imageData,\n this.width,\n this.height,\n this.colorScheme,\n ditherMode\n );\n\n // Choose protocol based on compression and size\n let compressedData: Uint8Array | null = null;\n if (compress) {\n onStatusChange?.('Compressing...');\n compressedData = compressImageData(encodedData, 6);\n\n if (compressedData.length < MAX_COMPRESSED_SIZE) {\n console.log(`Using compressed upload protocol (size: ${compressedData.length} bytes)`);\n onStatusChange?.('Uploading...');\n await this.executeUpload({\n imageData: encodedData,\n refreshMode,\n useCompression: true,\n compressedData,\n uncompressedSize: encodedData.length,\n onProgress,\n onStatusChange,\n });\n } else {\n console.log(\n `Compressed size exceeds ${MAX_COMPRESSED_SIZE} bytes, using uncompressed protocol`\n );\n onStatusChange?.('Uploading...');\n await this.executeUpload({\n imageData: encodedData,\n refreshMode,\n onProgress,\n onStatusChange,\n });\n }\n } else {\n console.log('Compression disabled, using uncompressed protocol');\n onStatusChange?.('Uploading...');\n await this.executeUpload({\n imageData: encodedData,\n refreshMode,\n onProgress,\n onStatusChange,\n });\n }\n\n console.log('Image upload complete');\n onStatusChange?.('Upload complete!');\n }\n\n /**\n * Execute image upload using compressed or uncompressed protocol.\n */\n private async executeUpload(params: {\n imageData: Uint8Array;\n refreshMode: RefreshMode;\n useCompression?: boolean;\n compressedData?: Uint8Array;\n uncompressedSize?: number;\n onProgress?: (current: number, total: number, stage: string) => void;\n onStatusChange?: (message: string) => void;\n }): Promise<void> {\n const {\n imageData,\n refreshMode,\n useCompression = false,\n compressedData,\n uncompressedSize,\n onProgress,\n onStatusChange,\n } = params;\n\n // 1. Send START command (different for each protocol)\n let startCmd: Uint8Array;\n let remainingCompressed: Uint8Array | null = null;\n\n if (useCompression && compressedData && uncompressedSize) {\n [startCmd, remainingCompressed] = buildDirectWriteStartCompressed(\n uncompressedSize,\n compressedData\n );\n } else {\n startCmd = buildDirectWriteStartUncompressed();\n }\n\n await this.connection!.writeCommand(startCmd);\n\n // 2. Wait for START ACK (identical for both protocols)\n let response = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_ACK\n );\n validateAckResponse(response, CommandCode.DIRECT_WRITE_START);\n\n // 3. Send data chunks\n let autoCompleted = false;\n if (useCompression && remainingCompressed && remainingCompressed.length > 0) {\n // Compressed upload: send remaining compressed data as chunks\n autoCompleted = await this.sendDataChunks(remainingCompressed, onProgress, onStatusChange);\n } else if (!useCompression) {\n // Uncompressed upload: send raw image data as chunks\n autoCompleted = await this.sendDataChunks(imageData, onProgress, onStatusChange);\n }\n\n // 4. Send END command if needed (identical for both protocols)\n if (!autoCompleted) {\n onStatusChange?.('Refreshing display...');\n const endCmd = buildDirectWriteEndCommand(refreshMode);\n await this.connection!.writeCommand(endCmd);\n\n // Wait for END ACK (90s timeout for display refresh)\n response = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_REFRESH\n );\n validateAckResponse(response, CommandCode.DIRECT_WRITE_END);\n }\n }\n\n /**\n * Send image data chunks with ACK handling.\n *\n * Sends image data in chunks via 0x0071 DATA commands. Handles:\n * - Timeout recovery when firmware starts display refresh\n * - Auto-completion detection (firmware sends 0x0072 END early)\n * - Progress logging\n *\n * @param imageData - Data to send in chunks\n * @param onProgress - Optional progress callback (current bytes, total bytes, stage)\n * @param onStatusChange - Optional status message callback\n * @returns True if device auto-completed (sent 0x0072 END early), false if all chunks sent normally\n * @throws {ProtocolError} If unexpected response received\n * @throws {BLETimeoutError} If no response within timeout\n */\n private async sendDataChunks(\n imageData: Uint8Array,\n onProgress?: (current: number, total: number, stage: string) => void,\n onStatusChange?: (message: string) => void\n ): Promise<boolean> {\n let bytesSent = 0;\n let chunksSent = 0;\n\n while (bytesSent < imageData.length) {\n // Get next chunk\n const chunkStart = bytesSent;\n const chunkEnd = Math.min(chunkStart + CHUNK_SIZE, imageData.length);\n const chunkData = imageData.subarray(chunkStart, chunkEnd);\n\n // Send DATA command\n const dataCmd = buildDirectWriteDataCommand(chunkData);\n await this.connection!.writeCommand(dataCmd);\n\n bytesSent += chunkData.length;\n chunksSent++;\n\n // Wait for response after every chunk (PIPELINE_CHUNKS=1)\n let response: Uint8Array;\n try {\n response = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_ACK\n );\n } catch (error) {\n if (error instanceof BLETimeoutError) {\n // Timeout on response - firmware might be doing display refresh\n console.log(\n `No response after chunk ${chunksSent} ` +\n `(${((bytesSent / imageData.length) * 100).toFixed(1)}%), ` +\n 'waiting for device refresh...'\n );\n onStatusChange?.('Refreshing display...');\n\n // Wait up to 90 seconds for the END response\n response = await this.connection!.readResponse(\n OpenDisplayDevice.TIMEOUT_REFRESH\n );\n } else {\n throw error;\n }\n }\n\n // Check what response we got (firmware can send 0x0072 on ANY chunk, not just last!)\n const [command, isAck] = checkResponseType(response);\n\n if (command === CommandCode.DIRECT_WRITE_DATA) {\n // Normal DATA ACK (0x0071) - continue sending chunks\n // Report progress\n onProgress?.(bytesSent, imageData.length, 'upload');\n } else if (command === CommandCode.DIRECT_WRITE_END) {\n // Firmware auto-triggered END (0x0072) after receiving all data\n console.log(\n `Received END response after chunk ${chunksSent} - device auto-completed`\n );\n onProgress?.(imageData.length, imageData.length, 'upload');\n // Note: 0x0072 is sent AFTER display refresh completes\n // So we're already done - no need to send our own 0x0072 END command!\n return true; // Auto-completed\n } else {\n // Unexpected response\n throw new ProtocolError(\n `Unexpected response: ${CommandCode[command]} (0x${command.toString(16).padStart(4, '0')})`\n );\n }\n\n // Log progress every 50 chunks to reduce spam\n if (chunksSent % 50 === 0 || bytesSent >= imageData.length) {\n console.debug(\n `Sent ${bytesSent}/${imageData.length} bytes ` +\n `(${((bytesSent / imageData.length) * 100).toFixed(1)}%)`\n );\n }\n }\n\n console.debug(`All data chunks sent (${chunksSent} chunks total)`);\n return false; // Normal completion, caller should send END\n }\n\n /**\n * Extract DeviceCapabilities from GlobalConfig.\n */\n private extractCapabilitiesFromConfig(): DeviceCapabilities {\n if (!this._config) {\n throw new Error('No config available');\n }\n\n if (!this._config.displays || this._config.displays.length === 0) {\n throw new Error('Config has no display information');\n }\n\n const display = this._config.displays[0]; // Primary display\n\n return {\n width: display.pixelWidth,\n height: display.pixelHeight,\n colorScheme: display.colorScheme as ColorScheme,\n rotation: display.rotation,\n };\n }\n\n /**\n * Ensure device capabilities are available.\n */\n private ensureCapabilities(): DeviceCapabilities {\n if (!this._capabilities) {\n throw new Error(\n 'Device capabilities unknown - interrogate first or provide config/capabilities'\n );\n }\n return this._capabilities;\n }\n\n /**\n * Ensure device is connected.\n */\n private ensureConnected(): void {\n if (!this.connection || !this.isConnected) {\n throw new BLEConnectionError('Not connected to device');\n }\n }\n}","/**\n * Image encoding for e-paper displays.\n */\n\nimport {\n ColorScheme,\n DitherMode,\n ditherImage,\n type PaletteImageBuffer,\n} from '@opendisplay/epaper-dithering';\nimport { ImageEncodingError } from '../exceptions';\nimport { encodeBitplanes } from './bitplanes';\n\n/**\n * Encode palette image to display format based on color scheme.\n *\n * @param paletteImage - Dithered palette image\n * @param colorScheme - Display color scheme\n * @returns Encoded image bytes\n * @throws {ImageEncodingError} For unsupported color schemes\n */\nexport function encodeImage(\n paletteImage: PaletteImageBuffer,\n colorScheme: ColorScheme\n): Uint8Array {\n switch (colorScheme) {\n case ColorScheme.MONO:\n return encode1bpp(paletteImage);\n\n case ColorScheme.BWR:\n case ColorScheme.BWY:\n throw new ImageEncodingError(\n `Color scheme ${ColorScheme[colorScheme]} requires bitplane encoding, ` +\n 'use encodeBitplanes() instead'\n );\n\n case ColorScheme.BWRY:\n return encode2bpp(paletteImage);\n\n case ColorScheme.BWGBRY:\n // 6-color Spectra 6 display uses 4bpp with special firmware mapping\n return encode4bpp(paletteImage, true);\n\n case ColorScheme.GRAYSCALE_4:\n return encode2bpp(paletteImage);\n\n default:\n throw new ImageEncodingError(\n `Unsupported color scheme: ${ColorScheme[colorScheme]}`\n );\n }\n}\n\n/**\n * Encode image to 1-bit-per-pixel format (monochrome).\n *\n * Format: 8 pixels per byte, MSB first\n * Palette index 0 = black (0), index 1 = white (1)\n *\n * @param paletteImage - Palette image\n * @returns Encoded bytes\n */\nexport function encode1bpp(paletteImage: PaletteImageBuffer): Uint8Array {\n const { width, height, indices: pixels } = paletteImage;\n\n // Calculate output size (round up to byte boundary)\n const bytesPerRow = Math.ceil(width / 8);\n const output = new Uint8Array(bytesPerRow * height);\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const byteIdx = y * bytesPerRow + Math.floor(x / 8);\n const bitIdx = 7 - (x % 8); // MSB first\n\n if (pixels[y * width + x] > 0) {\n // Non-zero palette index = white\n output[byteIdx] |= 1 << bitIdx;\n }\n }\n }\n\n return output;\n}\n\n/**\n * Encode image to 2-bits-per-pixel format (4 colors).\n *\n * Format: 4 pixels per byte, MSB first\n * Each 2-bit value maps to palette index (0-3)\n *\n * @param paletteImage - Palette image\n * @returns Encoded bytes\n */\nexport function encode2bpp(paletteImage: PaletteImageBuffer): Uint8Array {\n const { width, height, indices: pixels } = paletteImage;\n\n // Calculate output size (round up to 4-pixel boundary)\n const bytesPerRow = Math.ceil(width / 4);\n const output = new Uint8Array(bytesPerRow * height);\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const byteIdx = y * bytesPerRow + Math.floor(x / 4);\n const pixelInByte = x % 4;\n const bitShift = (3 - pixelInByte) * 2; // MSB first\n\n const paletteIdx = pixels[y * width + x] & 0x03; // 2-bit value\n output[byteIdx] |= paletteIdx << bitShift;\n }\n }\n\n return output;\n}\n\n/**\n * Encode image to 4-bits-per-pixel format (16 colors).\n *\n * Format: 2 pixels per byte, MSB first\n * Each 4-bit value maps to palette index (0-15)\n *\n * @param paletteImage - Palette image\n * @param bwgbryMapping - If true, remap palette indices for BWGBRY firmware\n * (0→0, 1→1, 2→2, 3→3, 4→5, 5→6)\n * @returns Encoded bytes\n */\nexport function encode4bpp(\n paletteImage: PaletteImageBuffer,\n bwgbryMapping: boolean = false\n): Uint8Array {\n const { width, height, indices: pixels } = paletteImage;\n\n // BWGBRY firmware color mapping (Spectra 6 display)\n // Palette indices to firmware values: 0→0, 1→1, 2→2, 3→3, 4→5, 5→6\n const BWGBRY_MAP: Record<number, number> = { 0: 0, 1: 1, 2: 2, 3: 3, 4: 5, 5: 6 };\n\n // Calculate output size (round up to 2-pixel boundary)\n const bytesPerRow = Math.ceil(width / 2);\n const output = new Uint8Array(bytesPerRow * height);\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const byteIdx = y * bytesPerRow + Math.floor(x / 2);\n const pixelInByte = x % 2;\n\n let paletteIdx = pixels[y * width + x] & 0x0f; // 4-bit value\n\n // Apply BWGBRY mapping if needed\n if (bwgbryMapping && paletteIdx in BWGBRY_MAP) {\n paletteIdx = BWGBRY_MAP[paletteIdx];\n }\n\n const bitShift = (1 - pixelInByte) * 4; // MSB first\n output[byteIdx] |= paletteIdx << bitShift;\n }\n }\n\n return output;\n}\n\n/**\n * Prepare image for upload: resize, dither, and encode.\n *\n * @param imageData - Input image as ImageData\n * @param targetWidth - Target display width\n * @param targetHeight - Target display height\n * @param colorScheme - Display color scheme\n * @param ditherMode - Dithering algorithm to use\n * @returns Encoded image data ready for upload\n */\nexport function prepareImageForUpload(\n imageData: ImageData,\n targetWidth: number,\n targetHeight: number,\n colorScheme: ColorScheme,\n ditherMode: DitherMode = DitherMode.BURKES\n): Uint8Array {\n // Resize if needed\n let resizedImageData = imageData;\n if (imageData.width !== targetWidth || imageData.height !== targetHeight) {\n console.warn(\n `Resizing image from ${imageData.width}x${imageData.height} to ${targetWidth}x${targetHeight}`\n );\n resizedImageData = resizeImageData(imageData, targetWidth, targetHeight);\n }\n\n // Apply dithering\n const paletteImage = ditherImage(resizedImageData, colorScheme, ditherMode);\n\n // Encode based on color scheme\n if (colorScheme === ColorScheme.BWR || colorScheme === ColorScheme.BWY) {\n // For BWR/BWY, encode to bitplanes and concatenate\n const [plane1, plane2] = encodeBitplanes(paletteImage, colorScheme);\n const result = new Uint8Array(plane1.length + plane2.length);\n result.set(plane1, 0);\n result.set(plane2, plane1.length);\n return result;\n } else {\n return encodeImage(paletteImage, colorScheme);\n }\n}\n\n/**\n * Resize ImageData to target dimensions using canvas.\n *\n * @param imageData - Source image data\n * @param targetWidth - Target width\n * @param targetHeight - Target height\n * @returns Resized image data\n */\nfunction resizeImageData(\n imageData: ImageData,\n targetWidth: number,\n targetHeight: number\n): ImageData {\n // Create offscreen canvas for resizing\n const canvas = new OffscreenCanvas(imageData.width, imageData.height);\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n throw new ImageEncodingError('Failed to get canvas context');\n }\n\n // Draw source image\n ctx.putImageData(imageData, 0, 0);\n\n // Create target canvas\n const targetCanvas = new OffscreenCanvas(targetWidth, targetHeight);\n const targetCtx = targetCanvas.getContext('2d');\n if (!targetCtx) {\n throw new ImageEncodingError('Failed to get target canvas context');\n }\n\n // Resize (browser handles interpolation)\n targetCtx.drawImage(canvas, 0, 0, targetWidth, targetHeight);\n\n return targetCtx.getImageData(0, 0, targetWidth, targetHeight);\n}","/**\n * Exception classes for OpenDisplay library.\n */\n\nexport class OpenDisplayError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'OpenDisplayError';\n }\n}\n\nexport class BLEConnectionError extends OpenDisplayError {\n constructor(message: string) {\n super(message);\n this.name = 'BLEConnectionError';\n }\n}\n\nexport class BLETimeoutError extends OpenDisplayError {\n constructor(message: string) {\n super(message);\n this.name = 'BLETimeoutError';\n }\n}\n\nexport class ProtocolError extends OpenDisplayError {\n constructor(message: string) {\n super(message);\n this.name = 'ProtocolError';\n }\n}\n\nexport class ConfigParseError extends ProtocolError {\n constructor(message: string) {\n super(message);\n this.name = 'ConfigParseError';\n }\n}\n\nexport class InvalidResponseError extends ProtocolError {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidResponseError';\n }\n}\n\nexport class ImageEncodingError extends OpenDisplayError {\n constructor(message: string) {\n super(message);\n this.name = 'ImageEncodingError';\n }\n}","/**\n * Bitplane encoding for multi-color e-paper displays.\n */\n\nimport { ColorScheme, PaletteImageBuffer } from '@opendisplay/epaper-dithering';\nimport { ImageEncodingError } from '../exceptions';\n\n/**\n * Encode image to bitplane format for BWR/BWY displays.\n *\n * BWR/BWY displays use two bitplanes:\n * - Plane 1 (BW): Black/White layer\n * - Plane 2 (R/Y): Red/Yellow accent color layer\n *\n * Palette mapping:\n * - Index 0 = Black -> BW=0, R/Y=0\n * - Index 1 = White -> BW=1, R/Y=0\n * - Index 2 = Red/Yellow -> BW=0, R/Y=1\n *\n * @param paletteImage - Dithered palette image from epaper-dithering\n * @param colorScheme - Must be BWR or BWY\n * @returns Tuple of [plane1Bytes, plane2Bytes]\n * @throws {ImageEncodingError} If color scheme is not BWR or BWY\n */\nexport function encodeBitplanes(\n paletteImage: PaletteImageBuffer,\n colorScheme: ColorScheme\n): [Uint8Array, Uint8Array] {\n if (colorScheme !== ColorScheme.BWR && colorScheme !== ColorScheme.BWY) {\n throw new ImageEncodingError(\n `Bitplane encoding only supports BWR/BWY, got ${ColorScheme[colorScheme]}`\n );\n }\n\n const { width, height, indices: pixels } = paletteImage;\n\n // Calculate output size (1bpp, 8 pixels per byte)\n const bytesPerRow = Math.ceil(width / 8);\n const plane1 = new Uint8Array(bytesPerRow * height); // BW plane\n const plane2 = new Uint8Array(bytesPerRow * height); // R/Y plane\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const byteIdx = y * bytesPerRow + Math.floor(x / 8);\n const bitIdx = 7 - (x % 8); // MSB first\n\n const paletteIdx = pixels[y * width + x];\n\n if (paletteIdx === 1) {\n // White - set BW plane\n plane1[byteIdx] |= 1 << bitIdx;\n } else if (paletteIdx === 2) {\n // Red/Yellow - set R/Y plane\n plane2[byteIdx] |= 1 << bitIdx;\n }\n // else: paletteIdx === 0 (black) - both planes stay 0\n }\n }\n\n return [plane1, plane2];\n}","/**\n * Image compression for BLE transfer using pako (zlib).\n */\n\nimport pako from 'pako';\n\n/**\n * Compress image data using zlib.\n *\n * @param data - Raw image data\n * @param level - Compression level (0-9, default: 6)\n * - 0 = no compression\n * - 1 = fastest\n * - 6 = default balance\n * - 9 = best compression\n * @returns Compressed data\n */\nexport function compressImageData(\n data: Uint8Array,\n level: number = 6\n): Uint8Array {\n if (level === 0) {\n return data;\n }\n\n const compressed = pako.deflate(data, { level: level as pako.DeflateOptions['level'] });\n\n const ratio = data.length > 0 ? (compressed.length / data.length) * 100 : 0;\n console.debug(\n `Compressed ${data.length} bytes -> ${compressed.length} bytes (${ratio.toFixed(1)}%)`\n );\n\n return compressed;\n}\n\n/**\n * Decompress zlib-compressed image data.\n *\n * @param data - Compressed data\n * @returns Decompressed data\n * @throws {Error} If decompression fails\n */\nexport function decompressImageData(data: Uint8Array): Uint8Array {\n return pako.inflate(data);\n}","/**\n * Enums for OpenDisplay device configuration.\n */\n\n/**\n * Display refresh modes.\n */\nexport enum RefreshMode {\n FULL = 0,\n FAST = 1,\n PARTIAL = 2,\n PARTIAL2 = 3,\n}\n\n/**\n * Microcontroller IC types.\n */\nexport enum ICType {\n NRF52840 = 1,\n ESP32_S3 = 2,\n ESP32_C3 = 3,\n ESP32_C6 = 4,\n}\n\n/**\n * Power source types.\n */\nexport enum PowerMode {\n BATTERY = 1,\n USB = 2,\n SOLAR = 3,\n}\n\n/**\n * Data bus types for sensors.\n */\nexport enum BusType {\n I2C = 0,\n SPI = 1,\n}\n\n/**\n * Display rotation angles in degrees.\n */\nexport enum Rotation {\n ROTATE_0 = 0,\n ROTATE_90 = 90,\n ROTATE_180 = 180,\n ROTATE_270 = 270,\n}","/**\n * BLE protocol constants for OpenDisplay devices.\n */\n\nexport const SERVICE_UUID = '00002446-0000-1000-8000-00805f9b34fb';\nexport const MANUFACTURER_ID = 0x2446;\nexport const RESPONSE_HIGH_BIT_FLAG = 0x8000;\n\n// Chunking constants\nexport const CHUNK_SIZE = 230; // Maximum data bytes per chunk\nexport const CONFIG_CHUNK_SIZE = 200; // Maximum config chunk size\nexport const PIPELINE_CHUNKS = 1; // Wait for ACK after each chunk\n\n// Upload protocol constants\nexport const MAX_COMPRESSED_SIZE = 50 * 1024; // 50KB firmware buffer limit\nexport const MAX_START_PAYLOAD = 200; // Maximum bytes in START command\n\n/**\n * BLE command codes for OpenDisplay protocol.\n */\nexport enum CommandCode {\n // Configuration commands\n READ_CONFIG = 0x0040,\n WRITE_CONFIG = 0x0041,\n WRITE_CONFIG_CHUNK = 0x0042,\n\n // Firmware commands\n READ_FW_VERSION = 0x0043,\n REBOOT = 0x000f,\n\n // Image upload commands (direct write mode)\n DIRECT_WRITE_START = 0x0070,\n DIRECT_WRITE_DATA = 0x0071,\n DIRECT_WRITE_END = 0x0072,\n}","/**\n * BLE protocol command builders for OpenDisplay devices.\n */\n\nimport {\n CommandCode,\n CHUNK_SIZE,\n CONFIG_CHUNK_SIZE,\n MAX_START_PAYLOAD,\n} from './constants';\n\n/**\n * Build command to read device TLV configuration.\n *\n * @returns Command bytes: 0x0040 (2 bytes, big-endian)\n */\nexport function buildReadConfigCommand(): Uint8Array {\n const buffer = new ArrayBuffer(2);\n const view = new DataView(buffer);\n view.setUint16(0, CommandCode.READ_CONFIG, false); // false = big-endian\n return new Uint8Array(buffer);\n}\n\n/**\n * Build command to read firmware version.\n *\n * @returns Command bytes: 0x0043 (2 bytes, big-endian)\n */\nexport function buildReadFwVersionCommand(): Uint8Array {\n const buffer = new ArrayBuffer(2);\n const view = new DataView(buffer);\n view.setUint16(0, CommandCode.READ_FW_VERSION, false);\n return new Uint8Array(buffer);\n}\n\n/**\n * Build command to reboot device.\n *\n * The device will perform an immediate system reset and will NOT send\n * an ACK response. The BLE connection will drop when the device resets.\n *\n * @returns Command bytes: 0x000F (2 bytes, big-endian)\n */\nexport function buildRebootCommand(): Uint8Array {\n const buffer = new ArrayBuffer(2);\n const view = new DataView(buffer);\n view.setUint16(0, CommandCode.REBOOT, false);\n return new Uint8Array(buffer);\n}\n\n/**\n * Build START command for compressed upload with chunking.\n *\n * To prevent BLE MTU issues, the START command is limited to MAX_START_PAYLOAD\n * bytes. For large compressed payloads, this returns:\n * - START command with header + first chunk of compressed data\n * - Remaining compressed data (to be sent via DATA chunks)\n *\n * @param uncompressedSize - Original uncompressed image size in bytes\n * @param compressedData - Complete compressed image data\n * @returns Tuple of [startCommand, remainingData]:\n * - startCommand: 0x0070 + uncompressed_size (4 bytes LE) + first chunk\n * - remainingData: Compressed data not included in START (empty if all fits)\n *\n * Format of START command:\n * [cmd:2][uncompressed_size:4][compressed_data:up to 194 bytes]\n * - cmd: 0x0070 (big-endian)\n * - uncompressed_size: Original size before compression (little-endian uint32)\n * - compressed_data: First chunk of compressed data\n */\nexport function buildDirectWriteStartCompressed(\n uncompressedSize: number,\n compressedData: Uint8Array\n): [Uint8Array, Uint8Array] {\n // Calculate max compressed data that fits in START\n // MAX_START_PAYLOAD = 200 total bytes\n // Header uses: 2 (cmd) + 4 (size) = 6 bytes\n // Remaining for compressed data: 200 - 6 = 194 bytes\n const maxDataInStart = MAX_START_PAYLOAD - 6; // 194 bytes\n\n const headerSize = 6;\n const totalSize =\n compressedData.length <= maxDataInStart\n ? headerSize + compressedData.length\n : MAX_START_PAYLOAD;\n\n const buffer = new ArrayBuffer(totalSize);\n const view = new DataView(buffer);\n\n // Command code (big-endian)\n view.setUint16(0, CommandCode.DIRECT_WRITE_START, false);\n\n // Uncompressed size (little-endian)\n view.setUint32(2, uncompressedSize, true);\n\n // Compressed data\n const startCommand = new Uint8Array(buffer);\n const dataLength = Math.min(compressedData.length, maxDataInStart);\n startCommand.set(compressedData.subarray(0, dataLength), 6);\n\n // Remaining data\n const remainingData =\n compressedData.length <= maxDataInStart\n ? new Uint8Array(0)\n : compressedData.subarray(maxDataInStart);\n\n return [startCommand, remainingData];\n}\n\n/**\n * Build START command for uncompressed upload protocol.\n *\n * This protocol sends NO data in START - all data follows via 0x0071 chunks.\n *\n * @returns Command bytes: 0x0070 (just the command, no data!)\n *\n * Format:\n * [cmd:2]\n * - cmd: 0x0070 (big-endian)\n * - NO size, NO data - everything sent via 0x0071 DATA chunks\n */\nexport function buildDirectWriteStartUncompressed(): Uint8Array {\n const buffer = new ArrayBuffer(2);\n const view = new DataView(buffer);\n view.setUint16(0, CommandCode.DIRECT_WRITE_START, false);\n return new Uint8Array(buffer);\n}\n\n/**\n * Build command to send image data chunk.\n *\n * @param chunkData - Image data chunk (max CHUNK_SIZE bytes)\n * @returns Command bytes: 0x0071 + chunk_data\n *\n * Format:\n * [cmd:2][data:230]\n * - cmd: 0x0071 (big-endian)\n * - data: Image data chunk\n */\nexport function buildDirectWriteDataCommand(chunkData: Uint8Array): Uint8Array {\n if (chunkData.length > CHUNK_SIZE) {\n throw new Error(\n `Chunk size ${chunkData.length} exceeds maximum ${CHUNK_SIZE}`\n );\n }\n\n const buffer = new ArrayBuffer(2 + chunkData.length);\n const view = new DataView(buffer);\n view.setUint16(0, CommandCode.DIRECT_WRITE_DATA, false);\n\n const result = new Uint8Array(buffer);\n result.set(chunkData, 2);\n\n return result;\n}\n\n/**\n * Build command to end image transfer and refresh display.\n *\n * @param refreshMode - Display refresh mode (0 = FULL, 1 = FAST/PARTIAL)\n * @returns Command bytes: 0x0072 + refresh_mode\n *\n * Format:\n * [cmd:2][refresh:1]\n * - cmd: 0x0072 (big-endian)\n * - refresh: Refresh mode (0=full, 1=fast)\n */\nexport function buildDirectWriteEndCommand(refreshMode: number = 0): Uint8Array {\n const buffer = new ArrayBuffer(3);\n const view = new DataView(buffer);\n view.setUint16(0, CommandCode.DIRECT_WRITE_END, false);\n view.setUint8(2, refreshMode);\n return new Uint8Array(buffer);\n}\n\n/**\n * Build WRITE_CONFIG command with chunking support.\n *\n * Protocol:\n * - Single chunk (≤200 bytes): [0x00][0x41][config_data]\n * - Multi-chunk (>200 bytes):\n * - First: [0x00][0x41][total_size:2LE][first_198_bytes]\n * - Rest: [0x00][0x42][chunk_data] (up to 200 bytes each)\n *\n * @param configData - Complete serialized config data\n * @returns Tuple of [firstCommand, remainingChunks]:\n * - firstCommand: 0x0041 command with first chunk\n * - remainingChunks: Array of 0x0042 commands for subsequent chunks\n */\nexport function buildWriteConfigCommand(\n configData: Uint8Array\n): [Uint8Array, Uint8Array[]] {\n const configLen = configData.length;\n\n // Single chunk mode (≤200 bytes)\n if (configLen <= CONFIG_CHUNK_SIZE) {\n const buffer = new ArrayBuffer(2 + configLen);\n const view = new DataView(buffer);\n view.setUint16(0, CommandCode.WRITE_CONFIG, false);\n\n const result = new Uint8Array(buffer);\n result.set(configData, 2);\n\n return [result, []];\n }\n\n // Multi-chunk mode (>200 bytes)\n // First chunk: [cmd][total_size:2LE][first_198_bytes]\n const firstChunkDataSize = CONFIG_CHUNK_SIZE - 2; // 198 bytes\n const firstBuffer = new ArrayBuffer(2 + 2 + firstChunkDataSize);\n const firstView = new DataView(firstBuffer);\n\n firstView.setUint16(0, CommandCode.WRITE_CONFIG, false);\n firstView.setUint16(2, configLen, true); // total size, little-endian\n\n const firstCommand = new Uint8Array(firstBuffer);\n firstCommand.set(configData.subarray(0, firstChunkDataSize), 4);\n\n // Remaining chunks: [cmd][chunk_data] (up to 200 bytes each)\n const chunks: Uint8Array[] = [];\n let offset = firstChunkDataSize;\n\n while (offset < configLen) {\n const chunkSize = Math.min(CONFIG_CHUNK_SIZE, configLen - offset);\n const buffer = new ArrayBuffer(2 + chunkSize);\n const view = new DataView(buffer);\n view.setUint16(0, CommandCode.WRITE_CONFIG_CHUNK, false);\n\n const chunk = new Uint8Array(buffer);\n chunk.set(configData.subarray(offset, offset + chunkSize), 2);\n chunks.push(chunk);\n\n offset += chunkSize;\n }\n\n return [firstCommand, chunks];\n}","/**\n * BLE response validation and parsing.\n */\n\nimport { InvalidResponseError } from '../exceptions';\nimport { FirmwareVersion } from '../models/firmware';\nimport { CommandCode, RESPONSE_HIGH_BIT_FLAG } from './constants';\n\n/**\n * Extract 2-byte big-endian command code from response data.\n *\n * @param data - Response data from device\n * @param offset - Byte offset to read from (default: 0)\n * @returns Command code as integer\n */\nexport function unpackCommandCode(data: Uint8Array, offset: number = 0): number {\n const view = new DataView(data.buffer, data.byteOffset + offset, 2);\n return view.getUint16(0, false); // false = big-endian\n}\n\n/**\n * Strip command echo from response data.\n *\n * Firmware echoes commands in responses, sometimes with high bit set.\n * This function removes the 2-byte echo if present.\n *\n * @param data - Response data from device\n * @param expectedCmd - Expected command echo\n * @returns Data with echo stripped (if present), otherwise original data\n */\nexport function stripCommandEcho(\n data: Uint8Array,\n expectedCmd: CommandCode\n): Uint8Array {\n if (data.length >= 2) {\n const echo = unpackCommandCode(data);\n if (echo === expectedCmd || echo === (expectedCmd | RESPONSE_HIGH_BIT_FLAG)) {\n return data.subarray(2);\n }\n }\n return data;\n}\n\n/**\n * Check response type and whether it's an ACK.\n *\n * @param response - Raw response data from device\n * @returns Tuple of [commandCode, isAck]\n * - commandCode: The command code (without high bit)\n * - isAck: True if response has high bit set (RESPONSE_HIGH_BIT_FLAG)\n */\nexport function checkResponseType(\n response: Uint8Array\n): [CommandCode, boolean] {\n const code = unpackCommandCode(response);\n const isAck = Boolean(code & RESPONSE_HIGH_BIT_FLAG);\n const command = (code & ~RESPONSE_HIGH_BIT_FLAG) as CommandCode;\n return [command, isAck];\n}\n\n/**\n * Validate ACK response from device.\n *\n * ACK responses echo the command code (sometimes with high bit set).\n *\n * @param data - Raw response data\n * @param expectedCommand - Command code that was sent\n * @throws {InvalidResponseError} If response invalid or doesn't match command\n */\nexport function validateAckResponse(\n data: Uint8Array,\n expectedCommand: number\n): void {\n if (data.length < 2) {\n throw new InvalidResponseError(\n `ACK too short: ${data.length} bytes (need at least 2)`\n );\n }\n\n const responseCode = unpackCommandCode(data);\n\n // Response can be exact echo or with high bit set (RESPONSE_HIGH_BIT_FLAG | cmd)\n const validResponses = new Set([\n expectedCommand,\n expectedCommand | RESPONSE_HIGH_BIT_FLAG,\n ]);\n\n if (!validResponses.has(responseCode)) {\n throw new InvalidResponseError(\n `ACK mismatch: expected 0x${expectedCommand.toString(16).padStart(4, '0')}, ` +\n `got 0x${responseCode.toString(16).padStart(4, '0')}`\n );\n }\n}\n\n/**\n * Parse firmware version response.\n *\n * Format: [echo:2][major:1][minor:1][shaLength:1][sha:variable]\n *\n * @param data - Raw firmware version response\n * @returns FirmwareVersion object with major, minor, and sha fields\n * @throws {InvalidResponseError} If response format invalid\n */\nexport function parseFirmwareVersion(data: Uint8Array): FirmwareVersion {\n if (data.length < 5) {\n throw new InvalidResponseError(\n `Firmware version response too short: ${data.length} bytes (need at least 5)`\n );\n }\n\n // Validate echo\n const echo = unpackCommandCode(data);\n if (\n echo !== 0x0043 &&\n echo !== (0x0043 | RESPONSE_HIGH_BIT_FLAG)\n ) {\n throw new InvalidResponseError(\n `Firmware version echo mismatch: expected 0x0043, ` +\n `got 0x${echo.toString(16).padStart(4, '0')}`\n );\n }\n\n const major = data[2];\n const minor = data[3];\n const shaLength = data[4];\n\n // SHA hash is always present in firmware responses\n if (shaLength === 0) {\n throw new InvalidResponseError(\n 'Firmware version missing SHA hash (shaLength is 0)'\n );\n }\n\n // Validate sufficient bytes for SHA\n const expectedTotalLength = 5 + shaLength;\n if (data.length < expectedTotalLength) {\n throw new InvalidResponseError(\n `Firmware version response incomplete: expected ${expectedTotalLength} bytes ` +\n `(5 header + ${shaLength} SHA), got ${data.length}`\n );\n }\n\n // Extract SHA bytes and decode as ASCII string\n const shaBytes = data.subarray(5, 5 + shaLength);\n const textDecoder = new TextDecoder('ascii');\n let sha: string;\n\n try {\n sha = textDecoder.decode(shaBytes);\n } catch (e) {\n throw new InvalidResponseError(\n `Invalid SHA hash encoding (expected ASCII): ${e}`\n );\n }\n\n return {\n major,\n minor,\n sha,\n };\n}","/**\n * TLV configuration data structures.\n *\n * These interfaces map directly to the firmware's TLV packet structures.\n * Reference: OpenDisplayFirmware/src/structs.h\n */\n\nimport { ColorScheme } from '@opendisplay/epaper-dithering';\nimport { BusType, ICType, PowerMode, Rotation } from './enums.js';\n\n/**\n * System configuration (TLV packet type 0x01).\n *\n * Size: 22 bytes (packed struct from firmware)\n */\nexport interface SystemConfig {\n /** uint16 - IC type identifier */\n icType: number;\n /** uint8 bitfield - Supported communication modes */\n communicationModes: number;\n /** uint8 bitfield - Device capability flags */\n deviceFlags: number;\n /** uint8 - External power pin (0xFF = none) */\n pwrPin: number;\n /** 17 bytes - Reserved for future use */\n reserved: Uint8Array;\n}\n\n/**\n * Helper functions for SystemConfig\n */\nexport namespace SystemConfig {\n export const SIZE = 22;\n\n /**\n * Check if device has external power management pin (DEVICE_FLAG_PWR_PIN).\n */\n export function hasPwrPin(config: SystemConfig): boolean {\n return !!(config.deviceFlags & 0x01);\n }\n\n /**\n * Check if xiaoinit() should be called after config load - nRF52840 only (DEVICE_FLAG_XIAOINIT).\n */\n export function needsXiaoinit(config: SystemConfig): boolean {\n return !!(config.deviceFlags & 0x02);\n }\n\n /**\n * Get IC type as enum, or raw int if unknown.\n */\n export function icTypeEnum(config: SystemConfig): ICType | number {\n if (Object.values(ICType).includes(config.icType)) {\n return config.icType as ICType;\n }\n return config.icType;\n }\n\n /**\n * Parse SystemConfig from TLV packet data.\n */\n export function fromBytes(data: Uint8Array): SystemConfig {\n if (data.length < SIZE) {\n throw new Error(`Invalid SystemConfig size: ${data.length} < ${SIZE}`);\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return {\n icType: view.getUint16(0, true), // little-endian\n communicationModes: view.getUint8(2),\n deviceFlags: view.getUint8(3),\n pwrPin: view.getUint8(4),\n reserved: data.slice(5, 22),\n };\n }\n}\n\n/**\n * Manufacturer data (TLV packet type 0x02).\n *\n * Size: 22 bytes (packed struct from firmware)\n */\nexport interface ManufacturerData {\n /** uint16 - Manufacturer identifier */\n manufacturerId: number;\n /** uint8 - Board type */\n boardType: number;\n /** uint8 - Board revision */\n boardRevision: number;\n /** 18 bytes - Reserved for future use */\n reserved: Uint8Array;\n}\n\n/**\n * Helper functions for ManufacturerData\n */\nexport namespace ManufacturerData {\n export const SIZE = 22;\n\n /**\n * Parse ManufacturerData from TLV packet data.\n */\n export function fromBytes(data: Uint8Array): ManufacturerData {\n if (data.length < SIZE) {\n throw new Error(`Invalid ManufacturerData size: ${data.length} < ${SIZE}`);\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return {\n manufacturerId: view.getUint16(0, true), // little-endian\n boardType: view.getUint8(2),\n boardRevision: view.getUint8(3),\n reserved: data.slice(4, 22),\n };\n }\n}\n\n/**\n * Power configuration (TLV packet type 0x04).\n *\n * Size: 30 bytes (packed struct from firmware - corrected from 32)\n */\nexport interface PowerOption {\n /** uint8 - Power mode identifier */\n powerMode: number;\n /** 3 bytes (24-bit value) - Battery capacity in mAh */\n batteryCapacityMah: number;\n /** uint16 - Sleep timeout in milliseconds */\n sleepTimeoutMs: number;\n /** uint8 - TX power level */\n txPower: number;\n /** uint8 bitfield - Sleep configuration flags */\n sleepFlags: number;\n /** uint8 - Battery sense pin (0xFF = none) */\n batterySensePin: number;\n /** uint8 - Battery sense enable pin (0xFF = none) */\n batterySenseEnablePin: number;\n /** uint8 bitfield - Battery sense flags */\n batterySenseFlags: number;\n /** uint8 - Capacity estimator type */\n capacityEstimator: number;\n /** uint16 - Voltage scaling factor */\n voltageScalingFactor: number;\n /** uint32 - Deep sleep current in microamps */\n deepSleepCurrentUa: number;\n /** uint16 - Deep sleep time in seconds */\n deepSleepTimeSeconds: number;\n /** 10 bytes - Reserved for future use */\n reserved: Uint8Array;\n}\n\n/**\n * Helper functions for PowerOption\n */\nexport namespace PowerOption {\n export const SIZE = 30;\n\n /**\n * Get battery capacity in mAh (already converted from 3-byte array).\n */\n export function batteryMah(config: PowerOption): number {\n return config.batteryCapacityMah;\n }\n\n /**\n * Get power mode as enum, or raw int if unknown.\n */\n export function powerModeEnum(config: PowerOption): PowerMode | number {\n if (Object.values(PowerMode).includes(config.powerMode)) {\n return config.powerMode as PowerMode;\n }\n return config.powerMode;\n }\n\n /**\n * Parse PowerOption from TLV packet data.\n */\n export function fromBytes(data: Uint8Array): PowerOption {\n if (data.length < SIZE) {\n throw new Error(`Invalid PowerOption size: ${data.length} < ${SIZE}`);\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n // Parse 3-byte battery capacity (little-endian)\n const batteryMah = data[1] | (data[2] << 8) | (data[3] << 16);\n\n return {\n powerMode: view.getUint8(0),\n batteryCapacityMah: batteryMah,\n sleepTimeoutMs: view.getUint16(4, true), // little-endian\n txPower: view.getInt8(6),\n sleepFlags: view.getUint8(7),\n batterySensePin: view.getUint8(8),\n batterySenseEnablePin: view.getUint8(9),\n batterySenseFlags: view.getUint8(10),\n capacityEstimator: view.getUint8(11),\n voltageScalingFactor: view.getUint16(12, true), // little-endian\n deepSleepCurrentUa: view.getUint32(14, true), // little-endian\n deepSleepTimeSeconds: view.getUint16(18, true), // little-endian\n reserved: data.slice(20, 30),\n };\n }\n}\n\n/**\n * Display configuration (TLV packet type 0x20, repeatable max 4).\n *\n * Size: 46 bytes (packed struct from firmware - corrected from 66)\n */\nexport interface DisplayConfig {\n /** uint8 - Display instance number (0-3) */\n instanceNumber: number;\n /** uint8 - Display technology type */\n displayTechnology: number;\n /** uint16 - Panel IC type */\n panelIcType: number;\n /** uint16 - Display width in pixels */\n pixelWidth: number;\n /** uint16 - Display height in pixels */\n pixelHeight: number;\n /** uint16 - Active area width in millimeters */\n activeWidthMm: number;\n /** uint16 - Active area height in millimeters */\n activeHeightMm: number;\n /** uint16 - Tag type (legacy) */\n tagType: number;\n /** uint8 - Display rotation in degrees */\n rotation: number;\n /** uint8 - Reset pin (0xFF = none) */\n resetPin: number;\n /** uint8 - Busy pin (0xFF = none) */\n busyPin: number;\n /** uint8 - Data/Command pin (0xFF = none) */\n dcPin: number;\n /** uint8 - Chip select pin (0xFF = none) */\n csPin: number;\n /** uint8 - Data pin */\n dataPin: number;\n /** uint8 - Partial update support level */\n partialUpdateSupport: number;\n /** uint8 - Color scheme identifier */\n colorScheme: number;\n /** uint8 bitfield - Supported transmission modes */\n transmissionModes: number;\n /** uint8 - Clock pin */\n clkPin: number;\n /** 7 bytes - Reserved pin slots */\n reservedPins: Uint8Array;\n /** 15 bytes - Reserved for future use */\n reserved: Uint8Array;\n}\n\n/**\n * Helper functions for DisplayConfig\n */\nexport namespace DisplayConfig {\n export const SIZE = 46;\n\n /**\n * Check if display supports raw image transmission (TRANSMISSION_MODE_RAW).\n */\n export function supportsRaw(config: DisplayConfig): boolean {\n return !!(config.transmissionModes & 0x01);\n }\n\n /**\n * Check if display supports ZIP compressed transmission (TRANSMISSION_MODE_ZIP).\n */\n export function supportsZip(config: DisplayConfig): boolean {\n return !!(config.transmissionModes & 0x02);\n }\n\n /**\n * Check if display supports Group 5 compression (TRANSMISSION_MODE_G5).\n */\n export function supportsG5(config: DisplayConfig): boolean {\n return !!(config.transmissionModes & 0x04);\n }\n\n /**\n * Check if display supports direct write mode - bufferless (TRANSMISSION_MODE_DIRECT_WRITE).\n */\n export function supportsDirectWrite(config: DisplayConfig): boolean {\n return !!(config.transmissionModes & 0x08);\n }\n\n /**\n * Check if display should clear screen at bootup (TRANSMISSION_MODE_CLEAR_ON_BOOT).\n */\n export function clearOnBoot(config: DisplayConfig): boolean {\n return !!(config.transmissionModes & 0x80);\n }\n\n /**\n * Get color scheme as enum, or raw int if unknown.\n */\n export function colorSchemeEnum(config: DisplayConfig): ColorScheme | number {\n if (Object.values(ColorScheme).includes(config.colorScheme)) {\n return config.colorScheme as ColorScheme;\n }\n return config.colorScheme;\n }\n\n /**\n * Get rotation as enum, or raw int if unknown.\n */\n export function rotationEnum(config: DisplayConfig): Rotation | number {\n if (Object.values(Rotation).includes(config.rotation)) {\n return config.rotation as Rotation;\n }\n return config.rotation;\n }\n\n /**\n * Parse DisplayConfig from TLV packet data.\n */\n export function fromBytes(data: Uint8Array): DisplayConfig {\n if (data.length < SIZE) {\n throw new Error(`Invalid DisplayConfig size: ${data.length} < ${SIZE}`);\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return {\n instanceNumber: view.getUint8(0),\n displayTechnology: view.getUint8(1),\n panelIcType: view.getUint16(2, true), // little-endian\n pixelWidth: view.getUint16(4, true), // little-endian\n pixelHeight: view.getUint16(6, true), // little-endian\n activeWidthMm: view.getUint16(8, true), // little-endian\n activeHeightMm: view.getUint16(10, true), // little-endian\n tagType: view.getUint16(12, true), // little-endian\n rotation: view.getUint8(14),\n resetPin: view.getUint8(15),\n busyPin: view.getUint8(16),\n dcPin: view.getUint8(17),\n csPin: view.getUint8(18),\n dataPin: view.getUint8(19),\n partialUpdateSupport: view.getUint8(20),\n colorScheme: view.getUint8(21),\n transmissionModes: view.getUint8(22),\n clkPin: view.getUint8(23),\n reservedPins: data.slice(24, 31), // 7 pins\n reserved: data.slice(31, 46), // 15 bytes\n };\n }\n}\n\n/**\n * LED configuration (TLV packet type 0x21, repeatable max 4).\n *\n * Size: 22 bytes (packed struct from firmware)\n */\nexport interface LedConfig {\n /** uint8 - LED instance number */\n instanceNumber: number;\n /** uint8 - LED type */\n ledType: number;\n /** uint8 - Red channel pin */\n led1R: number;\n /** uint8 - Green channel pin */\n led2G: number;\n /** uint8 - Blue channel pin */\n led3B: number;\n /** uint8 - 4th channel pin */\n led4: number;\n /** uint8 bitfield - LED configuration flags */\n ledFlags: number;\n /** 15 bytes - Reserved for future use */\n reserved: Uint8Array;\n}\n\n/**\n * Helper functions for LedConfig\n */\nexport namespace LedConfig {\n export const SIZE = 22;\n\n /**\n * Parse LedConfig from TLV packet data.\n */\n export function fromBytes(data: Uint8Array): LedConfig {\n if (data.length < SIZE) {\n throw new Error(`Invalid LedConfig size: ${data.length} < ${SIZE}`);\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return {\n instanceNumber: view.getUint8(0),\n ledType: view.getUint8(1),\n led1R: view.getUint8(2),\n led2G: view.getUint8(3),\n led3B: view.getUint8(4),\n led4: view.getUint8(5),\n ledFlags: view.getUint8(6),\n reserved: data.slice(7, 22),\n };\n }\n}\n\n/**\n * Sensor configuration (TLV packet type 0x23, repeatable max 4).\n *\n * Size: 30 bytes (packed struct from firmware)\n */\nexport interface SensorData {\n /** uint8 - Sensor instance number */\n instanceNumber: number;\n /** uint16 - Sensor type identifier */\n sensorType: number;\n /** uint8 - Data bus ID */\n busId: number;\n /** 26 bytes - Reserved for future use */\n reserved: Uint8Array;\n}\n\n/**\n * Helper functions for SensorData\n */\nexport namespace SensorData {\n export const SIZE = 30;\n\n /**\n * Parse SensorData from TLV packet data.\n */\n export function fromBytes(data: Uint8Array): SensorData {\n if (data.length < SIZE) {\n throw new Error(`Invalid SensorData size: ${data.length} < ${SIZE}`);\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return {\n instanceNumber: view.getUint8(0),\n sensorType: view.getUint16(1, true), // little-endian\n busId: view.getUint8(3),\n reserved: data.slice(4, 30),\n };\n }\n}\n\n/**\n * Data bus configuration (TLV packet type 0x24, repeatable max 4).\n *\n * Size: 30 bytes (packed struct from firmware - corrected from 28)\n */\nexport interface DataBus {\n /** uint8 - Bus instance number */\n instanceNumber: number;\n /** uint8 - Bus type identifier */\n busType: number;\n /** uint8 - Pin 1 (SCL for I2C) */\n pin1: number;\n /** uint8 - Pin 2 (SDA for I2C) */\n pin2: number;\n /** uint8 - Pin 3 */\n pin3: number;\n /** uint8 - Pin 4 */\n pin4: number;\n /** uint8 - Pin 5 */\n pin5: number;\n /** uint8 - Pin 6 */\n pin6: number;\n /** uint8 - Pin 7 */\n pin7: number;\n /** uint32 - Bus speed in Hz */\n busSpeedHz: number;\n /** uint8 bitfield - Bus configuration flags */\n busFlags: number;\n /** uint8 bitfield - Pullup resistors configuration */\n pullups: number;\n /** uint8 bitfield - Pulldown resistors configuration */\n pulldowns: number;\n /** 14 bytes - Reserved for future use */\n reserved: Uint8Array;\n}\n\n/**\n * Helper functions for DataBus\n */\nexport namespace DataBus {\n export const SIZE = 30;\n\n /**\n * Get bus type as enum, or raw int if unknown.\n */\n export function busTypeEnum(config: DataBus): BusType | number {\n if (Object.values(BusType).includes(config.busType)) {\n return config.busType as BusType;\n }\n return config.busType;\n }\n\n /**\n * Parse DataBus from TLV packet data.\n */\n export function fromBytes(data: Uint8Array): DataBus {\n if (data.length < SIZE) {\n throw new Error(`Invalid DataBus size: ${data.length} < ${SIZE}`);\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return {\n instanceNumber: view.getUint8(0),\n busType: view.getUint8(1),\n pin1: view.getUint8(2),\n pin2: view.getUint8(3),\n pin3: view.getUint8(4),\n pin4: view.getUint8(5),\n pin5: view.getUint8(6),\n pin6: view.getUint8(7),\n pin7: view.getUint8(8),\n busSpeedHz: view.getUint32(9, true), // little-endian\n busFlags: view.getUint8(13),\n pullups: view.getUint8(14),\n pulldowns: view.getUint8(15),\n reserved: data.slice(16, 30),\n };\n }\n}\n\n/**\n * Binary inputs configuration (TLV packet type 0x25, repeatable max 4).\n *\n * Size: 30 bytes (packed struct from firmware - corrected from 29)\n */\nexport interface BinaryInputs {\n /** uint8 - Input instance number */\n instanceNumber: number;\n /** uint8 - Input type */\n inputType: number;\n /** uint8 - Display representation type */\n displayAs: number;\n /** 8 bytes - Reserved pin slots */\n reservedPins: Uint8Array;\n /** uint8 bitfield - Input configuration flags */\n inputFlags: number;\n /** uint8 bitfield - Invert flags */\n invert: number;\n /** uint8 bitfield - Pullup resistors configuration */\n pullups: number;\n /** uint8 bitfield - Pulldown resistors configuration */\n pulldowns: number;\n /** 15 bytes - Reserved for future use */\n reserved: Uint8Array;\n}\n\n/**\n * Helper functions for BinaryInputs\n */\nexport namespace BinaryInputs {\n export const SIZE = 30;\n\n /**\n * Parse BinaryInputs from TLV packet data.\n */\n export function fromBytes(data: Uint8Array): BinaryInputs {\n if (data.length < SIZE) {\n throw new Error(`Invalid BinaryInputs size: ${data.length} < ${SIZE}`);\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return {\n instanceNumber: view.getUint8(0),\n inputType: view.getUint8(1),\n displayAs: view.getUint8(2),\n reservedPins: data.slice(3, 11), // 8 pins\n inputFlags: view.getUint8(11),\n invert: view.getUint8(12),\n pullups: view.getUint8(13),\n pulldowns: view.getUint8(14),\n reserved: data.slice(15, 30),\n };\n }\n}\n\n/**\n * Complete device configuration parsed from TLV data.\n *\n * Corresponds to GlobalConfig struct in firmware.\n */\nexport interface GlobalConfig {\n /** System configuration (single instance) */\n system?: SystemConfig;\n /** Manufacturer data (single instance) */\n manufacturer?: ManufacturerData;\n /** Power configuration (single instance) */\n power?: PowerOption;\n\n /** Display configurations (max 4) */\n displays: DisplayConfig[];\n /** LED configurations (max 4) */\n leds: LedConfig[];\n /** Sensor configurations (max 4) */\n sensors: SensorData[];\n /** Data bus configurations (max 4) */\n dataBuses: DataBus[];\n /** Binary input configurations (max 4) */\n binaryInputs: BinaryInputs[];\n\n /** Configuration version from device */\n version: number;\n /** Minor version (not stored in device) */\n minorVersion: number;\n /** Whether config was successfully loaded */\n loaded: boolean;\n}","/**\n * TLV configuration parser for OpenDisplay devices.\n */\n\nimport { ConfigParseError } from '../exceptions.js';\nimport {\n BinaryInputs,\n DataBus,\n DisplayConfig,\n GlobalConfig,\n LedConfig,\n ManufacturerData,\n PowerOption,\n SensorData,\n SystemConfig,\n} from '../models/config.js';\n\n// TLV packet type IDs\nexport const PACKET_TYPE_SYSTEM = 0x01;\nexport const PACKET_TYPE_MANUFACTURER = 0x02;\nexport const PACKET_TYPE_POWER = 0x04;\nexport const PACKET_TYPE_DISPLAY = 0x20;\nexport const PACKET_TYPE_LED = 0x21;\nexport const PACKET_TYPE_SENSOR = 0x23;\nexport const PACKET_TYPE_DATABUS = 0x24;\nexport const PACKET_TYPE_BINARY_INPUT = 0x25;\n\n/**\n * Parse complete TLV config response from device.\n *\n * Firmware sends config data with a wrapper: [length:2][version:1][packets...][crc:2]\n * This function strips the wrapper and passes clean packet data to the TLV parser.\n *\n * @param rawData - Complete TLV data assembled from all BLE chunks\n * @returns Parsed GlobalConfig\n * @throws {ConfigParseError} If data is too short or invalid\n */\nexport function parseConfigResponse(rawData: Uint8Array): GlobalConfig {\n if (rawData.length < 5) {\n // Min: 2 (length) + 1 (version) + 0 (packets) + 2 (crc)\n throw new ConfigParseError(\n `Config data too short: ${rawData.length} bytes (need at least 5)`\n );\n }\n\n // Parse TLV wrapper header\n const view = new DataView(rawData.buffer, rawData.byteOffset, rawData.byteLength);\n const configLength = view.getUint16(0, true); // little-endian\n const configVersion = view.getUint8(2);\n\n console.debug(\n `TLV wrapper: length=${configLength} bytes, version=${configVersion}`\n );\n\n // Extract packet data (skip 3-byte header, ignore 2-byte CRC at end)\n let packetData: Uint8Array;\n if (rawData.length > 5) {\n packetData = rawData.slice(3, -2); // Skip header, ignore CRC\n } else {\n packetData = rawData.slice(3); // Skip header only\n }\n\n console.debug(`Packet data after wrapper strip: ${packetData.length} bytes`);\n\n // Parse TLV packets\n return parseTlvConfig(packetData, configVersion);\n}\n\n/**\n * Parse complete TLV configuration from device response.\n *\n * BLE format: [TLV packets...] (raw TLV data, no header)\n *\n * Each TLV packet: [packet_number:1][packet_type:1][data:fixed_size]\n *\n * @param data - Raw TLV data from device (after echo bytes stripped)\n * @param version - Config version from wrapper (default 1 if called directly)\n * @returns GlobalConfig with all parsed configuration\n * @throws {ConfigParseError} If parsing fails\n */\nexport function parseTlvConfig(data: Uint8Array, version: number = 1): GlobalConfig {\n if (data.length < 2) {\n throw new ConfigParseError(\n `TLV data too short: ${data.length} bytes (need at least 2)`\n );\n }\n\n console.debug(`Parsing TLV config, ${data.length} bytes`);\n\n // Parse TLV packets (OEPL format: [packet_number:1][packet_id:1][fixed_data])\n let offset = 0;\n const packets = new Map<string, Uint8Array>();\n\n while (offset < data.length - 1) {\n if (offset + 2 > data.length) {\n break; // Not enough data for packet header\n }\n\n const packetNumber = data[offset];\n const packetType = data[offset + 1];\n offset += 2;\n\n // Determine packet size based on type\n const packetSize = getPacketSize(packetType);\n if (packetSize === null) {\n console.warn(\n `Unknown packet type 0x${packetType.toString(16).padStart(2, '0')} at offset ${offset - 2}, skipping`\n );\n break;\n }\n\n // Extract packet data\n if (offset + packetSize > data.length) {\n throw new ConfigParseError(\n `Packet type 0x${packetType.toString(16).padStart(2, '0')} truncated: ` +\n `need ${packetSize} bytes, have ${data.length - offset}`\n );\n }\n\n const packetData = data.slice(offset, offset + packetSize);\n offset += packetSize;\n\n // Store packet (use type and number as key)\n const key = `${packetType}-${packetNumber}`;\n packets.set(key, packetData);\n\n console.debug(\n `Parsed packet: type=0x${packetType.toString(16).padStart(2, '0')}, num=${packetNumber}, size=${packetSize}`\n );\n }\n\n // Parse packets in a single pass\n // Note: Firmware uses global sequential numbering across all packet types\n let system: SystemConfig | undefined;\n let manufacturer: ManufacturerData | undefined;\n let power: PowerOption | undefined;\n const displays: DisplayConfig[] = [];\n const leds: LedConfig[] = [];\n const sensors: SensorData[] = [];\n const dataBuses: DataBus[] = [];\n const binaryInputs: BinaryInputs[] = [];\n\n for (const [key, packetData] of packets) {\n const [packetTypeStr] = key.split('-');\n const packetType = parseInt(packetTypeStr, 10);\n\n switch (packetType) {\n case PACKET_TYPE_SYSTEM:\n system = parseSystemConfig(packetData);\n break;\n case PACKET_TYPE_MANUFACTURER:\n manufacturer = parseManufacturerData(packetData);\n break;\n case PACKET_TYPE_POWER:\n power = parsePowerOption(packetData);\n break;\n case PACKET_TYPE_DISPLAY:\n displays.push(parseDisplayConfig(packetData));\n break;\n case PACKET_TYPE_LED:\n leds.push(parseLedConfig(packetData));\n break;\n case PACKET_TYPE_SENSOR:\n sensors.push(parseSensorData(packetData));\n break;\n case PACKET_TYPE_DATABUS:\n dataBuses.push(parseDataBus(packetData));\n break;\n case PACKET_TYPE_BINARY_INPUT:\n binaryInputs.push(parseBinaryInputs(packetData));\n break;\n }\n }\n\n return {\n system,\n manufacturer,\n power,\n displays,\n leds,\n sensors,\n dataBuses,\n binaryInputs,\n version, // From firmware wrapper\n minorVersion: 1, // Not stored in device (only single version byte exists)\n loaded: true,\n };\n}\n\n/**\n * Get expected size for a packet type.\n *\n * @param packetType - TLV packet type ID\n * @returns Expected packet size in bytes, or null if unknown type\n */\nfunction getPacketSize(packetType: number): number | null {\n const sizes: Record<number, number> = {\n [PACKET_TYPE_SYSTEM]: 22,\n [PACKET_TYPE_MANUFACTURER]: 22,\n [PACKET_TYPE_POWER]: 30, // Fixed: was 32\n [PACKET_TYPE_DISPLAY]: 46, // Fixed: was 66\n [PACKET_TYPE_LED]: 22,\n [PACKET_TYPE_SENSOR]: 30,\n [PACKET_TYPE_DATABUS]: 30, // Fixed: was 28\n [PACKET_TYPE_BINARY_INPUT]: 30, // Fixed: was 29\n };\n return sizes[packetType] ?? null;\n}\n\n/**\n * Parse SystemConfig packet (0x01, 22 bytes).\n */\nfunction parseSystemConfig(data: Uint8Array): SystemConfig {\n if (data.length < 22) {\n throw new ConfigParseError(\n `SystemConfig too short: ${data.length} bytes (need 22)`\n );\n }\n\n return SystemConfig.fromBytes(data);\n}\n\n/**\n * Parse ManufacturerData packet (0x02, 22 bytes).\n */\nfunction parseManufacturerData(data: Uint8Array): ManufacturerData {\n if (data.length < 22) {\n throw new ConfigParseError(\n `ManufacturerData too short: ${data.length} bytes (need 22)`\n );\n }\n\n return ManufacturerData.fromBytes(data);\n}\n\n/**\n * Parse PowerOption packet (0x04, 30 bytes).\n */\nfunction parsePowerOption(data: Uint8Array): PowerOption {\n if (data.length < 30) {\n throw new ConfigParseError(\n `PowerOption too short: ${data.length} bytes (need 30)`\n );\n }\n\n return PowerOption.fromBytes(data);\n}\n\n/**\n * Parse DisplayConfig packet (0x20, 46 bytes).\n */\nfunction parseDisplayConfig(data: Uint8Array): DisplayConfig {\n if (data.length < 46) {\n throw new ConfigParseError(\n `DisplayConfig too short: ${data.length} bytes (need 46)`\n );\n }\n\n return DisplayConfig.fromBytes(data);\n}\n\n/**\n * Parse LedConfig packet (0x21, 22 bytes).\n */\nfunction parseLedConfig(data: Uint8Array): LedConfig {\n if (data.length < 22) {\n throw new ConfigParseError(\n `LedConfig too short: ${data.length} bytes (need 22)`\n );\n }\n\n return LedConfig.fromBytes(data);\n}\n\n/**\n * Parse SensorData packet (0x23, 30 bytes).\n */\nfunction parseSensorData(data: Uint8Array): SensorData {\n if (data.length < 30) {\n throw new ConfigParseError(\n `SensorData too short: ${data.length} bytes (need 30)`\n );\n }\n\n return SensorData.fromBytes(data);\n}\n\n/**\n * Parse DataBus packet (0x24, 30 bytes).\n */\nfunction parseDataBus(data: Uint8Array): DataBus {\n if (data.length < 30) {\n throw new ConfigParseError(\n `DataBus too short: ${data.length} bytes (need 30)`\n );\n }\n\n return DataBus.fromBytes(data);\n}\n\n/**\n * Parse BinaryInputs packet (0x25, 30 bytes).\n */\nfunction parseBinaryInputs(data: Uint8Array): BinaryInputs {\n if (data.length < 30) {\n throw new ConfigParseError(\n `BinaryInputs too short: ${data.length} bytes (need 30)`\n );\n }\n\n return BinaryInputs.fromBytes(data);\n}","/**\n * TLV configuration serializer for OpenDisplay devices.\n */\n\nimport type {\n BinaryInputs,\n DataBus,\n DisplayConfig,\n GlobalConfig,\n LedConfig,\n ManufacturerData,\n PowerOption,\n SensorData,\n SystemConfig,\n} from '../models/config';\n\n// Packet type IDs (same as config-parser.ts)\nconst PACKET_TYPE_SYSTEM = 0x01;\nconst PACKET_TYPE_MANUFACTURER = 0x02;\nconst PACKET_TYPE_POWER = 0x04;\nconst PACKET_TYPE_DISPLAY = 0x20;\nconst PACKET_TYPE_LED = 0x21;\nconst PACKET_TYPE_SENSOR = 0x23;\nconst PACKET_TYPE_DATABUS = 0x24;\nconst PACKET_TYPE_BINARY_INPUT = 0x25;\n\n/**\n * Calculate CRC32 and return lower 16 bits.\n *\n * Uses standard CRC32 algorithm (same as zlib/firmware) but only returns\n * the lower 16 bits for backwards compatibility with firmware.\n *\n * @param data - Config data to calculate CRC over\n * @returns Lower 16 bits of CRC32 value\n */\nexport function calculateConfigCrc(data: Uint8Array): number {\n let crc = 0xffffffff;\n\n for (const byte of data) {\n crc ^= byte;\n for (let i = 0; i < 8; i++) {\n if (crc & 1) {\n crc = (crc >>> 1) ^ 0xedb88320;\n } else {\n crc = crc >>> 1;\n }\n }\n }\n\n const crc32 = (~crc) >>> 0;\n return crc32 & 0xffff; // Return lower 16 bits only\n}\n\n/**\n * Serialize SystemConfig to 22 bytes.\n */\nexport function serializeSystemConfig(config: SystemConfig): Uint8Array {\n const buffer = new ArrayBuffer(22);\n const view = new DataView(buffer);\n const result = new Uint8Array(buffer);\n\n view.setUint16(0, config.icType, true);\n view.setUint8(2, config.communicationModes);\n view.setUint8(3, config.deviceFlags);\n view.setUint8(4, config.pwrPin);\n\n // Reserved bytes (17 bytes)\n const reserved = config.reserved || new Uint8Array(17);\n result.set(reserved.subarray(0, 17), 5);\n\n return result;\n}\n\n/**\n * Serialize ManufacturerData to 22 bytes.\n */\nexport function serializeManufacturerData(\n config: ManufacturerData\n): Uint8Array {\n const buffer = new ArrayBuffer(22);\n const view = new DataView(buffer);\n const result = new Uint8Array(buffer);\n\n view.setUint16(0, config.manufacturerId, true);\n view.setUint8(2, config.boardType);\n view.setUint8(3, config.boardRevision);\n\n // Reserved bytes (18 bytes)\n const reserved = config.reserved || new Uint8Array(18);\n result.set(reserved.subarray(0, 18), 4);\n\n return result;\n}\n\n/**\n * Serialize PowerOption to 30 bytes.\n */\nexport function serializePowerOption(config: PowerOption): Uint8Array {\n const buffer = new ArrayBuffer(30);\n const view = new DataView(buffer);\n const result = new Uint8Array(buffer);\n\n view.setUint8(0, config.powerMode);\n\n // 3-byte battery capacity (little-endian)\n view.setUint8(1, config.batteryCapacityMah & 0xff);\n view.setUint8(2, (config.batteryCapacityMah >> 8) & 0xff);\n view.setUint8(3, (config.batteryCapacityMah >> 16) & 0xff);\n\n view.setUint16(4, config.sleepTimeoutMs, true);\n view.setInt8(6, config.txPower);\n view.setUint8(7, config.sleepFlags);\n view.setUint8(8, config.batterySensePin);\n view.setUint8(9, config.batterySenseEnablePin);\n view.setUint8(10, config.batterySenseFlags);\n view.setUint8(11, config.capacityEstimator);\n view.setUint16(12, config.voltageScalingFactor, true);\n view.setUint32(14, config.deepSleepCurrentUa, true);\n view.setUint16(18, config.deepSleepTimeSeconds, true);\n\n // Reserved bytes (10 bytes)\n const reserved = config.reserved || new Uint8Array(10);\n result.set(reserved.subarray(0, 10), 20);\n\n return result;\n}\n\n/**\n * Serialize DisplayConfig to 46 bytes.\n */\nexport function serializeDisplayConfig(config: DisplayConfig): Uint8Array {\n const buffer = new ArrayBuffer(46);\n const view = new DataView(buffer);\n const result = new Uint8Array(buffer);\n\n view.setUint8(0, config.instanceNumber);\n view.setUint8(1, config.displayTechnology);\n view.setUint16(2, config.panelIcType, true);\n view.setUint16(4, config.pixelWidth, true);\n view.setUint16(6, config.pixelHeight, true);\n view.setUint16(8, config.activeWidthMm, true);\n view.setUint16(10, config.activeHeightMm, true);\n view.setUint16(12, config.tagType, true);\n view.setUint8(14, config.rotation);\n view.setUint8(15, config.resetPin);\n view.setUint8(16, config.busyPin);\n view.setUint8(17, config.dcPin);\n view.setUint8(18, config.csPin);\n view.setUint8(19, config.dataPin);\n view.setUint8(20, config.partialUpdateSupport);\n view.setUint8(21, config.colorScheme);\n view.setUint8(22, config.transmissionModes);\n view.setUint8(23, config.clkPin);\n\n // Reserved pins (7 bytes)\n const reservedPins = config.reservedPins || new Uint8Array(7).fill(0xff);\n result.set(reservedPins.subarray(0, 7), 24);\n\n // Reserved bytes (15 bytes)\n const reserved = config.reserved || new Uint8Array(15);\n result.set(reserved.subarray(0, 15), 31);\n\n return result;\n}\n\n/**\n * Serialize LedConfig to 22 bytes.\n */\nexport function serializeLedConfig(config: LedConfig): Uint8Array {\n const buffer = new ArrayBuffer(22);\n const view = new DataView(buffer);\n const result = new Uint8Array(buffer);\n\n view.setUint8(0, config.instanceNumber);\n view.setUint8(1, config.ledType);\n view.setUint8(2, config.led1R);\n view.setUint8(3, config.led2G);\n view.setUint8(4, config.led3B);\n view.setUint8(5, config.led4);\n view.setUint8(6, config.ledFlags);\n\n // Reserved bytes (15 bytes)\n const reserved = config.reserved || new Uint8Array(15);\n result.set(reserved.subarray(0, 15), 7);\n\n return result;\n}\n\n/**\n * Serialize SensorData to 30 bytes.\n */\nexport function serializeSensorData(config: SensorData): Uint8Array {\n const buffer = new ArrayBuffer(30);\n const view = new DataView(buffer);\n const result = new Uint8Array(buffer);\n\n view.setUint8(0, config.instanceNumber);\n view.setUint16(1, config.sensorType, true);\n view.setUint8(3, config.busId);\n\n // Reserved bytes (26 bytes)\n const reserved = config.reserved || new Uint8Array(26);\n result.set(reserved.subarray(0, 26), 4);\n\n return result;\n}\n\n/**\n * Serialize DataBus to 30 bytes.\n */\nexport function serializeDataBus(config: DataBus): Uint8Array {\n const buffer = new ArrayBuffer(30);\n const view = new DataView(buffer);\n const result = new Uint8Array(buffer);\n\n view.setUint8(0, config.instanceNumber);\n view.setUint8(1, config.busType);\n view.setUint8(2, config.pin1);\n view.setUint8(3, config.pin2);\n view.setUint8(4, config.pin3);\n view.setUint8(5, config.pin4);\n view.setUint8(6, config.pin5);\n view.setUint8(7, config.pin6);\n view.setUint8(8, config.pin7);\n view.setUint32(9, config.busSpeedHz, true);\n view.setUint8(13, config.busFlags);\n view.setUint8(14, config.pullups);\n view.setUint8(15, config.pulldowns);\n\n // Reserved bytes (14 bytes)\n const reserved = config.reserved || new Uint8Array(14);\n result.set(reserved.subarray(0, 14), 16);\n\n return result;\n}\n\n/**\n * Serialize BinaryInputs to 30 bytes.\n */\nexport function serializeBinaryInputs(config: BinaryInputs): Uint8Array {\n const buffer = new ArrayBuffer(30);\n const view = new DataView(buffer);\n const result = new Uint8Array(buffer);\n\n view.setUint8(0, config.instanceNumber);\n view.setUint8(1, config.inputType);\n view.setUint8(2, config.displayAs);\n\n // Reserved pins (8 bytes)\n const reservedPins = config.reservedPins || new Uint8Array(8);\n result.set(reservedPins.subarray(0, 8), 3);\n\n view.setUint8(11, config.inputFlags);\n view.setUint8(12, config.invert);\n view.setUint8(13, config.pullups);\n view.setUint8(14, config.pulldowns);\n\n // Reserved bytes (15 bytes)\n const reserved = config.reserved || new Uint8Array(15);\n result.set(reserved.subarray(0, 15), 15);\n\n return result;\n}\n\n/**\n * Serialize complete GlobalConfig to TLV binary format.\n *\n * Format:\n * [2 bytes: padding/reserved]\n * [1 byte: version]\n * [TLV packets...]\n * [2 bytes: CRC16 (lower 16 bits of CRC32)]\n *\n * TLV Packet Format:\n * [1 byte: packet_number] // 0-3 for repeatable types\n * [1 byte: packet_type] // 0x01, 0x02, 0x04, 0x20-0x25\n * [N bytes: fixed-size data]\n *\n * @param config - GlobalConfig to serialize\n * @returns Complete config data ready to send to device\n * @throws {Error} If config exceeds maximum size (4096 bytes)\n */\nexport function serializeConfig(config: GlobalConfig): Uint8Array {\n // Build packet data in chunks\n const chunks: Uint8Array[] = [];\n\n // Start with 2 bytes padding and 1 byte version\n const header = new Uint8Array([0x00, 0x00, config.version]);\n chunks.push(header);\n\n // Serialize single-instance packets\n if (config.system) {\n chunks.push(new Uint8Array([0, PACKET_TYPE_SYSTEM]));\n chunks.push(serializeSystemConfig(config.system));\n }\n\n if (config.manufacturer) {\n chunks.push(new Uint8Array([0, PACKET_TYPE_MANUFACTURER]));\n chunks.push(serializeManufacturerData(config.manufacturer));\n }\n\n if (config.power) {\n chunks.push(new Uint8Array([0, PACKET_TYPE_POWER]));\n chunks.push(serializePowerOption(config.power));\n }\n\n // Serialize repeatable packets (max 4 instances each)\n for (let i = 0; i < Math.min(config.displays.length, 4); i++) {\n chunks.push(new Uint8Array([i, PACKET_TYPE_DISPLAY]));\n chunks.push(serializeDisplayConfig(config.displays[i]));\n }\n\n for (let i = 0; i < Math.min(config.leds.length, 4); i++) {\n chunks.push(new Uint8Array([i, PACKET_TYPE_LED]));\n chunks.push(serializeLedConfig(config.leds[i]));\n }\n\n for (let i = 0; i < Math.min(config.sensors.length, 4); i++) {\n chunks.push(new Uint8Array([i, PACKET_TYPE_SENSOR]));\n chunks.push(serializeSensorData(config.sensors[i]));\n }\n\n for (let i = 0; i < Math.min(config.dataBuses.length, 4); i++) {\n chunks.push(new Uint8Array([i, PACKET_TYPE_DATABUS]));\n chunks.push(serializeDataBus(config.dataBuses[i]));\n }\n\n for (let i = 0; i < Math.min(config.binaryInputs.length, 4); i++) {\n chunks.push(new Uint8Array([i, PACKET_TYPE_BINARY_INPUT]));\n chunks.push(serializeBinaryInputs(config.binaryInputs[i]));\n }\n\n // Calculate total size\n const totalDataSize = chunks.reduce((sum, chunk) => sum + chunk.length, 0);\n\n // Validate size (max 4096 bytes including CRC)\n if (totalDataSize + 2 > 4096) {\n throw new Error(\n `Config size ${totalDataSize + 2} bytes exceeds maximum 4096 bytes`\n );\n }\n\n // Concatenate all chunks\n const packetData = new Uint8Array(totalDataSize);\n let offset = 0;\n for (const chunk of chunks) {\n packetData.set(chunk, offset);\n offset += chunk.length;\n }\n\n // Calculate CRC over packet data (excluding CRC itself)\n const crc16 = calculateConfigCrc(packetData);\n\n // Append CRC as 2 bytes little-endian\n const result = new Uint8Array(totalDataSize + 2);\n result.set(packetData, 0);\n\n const crcView = new DataView(result.buffer, totalDataSize, 2);\n crcView.setUint16(0, crc16, true);\n\n return result;\n}","/**\n * Notification queue for BLE responses.\n *\n * Web Bluetooth delivers notifications asynchronously via events.\n * This queue buffers notifications and provides a Promise-based interface\n * for consuming them, with timeout support.\n */\n\nimport { BLETimeoutError } from '../exceptions';\n\ninterface PendingResolver {\n resolve: (data: Uint8Array) => void;\n reject: (error: Error) => void;\n timeoutId: number;\n}\n\n/**\n * Queue for managing BLE notification responses.\n *\n * Handles the asynchronous nature of Web Bluetooth notifications by:\n * - Buffering notifications that arrive before being requested\n * - Queuing requests that wait for future notifications\n * - Providing timeout support for all operations\n */\nexport class NotificationQueue {\n private queue: Uint8Array[] = [];\n private pendingResolvers: PendingResolver[] = [];\n\n /**\n * Add a notification to the queue.\n *\n * If there are pending consumers waiting, immediately resolve the oldest one.\n * Otherwise, buffer the notification for future consumption.\n *\n * @param data - Notification data received from BLE characteristic\n */\n enqueue(data: Uint8Array): void {\n if (this.pendingResolvers.length > 0) {\n // Immediately resolve a waiting consumer\n const pending = this.pendingResolvers.shift()!;\n clearTimeout(pending.timeoutId);\n pending.resolve(data);\n } else {\n // Buffer for later\n this.queue.push(data);\n }\n }\n\n /**\n * Get the next notification from the queue.\n *\n * If a notification is already buffered, return it immediately.\n * Otherwise, wait for the next notification or timeout.\n *\n * @param timeoutMs - Maximum time to wait in milliseconds\n * @returns Promise that resolves with notification data\n * @throws {BLETimeoutError} If timeout expires before notification arrives\n */\n async dequeue(timeoutMs: number): Promise<Uint8Array> {\n if (this.queue.length > 0) {\n return this.queue.shift()!;\n }\n\n return new Promise<Uint8Array>((resolve, reject) => {\n const timeoutId = window.setTimeout(() => {\n const index = this.pendingResolvers.findIndex(\n (p) => p.resolve === resolve\n );\n if (index !== -1) {\n this.pendingResolvers.splice(index, 1);\n reject(\n new BLETimeoutError(\n `No response received within ${timeoutMs}ms timeout`\n )\n );\n }\n }, timeoutMs);\n\n this.pendingResolvers.push({ resolve, reject, timeoutId });\n });\n }\n\n /**\n * Clear the queue and reject all pending requests.\n *\n * Called when the connection is closed or reset.\n *\n * @param reason - Reason for clearing (default: \"Connection closed\")\n */\n clear(reason: string = 'Connection closed'): void {\n this.queue = [];\n\n for (const pending of this.pendingResolvers) {\n clearTimeout(pending.timeoutId);\n pending.reject(new Error(reason));\n }\n\n this.pendingResolvers = [];\n }\n\n /**\n * Get the number of buffered notifications.\n */\n get size(): number {\n return this.queue.length;\n }\n\n /**\n * Get the number of pending consumers waiting for notifications.\n */\n get pendingCount(): number {\n return this.pendingResolvers.length;\n }\n}","/**\n * Web Bluetooth connection wrapper for OpenDisplay devices.\n *\n * Provides a clean interface for BLE operations:\n * - Device discovery and connection\n * - Command transmission\n * - Response reception with queuing\n * - Automatic reconnection handling\n */\n\nimport { BLEConnectionError, BLETimeoutError } from '../exceptions';\nimport { SERVICE_UUID, MANUFACTURER_ID } from '../protocol/constants';\nimport { NotificationQueue } from './notification-queue';\n\n/**\n * Connection options for BLE device.\n */\nexport interface BLEConnectionOptions {\n /**\n * Name prefix to filter devices (e.g., \"OpenDisplay\")\n * If not provided, user will see all devices with the OpenDisplay service\n */\n namePrefix?: string;\n\n /**\n * Optional BluetoothDevice to connect to directly\n */\n device?: BluetoothDevice;\n}\n\n/**\n * BLE connection manager for OpenDisplay devices.\n *\n * Handles all low-level Web Bluetooth operations and provides a\n * command/response interface for the protocol layer.\n */\nexport class BLEConnection {\n private device: BluetoothDevice | null = null;\n private gattServer: BluetoothRemoteGATTServer | null = null;\n private characteristic: BluetoothRemoteGATTCharacteristic | null = null;\n private notificationQueue = new NotificationQueue();\n private disconnectHandler: ((event: Event) => void) | null = null;\n\n /**\n * Check if currently connected to a device.\n */\n get isConnected(): boolean {\n return this.gattServer?.connected ?? false;\n }\n\n /**\n * Get the connected device name, if available.\n */\n get deviceName(): string | undefined {\n return this.device?.name;\n }\n\n /**\n * Connect to an OpenDisplay device.\n *\n * @param options - Connection options (device or namePrefix filter)\n * @throws {BLEConnectionError} If connection fails\n * @throws {Error} If Web Bluetooth is not supported\n */\n async connect(options: BLEConnectionOptions = {}): Promise<void> {\n if (!navigator.bluetooth) {\n throw new Error(\n 'Web Bluetooth API not supported in this browser. ' +\n 'Try Chrome, Edge, or Opera on desktop/Android.'\n );\n }\n\n try {\n // Get or request device\n if (options.device) {\n this.device = options.device;\n } else {\n // Request device with filters\n const filters: BluetoothLEScanFilter[] = [];\n\n // Create filter with service UUID and optional name prefix\n if (options.namePrefix) {\n filters.push({\n services: [SERVICE_UUID],\n namePrefix: options.namePrefix,\n });\n } else {\n filters.push({\n services: [SERVICE_UUID],\n });\n }\n\n // Add manufacturer data filter\n filters.push({\n manufacturerData: [\n {\n companyIdentifier: MANUFACTURER_ID,\n },\n ],\n });\n\n this.device = await navigator.bluetooth.requestDevice({\n filters,\n optionalServices: [SERVICE_UUID],\n });\n }\n\n // Connect to GATT server\n if (!this.device.gatt) {\n throw new BLEConnectionError('Device does not support GATT');\n }\n\n this.gattServer = await this.device.gatt.connect();\n\n // Get service and characteristic\n const service = await this.gattServer.getPrimaryService(SERVICE_UUID);\n const characteristics = await service.getCharacteristics();\n\n if (characteristics.length === 0) {\n throw new BLEConnectionError(\n 'No characteristics found in OpenDisplay service'\n );\n }\n\n // Use the first characteristic (OpenDisplay uses single characteristic)\n this.characteristic = characteristics[0];\n\n // Start notifications\n await this.characteristic.startNotifications();\n\n // Bind notification handler\n this.characteristic.addEventListener(\n 'characteristicvaluechanged',\n this.handleNotification.bind(this)\n );\n\n // Listen for disconnection\n this.disconnectHandler = this.handleDisconnect.bind(this);\n this.device.addEventListener(\n 'gattserverdisconnected',\n this.disconnectHandler\n );\n\n console.log(\n `Connected to ${this.device.name || 'OpenDisplay device'}`\n );\n } catch (error) {\n this.cleanup();\n if (error instanceof Error) {\n throw new BLEConnectionError(\n `Failed to connect: ${error.message}`\n );\n }\n throw new BLEConnectionError('Failed to connect to device');\n }\n }\n\n /**\n * Disconnect from the device.\n */\n async disconnect(): Promise<void> {\n try {\n // Stop notifications if characteristic is available\n if (this.characteristic) {\n try {\n await this.characteristic.stopNotifications();\n } catch {\n // Ignore errors during cleanup\n }\n }\n\n // Disconnect GATT server\n if (this.gattServer?.connected) {\n this.gattServer.disconnect();\n }\n } finally {\n this.cleanup();\n }\n }\n\n /**\n * Write a command to the device.\n *\n * @param data - Command data to send\n * @throws {BLEConnectionError} If not connected or write fails\n */\n async writeCommand(data: Uint8Array): Promise<void> {\n if (!this.isConnected || !this.characteristic) {\n throw new BLEConnectionError('Not connected to device');\n }\n\n try {\n await this.characteristic.writeValueWithResponse(data as BufferSource);\n } catch (error) {\n if (error instanceof Error) {\n throw new BLEConnectionError(\n `Failed to write command: ${error.message}`\n );\n }\n throw new BLEConnectionError('Failed to write command');\n }\n }\n\n /**\n * Read the next response from the device.\n *\n * @param timeoutMs - Maximum time to wait for response\n * @returns Promise that resolves with response data\n * @throws {BLETimeoutError} If timeout expires\n * @throws {BLEConnectionError} If not connected\n */\n async readResponse(timeoutMs: number): Promise<Uint8Array> {\n if (!this.isConnected) {\n throw new BLEConnectionError('Not connected to device');\n }\n\n return this.notificationQueue.dequeue(timeoutMs);\n }\n\n /**\n * Handle incoming BLE notifications.\n *\n * @param event - Characteristic value changed event\n */\n private handleNotification(event: Event): void {\n const characteristic = event.target as BluetoothRemoteGATTCharacteristic;\n if (!characteristic.value) {\n return;\n }\n\n const data = new Uint8Array(characteristic.value.buffer);\n this.notificationQueue.enqueue(data);\n }\n\n /**\n * Handle device disconnection.\n */\n private handleDisconnect(): void {\n console.log('Device disconnected');\n this.cleanup();\n }\n\n /**\n * Clean up resources and reset state.\n */\n private cleanup(): void {\n // Clear notification queue\n this.notificationQueue.clear();\n\n // Remove event listeners\n if (this.characteristic) {\n this.characteristic.removeEventListener(\n 'characteristicvaluechanged',\n this.handleNotification.bind(this)\n );\n this.characteristic = null;\n }\n\n if (this.device && this.disconnectHandler) {\n this.device.removeEventListener(\n 'gattserverdisconnected',\n this.disconnectHandler\n );\n this.disconnectHandler = null;\n }\n\n this.gattServer = null;\n this.device = null;\n }\n}","/**\n * BLE device discovery for OpenDisplay devices.\n *\n * Note: Web Bluetooth API does not provide general scanning like Python's Bleak.\n * Instead, we use requestDevice() which shows a browser picker dialog.\n */\n\nimport { MANUFACTURER_ID, SERVICE_UUID } from './protocol/constants';\n\n/**\n * Discover OpenDisplay BLE devices using browser picker.\n *\n * Shows the browser's device picker dialog filtered to OpenDisplay devices.\n * User selects a device, and we return it for connection.\n *\n * @param namePrefix - Optional name prefix filter (e.g., \"OpenDisplay\")\n * @returns Selected BluetoothDevice\n * @throws {Error} If Web Bluetooth not supported or user cancels\n *\n * @example\n * ```typescript\n * const device = await discoverDevices();\n * // or with name filter:\n * const device = await discoverDevices('OpenDisplay');\n * ```\n */\nexport async function discoverDevices(\n namePrefix?: string\n): Promise<BluetoothDevice> {\n if (!navigator.bluetooth) {\n throw new Error(\n 'Web Bluetooth API not supported in this browser. ' +\n 'Try Chrome, Edge, or Opera on desktop/Android.'\n );\n }\n\n const filters: BluetoothLEScanFilter[] = [];\n\n // Create filter with service UUID and optional name prefix\n if (namePrefix) {\n filters.push({\n services: [SERVICE_UUID],\n namePrefix,\n });\n } else {\n filters.push({\n services: [SERVICE_UUID],\n });\n }\n\n // Add manufacturer data filter\n filters.push({\n manufacturerData: [\n {\n companyIdentifier: MANUFACTURER_ID,\n },\n ],\n });\n\n try {\n const device = await navigator.bluetooth.requestDevice({\n filters,\n optionalServices: [SERVICE_UUID],\n });\n\n console.log(`Selected device: ${device.name || 'Unknown'}`);\n return device;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Device selection failed: ${error.message}`);\n }\n throw new Error('Device selection cancelled or failed');\n }\n}","/**\n * BLE advertisement data structures.\n */\n\n/**\n * Parsed BLE advertisement manufacturer data.\n *\n * Advertisement format (11 bytes, manufacturer ID already stripped by Web Bluetooth):\n *\n * - [0-6]: Fixed protocol bytes\n * - [7-8]: Battery voltage in millivolts (little-endian uint16)\n * - [9]: Chip temperature in Celsius (signed int8)\n * - [10]: Loop counter (uint8, increments each advertisement)\n *\n * Note: Web Bluetooth provides manufacturer data without the manufacturer ID prefix.\n */\nexport interface AdvertisementData {\n /** Battery voltage in millivolts */\n batteryMv: number;\n\n /** Chip temperature in Celsius */\n temperatureC: number;\n\n /** Incrementing counter for each advertisement */\n loopCounter: number;\n}\n\n/**\n * Parse BLE advertisement manufacturer data.\n *\n * Note: The manufacturer ID (0x2446) is already stripped by Web Bluetooth\n * and provided as the key in manufacturerData.\n *\n * @param data - Raw manufacturer data (11 bytes, without the manufacturer ID prefix)\n * @returns AdvertisementData with parsed values\n * @throws {Error} If data is too short\n */\nexport function parseAdvertisement(data: Uint8Array): AdvertisementData {\n if (data.length < 11) {\n throw new Error(\n `Advertisement data too short: ${data.length} bytes (need 11)`\n );\n }\n\n const view = new DataView(data.buffer, data.byteOffset);\n\n // Parse sensor data\n // Bytes 0-6 are fixed protocol bytes (ignored)\n const batteryMv = view.getUint16(7, true); // uint16, little-endian\n const temperatureC = view.getInt8(9); // int8, signed\n const loopCounter = data[10]; // uint8\n\n return {\n batteryMv,\n temperatureC,\n loopCounter,\n };\n}","/**\n * @opendisplay/opendisplay - TypeScript library for OpenDisplay BLE e-paper displays\n *\n * Main entry point exporting the public API.\n */\n\n// Core device API\nexport { OpenDisplayDevice } from './device';\nexport { discoverDevices } from './discovery';\n\n// Models and types\nexport * from './models/enums';\nexport * from './models/config';\nexport * from './models/capabilities';\nexport * from './models/firmware';\nexport * from './models/advertisement';\n\n// Exceptions\nexport * from './exceptions';\n\n// Re-export from epaper-dithering for convenience\nexport { ColorScheme, DitherMode } from '@opendisplay/epaper-dithering';"],"mappings":"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","names":["ColorScheme","DitherMode","ColorScheme","ColorScheme","RefreshMode","ICType","PowerMode","BusType","Rotation","CommandCode","ColorScheme","SystemConfig","ManufacturerData","PowerOption","batteryMah","DisplayConfig","ColorScheme","LedConfig","SensorData","DataBus","BinaryInputs","PACKET_TYPE_SYSTEM","PACKET_TYPE_MANUFACTURER","PACKET_TYPE_POWER","PACKET_TYPE_DISPLAY","PACKET_TYPE_LED","PACKET_TYPE_SENSOR","PACKET_TYPE_DATABUS","PACKET_TYPE_BINARY_INPUT","ColorScheme","DitherMode","ColorScheme","DitherMode"]}