@opendisplay/opendisplay 1.0.0 → 1.0.1

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.
Files changed (2) hide show
  1. package/README.md +455 -0
  2. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,455 @@
1
+ # @opendisplay/opendisplay
2
+
3
+ TypeScript library for OpenDisplay BLE e-paper displays using Web Bluetooth API. Control your OpenDisplay devices directly from the browser.
4
+
5
+ [![npm version](https://badge.fury.io/js/@opendisplay%2Fopendisplay.svg)](https://www.npmjs.com/package/@opendisplay/opendisplay)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Features
9
+
10
+ - **Web Bluetooth Integration**: Control OpenDisplay devices directly from the browser
11
+ - **Complete Protocol Support**: Image upload, device configuration, firmware management
12
+ - **Automatic Image Processing**: Built-in dithering, encoding, and compression
13
+ - **Type-Safe**: Full TypeScript support with exported types
14
+ - **Device Discovery**: Browser-native device picker
15
+ - **Protocol Compliance**: Byte-for-byte compatible with [py-opendisplay](https://github.com/OpenDisplay-org/py-opendisplay)
16
+
17
+ ## Browser Compatibility
18
+
19
+ Web Bluetooth API is required. Supported browsers:
20
+
21
+ - ✅ Chrome/Edge 56+ (Desktop & Android)
22
+ - ✅ Opera 43+ (Desktop & Android)
23
+ - ✅ Samsung Internet 6.0+
24
+ - ❌ Firefox (no Web Bluetooth support)
25
+ - ❌ Safari (no Web Bluetooth support)
26
+
27
+ **Note**: HTTPS or localhost is required for Web Bluetooth API.
28
+
29
+ ## Installation
30
+
31
+ ### NPM/Bun/Yarn
32
+
33
+ ```bash
34
+ npm install @opendisplay/opendisplay
35
+ # or
36
+ bun add @opendisplay/opendisplay
37
+ # or
38
+ yarn add @opendisplay/opendisplay
39
+ ```
40
+
41
+ ### CDN (for quick prototyping)
42
+
43
+ ```html
44
+ <script type="module">
45
+ import { OpenDisplayDevice } from 'https://esm.sh/@opendisplay/opendisplay@1.0.0';
46
+ // Your code here
47
+ </script>
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ ```typescript
53
+ import { OpenDisplayDevice, DitherMode, RefreshMode } from '@opendisplay/opendisplay';
54
+
55
+ // Create device instance
56
+ const device = new OpenDisplayDevice();
57
+
58
+ // Connect to device (shows browser picker)
59
+ await device.connect();
60
+
61
+ // Device is auto-interrogated on first connect
62
+ console.log(`Connected to ${device.width}x${device.height} display`);
63
+
64
+ // Load image from canvas
65
+ const canvas = document.getElementById('myCanvas') as HTMLCanvasElement;
66
+ const ctx = canvas.getContext('2d')!;
67
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
68
+
69
+ // Upload image
70
+ await device.uploadImage(imageData, {
71
+ refreshMode: RefreshMode.FULL,
72
+ ditherMode: DitherMode.FLOYD_STEINBERG,
73
+ compress: true
74
+ });
75
+
76
+ // Disconnect when done
77
+ await device.disconnect();
78
+ ```
79
+
80
+ ## API Documentation
81
+
82
+ ### OpenDisplayDevice
83
+
84
+ Main class for interacting with OpenDisplay devices.
85
+
86
+ #### Constructor
87
+
88
+ ```typescript
89
+ new OpenDisplayDevice(options?: {
90
+ config?: GlobalConfig;
91
+ capabilities?: DeviceCapabilities;
92
+ device?: BluetoothDevice;
93
+ namePrefix?: string;
94
+ })
95
+ ```
96
+
97
+ **Options:**
98
+ - `config`: Cached device configuration to skip interrogation
99
+ - `capabilities`: Minimal device info (width, height, color scheme) to skip interrogation
100
+ - `device`: Pre-selected BluetoothDevice instance
101
+ - `namePrefix`: Device name filter for picker (e.g., "OpenDisplay")
102
+
103
+ #### Methods
104
+
105
+ ##### `connect(options?: BLEConnectionOptions): Promise<void>`
106
+
107
+ Connect to an OpenDisplay device. Shows browser's Bluetooth device picker if no device was provided in constructor.
108
+
109
+ ```typescript
110
+ await device.connect();
111
+ // or with name filter
112
+ await device.connect({ namePrefix: 'OpenDisplay' });
113
+ ```
114
+
115
+ Automatically interrogates device on first connect unless config/capabilities were provided.
116
+
117
+ ##### `disconnect(): Promise<void>`
118
+
119
+ Disconnect from the device.
120
+
121
+ ```typescript
122
+ await device.disconnect();
123
+ ```
124
+
125
+ ##### `uploadImage(imageData: ImageData, options?): Promise<void>`
126
+
127
+ Upload image to device display. Handles resizing, dithering, encoding, and compression automatically.
128
+
129
+ **Parameters:**
130
+ - `imageData`: Image as ImageData from canvas
131
+ - `options.refreshMode`: Display refresh mode (default: `RefreshMode.FULL`)
132
+ - `options.ditherMode`: Dithering algorithm (default: `DitherMode.BURKES`)
133
+ - `options.compress`: Enable zlib compression (default: `true`)
134
+
135
+ ```typescript
136
+ // From canvas
137
+ const canvas = document.getElementById('canvas') as HTMLCanvasElement;
138
+ const ctx = canvas.getContext('2d')!;
139
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
140
+
141
+ await device.uploadImage(imageData, {
142
+ refreshMode: RefreshMode.FULL,
143
+ ditherMode: DitherMode.FLOYD_STEINBERG,
144
+ compress: true
145
+ });
146
+ ```
147
+
148
+ **Supported Refresh Modes:**
149
+ - `RefreshMode.FULL` - Full refresh (recommended, ~15s)
150
+ - `RefreshMode.FAST` - Fast refresh if supported (~2s, may have ghosting)
151
+ - `RefreshMode.PARTIAL` - Partial refresh if supported
152
+
153
+ **Supported Dither Modes** (from [@opendisplay/epaper-dithering](https://www.npmjs.com/package/@opendisplay/epaper-dithering)):
154
+ - `DitherMode.FLOYD_STEINBERG` - Classic error diffusion (recommended)
155
+ - `DitherMode.BURKES` - Burkes error diffusion
156
+ - `DitherMode.SIERRA` - Sierra error diffusion
157
+ - `DitherMode.ATKINSON` - Atkinson dithering (HyperCard style)
158
+ - `DitherMode.STUCKI` - Stucki error diffusion
159
+ - `DitherMode.JARVIS` - Jarvis-Judice-Ninke
160
+ - `DitherMode.SIMPLE_2D` - Fast 2D error diffusion
161
+ - `DitherMode.ORDERED_BAYER_2` - 2x2 Bayer ordered dithering
162
+ - `DitherMode.ORDERED_BAYER_4` - 4x4 Bayer ordered dithering
163
+
164
+ ##### `interrogate(): Promise<GlobalConfig>`
165
+
166
+ Read complete device configuration from device. Automatically called on first connect unless config/capabilities were provided.
167
+
168
+ ```typescript
169
+ const config = await device.interrogate();
170
+ console.log(`Device has ${config.displays.length} display(s)`);
171
+ ```
172
+
173
+ ##### `readFirmwareVersion(): Promise<FirmwareVersion>`
174
+
175
+ Read firmware version from device.
176
+
177
+ ```typescript
178
+ const fw = await device.readFirmwareVersion();
179
+ console.log(`Firmware v${fw.major}.${fw.minor} (${fw.sha})`);
180
+ ```
181
+
182
+ ##### `writeConfig(config: GlobalConfig): Promise<void>`
183
+
184
+ Write configuration to device. Device must be rebooted for changes to take effect.
185
+
186
+ ```typescript
187
+ // Read current config
188
+ const config = device.config!;
189
+
190
+ // Modify config
191
+ config.displays[0].rotation = 180;
192
+
193
+ // Write back to device
194
+ await device.writeConfig(config);
195
+
196
+ // Reboot to apply changes
197
+ await device.reboot();
198
+ ```
199
+
200
+ ##### `reboot(): Promise<void>`
201
+
202
+ Reboot the device. Connection will drop as device resets.
203
+
204
+ ```typescript
205
+ await device.reboot();
206
+ // Device will disconnect automatically
207
+ ```
208
+
209
+ #### Properties
210
+
211
+ ##### `isConnected: boolean`
212
+
213
+ Check if currently connected to a device.
214
+
215
+ ##### `width: number`
216
+
217
+ Display width in pixels (throws if not interrogated).
218
+
219
+ ##### `height: number`
220
+
221
+ Display height in pixels (throws if not interrogated).
222
+
223
+ ##### `colorScheme: ColorScheme`
224
+
225
+ Display color scheme (throws if not interrogated).
226
+
227
+ Possible values:
228
+ - `ColorScheme.MONO` - Black and white
229
+ - `ColorScheme.BWR` - Black, white, red
230
+ - `ColorScheme.BWY` - Black, white, yellow
231
+ - `ColorScheme.BWRY` - Black, white, red, yellow (4-color)
232
+ - `ColorScheme.BWGBRY` - Black, white, green, blue, red, yellow (6-color Spectra)
233
+ - `ColorScheme.GRAYSCALE_4` - 4-level grayscale
234
+
235
+ ##### `rotation: number`
236
+
237
+ Display rotation in degrees (0, 90, 180, 270).
238
+
239
+ ##### `config: GlobalConfig | null`
240
+
241
+ Full device configuration (null if not interrogated).
242
+
243
+ ##### `capabilities: DeviceCapabilities | null`
244
+
245
+ Minimal device info (width, height, colorScheme, rotation).
246
+
247
+ ### Discovery
248
+
249
+ ```typescript
250
+ import { discoverDevices } from '@opendisplay/opendisplay';
251
+
252
+ // Show device picker
253
+ const device = await discoverDevices();
254
+ // or with name filter
255
+ const device = await discoverDevices('OpenDisplay');
256
+ ```
257
+
258
+ ### Types
259
+
260
+ All types are exported for TypeScript users:
261
+
262
+ ```typescript
263
+ import type {
264
+ GlobalConfig,
265
+ DisplayConfig,
266
+ DeviceCapabilities,
267
+ FirmwareVersion,
268
+ AdvertisementData
269
+ } from '@opendisplay/opendisplay';
270
+ ```
271
+
272
+ ## Usage Examples
273
+
274
+ ### Basic Image Upload
275
+
276
+ ```typescript
277
+ import { OpenDisplayDevice } from '@opendisplay/opendisplay';
278
+
279
+ const device = new OpenDisplayDevice();
280
+ await device.connect();
281
+
282
+ // Create canvas with image
283
+ const canvas = document.createElement('canvas');
284
+ canvas.width = device.width;
285
+ canvas.height = device.height;
286
+ const ctx = canvas.getContext('2d')!;
287
+
288
+ // Draw something
289
+ ctx.fillStyle = 'white';
290
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
291
+ ctx.fillStyle = 'black';
292
+ ctx.font = '48px Arial';
293
+ ctx.fillText('Hello OpenDisplay!', 50, 100);
294
+
295
+ // Upload to device
296
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
297
+ await device.uploadImage(imageData);
298
+
299
+ await device.disconnect();
300
+ ```
301
+
302
+ ### Upload Image from File
303
+
304
+ ```typescript
305
+ // HTML: <input type="file" id="imageInput" accept="image/*">
306
+
307
+ const input = document.getElementById('imageInput') as HTMLInputElement;
308
+ input.addEventListener('change', async (e) => {
309
+ const file = (e.target as HTMLInputElement).files?.[0];
310
+ if (!file) return;
311
+
312
+ // Load image
313
+ const img = new Image();
314
+ img.src = URL.createObjectURL(file);
315
+ await img.decode();
316
+
317
+ // Convert to ImageData
318
+ const canvas = document.createElement('canvas');
319
+ canvas.width = img.width;
320
+ canvas.height = img.height;
321
+ const ctx = canvas.getContext('2d')!;
322
+ ctx.drawImage(img, 0, 0);
323
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
324
+
325
+ // Upload
326
+ await device.uploadImage(imageData);
327
+ });
328
+ ```
329
+
330
+ ### Skip Interrogation with Cached Config
331
+
332
+ ```typescript
333
+ import { OpenDisplayDevice } from '@opendisplay/opendisplay';
334
+
335
+ // First connection - interrogate and cache
336
+ const device = new OpenDisplayDevice();
337
+ await device.connect();
338
+ const cachedConfig = device.config!;
339
+
340
+ // Store config in localStorage
341
+ localStorage.setItem('deviceConfig', JSON.stringify(cachedConfig));
342
+
343
+ // Later - reuse cached config
344
+ const savedConfig = JSON.parse(localStorage.getItem('deviceConfig')!);
345
+ const fastDevice = new OpenDisplayDevice({ config: savedConfig });
346
+ await fastDevice.connect();
347
+ // No interrogation needed!
348
+ ```
349
+
350
+ ### Skip Interrogation with Minimal Capabilities
351
+
352
+ ```typescript
353
+ import { OpenDisplayDevice, ColorScheme } from '@opendisplay/opendisplay';
354
+
355
+ // If you know your device specs
356
+ const device = new OpenDisplayDevice({
357
+ capabilities: {
358
+ width: 296,
359
+ height: 128,
360
+ colorScheme: ColorScheme.BWR,
361
+ rotation: 0
362
+ }
363
+ });
364
+
365
+ await device.connect();
366
+ // Fast connection - no interrogation!
367
+ ```
368
+
369
+ ### Read and Modify Device Configuration
370
+
371
+ ```typescript
372
+ const device = new OpenDisplayDevice();
373
+ await device.connect();
374
+
375
+ // Read config
376
+ const config = await device.interrogate();
377
+ console.log(`Display: ${config.displays[0].pixelWidth}x${config.displays[0].pixelHeight}`);
378
+ console.log(`Battery: ${config.power?.batteryCapacityMah}mAh`);
379
+
380
+ // Modify rotation
381
+ config.displays[0].rotation = 180;
382
+
383
+ // Write back
384
+ await device.writeConfig(config);
385
+ await device.reboot(); // Reboot to apply
386
+ ```
387
+
388
+ ### Error Handling
389
+
390
+ ```typescript
391
+ import {
392
+ OpenDisplayDevice,
393
+ BLEConnectionError,
394
+ BLETimeoutError,
395
+ ProtocolError
396
+ } from '@opendisplay/opendisplay';
397
+
398
+ try {
399
+ const device = new OpenDisplayDevice();
400
+ await device.connect();
401
+ await device.uploadImage(imageData);
402
+ } catch (error) {
403
+ if (error instanceof BLEConnectionError) {
404
+ console.error('Failed to connect:', error.message);
405
+ } else if (error instanceof BLETimeoutError) {
406
+ console.error('Operation timed out:', error.message);
407
+ } else if (error instanceof ProtocolError) {
408
+ console.error('Protocol error:', error.message);
409
+ } else {
410
+ console.error('Unexpected error:', error);
411
+ }
412
+ }
413
+ ```
414
+
415
+ ## Architecture
416
+
417
+ This library mirrors the architecture of [py-opendisplay](https://github.com/OpenDisplay-org/py-opendisplay):
418
+
419
+ - **Protocol Layer**: Command builders, response parsers, TLV config handling
420
+ - **Transport Layer**: Web Bluetooth wrapper with notification queue
421
+ - **Encoding Layer**: Image encoding, compression, bitplane handling
422
+ - **Models Layer**: TypeScript interfaces for all data structures
423
+ - **Public API**: `OpenDisplayDevice` class and helper functions
424
+
425
+ ## Development
426
+
427
+ ```bash
428
+ # Install dependencies
429
+ npm install
430
+
431
+ # Build library
432
+ npm run build
433
+
434
+ # Type check
435
+ npm run type-check
436
+
437
+ # Lint
438
+ npm run lint
439
+ ```
440
+
441
+ ## Related Packages
442
+
443
+ - [@opendisplay/epaper-dithering](https://www.npmjs.com/package/@opendisplay/epaper-dithering) - Dithering algorithms for e-paper displays
444
+ - [py-opendisplay](https://github.com/OpenDisplay-org/py-opendisplay) - Python version for desktop/server applications
445
+
446
+ ## License
447
+
448
+ MIT
449
+
450
+ ## Links
451
+
452
+ - [npm Package](https://www.npmjs.com/package/@opendisplay/opendisplay)
453
+ - [GitHub Repository](https://github.com/OpenDisplay-org/opendisplay-js)
454
+ - [OpenDisplay Website](https://opendisplay.io)
455
+ - [Python Library](https://github.com/OpenDisplay-org/py-opendisplay)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendisplay/opendisplay",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "TypeScript library for OpenDisplay BLE e-paper displays (Web Bluetooth)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -35,7 +35,7 @@
35
35
  "web-bluetooth"
36
36
  ],
37
37
  "dependencies": {
38
- "@opendisplay/epaper-dithering": "^2.1.1",
38
+ "@opendisplay/epaper-dithering": "^2.1.2",
39
39
  "pako": "^2.1.0"
40
40
  },
41
41
  "devDependencies": {