2019-03-21 16:04:48 +01:00
/// <reference path="../node_modules/pxt-core/localtypings/pxtarget.d.ts" />
/// <reference path="../node_modules/pxt-core/built/pxtblocks.d.ts" />
/// <reference path="../node_modules/pxt-core/built/pxtcompiler.d.ts" />
/// <reference path="../node_modules/pxt-core/built/pxtlib.d.ts" />
/// <reference path="../node_modules/pxt-core/built/pxteditor.d.ts" />
2019-04-05 05:42:04 +02:00
/// <reference path="dapjs.d.ts" />
2019-03-21 16:04:48 +01:00
import * as React from "react" ;
const imul = ( Math as any ) . imul ;
const pageSize = 1024 ;
const numPages = 256 ;
const timeoutMessage = "timeout" ;
function murmur3_core ( data : Uint8Array ) {
let h0 = 0x2F9BE6CC ;
let h1 = 0x1EC3A6C8 ;
for ( let i = 0 ; i < data . length ; i += 4 ) {
let k = pxt . HF2 . read32 ( data , i ) >>> 0
k = imul ( k , 0xcc9e2d51 ) ;
k = ( k << 15 ) | ( k >>> 17 ) ;
k = imul ( k , 0x1b873593 ) ;
h0 ^= k ;
h1 ^= k ;
h0 = ( h0 << 13 ) | ( h0 >>> 19 ) ;
h1 = ( h1 << 13 ) | ( h1 >>> 19 ) ;
h0 = ( imul ( h0 , 5 ) + 0xe6546b64 ) >>> 0 ;
h1 = ( imul ( h1 , 5 ) + 0xe6546b64 ) >>> 0 ;
}
return [ h0 , h1 ]
}
class DAPWrapper {
cortexM : DapJS.CortexM
packetIo : pxt.HF2.PacketIO ;
cmsisdap : any ;
flashing = true ;
pbuf = new pxt . U . PromiseBuffer < Uint8Array > ( ) ;
private useSerial = true ;
constructor ( h : pxt.HF2.PacketIO ) {
this . packetIo = h ;
h . onData = buf = > {
// console.log("RD: " + pxt.Util.toHex(buf))
this . pbuf . push ( buf ) ;
}
this . allocDAP ( )
const readSerial = ( ) = > {
if ( ! this . useSerial ) {
return
}
if ( this . flashing ) {
setTimeout ( readSerial , 300 )
return
}
this . cmsisdap . cmdNums ( 0x83 , [ ] )
. then ( ( r : number [ ] ) = > {
const len = r [ 1 ]
let str = ""
for ( let i = 2 ; i < len + 2 ; ++ i ) {
str += String . fromCharCode ( r [ i ] )
}
if ( str . length > 0 ) {
pxt . U . nextTick ( readSerial )
window . postMessage ( {
type : 'serial' ,
id : 'n/a' , // TODO
data : str
} , "*" )
// console.log("SERIAL: " + str)
} else
setTimeout ( readSerial , 50 )
} , ( err : any ) = > {
setTimeout ( readSerial , 1000 )
} )
}
readSerial ( )
}
private allocDAP() {
/ *
let sendMany = ( cmds : Uint8Array [ ] ) = > {
return h . talksAsync ( cmds . map ( c = > ( { cmd : 0 , data : c } ) ) ) ;
}
if ( ! h . talksAsync )
sendMany = null ;
* /
let dev = new DapJS . DAP ( {
write : writeAsync ,
close : this.disconnectAsync ,
read : readAsync ,
//sendMany: sendMany
} ) ;
this . cmsisdap = ( dev as any ) . dap ;
this . cortexM = new DapJS . CortexM ( dev ) ;
let h = this . packetIo
let pbuf = this . pbuf
function writeAsync ( data : ArrayBuffer ) {
// console.log("WR: " + pxt.Util.toHex(new Uint8Array(data)));
return h . sendPacketAsync ( new Uint8Array ( data ) ) ;
}
function readAsync() {
return pbuf . shiftAsync ( ) ;
}
}
reconnectAsync ( first : boolean ) {
// configure serial at 115200
let p = Promise . resolve ( ) ;
if ( ! first ) {
p = this . packetIo . reconnectAsync ( )
. then ( ( ) = > this . allocDAP ( ) ) ;
}
return p
. then ( ( ) = > this . cortexM . init ( ) )
. then ( ( ) = > {
return this . cmsisdap . cmdNums ( 0x82 , [ 0x00 , 0xC2 , 0x01 , 0x00 ] )
. then ( ( ) = > { this . useSerial = true } , ( err : any ) = > { this . useSerial = false ; } ) ;
} ) ;
}
disconnectAsync() {
return this . packetIo . disconnectAsync ( ) ;
}
}
let packetIoPromise : Promise < pxt.HF2.PacketIO > ;
function initPacketIOAsync ( ) : Promise < pxt.HF2.PacketIO > {
if ( ! packetIoPromise ) {
packetIoPromise = pxt . HF2 . mkPacketIOAsync ( )
. catch ( err = > {
packetIoPromise = null ;
return Promise . reject ( err ) ;
} ) ;
return packetIoPromise ;
} else {
let packetIo : pxt.HF2.PacketIO ;
return packetIoPromise
. then ( ( io ) = > {
packetIo = io ;
return io . reconnectAsync ( ) ;
} )
. then ( ( ) = > packetIo ) ;
}
}
let previousDapWrapper : DAPWrapper ;
function dapAsync() {
if ( previousDapWrapper )
return previousDapWrapper . reconnectAsync ( false ) // Always fully reconnect to handle device unplugged mid-session
. then ( ( ) = > previousDapWrapper ) ;
return Promise . resolve ( )
. then ( ( ) = > {
if ( previousDapWrapper ) {
return previousDapWrapper . disconnectAsync ( )
. finally ( ( ) = > {
previousDapWrapper = null ;
} ) ;
}
return Promise . resolve ( ) ;
} )
. then ( ( ) = > initPacketIOAsync ( ) )
. then ( h = > {
let w = new DAPWrapper ( h )
previousDapWrapper = w ;
return w . reconnectAsync ( true )
. then ( ( ) = > {
return w
} )
} )
}
function canHID ( ) : boolean {
let r = false
if ( pxt . usb . isEnabled ) {
r = true
} else if ( pxt . U . isNodeJS ) {
r = true
} else {
const forceHexDownload = /forceHexDownload/i . test ( window . location . href ) ;
const isUwp = ! ! ( window as any ) . Windows ;
if ( pxt . BrowserUtils . isLocalHost ( ) && pxt . Cloud . localToken && ! forceHexDownload || isUwp )
r = true
}
return r ;
}
function initAsync() {
if ( canHID ( ) ) {
return dapAsync ( ) ;
} else {
return Promise . reject ( new Error ( "no HID" ) )
}
}
function pageAlignBlocks ( blocks : ts.pxtc.UF2.Block [ ] , pageSize : number ) {
pxt . U . assert ( pageSize % 256 == 0 )
let res : ts.pxtc.UF2.Block [ ] = [ ]
for ( let i = 0 ; i < blocks . length ; ) {
let b0 = blocks [ i ]
let newbuf = new Uint8Array ( pageSize )
let startPad = b0 . targetAddr & ( pageSize - 1 )
let newAddr = b0 . targetAddr - startPad
for ( ; i < blocks . length ; ++ i ) {
let b = blocks [ i ]
if ( b . targetAddr + b . payloadSize > newAddr + pageSize )
break
pxt . U . memcpy ( newbuf , b . targetAddr - newAddr , b . data , 0 , b . payloadSize )
}
let bb = pxt . U . flatClone ( b0 )
bb . data = newbuf
bb . targetAddr = newAddr
bb . payloadSize = pageSize
res . push ( bb )
}
return res
}
const flashPageBINquick = new Uint32Array ( [
0xbe00be00 , // bkpt - LR is set to this
0x2480b5f0 , 0x00e42300 , 0x58cd58c2 , 0xd10342aa , 0x42a33304 , 0xbdf0d1f8 ,
0x4b162502 , 0x509d4a16 , 0x2d00591d , 0x24a1d0fc , 0x511800e4 , 0x3cff3c09 ,
0x591e0025 , 0xd0fc2e00 , 0x509c2400 , 0x2c00595c , 0x2401d0fc , 0x509c2580 ,
0x595c00ed , 0xd0fc2c00 , 0x00ed2580 , 0x002e2400 , 0x5107590f , 0x2f00595f ,
0x3404d0fc , 0xd1f742ac , 0x50992100 , 0x2a00599a , 0xe7d0d0fc , 0x4001e000 ,
0x00000504 ,
] )
// doesn't check if data is already there - for timing
const flashPageBIN = new Uint32Array ( [
0xbe00be00 , // bkpt - LR is set to this
0x2402b5f0 , 0x4a174b16 , 0x2480509c , 0x002500e4 , 0x2e00591e , 0x24a1d0fc ,
0x511800e4 , 0x2c00595c , 0x2400d0fc , 0x2480509c , 0x002500e4 , 0x2e00591e ,
0x2401d0fc , 0x595c509c , 0xd0fc2c00 , 0x00ed2580 , 0x002e2400 , 0x5107590f ,
0x2f00595f , 0x3404d0fc , 0xd1f742ac , 0x50992100 , 0x2a00599a , 0xbdf0d0fc ,
0x4001e000 , 0x00000504 ,
] )
// void computeHashes(uint32_t *dst, uint8_t *ptr, uint32_t pageSize, uint32_t numPages)
const computeChecksums2 = new Uint32Array ( [
0x4c27b5f0 , 0x44a52680 , 0x22009201 , 0x91004f25 , 0x00769303 , 0x24080013 ,
0x25010019 , 0x40eb4029 , 0xd0002900 , 0x3c01407b , 0xd1f52c00 , 0x468c0091 ,
0xa9044665 , 0x506b3201 , 0xd1eb42b2 , 0x089b9b01 , 0x23139302 , 0x9b03469c ,
0xd104429c , 0x2000be2a , 0x449d4b15 , 0x9f00bdf0 , 0x4d149e02 , 0x49154a14 ,
0x3e01cf08 , 0x2111434b , 0x491341cb , 0x405a434b , 0x4663405d , 0x230541da ,
0x4b10435a , 0x466318d2 , 0x230541dd , 0x4b0d435d , 0x2e0018ed , 0x6002d1e7 ,
0x9a009b01 , 0x18d36045 , 0x93003008 , 0xe7d23401 , 0xfffffbec , 0xedb88320 ,
0x00000414 , 0x1ec3a6c8 , 0x2f9be6cc , 0xcc9e2d51 , 0x1b873593 , 0xe6546b64 ,
] )
let startTime = 0
function log ( msg : string ) {
let now = Date . now ( )
if ( ! startTime ) startTime = now
now -= startTime
let ts = ( "00000" + now ) . slice ( - 5 )
pxt . log ( ` HID ${ ts } : ${ msg } ` )
}
const membase = 0x20000000
const loadAddr = membase
const dataAddr = 0x20002000
const stackAddr = 0x20001000
export const bufferConcat = ( bufs : Uint8Array [ ] ) = > {
let len = 0 ;
for ( const b of bufs ) {
len += b . length ;
}
const r = new Uint8Array ( len ) ;
len = 0 ;
for ( const b of bufs ) {
r . set ( b , len ) ;
len += b . length ;
}
return r ;
} ;
function fullVendorCommandFlashAsync ( resp : pxtc.CompileResult , wrap : DAPWrapper ) : Promise < void > {
const chunkSize = 62 ;
let aborted = false ;
return Promise . resolve ( )
. then ( ( ) = > {
return wrap . cmsisdap . cmdNums ( 0x8A /* DAPLinkFlash.OPEN */ , [ 1 ] ) ;
} )
. then ( ( res ) = > {
const hexUint8 = pxt . U . stringToUint8Array ( resp . outfiles [ pxtc . BINARY_HEX ] ) ;
const hexArray : number [ ] = Array . prototype . slice . call ( hexUint8 ) ;
const sendPages = ( offset : number = 0 ) : Promise < void > = > {
const end = Math . min ( hexArray . length , offset + chunkSize ) ;
const nextPage = hexArray . slice ( offset , end ) ;
nextPage . unshift ( nextPage . length ) ;
return wrap . cmsisdap . cmdNums ( 0x8C /* DAPLinkFlash.WRITE */ , nextPage )
. then ( ( ) = > {
if ( ! aborted && end < hexArray . length ) {
return sendPages ( end ) ;
}
return Promise . resolve ( ) ;
} ) ;
}
return sendPages ( ) ;
} )
. then ( ( res ) = > {
return wrap . cmsisdap . cmdNums ( 0x8B /* DAPLinkFlash.CLOSE */ , [ ] ) ;
} )
. timeout ( 60000 , timeoutMessage )
. catch ( ( e ) = > {
aborted = true ;
return wrap . cmsisdap . cmdNums ( 0x89 /* DAPLinkFlash.RESET */ , [ ] )
. catch ( ( e2 : any ) = > {
// Best effort reset, no-op if there's an error
} )
. then ( ( ) = > {
return Promise . reject ( e ) ;
} ) ;
} ) ;
}
function quickHidFlashAsync ( resp : pxtc.CompileResult , wrap : DAPWrapper ) : Promise < void > {
let logV = ( msg : string ) = > { }
//let logV = log
let aborted = false ;
const runFlash = ( b : ts.pxtc.UF2.Block , dataAddr : number ) = > {
const cmd = wrap . cortexM . prepareCommand ( ) ;
cmd . halt ( ) ;
cmd . writeCoreRegister ( DapJS . CortexReg . PC , loadAddr + 4 + 1 ) ;
cmd . writeCoreRegister ( DapJS . CortexReg . LR , loadAddr + 1 ) ;
cmd . writeCoreRegister ( DapJS . CortexReg . SP , stackAddr ) ;
cmd . writeCoreRegister ( 0 , b . targetAddr ) ;
cmd . writeCoreRegister ( 1 , dataAddr ) ;
return Promise . resolve ( )
. then ( ( ) = > {
logV ( "setregs" )
return cmd . go ( )
} )
. then ( ( ) = > {
logV ( "dbg en" )
// starts the program
return wrap . cortexM . debug . enable ( )
} )
}
let checksums : Uint8Array
return getFlashChecksumsAsync ( wrap )
. then ( buf = > {
checksums = buf ;
log ( "write code" ) ;
return wrap . cortexM . memory . writeBlock ( loadAddr , flashPageBIN ) ;
} )
. then ( ( ) = > {
log ( "convert" ) ;
// TODO this is seriously inefficient (130ms on a fast machine)
let uf2 = ts . pxtc . UF2 . newBlockFile ( ) ;
ts . pxtc . UF2 . writeHex ( uf2 , resp . outfiles [ pxtc . BINARY_HEX ] . split ( /\r?\n/ ) ) ;
let bytes = pxt . U . stringToUint8Array ( ts . pxtc . UF2 . serializeFile ( uf2 ) ) ;
let parsed = ts . pxtc . UF2 . parseFile ( bytes ) ;
let aligned = pageAlignBlocks ( parsed , pageSize ) ;
log ( ` initial: ${ aligned . length } pages ` ) ;
aligned = onlyChanged ( aligned , checksums ) ;
log ( ` incremental: ${ aligned . length } pages ` ) ;
return Promise . mapSeries ( pxt . U . range ( aligned . length ) ,
i = > {
if ( aborted ) return Promise . resolve ( ) ;
let b = aligned [ i ] ;
if ( b . targetAddr >= 0x10000000 )
return Promise . resolve ( ) ;
logV ( "about to write at 0x" + b . targetAddr . toString ( 16 ) ) ;
let writeBl = Promise . resolve ( ) ;
let thisAddr = ( i & 1 ) ? dataAddr : dataAddr + pageSize ;
let nextAddr = ( i & 1 ) ? dataAddr + pageSize : dataAddr ;
if ( i == 0 ) {
let u32data = new Uint32Array ( b . data . length / 4 ) ;
for ( let i = 0 ; i < b . data . length ; i += 4 )
u32data [ i >> 2 ] = pxt . HF2 . read32 ( b . data , i ) ;
writeBl = wrap . cortexM . memory . writeBlock ( thisAddr , u32data ) ;
}
return writeBl
. then ( ( ) = > runFlash ( b , thisAddr ) )
. then ( ( ) = > {
let next = aligned [ i + 1 ] ;
if ( ! next )
return Promise . resolve ( ) ;
logV ( "write next" ) ;
let buf = new Uint32Array ( next . data . buffer ) ;
return wrap . cortexM . memory . writeBlock ( nextAddr , buf ) ;
} )
. then ( ( ) = > {
logV ( "wait" ) ;
return wrap . cortexM . waitForHalt ( 500 ) ;
} )
. then ( ( ) = > {
logV ( "done block" ) ;
} ) ;
} )
. then ( ( ) = > {
log ( "flash done" ) ;
pxt . tickEvent ( "hid.flash.done" ) ;
return wrap . cortexM . reset ( false ) ;
} )
. then ( ( ) = > {
wrap . flashing = false ;
} ) ;
} )
. timeout ( 25000 , timeoutMessage )
. catch ( ( e ) = > {
aborted = true ;
return Promise . reject ( e ) ;
} ) ;
}
function flashAsync ( resp : pxtc.CompileResult , d : pxt.commands.DeployOptions = { } ) : Promise < void > {
startTime = 0
let wrap : DAPWrapper
log ( "init" )
d . showNotification ( pxt . U . lf ( "Downloading..." ) ) ;
pxt . tickEvent ( "hid.flash.start" ) ;
return Promise . resolve ( )
. then ( ( ) = > {
if ( previousDapWrapper ) {
previousDapWrapper . flashing = true ;
return Promise . delay ( 100 ) ;
}
return Promise . resolve ( ) ;
} )
. then ( initAsync )
. then ( w = > {
wrap = w
log ( "reset" ) ;
return wrap . cortexM . init ( )
. then ( ( ) = > wrap . cortexM . reset ( true ) )
. catch ( e = > {
log ( "trying re-connect" ) ;
return wrap . reconnectAsync ( false )
. then ( ( ) = > wrap . cortexM . reset ( true ) ) ;
} ) ;
} )
. then ( ( ) = > wrap . cortexM . memory . readBlock ( 0x10001014 , 1 , pageSize ) )
. then ( v = > {
if ( pxt . HF2 . read32 ( v , 0 ) != 0x3C000 ) {
pxt . tickEvent ( "hid.flash.uicrfail" ) ;
return fullVendorCommandFlashAsync ( resp , wrap ) ;
}
return quickHidFlashAsync ( resp , wrap ) ;
} )
. catch ( e = > {
2019-04-05 05:42:04 +02:00
pxt . log ( ` flash error: ${ e . type } ` ) ;
2019-03-21 16:04:48 +01:00
if ( e . type === "devicenotfound" && d . reportDeviceNotFoundAsync ) {
pxt . tickEvent ( "hid.flash.devicenotfound" ) ;
return d . reportDeviceNotFoundAsync ( "/device/windows-app/troubleshoot" , resp ) ;
} else if ( e . message === timeoutMessage ) {
pxt . tickEvent ( "hid.flash.timeout" ) ;
return previousDapWrapper . reconnectAsync ( true )
2019-04-05 05:42:04 +02:00
. catch ( ( e ) = > { } )
. then ( ( ) = > {
2019-03-21 16:04:48 +01:00
// Best effort disconnect; at this point we don't even know the state of the device
pxt . reportException ( e ) ;
return resp . confirmAsync ( {
header : lf ( "Something went wrong..." ) ,
body : lf ( "One-click download took too long. Please disconnect your {0} from your computer and reconnect it, then manually download your program using drag and drop." , pxt . appTarget . appTheme . boardName || lf ( "device" ) ) ,
2019-04-05 05:42:04 +02:00
agreeLbl : lf ( "Ok" ) ,
hideCancel : true
2019-03-21 16:04:48 +01:00
} ) ;
} )
. then ( ( ) = > {
return pxt . commands . saveOnlyAsync ( resp ) ;
} ) ;
} else if ( e . isUserError ) {
d . reportError ( e . message ) ;
return Promise . resolve ( ) ;
} else {
pxt . tickEvent ( "hid.flash.unknownerror" ) ;
2019-04-05 05:42:04 +02:00
pxt . reportException ( e ) ;
2019-03-21 16:04:48 +01:00
return resp . confirmAsync ( {
header : pxt.U.lf ( "Something went wrong..." ) ,
body : pxt.U.lf ( "Please manually download your program to your device using drag and drop. One-click download might work afterwards." ) ,
2019-04-05 05:42:04 +02:00
agreeLbl : lf ( "Ok" ) ,
hideCancel : true
2019-03-21 16:04:48 +01:00
} )
. then ( ( ) = > {
return pxt . commands . saveOnlyAsync ( resp ) ;
} ) ;
}
} ) ;
}
function getFlashChecksumsAsync ( wrap : DAPWrapper ) {
log ( "getting existing flash checksums" )
let pages = numPages
return wrap . cortexM . runCode ( computeChecksums2 , loadAddr , loadAddr + 1 , 0xffffffff , stackAddr , true ,
dataAddr , 0 , pageSize , pages )
. then ( ( ) = > wrap . cortexM . memory . readBlock ( dataAddr , pages * 2 , pageSize ) )
}
function onlyChanged ( blocks : ts.pxtc.UF2.Block [ ] , checksums : Uint8Array ) {
return blocks . filter ( b = > {
let idx = b . targetAddr / pageSize
pxt . U . assert ( ( idx | 0 ) == idx )
pxt . U . assert ( b . data . length == pageSize )
if ( idx * 8 + 8 > checksums . length )
return true // out of range?
let c0 = pxt . HF2 . read32 ( checksums , idx * 8 )
let c1 = pxt . HF2 . read32 ( checksums , idx * 8 + 4 )
let ch = murmur3_core ( b . data )
if ( c0 == ch [ 0 ] && c1 == ch [ 1 ] )
return false
return true
} )
}
function uwpDeployCoreAsync ( resp : pxtc.CompileResult , d : pxt.commands.DeployOptions = { } ) : Promise < void > {
// Go straight to flashing
return flashAsync ( resp , d ) ;
}
function deployCoreAsync ( resp : pxtc.CompileResult , d : pxt.commands.DeployOptions = { } ) : Promise < void > {
return pxt . usb . isPairedAsync ( )
. then ( isPaired = > {
if ( isPaired ) {
// Already paired from earlier in the session or from previous session
return flashAsync ( resp , d ) ;
}
// try bluetooth if device is paired
if ( pxt . webBluetooth . isPaired ( ) )
return pxt . webBluetooth . flashAsync ( resp , d )
. catch ( e = > pxt . commands . saveOnlyAsync ( resp ) ) ;
// No device paired, prompt user
return pxt . commands . saveOnlyAsync ( resp ) ;
} ) ;
}
/ * *
* < block type = "device_show_leds" >
< field name = "LED00" > FALSE < / field >
< field name = "LED10" > FALSE < / field >
< field name = "LED20" > FALSE < / field >
< field name = "LED30" > FALSE < / field >
< field name = "LED40" > FALSE < / field >
< field name = "LED01" > FALSE < / field >
< field name = "LED11" > FALSE < / field >
< field name = "LED21" > FALSE < / field >
< field name = "LED31" > TRUE < / field >
< field name = "LED41" > FALSE < / field >
< field name = "LED02" > FALSE < / field >
< field name = "LED12" > FALSE < / field >
< field name = "LED22" > FALSE < / field >
< field name = "LED32" > FALSE < / field >
< field name = "LED42" > FALSE < / field >
< field name = "LED03" > FALSE < / field >
< field name = "LED13" > TRUE < / field >
< field name = "LED23" > FALSE < / field >
< field name = "LED33" > FALSE < / field >
< field name = "LED43" > FALSE < / field >
< field name = "LED04" > FALSE < / field >
< field name = "LED14" > FALSE < / field >
< field name = "LED24" > FALSE < / field >
< field name = "LED34" > FALSE < / field >
< field name = "LED44" > FALSE < / field >
< / block >
to
< block type = "device_show_leds" >
< field name = "LEDS" > `
# # # # #
. . . . #
. . . . .
. . . . #
. . . . #
`
< / field >
< / block >
* /
function patchBlocks ( pkgTargetVersion : string , dom : Element ) {
// is this a old script?
if ( pxt . semver . majorCmp ( pkgTargetVersion || "0.0.0" , "1.0.0" ) >= 0 ) return ;
// showleds
const nodes = pxt . U . toArray ( dom . querySelectorAll ( "block[type=device_show_leds]" ) )
. concat ( pxt . U . toArray ( dom . querySelectorAll ( "block[type=device_build_image]" ) ) )
. concat ( pxt . U . toArray ( dom . querySelectorAll ( "shadow[type=device_build_image]" ) ) )
. concat ( pxt . U . toArray ( dom . querySelectorAll ( "block[type=device_build_big_image]" ) ) )
. concat ( pxt . U . toArray ( dom . querySelectorAll ( "shadow[type=device_build_big_image]" ) ) ) ;
nodes . forEach ( node = > {
// don't rewrite if already upgraded, eg. field LEDS already present
if ( pxt . U . toArray ( node . children ) . filter ( child = > child . tagName == "field" && "LEDS" == child . getAttribute ( "name" ) ) [ 0 ] )
return ;
// read LEDxx value and assmebly into a new field
const leds : string [ ] [ ] = [ [ ] , [ ] , [ ] , [ ] , [ ] ] ;
pxt . U . toArray ( node . children )
. filter ( child = > child . tagName == "field" && /^LED\d+$/ . test ( child . getAttribute ( "name" ) ) )
. forEach ( lednode = > {
let n = lednode . getAttribute ( "name" ) ;
let col = parseInt ( n [ 3 ] ) ;
let row = parseInt ( n [ 4 ] ) ;
leds [ row ] [ col ] = lednode . innerHTML == "TRUE" ? "#" : "." ;
// remove node
node . removeChild ( lednode ) ;
} ) ;
// add new field
const f = node . ownerDocument . createElement ( "field" ) ;
f . setAttribute ( "name" , "LEDS" ) ;
const s = '`\n' + leds . map ( row = > row . join ( '' ) ) . join ( '\n' ) + '\n`' ;
f . appendChild ( node . ownerDocument . createTextNode ( s ) ) ;
node . insertBefore ( f , null ) ;
} ) ;
// radio
/ *
< block type = "radio_on_packet" x = "174" y = "120" >
< mutation callbackproperties = "receivedNumber" renamemap = "{}" > < / mutation >
< field name = "receivedNumber" > receivedNumber < / field >
< / block >
< block type = "radio_on_packet" disabled = "true" x = "127" y = "263" >
< mutation callbackproperties = "receivedString,receivedNumber" renamemap = "{"receivedString":"name","receivedNumber":"value"}" > < / mutation >
< field name = "receivedString" > name < / field >
< field name = "receivedNumber" > value < / field >
< / block >
< block type = "radio_on_packet" disabled = "true" x = "162" y = "420" >
< mutation callbackproperties = "receivedString" renamemap = "{}" > < / mutation >
< field name = "receivedString" > receivedString < / field >
< / block >
converts to
< block type = "radio_on_number" x = "196" y = "208" >
< field name = "HANDLER_receivedNumber" id = "DCy(W;1)*jLWQUpoy4Mm" variabletype = "" > receivedNumber < / field >
< / block >
< block type = "radio_on_value" x = "134" y = "408" >
< field name = "HANDLER_name" id = "*d-Jm^MJXO]Djs(dTR*?" variabletype = "" > name < / field >
< field name = "HANDLER_value" id = "A6HQjH[k^X43o3h775+G" variabletype = "" > value < / field >
< / block >
< block type = "radio_on_string" x = "165" y = "583" >
< field name = "HANDLER_receivedString" id = "V9KsE!h$(iO?%W:[32CV" variabletype = "" > receivedString < / field >
< / block >
* /
const varids : pxt.Map < string > = { } ;
function addField ( node : Element , renameMap : pxt.Map < string > , name : string ) {
const f = node . ownerDocument . createElement ( "field" ) ;
f . setAttribute ( "name" , "HANDLER_" + name )
f . setAttribute ( "id" , varids [ renameMap [ name ] || name ] ) ;
f . appendChild ( node . ownerDocument . createTextNode ( name ) ) ;
node . appendChild ( f ) ;
}
pxt . U . toArray ( dom . querySelectorAll ( "variable" ) ) . forEach ( node = > varids [ node . innerHTML ] = node . getAttribute ( "id" ) ) ;
pxt . U . toArray ( dom . querySelectorAll ( "block[type=radio_on_packet]" ) )
. forEach ( node = > {
const mutation = node . querySelector ( "mutation" ) ;
if ( ! mutation ) return ;
const renameMap = JSON . parse ( node . getAttribute ( "renamemap" ) || "{}" ) ;
const props = mutation . getAttribute ( "callbackproperties" ) ;
if ( props ) {
const parts = props . split ( "," ) ;
// It's tempting to generate radio_on_number if parts.length === 0 but
// that would create a variable named "receivedNumber" and possibly shadow
// an existing variable in the user's program. It's safer to stick to the
// old block.
if ( parts . length === 1 ) {
if ( parts [ 0 ] === "receivedNumber" ) {
node . setAttribute ( "type" , "radio_on_number" ) ;
node . removeChild ( node . querySelector ( "field[name=receivedNumber]" ) ) ;
addField ( node , renameMap , "receivedNumber" ) ;
}
else if ( parts [ 0 ] === "receivedString" ) {
node . setAttribute ( "type" , "radio_on_string" ) ;
node . removeChild ( node . querySelector ( "field[name=receivedString]" ) ) ;
addField ( node , renameMap , "receivedString" ) ;
}
else {
return ;
}
node . removeChild ( mutation ) ;
}
else if ( parts . length === 2 && parts . indexOf ( "receivedNumber" ) !== - 1 && parts . indexOf ( "receivedString" ) !== - 1 ) {
node . setAttribute ( "type" , "radio_on_value" ) ;
node . removeChild ( node . querySelector ( "field[name=receivedNumber]" ) ) ;
node . removeChild ( node . querySelector ( "field[name=receivedString]" ) ) ;
addField ( node , renameMap , "name" ) ;
addField ( node , renameMap , "value" ) ;
node . removeChild ( mutation ) ;
}
}
} )
// device_random now refers to randomRange() so we need to add the missing lower bound argument
pxt . U . toArray ( dom . querySelectorAll ( "block[type=device_random]" ) )
. concat ( pxt . U . toArray ( dom . querySelectorAll ( "shadow[type=device_random]" ) ) )
. forEach ( node = > {
if ( getValue ( node , "min" ) ) return ;
const v = node . ownerDocument . createElement ( "value" ) ;
v . setAttribute ( "name" , "min" ) ;
addNumberShadow ( v ) ;
node . appendChild ( v ) ;
} ) ;
/ *
< block type = "math_arithmetic" >
< field name = "OP" > DIVIDE < / field >
< value name = "A" >
< shadow type = "math_number" > < field name = "NUM" > 0 < / field > < / shadow >
< block type = "math_number" > < field name = "NUM" > 2 < / field > < / block >
< / value >
< value name = "B" >
< shadow type = "math_number" > < field name = "NUM" > 1 < / field > < / shadow >
< block type = "math_number" > < field name = "NUM" > 3 < / field > < / block >
< / value >
< / block >
* /
pxt . U . toArray ( dom . querySelectorAll ( "block[type=math_arithmetic]" ) )
. concat ( pxt . U . toArray ( dom . querySelectorAll ( "shadow[type=math_arithmetic]" ) ) )
. forEach ( node = > {
const op = getField ( node , "OP" ) ;
if ( ! op || op . textContent . trim ( ) !== "DIVIDE" ) return ;
// Convert to integer division
/ *
< block type = "math_js_op" >
< mutation op - type = "infix" > < / mutation >
< field name = "OP" > idiv < / field >
< value name = "ARG0" >
< shadow type = "math_number" > < field name = "NUM" > 0 < / field > < / shadow >
< / value >
< value name = "ARG1" >
< shadow type = "math_number" > < field name = "NUM" > 0 < / field > < / shadow >
< / value >
< / block >
* /
node . setAttribute ( "type" , "math_js_op" ) ;
op . textContent = "idiv" ;
const mutation = node . ownerDocument . createElement ( "mutation" ) ;
mutation . setAttribute ( "op-type" , "infix" ) ;
// mutation has to be first or Blockly will drop the second argument
node . insertBefore ( mutation , node . firstChild ) ;
const a = getValue ( node , "A" ) ;
if ( a ) a . setAttribute ( "name" , "ARG0" ) ;
const b = getValue ( node , "B" ) ;
if ( b ) b . setAttribute ( "name" , "ARG1" ) ;
} ) ;
renameField ( dom , "math_number_minmax" , "NUM" , "SLIDER" ) ;
renameField ( dom , "device_note" , "note" , "name" ) ;
}
function renameField ( dom : Element , blockType : string , oldName : string , newName : string ) {
pxt . U . toArray ( dom . querySelectorAll ( ` block[type= ${ blockType } ] ` ) )
. concat ( pxt . U . toArray ( dom . querySelectorAll ( ` shadow[type= ${ blockType } ] ` ) ) )
. forEach ( node = > {
const thefield = getField ( node , oldName ) ;
if ( thefield ) {
thefield . setAttribute ( "name" , newName ) ;
}
} ) ;
}
pxt . editor . initExtensionsAsync = function ( opts : pxt.editor.ExtensionOptions ) : Promise < pxt.editor.ExtensionResult > {
pxt . debug ( 'loading microbit target extensions...' )
function cantImportAsync ( project : pxt.editor.IProjectView ) {
// this feature is support in v0 only
return project . showModalDialogAsync ( {
header : lf ( "Can't import microbit.co.uk scripts..." ) ,
body : lf ( "Importing microbit.co.uk programs is not supported in this editor anymore. Please open this script in the https://makecode.microbit.org/v0 editor." ) ,
buttons : [
{
label : lf ( "Go to the old editor" ) ,
url : ` https://makecode.microbit.org/v0 `
}
]
} ) . then ( ( ) = > project . openHome ( ) )
}
const manyAny = Math as any ;
if ( ! manyAny . imul )
manyAny . imul = function ( a : number , b : number ) : number {
const ah = ( a >>> 16 ) & 0xffff ;
const al = a & 0xffff ;
const bh = ( b >>> 16 ) & 0xffff ;
const bl = b & 0xffff ;
// the shift by 0 fixes the sign on the high part
// the final |0 converts the unsigned value into a signed value
return ( ( al * bl ) + ( ( ( ah * bl + al * bh ) << 16 ) >>> 0 ) | 0 ) ;
} ;
const res : pxt.editor.ExtensionResult = {
hexFileImporters : [ {
id : "blockly" ,
canImport : data = > data . meta . cloudId == "microbit.co.uk" && data . meta . editor == "blockly" ,
importAsync : ( project , data ) = > {
pxt . tickEvent ( 'import.legacyblocks.redirect' ) ;
return cantImportAsync ( project ) ;
}
} , {
id : "td" ,
canImport : data = > data . meta . cloudId == "microbit.co.uk" && data . meta . editor == "touchdevelop" ,
importAsync : ( project , data ) = > {
pxt . tickEvent ( 'import.legacytd.redirect' ) ;
return cantImportAsync ( project ) ;
}
} ]
} ;
pxt . usb . setFilters ( [ {
vendorId : 0x0D28 ,
productId : 0x0204 ,
classCode : 0xff ,
subclassCode : 0x03
} ] )
const isUwp = ! ! ( window as any ) . Windows ;
if ( isUwp )
pxt . commands . deployCoreAsync = uwpDeployCoreAsync ;
2019-06-07 17:59:41 +02:00
else if ( ( canHID ( ) || pxt . webBluetooth . hasPartialFlash ( ) ) && ! pxt . BrowserUtils . isPxtElectron ( ) )
2019-03-21 16:04:48 +01:00
pxt . commands . deployCoreAsync = deployCoreAsync ;
res . blocklyPatch = patchBlocks ;
res . showUploadInstructionsAsync = showUploadInstructionsAsync ;
res . webUsbPairDialogAsync = webUsbPairDialogAsync ;
return Promise . resolve < pxt.editor.ExtensionResult > ( res ) ;
}
function getField ( parent : Element , name : string ) {
return getFieldOrValue ( parent , name , true ) ;
}
function getValue ( parent : Element , name : string ) {
return getFieldOrValue ( parent , name , false ) ;
}
function getFieldOrValue ( parent : Element , name : string , isField : boolean ) {
const nodeType = isField ? "field" : "value" ;
for ( let i = 0 ; i < parent . children . length ; i ++ ) {
const child = parent . children . item ( i ) ;
if ( child . tagName === nodeType && child . getAttribute ( "name" ) === name ) {
return child ;
}
}
return undefined ;
}
function addNumberShadow ( valueNode : Element ) {
const s = valueNode . ownerDocument . createElement ( "shadow" ) ;
s . setAttribute ( "type" , "math_number" ) ;
const f = valueNode . ownerDocument . createElement ( "field" ) ;
f . setAttribute ( "name" , "NUM" ) ;
f . textContent = "0" ;
s . appendChild ( f ) ;
valueNode . appendChild ( s ) ;
}
function webUsbPairDialogAsync ( confirmAsync : ( options : any ) = > Promise < number > ) : Promise < number > {
const boardName = pxt . appTarget . appTheme . boardName || "???" ;
const docUrl = pxt . appTarget . appTheme . usbDocs ;
const jsx =
< div className = "ui grid stackable" >
< div className = "column five wide firmware" >
< div className = "ui header" > { lf ( "First time here?" ) } < / div >
< strong className = "ui small" > { lf ( "You must have version 0249 or above of the firmware" ) } < / strong >
< div className = "image" >
< img className = "ui image" src = "./static/download/firmware.png" / >
< / div >
< a href = { ` ${ docUrl } /webusb/troubleshoot ` } target = "_blank" > { lf ( "Check your firmware version here and update if needed" ) } < / a >
< / div >
< div className = "column eleven wide instructions" >
< div className = "ui grid" >
< div className = "row" >
< div className = "column" >
< div className = "ui two column grid padded" >
< div className = "column" >
< div className = "ui" >
< div className = "image" >
< img className = "ui medium rounded image" src = "./static/download/connect.png" / >
< / div >
< div className = "content" >
< div className = "description" >
< span className = "ui purple circular label" > 1 < / span >
< strong > { lf ( "Connect the {0} to your computer with a USB cable" , boardName ) } < / strong >
< br / >
< span className = "ui small" > { lf ( "Use the microUSB port on the top of the {0}" , boardName ) } < / span >
< / div >
< / div >
< / div >
< / div >
< div className = "column" >
< div className = "ui" >
< div className = "image" >
< img className = "ui medium rounded image" src = "./static/download/pair.png" / >
< / div >
< div className = "content" >
< div className = "description" >
< span className = "ui purple circular label" > 2 < / span >
< strong > { lf ( "Pair your {0}" , boardName ) } < / strong >
< br / >
< span className = "ui small" > { lf ( "Click 'Pair device' below and select BBC micro:bit CMSIS-DAP or DAPLink CMSIS-DAP from the list" ) } < / span >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div > ;
const buttons : any [ ] = [ ] ;
if ( docUrl ) {
buttons . push ( {
label : lf ( "Help" ) ,
icon : "help" ,
className : "lightgrey" ,
url : ` ${ docUrl } /webusb `
} ) ;
}
return confirmAsync ( {
header : lf ( "Pair device for one-click downloads" ) ,
jsx ,
hasCloseIcon : true ,
agreeLbl : lf ( "Pair device" ) ,
agreeIcon : "usb" ,
hideCancel : true ,
className : 'downloaddialog' ,
buttons
} ) ;
}
function showUploadInstructionsAsync ( fn : string , url : string , confirmAsync : ( options : any ) = > Promise < number > ) {
const boardName = pxt . appTarget . appTheme . boardName || "???" ;
const boardDriveName = pxt . appTarget . appTheme . driveDisplayName || pxt . appTarget . compile . driveName || "???" ;
// https://msdn.microsoft.com/en-us/library/cc848897.aspx
// "For security reasons, data URIs are restricted to downloaded resources.
// Data URIs cannot be used for navigation, for scripting, or to populate frame or iframe elements"
const userDownload = pxt . BrowserUtils . isBrowserDownloadWithinUserContext ( ) ;
const downloadAgain = ! pxt . BrowserUtils . isIE ( ) && ! pxt . BrowserUtils . isEdge ( ) ;
const docUrl = pxt . appTarget . appTheme . usbDocs ;
const body =
userDownload
? lf ( "Click 'Download' to open the {0} app." , pxt . appTarget . appTheme . boardName || "" )
: undefined ;
const jsx = ! userDownload ?
< div className = "ui grid stackable upload" >
< div className = "column sixteen wide instructions" >
< div className = "ui grid" >
< div className = "row" >
< div className = "column" >
< div className = "ui two column grid padded" >
< div className = "column" >
< div className = "ui" >
< div className = "image" >
< img className = "ui medium rounded image" src = "./static/download/connect.png" / >
< / div >
< div className = "content" >
< div className = "description" >
< span className = "ui purple circular label" > 1 < / span >
< strong > { lf ( "Connect the {0} to your computer with a USB cable" , boardName ) } < / strong >
< br / >
< span className = "ui small" > { lf ( "Use the microUSB port on the top of the {0}" , boardName ) } < / span >
< / div >
< / div >
< / div >
< / div >
< div className = "column" >
< div className = "ui" >
< div className = "image" >
< img className = "ui medium rounded image" src = "./static/download/transfer.png" / >
< / div >
< div className = "content" >
< div className = "description" >
< span className = "ui purple circular label" > 2 < / span >
< strong > { lf ( "Move the .hex file to the {0}" , boardName ) } < / strong >
< br / >
< span className = "ui small" > { lf ( "Locate the downloaded .hex file and drag it to the {0} drive" , boardDriveName ) } < / span >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div > : undefined ;
const buttons : any [ ] = [ ] ;
if ( downloadAgain ) {
buttons . push ( {
label : userDownload ? lf ( "Download" ) : fn ,
icon : "download" ,
class : ` ${ userDownload ? "primary" : "lightgrey" } ` ,
url ,
fileName : fn
} ) ;
}
if ( docUrl ) {
buttons . push ( {
label : lf ( "Help" ) ,
icon : "help" ,
className : "lightgrey" ,
url : docUrl
} ) ;
}
return confirmAsync ( {
header : lf ( "Download to your {0}" , pxt . appTarget . appTheme . boardName ) ,
body ,
jsx ,
hasCloseIcon : true ,
hideCancel : true ,
hideAgree : true ,
className : 'downloaddialog' ,
buttons
//timeout: 20000
} ) . then ( ( ) = > { } ) ;
}