|
1 | 1 | const ArduinoUSBVendorId = 0x2341; |
2 | 2 | const UserActionAbortError = 8; |
3 | 3 |
|
| 4 | +class BytesWaitTransformer { |
| 5 | + constructor(waitBytes) { |
| 6 | + this.waitBytes = waitBytes; |
| 7 | + this.buffer = new Uint8Array(0); |
| 8 | + this.controller = undefined; |
| 9 | + } |
| 10 | + |
| 11 | + async transform(chunk, controller) { |
| 12 | + this.controller = controller; |
| 13 | + |
| 14 | + // Concatenate incoming chunk with existing buffer |
| 15 | + this.buffer = new Uint8Array([...this.buffer, ...chunk]); |
| 16 | + |
| 17 | + while (this.buffer.length >= this.waitBytes) { |
| 18 | + // Extract the required number of bytes |
| 19 | + const bytesToProcess = this.buffer.slice(0, this.waitBytes); |
| 20 | + |
| 21 | + // Remove processed bytes from the buffer |
| 22 | + this.buffer = this.buffer.slice(this.waitBytes); |
| 23 | + |
| 24 | + // Notify the controller that bytes have been processed |
| 25 | + controller.enqueue(bytesToProcess); |
| 26 | + } |
| 27 | + } |
| 28 | + |
| 29 | + flush(controller) { |
| 30 | + if (this.buffer.length > 0) { |
| 31 | + // Handle remaining bytes (if any) when the stream is closed |
| 32 | + const remainingBytes = this.buffer.slice(); |
| 33 | + console.log("Remaining bytes:", remainingBytes); |
| 34 | + |
| 35 | + // Notify the controller that remaining bytes have been processed |
| 36 | + controller.enqueue(remainingBytes); |
| 37 | + } |
| 38 | + } |
| 39 | + } |
| 40 | + |
4 | 41 | /** |
5 | 42 | * Handles the connection between the browser and the Arduino board via Web Serial. |
6 | 43 | */ |
@@ -120,61 +157,42 @@ class SerialConnectionHandler { |
120 | 157 | * If the timeout is reached, the reader will be canceled and the read lock will be released. |
121 | 158 | */ |
122 | 159 | async readBytes(numBytes, timeout = null) { |
123 | | - if (this.currentPort.readable.locked) { |
| 160 | + if(!this.currentPort) return null; |
| 161 | + if(this.currentPort.readable.locked) { |
124 | 162 | console.log('🔒 Stream is already locked. Ignoring request...'); |
125 | 163 | return null; |
126 | 164 | } |
127 | 165 |
|
128 | | - const bytesRead = new Uint8Array(numBytes); |
129 | | - let bytesReadIdx = 0; |
130 | | - let keepReading = true; |
131 | | - |
132 | | - // As long as the errors are non-fatal, a new ReadableStream is created automatically and hence port.readable is non-null. |
133 | | - // If a fatal error occurs, such as the serial device being removed, then port.readable becomes null. |
134 | | - |
135 | | - while (this.currentPort?.readable && keepReading) { |
136 | | - const reader = this.currentPort.readable.getReader(); |
137 | | - this.currentReader = reader; |
138 | | - let timeoutID = null; |
139 | | - // let count = 0; |
140 | | - |
141 | | - try { |
142 | | - while (bytesReadIdx < numBytes) { |
143 | | - if (timeout) { |
144 | | - timeoutID = setTimeout(() => { |
145 | | - console.log('⌛️ Timeout occurred while reading.'); |
146 | | - if (this.currentPort?.readable) reader?.cancel(); |
147 | | - }, timeout); |
148 | | - } |
149 | | - |
150 | | - const { value, done } = await reader.read(); |
151 | | - if (timeoutID) clearTimeout(timeoutID); |
152 | | - |
153 | | - if (value) { |
154 | | - for (const byte of value) { |
155 | | - bytesRead[bytesReadIdx++] = byte; |
156 | | - if (bytesReadIdx >= numBytes) break; |
157 | | - } |
158 | | - // count += value.byteLength; |
159 | | - // console.log(`Read ${value.byteLength} (Total: ${count}) out of ${numBytes} bytes.}`); |
160 | | - } |
161 | | - |
162 | | - if (done) { |
163 | | - console.log('🚫 Reader has been canceled'); |
164 | | - break; |
165 | | - } |
166 | | - } |
167 | | - |
168 | | - } catch (error) { |
169 | | - console.error('💣 Error occurred while reading: ' + error.message); |
170 | | - } finally { |
171 | | - keepReading = false; |
172 | | - // console.log('🔓 Releasing reader lock...'); |
173 | | - reader?.releaseLock(); |
174 | | - this.currentReader = null; |
| 166 | + const transformer = new BytesWaitTransformer(numBytes); |
| 167 | + const transformStream = new TransformStream(transformer); |
| 168 | + const pipedStream = this.currentPort.readable.pipeThrough(transformStream); |
| 169 | + const reader = pipedStream.getReader(); |
| 170 | + this.currentReader = reader; |
| 171 | + let timeoutID = null; |
| 172 | + |
| 173 | + try { |
| 174 | + if (timeout) { |
| 175 | + timeoutID = setTimeout(() => { |
| 176 | + console.log('⌛️ Timeout occurred while reading.'); |
| 177 | + if (this.currentPort?.readable) reader?.cancel(); |
| 178 | + }, timeout); |
| 179 | + } |
| 180 | + const { value, done } = await reader.read(); |
| 181 | + if (timeoutID) clearTimeout(timeoutID); |
| 182 | + |
| 183 | + if (done) { |
| 184 | + console.log('🚫 Reader has been canceled'); |
| 185 | + return null; |
175 | 186 | } |
| 187 | + return value; |
| 188 | + } catch (error) { |
| 189 | + console.error('💣 Error occurred while reading: ' + error.message); |
| 190 | + } finally { |
| 191 | + // console.log('🔓 Releasing reader lock...'); |
| 192 | + await reader?.cancel(); // Discards any enqueued data |
| 193 | + reader?.releaseLock(); |
| 194 | + this.currentReader = null; |
176 | 195 | } |
177 | | - return bytesRead; |
178 | 196 | } |
179 | 197 |
|
180 | 198 | async sendData(byteArray) { |
|
0 commit comments