1
+ /**
2
+ * @fileoverview This file contains the SerialConnectionHandler class.
3
+ * It handles the connection between the browser and the Arduino board via Web Serial.
4
+ * @author Sebastian Romero
5
+ */
6
+
1
7
const ArduinoUSBVendorId = 0x2341 ;
2
8
const UserActionAbortError = 8 ;
3
9
4
10
/**
5
11
* Handles the connection between the browser and the Arduino board via Web Serial.
12
+ * Please note that for board with software serial over USB, the baud rate and other serial settings have no effect.
6
13
*/
7
14
class SerialConnectionHandler {
15
+ /**
16
+ * Represents a serial connection handler.
17
+ * @constructor
18
+ * @param {number } [baudRate=115200] - The baud rate of the serial connection.
19
+ * @param {number } [dataBits=8] - The number of data bits.
20
+ * @param {number } [stopBits=1] - The number of stop bits.
21
+ * @param {string } [parity="none"] - The parity setting.
22
+ * @param {string } [flowControl="none"] - The flow control setting.
23
+ * @param {number } [bufferSize=2097152] - The size of the buffer in bytes. Max buffer size is 16MB
24
+ * @param {number } [timeout=2000] - The connection timeout value in milliseconds.
25
+ */
8
26
constructor ( baudRate = 115200 , dataBits = 8 , stopBits = 1 , parity = "none" , flowControl = "none" , bufferSize = 2 * 1024 * 1024 , timeout = 2000 ) {
9
27
this . baudRate = baudRate ;
10
28
this . dataBits = dataBits ;
11
29
this . stopBits = stopBits ;
12
30
this . flowControl = flowControl ;
13
- // Max buffer size is 16MB
14
31
this . bufferSize = bufferSize ;
15
32
this . parity = parity ;
16
33
this . timeout = timeout ;
17
34
this . currentPort = null ;
18
35
this . currentReader = null ;
36
+ this . currentTransformer = null ;
19
37
this . readableStreamClosed = null ;
20
- this . transformer = new BytesWaitTransformer ( ) ;
21
38
this . registerEvents ( ) ;
22
39
}
23
40
@@ -38,14 +55,6 @@ class SerialConnectionHandler {
38
55
}
39
56
}
40
57
41
- /**
42
- * Sets the transformer that is used to convert bytes into higher-level data types.
43
- * @param {* } transformer
44
- */
45
- setTransformer ( transformer ) {
46
- this . transformer = transformer ;
47
- }
48
-
49
58
/**
50
59
* Checks if the browser is connected to a serial port.
51
60
* @returns {boolean } True if the browser is connected, false otherwise.
@@ -93,7 +102,7 @@ class SerialConnectionHandler {
93
102
this . currentPort = null ;
94
103
await this . currentReader ?. cancel ( ) ;
95
104
await this . readableStreamClosed . catch ( ( ) => { } ) ; // Ignores the error
96
- this . transformer . flush ( ) ;
105
+ this . currentTransformer ? .flush ( ) ;
97
106
await port . close ( ) ;
98
107
console . log ( '🔌 Disconnected from serial port.' ) ;
99
108
if ( this . onDisconnect ) this . onDisconnect ( ) ;
@@ -126,21 +135,31 @@ class SerialConnectionHandler {
126
135
return false ;
127
136
}
128
137
138
+
139
+ /**
140
+ * Reads a specified number of bytes from the serial connection.
141
+ * @param {number } numBytes - The number of bytes to read.
142
+ * @returns {Promise<Uint8Array> } - A promise that resolves to a Uint8Array containing the read bytes.
143
+ */
144
+ async readBytes ( numBytes ) {
145
+ return await this . readData ( new BytesWaitTransformer ( numBytes ) ) ;
146
+ }
147
+
129
148
/**
130
149
* Reads the specified number of bytes from the serial port.
131
- * @param {number } numBytes The number of bytes to read.
132
- * @param {number } timeout The timeout in milliseconds.
150
+ * @param {Transformer } transformer The transformer that is used to process the bytes.
133
151
* If the timeout is reached, the reader will be canceled and the read lock will be released.
134
152
*/
135
- async readBytes ( numBytes , timeout = null ) {
153
+ async readData ( transformer ) {
154
+ if ( ! transformer ) throw new Error ( 'Transformer is null' ) ;
136
155
if ( ! this . currentPort ) return null ;
137
156
if ( this . currentPort . readable . locked ) {
138
157
console . log ( '🔒 Stream is already locked. Ignoring request...' ) ;
139
158
return null ;
140
159
}
141
160
142
- this . transformer . setBytesToWait ( numBytes ) ;
143
- const transformStream = new TransformStream ( this . transformer ) ;
161
+ const transformStream = new TransformStream ( transformer ) ;
162
+ this . currentTransformer = transformer ;
144
163
// pipeThrough() cannot be used because we need a promise that resolves when the stream is closed
145
164
// to be able to close the port. pipeTo() returns such a promise.
146
165
// SEE: https://stackoverflow.com/questions/71262432/how-can-i-close-a-web-serial-port-that-ive-piped-through-a-transformstream
@@ -150,12 +169,12 @@ class SerialConnectionHandler {
150
169
let timeoutID = null ;
151
170
152
171
try {
153
- if ( timeout ) {
172
+ if ( this . timeout ) {
154
173
timeoutID = setTimeout ( ( ) => {
155
174
console . log ( '⌛️ Timeout occurred while reading.' ) ;
156
175
if ( this . currentPort ?. readable ) reader ?. cancel ( ) ;
157
176
this . transformer . flush ( ) ;
158
- } , timeout ) ;
177
+ } , this . timeout ) ;
159
178
}
160
179
const { value, done } = await reader . read ( ) ;
161
180
if ( timeoutID ) clearTimeout ( timeoutID ) ;
@@ -173,9 +192,16 @@ class SerialConnectionHandler {
173
192
await this . readableStreamClosed . catch ( ( ) => { } ) ; // Ignores the error
174
193
reader ?. releaseLock ( ) ;
175
194
this . currentReader = null ;
195
+ this . currentTransformer = null ;
176
196
}
177
197
}
178
198
199
+ /**
200
+ * Sends the provided byte array data through the current serial port.
201
+ *
202
+ * @param {ArrayBuffer } byteArray - The byte array data to send.
203
+ * @returns {Promise<void> } - A promise that resolves when the data has been sent.
204
+ */
179
205
async sendData ( byteArray ) {
180
206
if ( ! this . currentPort ?. writable ) {
181
207
console . log ( '🚫 Port is not writable. Ignoring request...' ) ;
@@ -196,10 +222,19 @@ class SerialConnectionHandler {
196
222
return this . sendData ( [ 1 ] ) ;
197
223
}
198
224
225
+ /**
226
+ * Requests the camera configuration from the board by writing a 2 to the serial port.
227
+ * @returns {Promise } A promise that resolves with the configuration data.
228
+ */
199
229
async requestConfig ( ) {
200
230
return this . sendData ( [ 2 ] ) ;
201
231
}
202
232
233
+ /**
234
+ * Requests the camera resolution from the board and reads it back from the serial port.
235
+ * The configuration simply consists of two bytes: the mode and the resolution.
236
+ * @returns {Promise<ArrayBuffer> } The raw configuration data as an ArrayBuffer.
237
+ */
203
238
async getConfig ( ) {
204
239
if ( ! this . currentPort ) return ;
205
240
@@ -211,15 +246,12 @@ class SerialConnectionHandler {
211
246
/**
212
247
* Requests a frame from the Arduino board and reads the specified number of bytes from the serial port afterwards.
213
248
* Times out after the timeout in milliseconds specified in the constructor.
214
- * @param {number } totalBytes The number of bytes to read.
249
+ * @param {Transformer } transformer The transformer that is used to process the bytes.
215
250
*/
216
- async getFrame ( totalBytes ) {
251
+ async getFrame ( transformer ) {
217
252
if ( ! this . currentPort ) return ;
218
-
219
253
await this . requestFrame ( ) ;
220
- // console.log(`Trying to read ${totalBytes} bytes...`);
221
- // Read the given amount of bytes
222
- return await this . readBytes ( totalBytes , this . timeout ) ;
254
+ return await this . readData ( transformer , this . timeout ) ;
223
255
}
224
256
225
257
/**
0 commit comments