@@ -18,6 +18,8 @@ import {
1818 ConnectionStatusEvent ,
1919 DeviceConnection ,
2020 DeviceConnectionEventMap ,
21+ FlashDataError ,
22+ FlashDataSource ,
2123} from "./device.js" ;
2224import { TypedEventTarget } from "./events.js" ;
2325import { LedMatrix } from "./led.js" ;
@@ -27,6 +29,18 @@ import {
2729 ServiceConnectionEventMap ,
2830 TypedServiceEvent ,
2931} from "./service-events.js" ;
32+ import { Capacitor } from "@capacitor/core" ;
33+ import { Device , requestDeviceNative } from "./capacitor-ble/bluetooth.js" ;
34+ import {
35+ FlashProgressStage ,
36+ FlashResult ,
37+ Progress ,
38+ } from "./capacitor-ble/model.js" ;
39+ import MemoryMap from "nrf-intel-hex" ;
40+ import partialFlash , {
41+ PartialFlashResult ,
42+ } from "./capacitor-ble/flashing-partial.js" ;
43+ import { fullFlash } from "./capacitor-ble/flashing-full.js" ;
3044
3145const requestDeviceTimeoutDuration : number = 30000 ;
3246
@@ -191,6 +205,10 @@ class MicrobitWebBluetoothConnectionImpl
191205 this . logging . log ( v ) ;
192206 }
193207
208+ private error ( message : string , e ?: unknown ) {
209+ this . logging . error ( message , e ) ;
210+ }
211+
194212 async initialize ( ) : Promise < void > {
195213 navigator . bluetooth ?. addEventListener (
196214 "availabilitychanged" ,
@@ -296,8 +314,16 @@ class MicrobitWebBluetoothConnectionImpl
296314 if ( this . device ) {
297315 return this . device ;
298316 }
317+
299318 this . dispatchTypedEvent ( "beforerequestdevice" , new BeforeRequestDevice ( ) ) ;
300319 try {
320+ const namePrefix = this . nameFilter
321+ ? `BBC micro:bit [${ this . nameFilter } ]`
322+ : "BBC micro:bit" ;
323+ if ( Capacitor . isNativePlatform ( ) ) {
324+ return requestDeviceNative ( namePrefix ) ;
325+ }
326+
301327 // In some situations the Chrome device prompt simply doesn't appear so we time this out after 30 seconds and reload the page
302328 // TODO: give control over this to the caller
303329 const result = await Promise . race ( [
@@ -413,4 +439,105 @@ class MicrobitWebBluetoothConnectionImpl
413439 const uartService = await this . connection ?. getUARTService ( ) ;
414440 uartService ?. writeData ( data ) ;
415441 }
442+
443+ // Extra API, matching USB case
444+
445+ /**
446+ * Flash the micro:bit.
447+ *
448+ * Note that this will always leave the connection disconnected.
449+ *
450+ * @param dataSource The data to use.
451+ * @param options Flash options and progress callback.
452+ */
453+ async flash (
454+ dataSource : FlashDataSource ,
455+ options : { progress ?: ( v : number | undefined ) => void } ,
456+ ) : Promise < void > {
457+ // TODO: deal with the need for richer progress for BLE
458+ const externalProgress = options . progress ?? ( ( ) => { } ) ;
459+ const progress : Progress = ( stage , v ) => {
460+ if (
461+ ( stage === FlashProgressStage . Partial ||
462+ stage === FlashProgressStage . Full ) &&
463+ v !== undefined
464+ ) {
465+ externalProgress ( v ) ;
466+ }
467+ } ;
468+
469+ // We'll disconnect/reconnect multiple times due to device resets, but reporting this is unhelpful.
470+ this . deferStatusUpdates = true ;
471+ try {
472+ if ( this . status !== ConnectionStatus . CONNECTED ) {
473+ const status = await this . connect ( ) ;
474+ if ( status !== ConnectionStatus . CONNECTED ) {
475+ throw new Error ( `Failed to connect ${ status } ` ) ;
476+ }
477+ }
478+ try {
479+ const memoryMap = convertDataToMemoryMap (
480+ await dataSource ( this . getBoardVersion ( ) ! ) ,
481+ ) ;
482+ if ( ! memoryMap ) {
483+ throw new FlashDataError ( ) ;
484+ }
485+
486+ const boardVersion = this . connection ?. boardVersion ;
487+ if ( ! this . device || ! boardVersion ) {
488+ throw new Error ( ) ;
489+ }
490+ const partialFlashResult = await partialFlash (
491+ this . device ,
492+ memoryMap ,
493+ progress ,
494+ ) ;
495+
496+ switch ( partialFlashResult ) {
497+ case PartialFlashResult . Success : {
498+ return ;
499+ }
500+ case PartialFlashResult . Failed : {
501+ throw new Error ( "Partial flash failed" ) ;
502+ }
503+ case PartialFlashResult . AttemptFullFlash : {
504+ const fullFlashResult = await fullFlash (
505+ this . device ,
506+ boardVersion ,
507+ memoryMap ,
508+ progress ,
509+ ) ;
510+ // TODO: get a grip on this return value
511+ if ( fullFlashResult !== FlashResult . Success ) {
512+ throw new Error ( ) ;
513+ }
514+ return ;
515+ }
516+ default : {
517+ throw new Error ( "Unexpected" ) ;
518+ }
519+ }
520+ } catch ( e ) {
521+ this . error ( "Failed to flash" , e ) ;
522+ throw e ;
523+ } finally {
524+ await this . disconnect ( ) ;
525+ }
526+ } finally {
527+ this . deferStatusUpdates = false ;
528+ this . setStatus ( this . status ) ;
529+ }
530+ }
416531}
532+
533+ const convertDataToMemoryMap = (
534+ data : string | Uint8Array | MemoryMap ,
535+ ) : MemoryMap => {
536+ if ( data instanceof MemoryMap ) {
537+ return data ;
538+ }
539+ if ( data instanceof Uint8Array ) {
540+ return MemoryMap . fromPaddedUint8Array ( data ) ;
541+ }
542+ return MemoryMap . fromHex ( data ) ;
543+ } ;
0 commit comments