import HF2 = pxt . HF2 ; import U = pxt . U ; import * as nodeutil from './nodeutil' ; const PXT_USE_HID = ! ! process . env [ "PXT_USE_HID" ] ; function useWebUSB ( ) { return ! ! pxt . appTarget . compile . webUSB ; } let HID = undefined ; function requireHID ( install ? ) { if ( ! PXT_USE_HID ) return false ; if ( useWebUSB ( ) ) { if ( pxt . Util . isNodeJS ) return ! ! nodeutil . lazyRequire ( "webusb" , install ) ; return pxt . usb . isAvailable ( ) ; } else { if ( ! HID ) HID = nodeutil . lazyRequire ( "node-hid" , install ) ; return ! ! HID ; } } export function isInstalled ( install ? ) { return requireHID ( ! ! install ) ; } export interface HidDevice { vendorId : number ; productId : number ; path : string ; serialNumber : string ; manufacturer : string ; product : string ; release : number ; } export function listAsync ( ) { if ( ! isInstalled ( true ) ) return Promise . resolve ( ) ; return getHF2DevicesAsync ( ) . then ( devices => { pxt . log ( `found ${ devices . length } HID devices` ) ; devices . forEach ( device => pxt . log ( device ) ) ; } ) ; } export function serialAsync ( ) { if ( ! isInstalled ( true ) ) return Promise . resolve ( ) ; return initAsync ( ) . then ( d => { d . autoReconnect = true ; connectSerial ( d ) ; } ) ; } export function dmesgAsync ( ) { HF2 . enableLog ( ) ; return initAsync ( ) . then ( d => d . talkAsync ( pxt . HF2 . HF2_CMD_DMESG ) . then ( resp => { console . log ( U . fromUTF8Array ( resp ) ) ; return d . disconnectAsync ( ) ; } ) ) ; } function hex ( n ) { return ( "000" + n . toString ( 16 ) ) . slice ( - 4 ) ; } export function deviceInfo ( h ) { return `${ h . product } (by ${ h . manufacturer } at USB ${ hex ( h . vendorId ) }:${ hex ( h . productId ) })` ; } function getHF2Devices ( ) { if ( ! isInstalled ( false ) ) return [ ] ; let devices = HID . devices ( ) as HidDevice [ ] ; for ( let d of devices ) { pxt . debug ( JSON . stringify ( d ) ) ; } let serial = pxt . appTarget . serial ; return devices . filter ( d => ( serial && parseInt ( serial . productId ) == d . productId && parseInt ( serial . vendorId ) == d . vendorId ) || ( d . release & 0xff00 ) == 0x4200 ) ; } export function getHF2DevicesAsync ( ) { return Promise . resolve ( getHF2Devices ( ) ) ; } function handleDevicesFound ( devices , selectFn ) { if ( devices . length > 1 ) { let d42 = devices . filter ( d => d . deviceVersionMajor == 42 ) ; if ( d42 . length > 0 ) devices = d42 ; } devices . forEach ( ( device ) => { console . log ( `DEV: ${ device . productName || device . serialNumber }` ) ; } ) ; selectFn ( devices [ 0 ] ) ; } export function hf2ConnectAsync ( path , raw = false ) { if ( useWebUSB ( ) ) { const g = global as any ; if ( ! g . navigator ) g . navigator = { } ; if ( ! g . navigator . usb ) { const webusb = nodeutil . lazyRequire ( "webusb" , true ) ; const load = webusb . USBAdapter . prototype . loadDevice ; webusb . USBAdapter . prototype . loadDevice = function ( device ) { if ( device . deviceDescriptor . bDeviceClass == 9 ) return Promise . resolve ( null ) ; return load . apply ( this , arguments ) ; } ; const USB = webusb . USB ; g . navigator . usb = new USB ( { devicesFound : handleDevicesFound } ) ; } return pxt . usb . pairAsync ( ) . then ( ( ) => pxt . usb . mkWebUSBHIDPacketIOAsync ( ) ) . then ( io => new HF2 . Wrapper ( io ) ) . then ( d => d . reconnectAsync ( ) . then ( ( ) => d ) ) ; } if ( ! isInstalled ( true ) ) return Promise . resolve ( undefined ) ; let h = new HF2 . Wrapper ( new HidIO ( path ) ) ; h . rawMode = raw ; return h . reconnectAsync ( ) . then ( ( ) => h ) ; } export function mkWebUSBOrHidPacketIOAsync ( ) { if ( useWebUSB ( ) ) { pxt . debug ( `packetio: mk cli webusb` ) ; return hf2ConnectAsync ( "" ) ; } pxt . debug ( `packetio: mk cli hidio` ) ; return Promise . resolve ( ) . then ( ( ) => { return new HidIO ( null ) ; } ) ; } pxt . packetio . mkPacketIOAsync = mkWebUSBOrHidPacketIOAsync ; let hf2Dev ; export function initAsync ( path = null ) { if ( ! hf2Dev ) { hf2Dev = hf2ConnectAsync ( path ) ; } return hf2Dev ; } export function connectSerial ( w ) { process . stdin . on ( "data" , ( buf ) => { w . sendSerialAsync ( new Uint8Array ( buf ) ) ; } ) ; w . onSerial = ( arr , iserr ) => { let buf = Buffer . from ( arr ) ; if ( iserr ) process . stderr . write ( buf ) ; else process . stdout . write ( buf ) ; } ; } export class HIDError extends Error { constructor ( m ) { super ( m ) ; this . message = m ; } } export class HidIO implements pxt . packetio . PacketIO { dev : any ; private path : string ; private connecting = false ; onDeviceConnectionChanged = ( connect ) => { } ; onConnectionChanged = ( ) => { } ; onData = ( v ) => { } ; onEvent = ( v ) => { } ; onError = ( e ) => { } ; constructor ( private requestedPath ) { this . connect ( ) ; } private setConnecting ( v ) { if ( v != this . connecting ) { this . connecting = v ; if ( this . onConnectionChanged ) this . onConnectionChanged ( ) ; } } private connect ( ) { U . assert ( isInstalled ( false ) ) ; this . setConnecting ( true ) ; try { if ( this . requestedPath == null ) { let devs = getHF2Devices ( ) ; if ( devs . length == 0 ) throw new HIDError ( "no devices found" ) ; this . path = devs [ 0 ] . path ; } else { this . path = this . requestedPath ; } this . dev = new HID . HID ( this . path ) ; this . dev . on ( "data" , ( v ) => { this . onData ( new Uint8Array ( v ) ) ; } ) ; this . dev . on ( "error" , ( v ) => this . onError ( v ) ) ; } finally { this . setConnecting ( false ) ; } } disposeAsync ( ) { return Promise . resolve ( ) ; } isConnecting ( ) { return this . connecting ; } isConnected ( ) { return ! ! this . dev ; } sendPacketAsync ( pkt ) { return Promise . resolve ( ) . then ( ( ) => { let lst = [ 0 ] ; for ( let i = 0 ; i < Math . max ( 64 , pkt . length ) ; ++ i ) lst . push ( pkt [ i ] || 0 ) ; this . dev . write ( lst ) ; } ) ; } error ( msg ) { let fullmsg = "HID error on " + this . path + ": " + msg ; console . error ( fullmsg ) ; throw new HIDError ( fullmsg ) ; } disconnectAsync ( ) { if ( ! this . dev ) return Promise . resolve ( ) ; this . dev . removeAllListeners ( "data" ) ; this . dev . removeAllListeners ( "error" ) ; const pkt = new Uint8Array ( [ 0x48 ] ) ; this . sendPacketAsync ( pkt ) . catch ( e => { } ) ; return U . delay ( 100 ) . then ( ( ) => { if ( this . dev ) { const d = this . dev ; delete this . dev ; d . close ( ) ; } if ( this . onConnectionChanged ) this . onConnectionChanged ( ) ; } ) ; } reconnectAsync ( ) { return this . disconnectAsync ( ) . then ( ( ) => { this . connect ( ) ; } ) ; } }