Skip to content

Commit c9299d4

Browse files
committed
Add start stop sequence transformer
1 parent e9aa024 commit c9299d4

File tree

3 files changed

+177
-5
lines changed

3 files changed

+177
-5
lines changed

libraries/Camera/examples/CameraCaptureWebSerial/CameraCaptureWebSerial.ino

+10-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ If resolution higher than 320x240 is required, please use external RAM via
5252
SDRAM.begin();
5353
*/
5454
#define CHUNK_SIZE 512 // Size of chunks in bytes
55-
#define RESOLUTION CAMERA_R320x240
55+
#define RESOLUTION CAMERA_R320x240 // CAMERA_R160x120
56+
constexpr uint8_t START_SEQUENCE[4] = { 0xfa, 0xce, 0xfe, 0xed };
57+
constexpr uint8_t STOP_SEQUENCE[4] = { 0xda, 0xbb, 0xad, 0x00 };
5658
FrameBuffer fb;
5759

5860
unsigned long lastUpdate = 0;
@@ -84,13 +86,20 @@ void sendFrame(){
8486
size_t bufferSize = cam.frameSize();
8587
digitalWrite(LED_BUILTIN, LOW);
8688

89+
Serial.write(START_SEQUENCE, sizeof(START_SEQUENCE));
90+
Serial.flush();
91+
delay(1);
92+
8793
// Split buffer into chunks
8894
for(size_t i = 0; i < bufferSize; i += CHUNK_SIZE) {
8995
size_t chunkSize = min(bufferSize - i, CHUNK_SIZE);
9096
Serial.write(buffer + i, chunkSize);
9197
Serial.flush();
9298
delay(1); // Optional: Add a small delay to allow the receiver to process the chunk
9399
}
100+
Serial.write(STOP_SEQUENCE, sizeof(STOP_SEQUENCE));
101+
Serial.flush();
102+
delay(1);
94103

95104
digitalWrite(LED_BUILTIN, HIGH);
96105
} else {

libraries/Camera/extras/WebSerialCamera/app.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* to the serial port if it has been previously connected.
1212
* 2. The application requests the camera configuration (mode and resolution) from the board.
1313
* 3. The application starts reading the image data stream from the serial port.
14-
* It waits until the calculated number of bytes have been read and then processes the data.
14+
* It waits until the expected amount of bytes have been read and then processes the data.
1515
* 4. The processed image data is rendered on the canvas.
1616
*
1717
* @author Sebastian Romero
@@ -25,6 +25,9 @@ const canvas = document.getElementById('bitmapCanvas');
2525
const ctx = canvas.getContext('2d');
2626

2727
const imageDataTransfomer = new ImageDataTransformer(ctx);
28+
imageDataTransfomer.setStartSequence([0xfa, 0xce, 0xfe, 0xed]);
29+
imageDataTransfomer.setStopSequence([0xda, 0xbb, 0xad, 0x00]);
30+
2831
// 🐣 Uncomment one of the following lines to apply a filter to the image data
2932
// imageDataTransfomer.filter = new GrayScaleFilter();
3033
// imageDataTransfomer.filter = new BlackAndWhiteFilter();

libraries/Camera/extras/WebSerialCamera/transformers.js

+163-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,166 @@
44
*/
55

66

7+
/**
8+
* Represents a transformer that processes incoming data between start and stop sequences.
9+
*/
10+
class StartStopSequenceTransformer {
11+
constructor(startSequence = null, stopSequence = null, expectedBytes = null) {
12+
this.startSequence = new Uint8Array(startSequence);
13+
this.stopSequence = new Uint8Array(stopSequence);
14+
this.expectedBytes = expectedBytes;
15+
this.buffer = new Uint8Array(0);
16+
this.controller = undefined;
17+
this.waitingForStart = true;
18+
}
19+
20+
/**
21+
* Sets the start sequence for the received data.
22+
* This is used to disregard any data before the start sequence.
23+
* @param {Array<number>} startSequence - The start sequence as an array of numbers.
24+
*/
25+
setStartSequence(startSequence) {
26+
this.startSequence = new Uint8Array(startSequence);
27+
}
28+
29+
/**
30+
* Sets the stop sequence for the received data.
31+
* This is used to know when the data has finished being sent and should be processed.
32+
* @param {Array<number>} stopSequence - The stop sequence as an array of numbers.
33+
*/
34+
setStopSequence(stopSequence) {
35+
this.stopSequence = new Uint8Array(stopSequence);
36+
}
37+
38+
/**
39+
* Sets the expected number of bytes for the received data.
40+
* This is used to check if the number of bytes matches the expected amount
41+
* and discard the data if it doesn't.
42+
*
43+
* @param {number} expectedBytes - The expected number of bytes.
44+
*/
45+
setExpectedBytes(expectedBytes) {
46+
this.expectedBytes = expectedBytes;
47+
}
48+
49+
/**
50+
* Transforms the incoming chunk of data and enqueues the processed bytes to the controller
51+
* between start and stop sequences.
52+
*
53+
* @param {Uint8Array} chunk - The incoming chunk of data.
54+
* @param {TransformStreamDefaultController} controller - The controller for enqueuing processed bytes.
55+
* @returns {Promise<void>} - A promise that resolves when the transformation is complete.
56+
*/
57+
async transform(chunk, controller) {
58+
this.controller = controller;
59+
60+
// Concatenate incoming chunk with existing buffer
61+
this.buffer = new Uint8Array([...this.buffer, ...chunk]);
62+
63+
let startIndex = 0;
64+
65+
while (startIndex < this.buffer.length) {
66+
if (this.waitingForStart) {
67+
// Look for the start sequence
68+
startIndex = this.indexOfSequence(this.buffer, this.startSequence, startIndex);
69+
70+
if (startIndex === -1) {
71+
// No start sequence found, discard the buffer
72+
this.buffer = new Uint8Array(0);
73+
return;
74+
}
75+
76+
// Remove bytes before the start sequence
77+
this.buffer = this.buffer.slice(startIndex + this.startSequence.length);
78+
startIndex = 0; // Reset startIndex after removing bytes
79+
this.waitingForStart = false;
80+
}
81+
82+
// Look for the stop sequence
83+
const stopIndex = this.indexOfSequence(this.buffer, this.stopSequence, startIndex);
84+
85+
if (stopIndex === -1) {
86+
// No stop sequence found, wait for more data
87+
return;
88+
}
89+
90+
// Extract bytes between start and stop sequences
91+
const bytesToProcess = this.buffer.slice(startIndex, stopIndex);
92+
this.buffer = this.buffer.slice(stopIndex + this.stopSequence.length);
93+
94+
// Check if the number of bytes matches the expected amount
95+
if (this.expectedBytes !== null && bytesToProcess.length !== this.expectedBytes) {
96+
// Drop all bytes in the buffer to avoid broken data
97+
throw new Error(`🚫 Expected ${this.expectedBytes} bytes, but got ${bytesToProcess.length} bytes instead.`);
98+
this.buffer = new Uint8Array(0);
99+
return;
100+
}
101+
102+
// Notify the controller that bytes have been processed
103+
controller.enqueue(this.convertBytes(bytesToProcess));
104+
this.waitingForStart = true;
105+
}
106+
}
107+
108+
/**
109+
* Flushes the buffer and processes any remaining bytes when the stream is closed.
110+
*
111+
* @param {WritableStreamDefaultController} controller - The controller for the writable stream.
112+
*/
113+
flush(controller) {
114+
// Only enqueue the remaining bytes if they meet the expectedBytes criteria
115+
if (this.buffer.length === this.expectedBytes || this.expectedBytes === null) {
116+
controller?.enqueue(this.buffer);
117+
}
118+
}
119+
120+
/**
121+
* Finds the index of the given sequence in the buffer.
122+
*
123+
* @param {Uint8Array} buffer - The buffer to search.
124+
* @param {Uint8Array} sequence - The sequence to find.
125+
* @param {number} startIndex - The index to start searching from.
126+
* @returns {number} - The index of the sequence in the buffer, or -1 if not found.
127+
*/
128+
indexOfSequence(buffer, sequence, startIndex) {
129+
for (let i = startIndex; i <= buffer.length - sequence.length; i++) {
130+
if (this.isSubarray(buffer, sequence, i)) {
131+
return i;
132+
}
133+
}
134+
return -1;
135+
}
136+
137+
/**
138+
* Checks if a subarray is present at a given index in the buffer.
139+
*
140+
* @param {Uint8Array} buffer - The buffer to check.
141+
* @param {Uint8Array} subarray - The subarray to check.
142+
* @param {number} index - The index to start checking from.
143+
* @returns {boolean} - True if the subarray is present at the given index, false otherwise.
144+
*/
145+
isSubarray(buffer, subarray, index) {
146+
for (let i = 0; i < subarray.length; i++) {
147+
if (buffer[index + i] !== subarray[i]) {
148+
return false;
149+
}
150+
}
151+
return true;
152+
}
153+
154+
/**
155+
* Converts bytes into higher-level data types.
156+
* This method is meant to be overridden by subclasses.
157+
* @param {Uint8Array} bytes
158+
* @returns
159+
*/
160+
convertBytes(bytes) {
161+
return bytes;
162+
}
163+
164+
}
165+
166+
7167
/**
8168
* A transformer class that waits for a specific number of bytes before processing them.
9169
*/
@@ -77,9 +237,9 @@ class BytesWaitTransformer {
77237
/**
78238
* Represents an Image Data Transformer that converts bytes into image data.
79239
* See other example for PNGs here: https://github.com/mdn/dom-examples/blob/main/streams/png-transform-stream/png-transform-stream.js
80-
* @extends BytesWaitTransformer
240+
* @extends StartStopSequenceTransformer
81241
*/
82-
class ImageDataTransformer extends BytesWaitTransformer {
242+
class ImageDataTransformer extends StartStopSequenceTransformer {
83243
/**
84244
* Creates a new instance of the Transformer class.
85245
* @param {CanvasRenderingContext2D} context - The canvas rendering context.
@@ -110,7 +270,7 @@ class ImageDataTransformer extends BytesWaitTransformer {
110270
this.height = height;
111271
this.imageDataProcessor.setResolution(width, height);
112272
if(this.isConfigured()){
113-
this.setBytesToWait(this.imageDataProcessor.getTotalBytes());
273+
this.setExpectedBytes(this.imageDataProcessor.getTotalBytes());
114274
}
115275
}
116276

0 commit comments

Comments
 (0)