1
+ /**
2
+ * @fileoverview This file contains the main application logic.
3
+ *
4
+ * The application uses the Web Serial API to connect to the serial port.
5
+ * Check the following links for more information on the Web Serial API:
6
+ * https://developer.chrome.com/articles/serial/
7
+ * https://wicg.github.io/serial/
8
+ *
9
+ * The flow of the application is as follows:
10
+ * 1. The user clicks the "Connect" button or the browser automatically connects
11
+ * to the serial port if it has been previously connected.
12
+ * 2. The application requests the camera configuration (mode and resolution) from the board.
13
+ * 3. The application starts reading the image data stream from the serial port.
14
+ * It waits until the expected amount of bytes have been read and then processes the data.
15
+ * 4. The processed image data is rendered on the canvas.
16
+ *
17
+ * @author Sebastian Romero
18
+ */
19
+
20
+ const connectButton = document . getElementById ( 'connect' ) ;
21
+ const refreshButton = document . getElementById ( 'refresh' ) ;
22
+ const startButton = document . getElementById ( 'start' ) ;
23
+ const saveImageButton = document . getElementById ( 'save-image' ) ;
24
+ const canvas = document . getElementById ( 'bitmapCanvas' ) ;
25
+ const ctx = canvas . getContext ( '2d' ) ;
26
+
27
+ const imageDataTransfomer = new ImageDataTransformer ( ctx ) ;
28
+ imageDataTransfomer . setStartSequence ( [ 0xfa , 0xce , 0xfe , 0xed ] ) ;
29
+ imageDataTransfomer . setStopSequence ( [ 0xda , 0xbb , 0xad , 0x00 ] ) ;
30
+
31
+ // 🐣 Uncomment one of the following lines to apply a filter to the image data
32
+ // imageDataTransfomer.filter = new GrayScaleFilter();
33
+ // imageDataTransfomer.filter = new BlackAndWhiteFilter();
34
+ // imageDataTransfomer.filter = new SepiaColorFilter();
35
+ // imageDataTransfomer.filter = new PixelateFilter(8);
36
+ // imageDataTransfomer.filter = new BlurFilter(8);
37
+ const connectionHandler = new SerialConnectionHandler ( ) ;
38
+
39
+
40
+ // Connection handler event listeners
41
+
42
+ connectionHandler . onConnect = async ( ) => {
43
+ connectButton . textContent = 'Disconnect' ;
44
+ cameraConfig = await connectionHandler . getConfig ( ) ;
45
+ if ( ! cameraConfig ) {
46
+ console . error ( '🚫 Could not read camera configuration. Aborting...' ) ;
47
+ return ;
48
+ }
49
+ const imageMode = CAMERA_MODES [ cameraConfig [ 0 ] ] ;
50
+ const imageResolution = CAMERA_RESOLUTIONS [ cameraConfig [ 1 ] ] ;
51
+ if ( ! imageMode || ! imageResolution ) {
52
+ console . error ( `🚫 Invalid camera configuration: ${ cameraConfig [ 0 ] } , ${ cameraConfig [ 1 ] } . Aborting...` ) ;
53
+ return ;
54
+ }
55
+ imageDataTransfomer . setImageMode ( imageMode ) ;
56
+ imageDataTransfomer . setResolution ( imageResolution . width , imageResolution . height ) ;
57
+ renderStream ( ) ;
58
+ } ;
59
+
60
+ connectionHandler . onDisconnect = ( ) => {
61
+ connectButton . textContent = 'Connect' ;
62
+ imageDataTransfomer . reset ( ) ;
63
+ } ;
64
+
65
+
66
+ // Rendering logic
67
+
68
+ async function renderStream ( ) {
69
+ while ( connectionHandler . isConnected ( ) ) {
70
+ if ( imageDataTransfomer . isConfigured ( ) ) await renderFrame ( ) ;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Renders the image data for one frame from the board and renders it.
76
+ * @returns {Promise<boolean> } True if a frame was rendered, false otherwise.
77
+ */
78
+ async function renderFrame ( ) {
79
+ if ( ! connectionHandler . isConnected ( ) ) return ;
80
+ const imageData = await connectionHandler . getFrame ( imageDataTransfomer ) ;
81
+ if ( ! imageData ) return false ; // Nothing to render
82
+ if ( ! ( imageData instanceof ImageData ) ) throw new Error ( '🚫 Image data is not of type ImageData' ) ;
83
+ renderBitmap ( ctx , imageData ) ;
84
+ return true ;
85
+ }
86
+
87
+ /**
88
+ * Renders the image data on the canvas.
89
+ * @param {CanvasRenderingContext2D } context The canvas context to render on.
90
+ * @param {ImageData } imageData The image data to render.
91
+ */
92
+ function renderBitmap ( context , imageData ) {
93
+ context . canvas . width = imageData . width ;
94
+ context . canvas . height = imageData . height ;
95
+ context . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
96
+ context . putImageData ( imageData , 0 , 0 ) ;
97
+ }
98
+
99
+
100
+ // UI Event listeners
101
+
102
+ startButton . addEventListener ( 'click' , renderStream ) ;
103
+
104
+ connectButton . addEventListener ( 'click' , async ( ) => {
105
+ if ( connectionHandler . isConnected ( ) ) {
106
+ connectionHandler . disconnectSerial ( ) ;
107
+ } else {
108
+ await connectionHandler . requestSerialPort ( ) ;
109
+ await connectionHandler . connectSerial ( ) ;
110
+ }
111
+ } ) ;
112
+
113
+ refreshButton . addEventListener ( 'click' , ( ) => {
114
+ if ( imageDataTransfomer . isConfigured ( ) ) renderFrame ( ) ;
115
+ } ) ;
116
+
117
+ saveImageButton . addEventListener ( 'click' , ( ) => {
118
+ const link = document . createElement ( 'a' ) ;
119
+ link . download = 'image.png' ;
120
+ link . href = canvas . toDataURL ( ) ;
121
+ link . click ( ) ;
122
+ link . remove ( ) ;
123
+ } ) ;
124
+
125
+ // On page load event, try to connect to the serial port
126
+ window . addEventListener ( 'load' , async ( ) => {
127
+ console . log ( '🚀 Page loaded. Trying to connect to serial port...' ) ;
128
+ setTimeout ( ( ) => {
129
+ connectionHandler . autoConnect ( ) ;
130
+ } , 1000 ) ;
131
+ } ) ;
132
+
133
+ if ( ! ( "serial" in navigator ) ) {
134
+ alert ( "The Web Serial API is not supported in your browser." ) ;
135
+ }
0 commit comments