11'use client'
2- import { useState } from 'react'
2+ import { useState , useRef } from 'react'
33import { Button } from '@/components/ui/button'
44import {
55 Card ,
66 CardContent ,
77 CardDescription ,
88 CardFooter ,
99 CardHeader ,
10- CardTitle ,
1110} from '@/components/ui/card'
1211import { Label } from '@/components/ui/label'
1312import {
@@ -21,13 +20,12 @@ import {
2120 FileText ,
2221 Cpu ,
2322 SearchIcon ,
24- Info ,
2523 CheckCircle ,
2624 InfoIcon ,
2725 PlugZap ,
2826} from 'lucide-react'
29- import { ESPLoader , FlashOptions , LoaderOptions , Transport } from 'esptool-js'
3027import { Terminal } from '@xterm/xterm'
28+ import { EspTerminal } from '../../lib/espTerminal'
3129import { Progress } from '../ui/progress'
3230import { FourSquare } from 'react-loading-indicators'
3331import {
@@ -36,19 +34,14 @@ import {
3634 AccordionItem ,
3735 AccordionTrigger ,
3836} from '../ui/accordion'
39- let esploader : ESPLoader
40- let transport : Transport
41- // eslint-disable-next-line @typescript-eslint/no-explicit-any
42- let device : any = null
37+ import { connectESP , formatMacAddr , sleep } from '../../lib/esp'
38+
4339interface BoardSelectProps {
4440 terminal : Terminal | null
4541}
4642
4743export default function BoardSelect ( { terminal } : BoardSelectProps ) {
4844 // Prüfe, ob der Browser die serielle API unterstützt
49- const [ canUseSerial ] = useState ( ( ) => 'serial' in navigator )
50- const serial = navigator [ 'serial' ]
51-
5245 const [ progress , setProgress ] = useState < number > ( 0 )
5346 const [ sketch , setSketch ] = useState < string > ( '' )
5447 const [ flashing , setFlashing ] = useState < boolean > ( false )
@@ -59,159 +52,157 @@ export default function BoardSelect({ terminal }: BoardSelectProps) {
5952 const [ debugMode , setDebugMode ] = useState < boolean > ( false )
6053 const [ baudRate , setBaudRate ] = useState < number > ( 921600 )
6154 const [ selected , setSelected ] = useState < string > ( 'ota' )
62- // Falls der Browser keine serielle Unterstützung bietet,
63- // wird hier ein Disclaimer angezeigt.
64- if ( ! canUseSerial ) {
65- return (
66- < Card className = "flex h-full w-full flex-col border-2 border-slate-300 shadow-md" >
67- < CardHeader className = "rounded-t-lg bg-white p-4" >
68- < div className = "flex items-center space-x-2" >
69- < Info className = "h-6 w-6" />
70- < CardTitle className = "font-bold" >
71- Browser nicht unterstützt
72- </ CardTitle >
73- </ div >
74- < CardDescription className = "font-semibold" >
75- Ihr Browser unterstützt die serielle Kommunikation nicht. Bitte
76- verwenden Sie Opera, Chrome oder Edge.
77- </ CardDescription >
78- </ CardHeader >
79- </ Card >
80- )
55+ interface EspStub {
56+ flashData ?: (
57+ buffer : ArrayBuffer ,
58+ onProgress : ( bytesWritten : number , totalBytes : number ) => void ,
59+ offset : number ,
60+ ) => Promise < void >
61+ disconnect ?: ( ) => Promise < void >
62+ port ?: {
63+ close ?: ( ) => Promise < void >
64+ addEventListener ?: ( event : string , handler : ( ) => void ) => void
65+ }
8166 }
67+ const [ espStub , setEspStub ] = useState < EspStub | undefined > ( undefined )
8268
83- const espLoaderTerminal = {
84- clean ( ) {
85- terminal ?. clear ( )
86- } ,
87- writeLine ( data : string ) {
88- const match = data . match ( / \( ( \d + ) % \) / )
89- if ( match ) {
90- setProgress ( parseInt ( match [ 1 ] , 10 ) )
91- }
69+ // Use refs for transport/device to avoid re-renders
70+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
71+ // @ts -expect-error
72+ const transportRef = useRef < Transport | null > ( null )
73+ const deviceRef = useRef < unknown > ( null )
9274
93- if ( data . includes ( 'Hard resetting via RTS pin...' ) ) {
94- console . log ( 'Upload abgeschlossen!' )
95- setProgress ( 100 )
96- setFlashing ( false )
97- setUploadSuccess ( true ) // Erfolgsmeldung setzen
98- }
99-
100- terminal ?. writeln ( data )
101- } ,
102- write ( data : string ) {
103- terminal ?. write ( data )
104- } ,
75+ // --- Modular helpers ---
76+ async function fetchFirmware ( selected : string ) : Promise < ArrayBuffer > {
77+ let response
78+ switch ( selected ) {
79+ case 'ota' :
80+ response = await fetch ( '/mergedOTA.bin' )
81+ break
82+ case 'circuitpython' :
83+ response = await fetch ( '/circuitpython9_2_8.bin' )
84+ break
85+ case 'uf2' :
86+ response = await fetch (
87+ 'tinyuf2-sensebox_mcu_esp32s2-0.35.0-combined.bin' ,
88+ )
89+ break
90+ default :
91+ response = await fetch ( '/mergedOTA.bin' )
92+ break
93+ }
94+ if ( ! response . ok ) {
95+ throw new Error ( `Fehler beim Abrufen der Datei: ${ response . statusText } ` )
96+ }
97+ return await response . arrayBuffer ( )
10598 }
10699
107- const listSerialPorts = async ( ) => {
108- try {
109- setError ( '' ) // Vorherige Fehler zurücksetzen
110- device = await serial . requestPort ( {
111- filters : [ { usbVendorId : 0x303a } ] ,
112- } )
113- transport = new Transport ( device , true )
114-
115- setConnecting ( true )
116- const flashOptions = {
117- transport,
118- baudrate : baudRate ,
119- terminal : espLoaderTerminal ,
120- debugLogging : debugMode ,
121- } as LoaderOptions
122-
123- esploader = new ESPLoader ( flashOptions )
124- await esploader . main ( )
125- setBoardFound ( true )
126-
127- let response
128- // Lade die Datei "mergedOTA.bin" aus "public/"
129- switch ( selected ) {
130- case 'ota' :
131- response = await fetch ( '/mergedOTA.bin' )
132- break
133- case 'circuitpython' :
134- response = await fetch ( '/circuitpython9_2_8.bin' )
135- break
136- case 'uf2' :
137- response = await fetch (
138- 'tinyuf2-sensebox_mcu_esp32s2-0.35.0-combined.bin' ,
139- )
140- break
141- default :
142- response = await fetch ( '/mergedOTA.bin' )
143- break
144- }
145-
146- if ( ! response . ok ) {
147- throw new Error ( `Fehler beim Abrufen der Datei: ${ response . statusText } ` )
148- }
100+ function readFirmwareToSketch ( buffer : ArrayBuffer ) {
101+ const blob = new Blob ( [ buffer ] )
102+ const reader = new FileReader ( )
103+ reader . onload = function ( ) {
104+ setSketch ( reader . result as string )
105+ }
106+ reader . onerror = function ( ) {
107+ console . error ( 'Fehler beim Lesen der Datei:' , reader . error )
108+ setError ( 'Fehler beim Lesen der Datei.' )
109+ }
110+ reader . readAsBinaryString ( blob )
111+ }
149112
150- const buffer = await response . arrayBuffer ( )
151- const blob = new Blob ( [ buffer ] )
152- const reader = new FileReader ( )
113+ // ...existing code...
153114
154- reader . onload = function ( ) {
155- setSketch ( reader . result as string )
156- }
157- reader . onerror = function ( ) {
158- console . error ( 'Fehler beim Lesen der Datei:' , reader . error )
159- setError ( 'Fehler beim Lesen der Datei.' )
160- }
115+ const espLoaderTerminal = new EspTerminal ( terminal , {
116+ setProgress,
117+ setFlashing,
118+ setUploadSuccess,
119+ } )
161120
162- reader . readAsBinaryString ( blob )
163- } catch ( error ) {
164- console . error ( 'Fehler beim Auflisten der seriellen Ports:' , error )
121+ const clickConnect = async ( ) => {
122+ if ( espStub ) {
123+ await espStub . disconnect ?.( )
124+ await espStub . port ?. close ?.( )
125+ setEspStub ( undefined )
126+ return
127+ }
128+ const esploader = await connectESP ( {
129+ log : ( ...args : unknown [ ] ) => espLoaderTerminal . writeLine ( args . join ( ' ' ) ) ,
130+ debug : ( ...args : unknown [ ] ) =>
131+ espLoaderTerminal . writeLine ( '[DEBUG] ' + args . join ( ' ' ) ) ,
132+ error : ( ...args : unknown [ ] ) =>
133+ espLoaderTerminal . writeLine ( '[ERROR] ' + args . join ( ' ' ) ) ,
134+ baudRate : baudRate ,
135+ } )
136+ setConnecting ( true )
137+ await esploader . initialize ( )
138+ console . log ( `Connected to ${ esploader . chipName } ` )
139+ console . log ( `MAC Address: ${ formatMacAddr ( esploader . macAddr ( ) ) } ` )
140+ const newEspStub = await esploader . runStub ( )
141+ setConnecting ( false )
142+ setEspStub ( newEspStub )
143+ setBoardFound ( true )
144+ try {
145+ const buffer = await fetchFirmware ( selected )
146+ readFirmwareToSketch ( buffer )
147+ } catch ( err ) {
165148 setError (
166- 'Fehler beim Auflisten der seriellen Ports : ' +
167- ( error instanceof Error ? error . message : error ) ,
149+ 'Fehler beim Laden der Firmware : ' +
150+ ( err instanceof Error ? err . message : err ) ,
168151 )
169- setBoardFound ( false )
170- } finally {
171- setConnecting ( false )
172152 }
153+ newEspStub . port ?. addEventListener ?.( 'disconnect' , ( ) => {
154+ setBoardFound ( false )
155+ setEspStub ( undefined )
156+ console . log ( 'Device disconnected' )
157+ } )
173158 }
174159
175- const flashSketch = async ( ) => {
160+ const program = async ( ) => {
161+ setError ( '' )
162+ setFlashing ( true )
163+ setUploadSuccess ( false )
176164 try {
177- setError ( '' ) // Vorherige Fehler zurücksetzen
178- setFlashing ( true )
179- setUploadSuccess ( false ) // Status zurücksetzen
180-
181- const flashOptions : FlashOptions = {
182- fileArray : [ { data : sketch , address : 0x0 } ] ,
183- flashSize : 'keep' ,
184- eraseAll : true ,
185- compress : true ,
186- reportProgress : ( fileIndex , written , total ) => {
187- setProgress ( ( written / total ) * 100 )
165+ if ( ! espStub ) {
166+ setError ( 'ESP nicht verbunden.' )
167+ setFlashing ( false )
168+ return
169+ }
170+ const buffer = await fetchFirmware ( selected )
171+ await espStub . flashData ?.(
172+ buffer ,
173+ ( bytesWritten : number , totalBytes : number ) => {
174+ const progress = bytesWritten / totalBytes
175+ const percentage = Math . floor ( progress * 100 )
176+ setProgress ( percentage )
177+ espLoaderTerminal . writeLine ( `Flashing... ${ percentage } %` )
188178 } ,
189- } as FlashOptions
190-
191- await esploader . writeFlash ( flashOptions )
192- await esploader . after ( )
193- } catch ( error ) {
194- console . error ( 'Fehler beim Flashen:' , error )
195- setError (
196- 'Fehler beim Flashen: ' +
197- ( error instanceof Error ? error . message : error ) ,
179+ 0x0 ,
198180 )
181+ await sleep ( 100 )
182+ espLoaderTerminal . writeLine ( `Done!` )
183+ espLoaderTerminal . writeLine (
184+ `To run the new firmware please reset your device.` ,
185+ )
186+ setUploadSuccess ( true )
187+ } catch ( e ) {
188+ console . error ( e )
189+ setError ( 'Fehler beim Flashen: ' + ( e instanceof Error ? e . message : e ) )
199190 } finally {
200191 setFlashing ( false )
201- transport ?. disconnect ( )
202192 }
203193 }
204194
205195 const disconnectBoard = async ( ) => {
206196 try {
207- console . log ( 'Hallo' )
208- await transport ?. disconnect ( ) // Transport sauber schließen
209- await device ?. close ( ) // Browser Serial-Port schließen
197+ await transportRef . current ?. disconnect ?.( )
198+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
199+ // @ts -expect-error
200+ await deviceRef . current ?. close ?.( )
210201 } catch ( err ) {
211202 console . error ( 'Fehler beim Disconnect:' , err )
212203 } finally {
213- device = null // Reset globale Referenz
214- setBoardFound ( false ) // State zurück
204+ deviceRef . current = null
205+ setBoardFound ( false )
215206 setUploadSuccess ( false )
216207 setError ( '' )
217208 setSketch ( '' )
@@ -264,7 +255,7 @@ export default function BoardSelect({ terminal }: BoardSelectProps) {
264255 ) : (
265256 < Button
266257 id = "boardSelect"
267- onClick = { listSerialPorts }
258+ onClick = { clickConnect }
268259 className = "w-full border-2 border-solid border-senseboxGreen bg-white text-senseboxGreen hover:bg-senseboxGreen/20"
269260 >
270261 < SearchIcon className = "h-5 w-5" />
@@ -366,7 +357,7 @@ export default function BoardSelect({ terminal }: BoardSelectProps) {
366357 </ CardContent >
367358 < CardFooter className = "mt-auto p-4" >
368359 < Button
369- onClick = { flashSketch }
360+ onClick = { program }
370361 className = "w-full bg-senseboxGreen text-white hover:bg-senseboxGreen/80"
371362 disabled = { ! boardFound || sketch === '' || flashing }
372363 >
0 commit comments