Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 5ced73c

Browse files
committedMay 24, 2023
Implement connection handler
1 parent 66f1a02 commit 5ced73c

File tree

3 files changed

+183
-155
lines changed

3 files changed

+183
-155
lines changed
 

‎libraries/Camera/extras/WebSerialCamera/app.js

Lines changed: 18 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ const saveImageButton = document.getElementById('save-image');
66
const canvas = document.getElementById('bitmapCanvas');
77
const ctx = canvas.getContext('2d');
88

9-
const UserActionAbortError = 8;
10-
const ArduinoUSBVendorId = 0x2341;
9+
// TODO check for signals
10+
// TODO implement transformer
11+
// TODO get image format from device
12+
// SEE: https://developer.chrome.com/articles/serial/#transforming-streams
13+
// SEE: https://developer.chrome.com/articles/serial/#signals
1114

1215
config = {
1316
"RGB565": {
@@ -34,114 +37,8 @@ const baudRate = 115200; // Adjust this value based on your device's baud rate
3437
const dataBits = 8; // Adjust this value based on your device's data bits
3538
const stopBits = 2; // Adjust this value based on your device's stop bits
3639

37-
let currentPort, currentReader;
3840
const imageDataProcessor = new ImageDataProcessor(ctx, mode, imageWidth, imageHeight);
39-
40-
async function requestSerialPort(){
41-
try {
42-
// Request a serial port
43-
const port = await navigator.serial.requestPort({ filters: [{ usbVendorId: ArduinoUSBVendorId }] });
44-
currentPort = port;
45-
return port;
46-
} catch (error) {
47-
if(error.code != UserActionAbortError){
48-
console.log(error);
49-
}
50-
return null;
51-
}
52-
}
53-
54-
async function autoConnect(){
55-
if(currentPort){
56-
console.log('🔌 Already connected to a serial port.');
57-
return false;
58-
}
59-
60-
// Get all serial ports the user has previously granted the website access to.
61-
const ports = await navigator.serial.getPorts();
62-
63-
for (const port of ports) {
64-
console.log('👀 Serial port found with VID: 0x' + port.getInfo().usbVendorId.toString(16));
65-
if(port.getInfo().usbVendorId === ArduinoUSBVendorId){
66-
currentPort = port;
67-
return await connectSerial(currentPort);
68-
}
69-
}
70-
return false;
71-
}
72-
73-
async function connectSerial(port, baudRate = 115200, dataBits = 8, stopBits = 2, bufferSize = 4096) {
74-
try {
75-
// If the port is already open, close it
76-
if (port.readable) await port.close();
77-
await port.open({ baudRate: baudRate, parity: "even", dataBits: dataBits, stopBits: stopBits, bufferSize: bufferSize });
78-
console.log('✅ Connected to serial port.');
79-
return true;
80-
} catch (error) {
81-
return false;
82-
}
83-
}
84-
85-
async function readBytes(port, numBytes, timeout = null){
86-
if(port.readable.locked){
87-
console.log('🔒 Stream is already locked. Ignoring request...');
88-
return null;
89-
}
90-
91-
const bytesRead = new Uint8Array(numBytes);
92-
let bytesReadIdx = 0;
93-
let keepReading = true;
94-
95-
// As long as the errors are non-fatal, a new ReadableStream is created automatically and hence port.readable is non-null.
96-
// If a fatal error occurs, such as the serial device being removed, then port.readable becomes null.
97-
98-
while (port.readable && keepReading) {
99-
const reader = port.readable.getReader();
100-
currentReader = reader;
101-
let timeoutID = null;
102-
// let count = 0;
103-
104-
try {
105-
while (bytesReadIdx < numBytes) {
106-
if(timeout){
107-
timeoutID = setTimeout(() => {
108-
console.log('⌛️ Timeout occurred while reading.');
109-
if(port.readable) reader?.cancel();
110-
}, timeout);
111-
}
112-
113-
const { value, done } = await reader.read();
114-
if(timeoutID) clearTimeout(timeoutID);
115-
116-
if(value){
117-
for (const byte of value) {
118-
bytesRead[bytesReadIdx++] = byte;
119-
if (bytesReadIdx >= numBytes) break;
120-
}
121-
// count += value.byteLength;
122-
// console.log(`Read ${value.byteLength} (Total: ${count}) out of ${numBytes} bytes.}`);
123-
}
124-
125-
if (done) {
126-
// |reader| has been canceled.
127-
console.log('🚫 Reader has been canceled');
128-
break;
129-
}
130-
}
131-
132-
} catch (error) {
133-
// Handle |error|...
134-
console.log('💣 Error occurred while reading: ');
135-
console.log(error);
136-
} finally {
137-
keepReading = false;
138-
// console.log('🔓 Releasing reader lock...');
139-
reader?.releaseLock();
140-
currentReader = null;
141-
}
142-
}
143-
return bytesRead;
144-
}
41+
const connectionHandler = new SerialConnectionHandler(baudRate, dataBits, stopBits, "even", "hardware", bufferSize);
14542

14643
function renderBitmap(bytes, width, height) {
14744
canvas.width = width;
@@ -151,65 +48,31 @@ function renderBitmap(bytes, width, height) {
15148
ctx.putImageData(imageData, 0, 0);
15249
}
15350

154-
async function requestFrame(port){
155-
if(!port?.writable) {
156-
console.log('🚫 Port is not writable. Ignoring request...');
157-
return;
158-
}
159-
// console.log('Writing 1 to the serial port...');
160-
// Write a 1 to the serial port
161-
const writer = port.writable.getWriter();
162-
await writer.write(new Uint8Array([1]));
163-
await writer.close();
164-
}
165-
16651
async function renderStream(){
167-
while(true && currentPort){
168-
await renderFrame(currentPort);
52+
while(connectionHandler.isConnected()){
53+
await renderFrame();
16954
}
17055
}
17156

172-
async function renderFrame(port){
173-
if(!port) return;
174-
const bytes = await getFrame(port);
175-
if(!bytes) return false; // Nothing to render
57+
async function renderFrame(){
58+
if(!connectionHandler.isConnected()) return;
59+
const bytes = await connectionHandler.getFrame(totalBytes);
60+
if(!bytes || bytes.length == 0) return false; // Nothing to render
17661
// console.log(`Reading done ✅. Rendering image...`);
177-
// Render the bytes as a grayscale bitmap
17862
renderBitmap(bytes, imageWidth, imageHeight);
17963
return true;
18064
}
18165

182-
async function getFrame(port) {
183-
if(!port) return;
184-
185-
await requestFrame(port);
186-
// console.log(`Trying to read ${totalBytes} bytes...`);
187-
// Read the given amount of bytes
188-
return await readBytes(port, totalBytes, 2000);
189-
}
190-
191-
async function disconnectSerial(port) {
192-
if(!port) return;
193-
try {
194-
currentPort = null;
195-
await currentReader?.cancel();
196-
await port.close();
197-
console.log('🔌 Disconnected from serial port.');
198-
} catch (error) {
199-
console.error('💣 Error occurred while disconnecting: ' + error.message);
200-
};
201-
}
202-
20366
startButton.addEventListener('click', renderStream);
20467
connectButton.addEventListener('click', async () => {
205-
currentPort = await requestSerialPort();
206-
if(await connectSerial(currentPort, baudRate, dataBits, stopBits, bufferSize, flowControl)){
68+
await connectionHandler.requestSerialPort();
69+
if(await connectionHandler.connectSerial()){
20770
renderStream();
20871
}
20972
});
210-
disconnectButton.addEventListener('click', () => disconnectSerial(currentPort));
73+
disconnectButton.addEventListener('click', () => connectionHandler.disconnectSerial());
21174
refreshButton.addEventListener('click', () => {
212-
renderFrame(currentPort);
75+
renderFrame();
21376
});
21477

21578
saveImageButton.addEventListener('click', () => {
@@ -223,7 +86,7 @@ saveImageButton.addEventListener('click', () => {
22386
navigator.serial.addEventListener("connect", (e) => {
22487
// Connect to `e.target` or add it to a list of available ports.
22588
console.log('🔌 Serial port became available. VID: 0x' + e.target.getInfo().usbVendorId.toString(16));
226-
autoConnect().then((connected) => {
89+
connectionHandler.autoConnect().then((connected) => {
22790
if(connected){
22891
renderStream();
22992
};
@@ -240,7 +103,7 @@ navigator.serial.addEventListener("disconnect", (e) => {
240103
window.addEventListener('load', async () => {
241104
console.log('🚀 Page loaded. Trying to connect to serial port...');
242105
setTimeout(() => {
243-
autoConnect().then((connected) => {
106+
connectionHandler.autoConnect().then((connected) => {
244107
if (connected) {
245108
renderStream();
246109
};

‎libraries/Camera/extras/WebSerialCamera/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
</div>
1919
</div>
2020
<script src="imageDataProcessor.js"></script>
21+
<script src="serialConnectionHandler.js"></script>
2122
<script src="app.js"></script>
2223
</body>
2324
</html>
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
const ArduinoUSBVendorId = 0x2341;
2+
const UserActionAbortError = 8;
3+
4+
class SerialConnectionHandler {
5+
constructor(baudRate = 115200, dataBits = 8, stopBits = 1, parity = "none", flowControl = "none", bufferSize = 4096, timeout = 2000) {
6+
this.baudRate = baudRate;
7+
this.dataBits = dataBits;
8+
this.stopBits = stopBits;
9+
this.flowControl = flowControl;
10+
this.bufferSize = bufferSize;
11+
this.parity = parity;
12+
this.timeout = timeout;
13+
this.currentPort = null;
14+
this.currentReader = null;
15+
}
16+
17+
async requestSerialPort() {
18+
try {
19+
const port = await navigator.serial.requestPort({ filters: [{ usbVendorId: ArduinoUSBVendorId }] });
20+
this.currentPort = port;
21+
return port;
22+
} catch (error) {
23+
if (error.code != UserActionAbortError) {
24+
console.log(error);
25+
}
26+
return null;
27+
}
28+
}
29+
30+
isConnected(){
31+
return this.currentPort?.readable != null;
32+
}
33+
34+
async connectSerial() {
35+
try {
36+
// If the port is already open, close it
37+
if (this.isConnected()) await this.currentPort.close();
38+
await this.currentPort.open({
39+
baudRate: this.baudRate,
40+
parity: this.parity,
41+
dataBits: this.dataBits,
42+
stopBits: this.stopBits,
43+
bufferSize: this.bufferSize,
44+
flowControl: this.flowControl
45+
});
46+
console.log('✅ Connected to serial port.');
47+
return true;
48+
} catch (error) {
49+
return false;
50+
}
51+
}
52+
53+
async disconnectSerial() {
54+
if (!this.currentPort) return;
55+
try {
56+
const port = this.currentPort;
57+
this.currentPort = null;
58+
await this.currentReader?.cancel();
59+
await port.close();
60+
console.log('🔌 Disconnected from serial port.');
61+
} catch (error) {
62+
console.error('💣 Error occurred while disconnecting: ' + error.message);
63+
};
64+
}
65+
66+
async autoConnect() {
67+
if (this.currentPort) {
68+
console.log('🔌 Already connected to a serial port.');
69+
return false;
70+
}
71+
72+
// Get all serial ports the user has previously granted the website access to.
73+
const ports = await navigator.serial.getPorts();
74+
75+
for (const port of ports) {
76+
console.log('👀 Serial port found with VID: 0x' + port.getInfo().usbVendorId.toString(16));
77+
if (port.getInfo().usbVendorId === ArduinoUSBVendorId) {
78+
this.currentPort = port;
79+
return await this.connectSerial(this.currentPort);
80+
}
81+
}
82+
return false;
83+
}
84+
85+
async readBytes(numBytes, timeout = null) {
86+
if (this.currentPort.readable.locked) {
87+
console.log('🔒 Stream is already locked. Ignoring request...');
88+
return null;
89+
}
90+
91+
const bytesRead = new Uint8Array(numBytes);
92+
let bytesReadIdx = 0;
93+
let keepReading = true;
94+
95+
// As long as the errors are non-fatal, a new ReadableStream is created automatically and hence port.readable is non-null.
96+
// If a fatal error occurs, such as the serial device being removed, then port.readable becomes null.
97+
98+
while (this.currentPort?.readable && keepReading) {
99+
const reader = this.currentPort.readable.getReader();
100+
this.currentReader = reader;
101+
let timeoutID = null;
102+
// let count = 0;
103+
104+
try {
105+
while (bytesReadIdx < numBytes) {
106+
if (timeout) {
107+
timeoutID = setTimeout(() => {
108+
console.log('⌛️ Timeout occurred while reading.');
109+
if (this.currentPort.readable) reader?.cancel();
110+
}, timeout);
111+
}
112+
113+
const { value, done } = await reader.read();
114+
if (timeoutID) clearTimeout(timeoutID);
115+
116+
if (value) {
117+
for (const byte of value) {
118+
bytesRead[bytesReadIdx++] = byte;
119+
if (bytesReadIdx >= numBytes) break;
120+
}
121+
// count += value.byteLength;
122+
// console.log(`Read ${value.byteLength} (Total: ${count}) out of ${numBytes} bytes.}`);
123+
}
124+
125+
if (done) {
126+
console.log('🚫 Reader has been canceled');
127+
break;
128+
}
129+
}
130+
131+
} catch (error) {
132+
console.log('💣 Error occurred while reading: ');
133+
console.log(error);
134+
} finally {
135+
keepReading = false;
136+
// console.log('🔓 Releasing reader lock...');
137+
reader?.releaseLock();
138+
this.currentReader = null;
139+
}
140+
}
141+
return bytesRead;
142+
}
143+
144+
async requestFrame(){
145+
if(!this.currentPort?.writable) {
146+
console.log('🚫 Port is not writable. Ignoring request...');
147+
return;
148+
}
149+
// console.log('Writing 1 to the serial port...');
150+
// Write a 1 to the serial port
151+
const writer = this.currentPort.writable.getWriter();
152+
await writer.write(new Uint8Array([1]));
153+
await writer.close();
154+
}
155+
156+
async getFrame(totalBytes) {
157+
if (!this.currentPort) return;
158+
159+
await this.requestFrame(this.currentPort);
160+
// console.log(`Trying to read ${totalBytes} bytes...`);
161+
// Read the given amount of bytes
162+
return await this.readBytes(totalBytes, this.timeout);
163+
}
164+
}

0 commit comments

Comments
 (0)
Please sign in to comment.