@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 +4 -18
- package/dist/index.cjs +50 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +50 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
[](https://www.npmjs.com/package/@opendisplay/opendisplay)
|
|
6
|
-
[](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 =
|
|
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
|
|
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('
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|