1+ <!DOCTYPE html>
2+ < html lang ="en ">
3+ < head >
4+ < title > Upload Multitool</ title >
5+ < meta charset ="UTF-8 ">
6+ < meta name ="viewport " content ="width=device-width, initial-scale=1 ">
7+ < script src ="../dist/index.umd.js "> </ script >
8+ < link rel ="
stylesheet "
href ="
https://cdn.jsdelivr.net/npm/[email protected] /css/xterm.css "
> 9+ < script src ="
https://cdn.jsdelivr.net/npm/[email protected] /lib/xterm.js "
> </ script > 10+ < script src ="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js "> </ script >
11+ </ head >
12+ < body >
13+ < noscript >
14+ You need to enable JavaScript to run this app.
15+ </ noscript >
16+ < div id ="app ">
17+ < div >
18+ < h1 > Upload Multitool</ h1 >
19+ < p >
20+ This is a demo of the Upload Multitool.
21+ It allows you to upload binaries to a microcontroller using a wide range of upload protocols.
22+ </ p >
23+ < p >
24+ Select a device test config below.
25+ </ p >
26+ < select id ="device "> </ select >
27+ < button id ="upload " disabled > Upload</ button >
28+ < button id ="reconnect " disabled > Reconnect</ button >
29+ </ div >
30+ < div id ="status "> </ div >
31+ < div id ="terminal "> </ div >
32+ </ div >
33+ < script >
34+ const { isSupported, upload, WebSerialPort } = uploadMultitool ;
35+
36+ const setStatus = ( status ) => {
37+ document . getElementById ( 'status' ) . innerHTML = status ;
38+ } ;
39+ const asyncTimeout = ( ms ) => new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
40+ setStatus ( 'Loading...' ) ;
41+
42+ const deviceSelectEl = document . getElementById ( 'device' ) ;
43+ const uploadButtonEl = document . getElementById ( 'upload' ) ;
44+ const reconnectButtonEl = document . getElementById ( 'reconnect' ) ;
45+
46+ const term = new Terminal ( ) ;
47+ term . open ( document . getElementById ( 'terminal' ) ) ;
48+
49+ let config = { devices : { } } ;
50+ let reconnectResolve ;
51+ let reconnectReject ;
52+ let reconnectOpts ;
53+
54+ const getFilters = ( deviceConfig ) => {
55+ const filters = [ ] ;
56+ if ( deviceConfig . vendorIds && deviceConfig . productIds ) {
57+ deviceConfig . vendorIds . forEach ( ( vendorId ) => {
58+ deviceConfig . productIds . forEach ( ( productId ) => {
59+ filters . push ( {
60+ usbVendorId : parseInt ( vendorId , 16 ) ,
61+ usbProductId : parseInt ( productId , 16 ) ,
62+ } ) ;
63+ } ) ;
64+ } ) ;
65+ } else if ( deviceConfig . espChip || deviceConfig . mac ) {
66+ filters . push ( { usbVendorId : 0x1a86 , usbProductId : 0x7523 } ) ;
67+ }
68+ return filters ;
69+ } ;
70+
71+ const getBin = async ( file , fqbn ) => {
72+ const key = Math . random ( ) . toString ( 16 ) . substring ( 7 ) ;
73+ const code = await fetch ( `../test/code/${ file } .ino` )
74+ . then ( ( r ) => r . text ( ) )
75+ . then ( txt => txt . replace ( / { { key} } / g, key ) ) ;
76+ const res = await fetch ( `${ config . compileServer } /v3/compile` , {
77+ method : 'POST' ,
78+ headers : {
79+ 'Content-Type' : 'application/json' ,
80+ } ,
81+ body : JSON . stringify ( {
82+ fqbn,
83+ files : [ {
84+ content : code ,
85+ name : `${ file } /${ file } .ino` ,
86+ } ] ,
87+ } ) ,
88+ } ) . then ( ( r ) => r . json ( ) ) ;
89+ return { bin : res . hex , key, code, ...res } ;
90+ } ;
91+
92+ const validateUpload = ( serial , key ) => new Promise ( ( resolve , reject ) => {
93+ let cleanup ;
94+ const timeout = setTimeout ( ( ) => {
95+ cleanup ( new Error ( 'Timeout validating upload' ) ) ;
96+ } , 10000 ) ;
97+ const onData = ( data ) => {
98+ if ( data . toString ( 'ascii' ) . includes ( key ) ) {
99+ cleanup ( ) ;
100+ }
101+ } ;
102+ const onError = ( err ) => {
103+ cleanup ( err ) ;
104+ } ;
105+ cleanup = ( err ) => {
106+ clearTimeout ( timeout ) ;
107+ serial . removeListener ( 'data' , onData ) ;
108+ serial . removeListener ( 'error' , onError ) ;
109+ if ( err ) {
110+ reject ( err ) ;
111+ } else {
112+ resolve ( ) ;
113+ }
114+ } ;
115+ serial . on ( 'data' , onData ) ;
116+ serial . on ( 'error' , onError ) ;
117+ serial . write ( 'ping\n' ) ;
118+ } ) ;
119+
120+ deviceSelectEl . addEventListener ( 'change' , async ( e ) => {
121+ const device = e . target . value ;
122+ const deviceConfig = config . devices [ device ] ;
123+ if ( ! deviceConfig ) return ;
124+ const { tool, cpu, name } = deviceConfig ;
125+ const isSupp = isSupported ( tool , cpu ) ;
126+ setStatus ( `${ name } is ${ isSupp ? '' : 'not ' } Supported!` ) ;
127+ uploadButtonEl . disabled = ! isSupp ;
128+
129+ } ) ;
130+
131+ reconnectButtonEl . addEventListener ( 'click' , async ( ) => {
132+ if ( ! deviceSelectEl . value ) return ;
133+ const deviceConfig = config . devices [ deviceSelectEl . value ] ;
134+ if ( ! deviceConfig ) return ;
135+ if ( ! reconnectResolve ) return ;
136+ const filters = getFilters ( deviceConfig ) ;
137+ try {
138+ const port = await WebSerialPort . requestPort (
139+ { filters } ,
140+ reconnectOpts ,
141+ ) ;
142+ if ( ! port ) throw new Error ( `could not locate ${ deviceConfig . name } ` ) ;
143+ else reconnectResolve ( port ) ;
144+ } catch ( err ) {
145+ reconnectReject ( err ) ;
146+ }
147+ reconnectButtonEl . disabled = true ;
148+ } ) ;
149+
150+ uploadButtonEl . addEventListener ( 'click' , async ( ) => {
151+ if ( ! deviceSelectEl . value ) return ;
152+ const deviceConfig = config . devices [ deviceSelectEl . value ] ;
153+ if ( ! deviceConfig ) return ;
154+ uploadButtonEl . disabled = true ;
155+ term . clear ( ) ;
156+
157+ try {
158+ setStatus ( 'Requesting Device...' ) ;
159+ filters = getFilters ( deviceConfig ) ;
160+ WebSerialPort . list ( ) . then ( console . log ) ;
161+ let serial = await WebSerialPort . requestPort (
162+ { filters } ,
163+ { baudRate : deviceConfig . speed || 115200 } ,
164+ ) ;
165+
166+ setStatus ( 'Compiling Device Code...' ) ;
167+ const {
168+ bin, files, flashMode, flashFreq, key,
169+ } = await getBin ( deviceConfig . code , deviceConfig . fqbn ) ;
170+
171+ setStatus ( 'Uploading...' ) ;
172+ const res = await upload ( serial , {
173+ bin,
174+ files,
175+ flashMode,
176+ flashFreq,
177+ speed : deviceConfig . speed ,
178+ uploadSpeed : deviceConfig . uploadSpeed ,
179+ tool : deviceConfig . tool ,
180+ cpu : deviceConfig . cpu ,
181+ verbose : true ,
182+ stdout : term ,
183+ avr109Reconnect : async ( opts ) => {
184+ console . log ( opts ) ;
185+ // await asyncTimeout(200);
186+ const list = await WebSerialPort . list ( ) ;
187+ const dev = list . find ( d => deviceConfig . productIds . includes ( d . productId ) && deviceConfig . vendorIds . includes ( d . vendorId ) ) ;
188+ console . log ( dev , dev ?. port ) ;
189+ if ( dev ) return new WebSerialPort ( dev . port , opts ) ;
190+ reconnectOpts = opts ;
191+ return new Promise ( ( resolve , reject ) => {
192+ reconnectResolve = resolve ;
193+ reconnectReject = reject ;
194+ reconnectButtonEl . disabled = false ;
195+ } ) ;
196+ }
197+ } ) ;
198+
199+ serial = res . serialport ;
200+
201+ setStatus ( 'Validating Upload...' ) ;
202+ await validateUpload ( serial , key ) ;
203+ setStatus ( 'Cleaning Up...' ) ;
204+ await serial . close ( ) ;
205+ setStatus ( `Done! Success! Awesome! (${ res . time } ms)` ) ;
206+ } catch ( err ) {
207+ console . error ( err ) ;
208+ setStatus ( `Error: ${ err . message } ` ) ;
209+ }
210+ uploadButtonEl . disabled = false ;
211+ } ) ;
212+
213+ ( async ( ) => {
214+ config = jsyaml . load ( await fetch ( '../test/test-config.yml' ) . then ( r => r . text ( ) ) ) ;
215+ console . log ( config ) ;
216+ Object . keys ( config . devices ) . forEach ( id => {
217+ const device = config . devices [ id ] ;
218+ const option = document . createElement ( 'option' ) ;
219+ option . value = id ;
220+ option . innerText = device . name ;
221+ deviceSelectEl . appendChild ( option ) ;
222+ } ) ;
223+ deviceSelectEl . value = '' ;
224+
225+ if ( ! isSupported ( 'avr' , 'atmega328p' ) ) {
226+ return setStatus ( 'Error: Could not load uploader.' ) ;
227+ }
228+ if ( ! navigator . serial ) {
229+ return setStatus ( 'Error: Could not load web Serial API.' ) ;
230+ }
231+ setStatus ( 'Ready.' ) ;
232+ console . log ( await WebSerialPort . list ( ) ) ;
233+ } ) ( ) ;
234+ </ script >
235+ </ body >
236+ </ html >
0 commit comments