1
+ const connectButton = document . getElementById ( 'connect' ) ;
2
+ const refreshButton = document . getElementById ( 'refresh' ) ;
3
+ const startButton = document . getElementById ( 'start' ) ;
4
+ const disconnectButton = document . getElementById ( 'disconnect' ) ;
5
+ const canvas = document . getElementById ( 'bitmapCanvas' ) ;
6
+ const ctx = canvas . getContext ( '2d' ) ;
7
+
8
+ const UserActionAbortError = 8 ;
9
+ const ArduinoUSBVendorId = 0x2341 ;
10
+
11
+ const imageWidth = 320 ; // Adjust this value based on your bitmap width
12
+ const imageHeight = 240 ; // Adjust this value based on your bitmap height
13
+ const bytesPerPixel = 1 ; // Adjust this value based on your bitmap format
14
+ // const mode = 'RGB565'; // Adjust this value based on your bitmap format
15
+ const totalBytes = imageWidth * imageHeight * bytesPerPixel ;
16
+
17
+ // Set the buffer size to the total bytes. This allows to read the entire bitmap in one go.
18
+ const bufferSize = Math . min ( totalBytes , 16 * 1024 * 1024 ) ; // Max buffer size is 16MB
19
+ const baudRate = 115200 ; // Adjust this value based on your device's baud rate
20
+ const dataBits = 8 ; // Adjust this value based on your device's data bits
21
+ const stopBits = 2 ; // Adjust this value based on your device's stop bits
22
+
23
+ let currentPort , currentReader ;
24
+
25
+ async function requestSerialPort ( ) {
26
+ try {
27
+ // Request a serial port
28
+ const port = await navigator . serial . requestPort ( { filters : [ { usbVendorId : ArduinoUSBVendorId } ] } ) ;
29
+ currentPort = port ;
30
+ return port ;
31
+ } catch ( error ) {
32
+ if ( error . code != UserActionAbortError ) {
33
+ console . log ( error ) ;
34
+ }
35
+ return null ;
36
+ }
37
+ }
38
+
39
+ async function autoConnect ( ) {
40
+ if ( currentPort ) {
41
+ console . log ( '🔌 Already connected to a serial port.' ) ;
42
+ return false ;
43
+ }
44
+
45
+ // Get all serial ports the user has previously granted the website access to.
46
+ const ports = await navigator . serial . getPorts ( ) ;
47
+
48
+ for ( const port of ports ) {
49
+ console . log ( '👀 Serial port found with VID: 0x' + port . getInfo ( ) . usbVendorId . toString ( 16 ) ) ;
50
+ if ( port . getInfo ( ) . usbVendorId === ArduinoUSBVendorId ) {
51
+ currentPort = port ;
52
+ return await connectSerial ( currentPort ) ;
53
+ }
54
+ }
55
+ return false ;
56
+ }
57
+
58
+ async function connectSerial ( port , baudRate = 115200 , dataBits = 8 , stopBits = 2 , bufferSize = 4096 ) {
59
+ try {
60
+ // If the port is already open, close it
61
+ if ( port . readable ) await port . close ( ) ;
62
+ await port . open ( { baudRate : baudRate , parity : "even" , dataBits : dataBits , stopBits : stopBits , bufferSize : bufferSize } ) ;
63
+ console . log ( '✅ Connected to serial port.' ) ;
64
+ return true ;
65
+ } catch ( error ) {
66
+ return false ;
67
+ }
68
+ }
69
+
70
+
71
+ async function readBytes ( port , numBytes , timeout = null ) {
72
+ if ( port . readable . locked ) {
73
+ console . log ( '🔒 Stream is already locked. Ignoring request...' ) ;
74
+ return null ;
75
+ }
76
+
77
+ const bytesRead = new Uint8Array ( numBytes ) ;
78
+ let bytesReadIdx = 0 ;
79
+ let keepReading = true ;
80
+
81
+ // As long as the errors are non-fatal, a new ReadableStream is created automatically and hence port.readable is non-null.
82
+ // If a fatal error occurs, such as the serial device being removed, then port.readable becomes null.
83
+
84
+ while ( port . readable && keepReading ) {
85
+ const reader = port . readable . getReader ( ) ;
86
+ currentReader = reader ;
87
+ let timeoutID = null ;
88
+ let count = 0 ;
89
+
90
+ try {
91
+ while ( bytesReadIdx < numBytes ) {
92
+ if ( timeout ) {
93
+ timeoutID = setTimeout ( ( ) => {
94
+ console . log ( '⌛️ Timeout occurred while reading.' ) ;
95
+ if ( port . readable ) reader ?. cancel ( ) ;
96
+ } , timeout ) ;
97
+ }
98
+
99
+ const { value, done } = await reader . read ( ) ;
100
+ if ( timeoutID ) clearTimeout ( timeoutID ) ;
101
+
102
+ if ( value ) {
103
+ for ( const byte of value ) {
104
+ bytesRead [ bytesReadIdx ++ ] = byte ;
105
+ }
106
+ // count += value.byteLength;
107
+ // console.log(`Read ${value.byteLength} (Total: ${count}) out of ${numBytes} bytes. }`);
108
+ }
109
+
110
+ if ( done ) {
111
+ // |reader| has been canceled.
112
+ console . log ( '🚫 Reader has been canceled' ) ;
113
+ break ;
114
+ }
115
+ }
116
+
117
+ } catch ( error ) {
118
+ // Handle |error|...
119
+ console . log ( '💣 Error occurred while reading: ' ) ;
120
+ console . log ( error ) ;
121
+ } finally {
122
+ keepReading = false ;
123
+ // console.log('🔓 Releasing reader lock...');
124
+ reader ?. releaseLock ( ) ;
125
+ currentReader = null ;
126
+ }
127
+ }
128
+ return bytesRead ;
129
+ }
130
+
131
+ function renderBitmap ( bytes , width , height ) {
132
+ canvas . width = width ;
133
+ canvas . height = height ;
134
+ const BYTES_PER_ROW = width * bytesPerPixel ;
135
+ const BYTES_PER_COL = height * bytesPerPixel ;
136
+
137
+ const imageData = ctx . createImageData ( canvas . width , canvas . height ) ;
138
+ const data = imageData . data ;
139
+
140
+ for ( let row = 0 ; row < BYTES_PER_ROW ; row ++ ) {
141
+ for ( let col = 0 ; col < BYTES_PER_COL ; col ++ ) {
142
+ const byte = bytes [ row * BYTES_PER_COL + col ] ;
143
+ const grayscaleValue = byte ;
144
+
145
+ const idx = ( row * BYTES_PER_COL + col ) * 4 ;
146
+ data [ idx ] = grayscaleValue ; // Red channel
147
+ data [ idx + 1 ] = grayscaleValue ; // Green channel
148
+ data [ idx + 2 ] = grayscaleValue ; // Blue channel
149
+ data [ idx + 3 ] = 255 ; // Alpha channel (opacity)
150
+ }
151
+ }
152
+ ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
153
+ ctx . putImageData ( imageData , 0 , 0 ) ;
154
+ }
155
+
156
+ async function requestFrame ( port ) {
157
+ if ( ! port ?. writable ) {
158
+ console . log ( '🚫 Port is not writable. Ignoring request...' ) ;
159
+ return ;
160
+ }
161
+ // console.log('Writing 1 to the serial port...');
162
+ // Write a 1 to the serial port
163
+ const writer = port . writable . getWriter ( ) ;
164
+ await writer . write ( new Uint8Array ( [ 1 ] ) ) ;
165
+ await writer . close ( ) ;
166
+ }
167
+ async function renderStream ( ) {
168
+ while ( true && currentPort ) {
169
+ await renderFrame ( currentPort ) ;
170
+ }
171
+ }
172
+
173
+ async function renderFrame ( port ) {
174
+ if ( ! port ) return ;
175
+ const bytes = await getFrame ( port ) ;
176
+ if ( ! bytes ) return false ; // Nothing to render
177
+ // console.log(`Reading done ✅. Rendering image...`);
178
+ // Render the bytes as a grayscale bitmap
179
+ renderBitmap ( bytes , imageWidth , imageHeight ) ;
180
+ return true ;
181
+ }
182
+
183
+ async function getFrame ( port ) {
184
+ if ( ! port ) return ;
185
+
186
+ await requestFrame ( port ) ;
187
+ // console.log(`Trying to read ${totalBytes} bytes...`);
188
+ // Read the given amount of bytes
189
+ return await readBytes ( port , totalBytes , 2000 ) ;
190
+ }
191
+
192
+ async function disconnectSerial ( port ) {
193
+ if ( ! port ) return ;
194
+ try {
195
+ currentPort = null ;
196
+ await currentReader ?. cancel ( ) ;
197
+ await port . close ( ) ;
198
+ console . log ( '🔌 Disconnected from serial port.' ) ;
199
+ } catch ( error ) {
200
+ console . error ( '💣 Error occurred while disconnecting: ' + error . message ) ;
201
+ } ;
202
+ }
203
+
204
+ startButton . addEventListener ( 'click' , renderStream ) ;
205
+ connectButton . addEventListener ( 'click' , async ( ) => {
206
+ currentPort = await requestSerialPort ( ) ;
207
+ if ( await connectSerial ( currentPort , baudRate , dataBits , stopBits , bufferSize ) ) {
208
+ renderStream ( ) ;
209
+ }
210
+ } ) ;
211
+ disconnectButton . addEventListener ( 'click' , ( ) => disconnectSerial ( currentPort ) ) ;
212
+ refreshButton . addEventListener ( 'click' , ( ) => {
213
+ renderFrame ( currentPort ) ;
214
+ } ) ;
215
+
216
+ navigator . serial . addEventListener ( "connect" , ( e ) => {
217
+ // Connect to `e.target` or add it to a list of available ports.
218
+ console . log ( '🔌 Serial port became available. VID: 0x' + e . target . getInfo ( ) . usbVendorId . toString ( 16 ) ) ;
219
+ autoConnect ( ) . then ( ( connected ) => {
220
+ if ( connected ) {
221
+ renderStream ( ) ;
222
+ } ;
223
+ } ) ;
224
+ } ) ;
225
+
226
+ navigator . serial . addEventListener ( "disconnect" , ( e ) => {
227
+ // Remove `e.target` from the list of available ports.
228
+ console . log ( '❌ Serial port lost. VID: 0x' + e . target . getInfo ( ) . usbVendorId . toString ( 16 ) ) ;
229
+ currentPort = null ;
230
+ } ) ;
231
+
232
+ // On page load event, try to connect to the serial port
233
+ window . addEventListener ( 'load' , async ( ) => {
234
+ console . log ( '🚀 Page loaded. Trying to connect to serial port...' ) ;
235
+ setTimeout ( ( ) => {
236
+ autoConnect ( ) . then ( ( connected ) => {
237
+ if ( connected ) {
238
+ renderStream ( ) ;
239
+ } ;
240
+ } ) ;
241
+ } , 1000 ) ;
242
+ } ) ;
243
+
244
+ if ( ! ( "serial" in navigator ) ) {
245
+ alert ( "The Web Serial API is not supported in your browser." ) ;
246
+ }
0 commit comments