@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/README.md +4 -18
- package/dist/index.cjs +39 -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 +39 -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
|
@@ -1716,7 +1716,10 @@ var OpenDisplayDevice = class _OpenDisplayDevice {
|
|
|
1716
1716
|
* await device.uploadImage(imageData, {
|
|
1717
1717
|
* refreshMode: RefreshMode.FULL,
|
|
1718
1718
|
* ditherMode: DitherMode.BURKES,
|
|
1719
|
-
* compress: true
|
|
1719
|
+
* compress: true,
|
|
1720
|
+
* onProgress: (current, total, stage) => {
|
|
1721
|
+
* console.log(`${stage}: ${current}/${total} (${Math.floor(current/total*100)}%)`);
|
|
1722
|
+
* }
|
|
1720
1723
|
* });
|
|
1721
1724
|
* ```
|
|
1722
1725
|
*/
|
|
@@ -1726,9 +1729,12 @@ var OpenDisplayDevice = class _OpenDisplayDevice {
|
|
|
1726
1729
|
const refreshMode = options.refreshMode ?? 0 /* FULL */;
|
|
1727
1730
|
const ditherMode = options.ditherMode ?? import_epaper_dithering4.DitherMode.BURKES;
|
|
1728
1731
|
const compress = options.compress ?? true;
|
|
1732
|
+
const onProgress = options.onProgress;
|
|
1733
|
+
const onStatusChange = options.onStatusChange;
|
|
1729
1734
|
console.log(
|
|
1730
1735
|
`Uploading image (${this.width}x${this.height}, ${import_epaper_dithering4.ColorScheme[this.colorScheme]})`
|
|
1731
1736
|
);
|
|
1737
|
+
onStatusChange?.("Preparing image...");
|
|
1732
1738
|
const encodedData = prepareImageForUpload(
|
|
1733
1739
|
imageData,
|
|
1734
1740
|
this.width,
|
|
@@ -1738,27 +1744,44 @@ var OpenDisplayDevice = class _OpenDisplayDevice {
|
|
|
1738
1744
|
);
|
|
1739
1745
|
let compressedData = null;
|
|
1740
1746
|
if (compress) {
|
|
1747
|
+
onStatusChange?.("Compressing...");
|
|
1741
1748
|
compressedData = compressImageData(encodedData, 6);
|
|
1742
1749
|
if (compressedData.length < MAX_COMPRESSED_SIZE) {
|
|
1743
1750
|
console.log(`Using compressed upload protocol (size: ${compressedData.length} bytes)`);
|
|
1751
|
+
onStatusChange?.("Uploading...");
|
|
1744
1752
|
await this.executeUpload({
|
|
1745
1753
|
imageData: encodedData,
|
|
1746
1754
|
refreshMode,
|
|
1747
1755
|
useCompression: true,
|
|
1748
1756
|
compressedData,
|
|
1749
|
-
uncompressedSize: encodedData.length
|
|
1757
|
+
uncompressedSize: encodedData.length,
|
|
1758
|
+
onProgress,
|
|
1759
|
+
onStatusChange
|
|
1750
1760
|
});
|
|
1751
1761
|
} else {
|
|
1752
1762
|
console.log(
|
|
1753
1763
|
`Compressed size exceeds ${MAX_COMPRESSED_SIZE} bytes, using uncompressed protocol`
|
|
1754
1764
|
);
|
|
1755
|
-
|
|
1765
|
+
onStatusChange?.("Uploading...");
|
|
1766
|
+
await this.executeUpload({
|
|
1767
|
+
imageData: encodedData,
|
|
1768
|
+
refreshMode,
|
|
1769
|
+
onProgress,
|
|
1770
|
+
onStatusChange
|
|
1771
|
+
});
|
|
1756
1772
|
}
|
|
1757
1773
|
} else {
|
|
1758
1774
|
console.log("Compression disabled, using uncompressed protocol");
|
|
1759
|
-
|
|
1775
|
+
onStatusChange?.("Uploading...");
|
|
1776
|
+
await this.executeUpload({
|
|
1777
|
+
imageData: encodedData,
|
|
1778
|
+
refreshMode,
|
|
1779
|
+
onProgress,
|
|
1780
|
+
onStatusChange
|
|
1781
|
+
});
|
|
1760
1782
|
}
|
|
1761
1783
|
console.log("Image upload complete");
|
|
1784
|
+
onStatusChange?.("Upload complete!");
|
|
1762
1785
|
}
|
|
1763
1786
|
/**
|
|
1764
1787
|
* Execute image upload using compressed or uncompressed protocol.
|
|
@@ -1769,7 +1792,9 @@ var OpenDisplayDevice = class _OpenDisplayDevice {
|
|
|
1769
1792
|
refreshMode,
|
|
1770
1793
|
useCompression = false,
|
|
1771
1794
|
compressedData,
|
|
1772
|
-
uncompressedSize
|
|
1795
|
+
uncompressedSize,
|
|
1796
|
+
onProgress,
|
|
1797
|
+
onStatusChange
|
|
1773
1798
|
} = params;
|
|
1774
1799
|
let startCmd;
|
|
1775
1800
|
let remainingCompressed = null;
|
|
@@ -1788,11 +1813,12 @@ var OpenDisplayDevice = class _OpenDisplayDevice {
|
|
|
1788
1813
|
validateAckResponse(response, 112 /* DIRECT_WRITE_START */);
|
|
1789
1814
|
let autoCompleted = false;
|
|
1790
1815
|
if (useCompression && remainingCompressed && remainingCompressed.length > 0) {
|
|
1791
|
-
autoCompleted = await this.sendDataChunks(remainingCompressed);
|
|
1816
|
+
autoCompleted = await this.sendDataChunks(remainingCompressed, onProgress, onStatusChange);
|
|
1792
1817
|
} else if (!useCompression) {
|
|
1793
|
-
autoCompleted = await this.sendDataChunks(imageData);
|
|
1818
|
+
autoCompleted = await this.sendDataChunks(imageData, onProgress, onStatusChange);
|
|
1794
1819
|
}
|
|
1795
1820
|
if (!autoCompleted) {
|
|
1821
|
+
onStatusChange?.("Refreshing display...");
|
|
1796
1822
|
const endCmd = buildDirectWriteEndCommand(refreshMode);
|
|
1797
1823
|
await this.connection.writeCommand(endCmd);
|
|
1798
1824
|
response = await this.connection.readResponse(
|
|
@@ -1810,11 +1836,13 @@ var OpenDisplayDevice = class _OpenDisplayDevice {
|
|
|
1810
1836
|
* - Progress logging
|
|
1811
1837
|
*
|
|
1812
1838
|
* @param imageData - Data to send in chunks
|
|
1839
|
+
* @param onProgress - Optional progress callback (current bytes, total bytes, stage)
|
|
1840
|
+
* @param onStatusChange - Optional status message callback
|
|
1813
1841
|
* @returns True if device auto-completed (sent 0x0072 END early), false if all chunks sent normally
|
|
1814
1842
|
* @throws {ProtocolError} If unexpected response received
|
|
1815
1843
|
* @throws {BLETimeoutError} If no response within timeout
|
|
1816
1844
|
*/
|
|
1817
|
-
async sendDataChunks(imageData) {
|
|
1845
|
+
async sendDataChunks(imageData, onProgress, onStatusChange) {
|
|
1818
1846
|
let bytesSent = 0;
|
|
1819
1847
|
let chunksSent = 0;
|
|
1820
1848
|
while (bytesSent < imageData.length) {
|
|
@@ -1835,6 +1863,7 @@ var OpenDisplayDevice = class _OpenDisplayDevice {
|
|
|
1835
1863
|
console.log(
|
|
1836
1864
|
`No response after chunk ${chunksSent} (${(bytesSent / imageData.length * 100).toFixed(1)}%), waiting for device refresh...`
|
|
1837
1865
|
);
|
|
1866
|
+
onStatusChange?.("Refreshing display...");
|
|
1838
1867
|
response = await this.connection.readResponse(
|
|
1839
1868
|
_OpenDisplayDevice.TIMEOUT_REFRESH
|
|
1840
1869
|
);
|
|
@@ -1844,10 +1873,12 @@ var OpenDisplayDevice = class _OpenDisplayDevice {
|
|
|
1844
1873
|
}
|
|
1845
1874
|
const [command, isAck] = checkResponseType(response);
|
|
1846
1875
|
if (command === 113 /* DIRECT_WRITE_DATA */) {
|
|
1876
|
+
onProgress?.(bytesSent, imageData.length, "upload");
|
|
1847
1877
|
} else if (command === 114 /* DIRECT_WRITE_END */) {
|
|
1848
1878
|
console.log(
|
|
1849
1879
|
`Received END response after chunk ${chunksSent} - device auto-completed`
|
|
1850
1880
|
);
|
|
1881
|
+
onProgress?.(imageData.length, imageData.length, "upload");
|
|
1851
1882
|
return true;
|
|
1852
1883
|
} else {
|
|
1853
1884
|
throw new ProtocolError(
|