@opendisplay/opendisplay 1.0.1 → 1.1.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.
package/README.md CHANGED
@@ -3,7 +3,6 @@
3
3
  TypeScript library for OpenDisplay BLE e-paper displays using Web Bluetooth API. Control your OpenDisplay devices directly from the browser.
4
4
 
5
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
6
 
8
7
  ## Features
9
8
 
@@ -12,8 +11,6 @@ TypeScript library for OpenDisplay BLE e-paper displays using Web Bluetooth API.
12
11
  - **Automatic Image Processing**: Built-in dithering, encoding, and compression
13
12
  - **Type-Safe**: Full TypeScript support with exported types
14
13
  - **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
14
  ## Browser Compatibility
18
15
 
19
16
  Web Bluetooth API is required. Supported browsers:
@@ -188,7 +185,7 @@ Write configuration to device. Device must be rebooted for changes to take effec
188
185
  const config = device.config!;
189
186
 
190
187
  // Modify config
191
- config.displays[0].rotation = 180;
188
+ config.displays[0].rotation = 1;
192
189
 
193
190
  // Write back to device
194
191
  await device.writeConfig(config);
@@ -234,7 +231,7 @@ Possible values:
234
231
 
235
232
  ##### `rotation: number`
236
233
 
237
- Display rotation in degrees (0, 90, 180, 270).
234
+ Display rotation steps (by 90 degrees) (0, 1, 2, 3).
238
235
 
239
236
  ##### `config: GlobalConfig | null`
240
237
 
@@ -252,7 +249,7 @@ import { discoverDevices } from '@opendisplay/opendisplay';
252
249
  // Show device picker
253
250
  const device = await discoverDevices();
254
251
  // or with name filter
255
- const device = await discoverDevices('OpenDisplay');
252
+ const device = await discoverDevices('OD');
256
253
  ```
257
254
 
258
255
  ### Types
@@ -441,15 +438,4 @@ npm run lint
441
438
  ## Related Packages
442
439
 
443
440
  - [@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)
441
+ - [py-opendisplay](https://github.com/OpenDisplay-org/py-opendisplay) - Python version
package/dist/index.cjs CHANGED
@@ -1396,6 +1396,16 @@ var BLEConnection = class {
1396
1396
  }
1397
1397
  return this.notificationQueue.dequeue(timeoutMs);
1398
1398
  }
1399
+ /**
1400
+ * Clear the notification queue.
1401
+ *
1402
+ * Useful for clearing any stale responses before starting a new operation.
1403
+ * This drains any buffered notifications and cancels pending read requests.
1404
+ */
1405
+ clearQueue() {
1406
+ console.debug(`Clearing notification queue (${this.notificationQueue.size} buffered)`);
1407
+ this.notificationQueue.clear("Queue cleared by request");
1408
+ }
1399
1409
  /**
1400
1410
  * Handle incoming BLE notifications.
1401
1411
  *
@@ -1716,7 +1726,10 @@ var OpenDisplayDevice = class _OpenDisplayDevice {
1716
1726
  * await device.uploadImage(imageData, {
1717
1727
  * refreshMode: RefreshMode.FULL,
1718
1728
  * ditherMode: DitherMode.BURKES,
1719
- * compress: true
1729
+ * compress: true,
1730
+ * onProgress: (current, total, stage) => {
1731
+ * console.log(`${stage}: ${current}/${total} (${Math.floor(current/total*100)}%)`);
1732
+ * }
1720
1733
  * });
1721
1734
  * ```
1722
1735
  */
@@ -1726,9 +1739,12 @@ var OpenDisplayDevice = class _OpenDisplayDevice {
1726
1739
  const refreshMode = options.refreshMode ?? 0 /* FULL */;
1727
1740
  const ditherMode = options.ditherMode ?? import_epaper_dithering4.DitherMode.BURKES;
1728
1741
  const compress = options.compress ?? true;
1742
+ const onProgress = options.onProgress;
1743
+ const onStatusChange = options.onStatusChange;
1729
1744
  console.log(
1730
1745
  `Uploading image (${this.width}x${this.height}, ${import_epaper_dithering4.ColorScheme[this.colorScheme]})`
1731
1746
  );
1747
+ onStatusChange?.("Preparing image...");
1732
1748
  const encodedData = prepareImageForUpload(
1733
1749
  imageData,
1734
1750
  this.width,
@@ -1738,27 +1754,44 @@ var OpenDisplayDevice = class _OpenDisplayDevice {
1738
1754
  );
1739
1755
  let compressedData = null;
1740
1756
  if (compress) {
1757
+ onStatusChange?.("Compressing...");
1741
1758
  compressedData = compressImageData(encodedData, 6);
1742
1759
  if (compressedData.length < MAX_COMPRESSED_SIZE) {
1743
1760
  console.log(`Using compressed upload protocol (size: ${compressedData.length} bytes)`);
1761
+ onStatusChange?.("Uploading...");
1744
1762
  await this.executeUpload({
1745
1763
  imageData: encodedData,
1746
1764
  refreshMode,
1747
1765
  useCompression: true,
1748
1766
  compressedData,
1749
- uncompressedSize: encodedData.length
1767
+ uncompressedSize: encodedData.length,
1768
+ onProgress,
1769
+ onStatusChange
1750
1770
  });
1751
1771
  } else {
1752
1772
  console.log(
1753
1773
  `Compressed size exceeds ${MAX_COMPRESSED_SIZE} bytes, using uncompressed protocol`
1754
1774
  );
1755
- await this.executeUpload({ imageData: encodedData, refreshMode });
1775
+ onStatusChange?.("Uploading...");
1776
+ await this.executeUpload({
1777
+ imageData: encodedData,
1778
+ refreshMode,
1779
+ onProgress,
1780
+ onStatusChange
1781
+ });
1756
1782
  }
1757
1783
  } else {
1758
1784
  console.log("Compression disabled, using uncompressed protocol");
1759
- await this.executeUpload({ imageData: encodedData, refreshMode });
1785
+ onStatusChange?.("Uploading...");
1786
+ await this.executeUpload({
1787
+ imageData: encodedData,
1788
+ refreshMode,
1789
+ onProgress,
1790
+ onStatusChange
1791
+ });
1760
1792
  }
1761
1793
  console.log("Image upload complete");
1794
+ onStatusChange?.("Upload complete!");
1762
1795
  }
1763
1796
  /**
1764
1797
  * Execute image upload using compressed or uncompressed protocol.
@@ -1769,8 +1802,11 @@ var OpenDisplayDevice = class _OpenDisplayDevice {
1769
1802
  refreshMode,
1770
1803
  useCompression = false,
1771
1804
  compressedData,
1772
- uncompressedSize
1805
+ uncompressedSize,
1806
+ onProgress,
1807
+ onStatusChange
1773
1808
  } = params;
1809
+ this.connection.clearQueue();
1774
1810
  let startCmd;
1775
1811
  let remainingCompressed = null;
1776
1812
  if (useCompression && compressedData && uncompressedSize) {
@@ -1788,11 +1824,12 @@ var OpenDisplayDevice = class _OpenDisplayDevice {
1788
1824
  validateAckResponse(response, 112 /* DIRECT_WRITE_START */);
1789
1825
  let autoCompleted = false;
1790
1826
  if (useCompression && remainingCompressed && remainingCompressed.length > 0) {
1791
- autoCompleted = await this.sendDataChunks(remainingCompressed);
1827
+ autoCompleted = await this.sendDataChunks(remainingCompressed, onProgress, onStatusChange);
1792
1828
  } else if (!useCompression) {
1793
- autoCompleted = await this.sendDataChunks(imageData);
1829
+ autoCompleted = await this.sendDataChunks(imageData, onProgress, onStatusChange);
1794
1830
  }
1795
1831
  if (!autoCompleted) {
1832
+ onStatusChange?.("Refreshing display...");
1796
1833
  const endCmd = buildDirectWriteEndCommand(refreshMode);
1797
1834
  await this.connection.writeCommand(endCmd);
1798
1835
  response = await this.connection.readResponse(
@@ -1810,11 +1847,13 @@ var OpenDisplayDevice = class _OpenDisplayDevice {
1810
1847
  * - Progress logging
1811
1848
  *
1812
1849
  * @param imageData - Data to send in chunks
1850
+ * @param onProgress - Optional progress callback (current bytes, total bytes, stage)
1851
+ * @param onStatusChange - Optional status message callback
1813
1852
  * @returns True if device auto-completed (sent 0x0072 END early), false if all chunks sent normally
1814
1853
  * @throws {ProtocolError} If unexpected response received
1815
1854
  * @throws {BLETimeoutError} If no response within timeout
1816
1855
  */
1817
- async sendDataChunks(imageData) {
1856
+ async sendDataChunks(imageData, onProgress, onStatusChange) {
1818
1857
  let bytesSent = 0;
1819
1858
  let chunksSent = 0;
1820
1859
  while (bytesSent < imageData.length) {
@@ -1835,6 +1874,7 @@ var OpenDisplayDevice = class _OpenDisplayDevice {
1835
1874
  console.log(
1836
1875
  `No response after chunk ${chunksSent} (${(bytesSent / imageData.length * 100).toFixed(1)}%), waiting for device refresh...`
1837
1876
  );
1877
+ onStatusChange?.("Refreshing display...");
1838
1878
  response = await this.connection.readResponse(
1839
1879
  _OpenDisplayDevice.TIMEOUT_REFRESH
1840
1880
  );
@@ -1844,10 +1884,12 @@ var OpenDisplayDevice = class _OpenDisplayDevice {
1844
1884
  }
1845
1885
  const [command, isAck] = checkResponseType(response);
1846
1886
  if (command === 113 /* DIRECT_WRITE_DATA */) {
1887
+ onProgress?.(bytesSent, imageData.length, "upload");
1847
1888
  } else if (command === 114 /* DIRECT_WRITE_END */) {
1848
1889
  console.log(
1849
1890
  `Received END response after chunk ${chunksSent} - device auto-completed`
1850
1891
  );
1892
+ onProgress?.(imageData.length, imageData.length, "upload");
1851
1893
  return true;
1852
1894
  } else {
1853
1895
  throw new ProtocolError(