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