Experiment BT support using Chrome web serial (#920)
* plumbing * plumbing * logging * more notes * fixing typing * more plumbing * more plumbing * different baud rate * talking to the brick * first over the air drop * fix buffer * tweak paraetmers * formatting fixing double upload * reduce console.log * cleanup * add BLE button to download dialog * changed label * recover from broken COM port * fix function call * reduce log level * adding ticks * some help * updated support matrix * more docs * updated browser help * more docs * add link * add device * added image
This commit is contained in:
		@@ -4,6 +4,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
* [Troubleshoot](/troubleshoot)
 | 
					* [Troubleshoot](/troubleshoot)
 | 
				
			||||||
* [EV3 Manager](https://ev3manager.education.lego.com/)
 | 
					* [EV3 Manager](https://ev3manager.education.lego.com/)
 | 
				
			||||||
 | 
					* [Bluetooth](/bluetooth)
 | 
				
			||||||
* [Forum](https://forum.makecode.com)
 | 
					* [Forum](https://forum.makecode.com)
 | 
				
			||||||
* [LEGO Support](https://www.lego.com/service/)
 | 
					* [LEGO Support](https://www.lego.com/service/)
 | 
				
			||||||
* [FIRST LEGO League](/fll)
 | 
					* [FIRST LEGO League](/fll)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,7 @@ program to a **.uf2** file, which you then copy to the **@drivename@** drive. Th
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### ~ hint
 | 
					### ~ hint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Not seeing the **@drivename@** drive? Make sure to upgrade your firmware at https://ev3manager.education.lego.com/. Try these [troubleshooting](/troubleshoot) tips if you still have trouble getting the drive to appear.
 | 
					**Experimental support** for Bluetooth download is now available. Please read the [Bluetooth](/bluetooth) page for more information.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### ~
 | 
					### ~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										51
									
								
								docs/bluetooth.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								docs/bluetooth.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					# Bluetooth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This page describes the procedure to download MakeCode program to the EV3 brick 
 | 
				
			||||||
 | 
					over Bluetooth.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## ~ hint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### WARNING: EXPERIMENTAL FEATURES AHEAD! 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Support for Bluetooth download relies on [Web Serial](https://wicg.github.io/serial/),
 | 
				
			||||||
 | 
					an experimental browser feature. Web Serial is a work [in progress](https://www.chromestatus.com/feature/6577673212002304); 
 | 
				
			||||||
 | 
					it may change or be removed in future versions without notice.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					By enabling these experimental browser features, you could lose browser data or compromise your device security 
 | 
				
			||||||
 | 
					or privacy.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## ~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Supported browsers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Chrome desktop, version 77 and higher, Windows 10
 | 
				
			||||||
 | 
					* [Edge Insider desktop](https://www.microsoftedgeinsider.com), version 77 and higher, Windows 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To make sure your browser is up to date, go to the '...' menu, click "Help" then "About".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Machine Setup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* pair your EV3 brick with your computer over Bluetooth. This is the usual pairing procedure.
 | 
				
			||||||
 | 
					* go to [chrome://flags/#enable-experimental-web-platform-features](chrome://flags/#enable-experimental-web-platform-features) and **enable** 
 | 
				
			||||||
 | 
					**Experimental Web Platform features**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Download over Bluetooth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* go to the **beta** editor https://makecode.mindstorms.com/beta
 | 
				
			||||||
 | 
					* click on **Download** to start a file download as usual
 | 
				
			||||||
 | 
					* on the download dialog, you should see a **Bluetooth** button. Click on the
 | 
				
			||||||
 | 
					**Bluetooth** button to enable the mode.
 | 
				
			||||||
 | 
					* **make sure the EV3 brick is not running a program**
 | 
				
			||||||
 | 
					* click on **Download** again to download over bluetooth.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Choosing the correct serial port
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unforunately, the browser dialog does not make it easy to select which serial port is the brick.
 | 
				
			||||||
 | 
					On Windows, it typically reads "Standard Serial over Bluetooth" and you may 
 | 
				
			||||||
 | 
					have multiple of those if you've paired different bricks.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Feedback
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Please send us your feedback through https://forum.makecode.com.
 | 
				
			||||||
@@ -92,6 +92,11 @@ You can share your projects by clicking on the **share** button in the top left
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Sharing programs is also shown in the [Tips and Tricks](https://legoeducation.videomarketingplatform.co/v.ihtml/player.html?token=5c594c2373367f7870196f519f3bfc7a&source=embed&photo%5fid=35719472) video.
 | 
					Sharing programs is also shown in the [Tips and Tricks](https://legoeducation.videomarketingplatform.co/v.ihtml/player.html?token=5c594c2373367f7870196f519f3bfc7a&source=embed&photo%5fid=35719472) video.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Can I use Bluetooth to transfer my program?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The official answer is currently no. That being said, we have **Experimental support** for Bluetooth download. Please read the [Bluetooth](/bluetooth) page for more information.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Why can't I delete my program (*.uf2) files from the Brick?
 | 
					### Why can't I delete my program (*.uf2) files from the Brick?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
There's a bug in the firmware which prevents you from deleting the programs (``*.uf2`` files) from your EV3 Brick. There isn't a firmware update to fix this yet.
 | 
					There's a bug in the firmware which prevents you from deleting the programs (``*.uf2`` files) from your EV3 Brick. There isn't a firmware update to fix this yet.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								docs/static/bluetooth/experimental.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/static/bluetooth/experimental.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 26 KiB  | 
							
								
								
									
										211
									
								
								editor/deploy.ts
									
									
									
									
									
								
							
							
						
						
									
										211
									
								
								editor/deploy.ts
									
									
									
									
									
								
							@@ -2,57 +2,198 @@
 | 
				
			|||||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
 | 
					/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import UF2 = pxtc.UF2;
 | 
					import UF2 = pxtc.UF2;
 | 
				
			||||||
 | 
					import { Ev3Wrapper } from "./wrap";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export let ev3: pxt.editor.Ev3Wrapper
 | 
					export let ev3: Ev3Wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function debug() {
 | 
					export function debug() {
 | 
				
			||||||
    return initAsync()
 | 
					    return initHidAsync()
 | 
				
			||||||
        .then(w => w.downloadFileAsync("/tmp/dmesg.txt", v => console.log(pxt.Util.uint8ArrayToString(v))))
 | 
					        .then(w => w.downloadFileAsync("/tmp/dmesg.txt", v => console.log(pxt.Util.uint8ArrayToString(v))))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Web Serial API https://wicg.github.io/serial/
 | 
				
			||||||
 | 
					// chromium bug https://bugs.chromium.org/p/chromium/issues/detail?id=884928
 | 
				
			||||||
 | 
					// Under experimental features in Chrome Desktop 77+
 | 
				
			||||||
 | 
					enum ParityType {
 | 
				
			||||||
 | 
					    "none",
 | 
				
			||||||
 | 
					    "even",
 | 
				
			||||||
 | 
					    "odd",
 | 
				
			||||||
 | 
					    "mark",
 | 
				
			||||||
 | 
					    "space"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					declare interface SerialOptions {
 | 
				
			||||||
 | 
					    baudrate?: number;
 | 
				
			||||||
 | 
					    databits?: number;
 | 
				
			||||||
 | 
					    stopbits?: number;
 | 
				
			||||||
 | 
					    parity?: ParityType;
 | 
				
			||||||
 | 
					    buffersize?: number;
 | 
				
			||||||
 | 
					    rtscts?: boolean;
 | 
				
			||||||
 | 
					    xon?: boolean;
 | 
				
			||||||
 | 
					    xoff?: boolean;
 | 
				
			||||||
 | 
					    xany?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					type SerialPortInfo = pxt.Map<string>;
 | 
				
			||||||
 | 
					type SerialPortRequestOptions = any;
 | 
				
			||||||
 | 
					declare class SerialPort {
 | 
				
			||||||
 | 
					    open(options?: SerialOptions): Promise<void>;
 | 
				
			||||||
 | 
					    close(): void;
 | 
				
			||||||
 | 
					    readonly readable: any;
 | 
				
			||||||
 | 
					    readonly writable: any;
 | 
				
			||||||
 | 
					    //getInfo(): SerialPortInfo;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					declare interface Serial extends EventTarget {
 | 
				
			||||||
 | 
					    onconnect: any;
 | 
				
			||||||
 | 
					    ondisconnect: any;
 | 
				
			||||||
 | 
					    getPorts(): Promise<SerialPort[]>
 | 
				
			||||||
 | 
					    requestPort(options: SerialPortRequestOptions): Promise<SerialPort>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WebSerialPackageIO implements pxt.HF2.PacketIO {
 | 
				
			||||||
 | 
					    onData: (v: Uint8Array) => void;
 | 
				
			||||||
 | 
					    onError: (e: Error) => void;
 | 
				
			||||||
 | 
					    onEvent: (v: Uint8Array) => void;
 | 
				
			||||||
 | 
					    onSerial: (v: Uint8Array, isErr: boolean) => void;
 | 
				
			||||||
 | 
					    sendSerialAsync: (buf: Uint8Array, useStdErr: boolean) => Promise<void>;
 | 
				
			||||||
 | 
					    private _reader: any;
 | 
				
			||||||
 | 
					    private _writer: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(private port: SerialPort, private options: SerialOptions) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // start reading
 | 
				
			||||||
 | 
					        this.readSerialAsync();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async readSerialAsync() {
 | 
				
			||||||
 | 
					        this._reader = this.port.readable.getReader();
 | 
				
			||||||
 | 
					        let buffer: Uint8Array;
 | 
				
			||||||
 | 
					        while (!!this._reader) {
 | 
				
			||||||
 | 
					            const { done, value } = await this._reader.read()
 | 
				
			||||||
 | 
					            if (!buffer) buffer = value;
 | 
				
			||||||
 | 
					            else { // concat
 | 
				
			||||||
 | 
					                let tmp = new Uint8Array(buffer.length + value.byteLength)
 | 
				
			||||||
 | 
					                tmp.set(buffer, 0)
 | 
				
			||||||
 | 
					                tmp.set(value, buffer.length)
 | 
				
			||||||
 | 
					                buffer = tmp;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (buffer && buffer.length >= 6) {
 | 
				
			||||||
 | 
					                this.onData(new Uint8Array(buffer));
 | 
				
			||||||
 | 
					                buffer = undefined;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static isSupported(): boolean {
 | 
				
			||||||
 | 
					        return !!(<any>navigator).serial;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static async mkPacketIOAsync(): Promise<pxt.HF2.PacketIO> {
 | 
				
			||||||
 | 
					        const serial = (<any>navigator).serial;
 | 
				
			||||||
 | 
					        if (serial) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                const requestOptions: SerialPortRequestOptions = {};
 | 
				
			||||||
 | 
					                const port = await serial.requestPort(requestOptions);
 | 
				
			||||||
 | 
					                const options: SerialOptions = {
 | 
				
			||||||
 | 
					                    baudrate: 460800,
 | 
				
			||||||
 | 
					                    buffersize: 4096
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                await port.open(options);
 | 
				
			||||||
 | 
					                if (port)
 | 
				
			||||||
 | 
					                    return new WebSerialPackageIO(port, options);
 | 
				
			||||||
 | 
					            } catch (e) {
 | 
				
			||||||
 | 
					                console.log(`connection error`, e)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw new Error("could not open serial port");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    error(msg: string): any {
 | 
				
			||||||
 | 
					        console.error(msg);
 | 
				
			||||||
 | 
					        throw new Error(lf("error on brick ({0})", msg))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async reconnectAsync(): Promise<void> {
 | 
				
			||||||
 | 
					        if (!this._reader) {
 | 
				
			||||||
 | 
					            await this.port.open(this.options);
 | 
				
			||||||
 | 
					            this.readSerialAsync();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return Promise.resolve();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async disconnectAsync(): Promise<void> {
 | 
				
			||||||
 | 
					        this.port.close();
 | 
				
			||||||
 | 
					        this._reader = undefined;
 | 
				
			||||||
 | 
					        this._writer = undefined;
 | 
				
			||||||
 | 
					        return Promise.resolve();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sendPacketAsync(pkt: Uint8Array): Promise<void> {
 | 
				
			||||||
 | 
					        if (!this._writer)
 | 
				
			||||||
 | 
					            this._writer = this.port.writable.getWriter();
 | 
				
			||||||
 | 
					        return this._writer.write(pkt);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function hf2Async() {
 | 
					function hf2Async() {
 | 
				
			||||||
    return pxt.HF2.mkPacketIOAsync()
 | 
					    const pktIOAsync: Promise<pxt.HF2.PacketIO> = useWebSerial
 | 
				
			||||||
        .then(h => {
 | 
					        ? WebSerialPackageIO.mkPacketIOAsync() : pxt.HF2.mkPacketIOAsync()
 | 
				
			||||||
            let w = new pxt.editor.Ev3Wrapper(h)
 | 
					    return pktIOAsync.then(h => {
 | 
				
			||||||
 | 
					        let w = new Ev3Wrapper(h)
 | 
				
			||||||
        ev3 = w
 | 
					        ev3 = w
 | 
				
			||||||
        return w.reconnectAsync(true)
 | 
					        return w.reconnectAsync(true)
 | 
				
			||||||
            .then(() => w)
 | 
					            .then(() => w)
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let noHID = false
 | 
					let useHID = false;
 | 
				
			||||||
 | 
					let useWebSerial = false;
 | 
				
			||||||
let initPromise: Promise<pxt.editor.Ev3Wrapper>
 | 
					export function initAsync(): Promise<void> {
 | 
				
			||||||
export function initAsync() {
 | 
					 | 
				
			||||||
    if (initPromise)
 | 
					 | 
				
			||||||
        return initPromise
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let canHID = false
 | 
					 | 
				
			||||||
    if (pxt.U.isNodeJS) {
 | 
					    if (pxt.U.isNodeJS) {
 | 
				
			||||||
        // doesn't seem to work ATM
 | 
					        // doesn't seem to work ATM
 | 
				
			||||||
        canHID = false
 | 
					        useHID = false
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        const forceHexDownload = /forceHexDownload/i.test(window.location.href);
 | 
					        const nodehid = /nodehid/i.test(window.location.href);
 | 
				
			||||||
        if (pxt.Cloud.isLocalHost() && pxt.Cloud.localToken && !forceHexDownload)
 | 
					        if (pxt.Cloud.isLocalHost() && pxt.Cloud.localToken && nodehid)
 | 
				
			||||||
            canHID = true
 | 
					            useHID = true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (noHID)
 | 
					    if(WebSerialPackageIO.isSupported())
 | 
				
			||||||
        canHID = false
 | 
					        pxt.tickEvent("bluetooth.supported");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (canHID) {
 | 
					    return Promise.resolve();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function canUseWebSerial() {
 | 
				
			||||||
 | 
					    return WebSerialPackageIO.isSupported();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function enableWebSerial() {
 | 
				
			||||||
 | 
					    initPromise = undefined;
 | 
				
			||||||
 | 
					    useWebSerial = WebSerialPackageIO.isSupported();
 | 
				
			||||||
 | 
					    useHID = useWebSerial;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let initPromise: Promise<Ev3Wrapper>
 | 
				
			||||||
 | 
					function initHidAsync() { // needs to run within a click handler
 | 
				
			||||||
 | 
					    if (initPromise)
 | 
				
			||||||
 | 
					        return initPromise
 | 
				
			||||||
 | 
					    if (useHID) {
 | 
				
			||||||
        initPromise = hf2Async()
 | 
					        initPromise = hf2Async()
 | 
				
			||||||
            .catch(err => {
 | 
					            .catch(err => {
 | 
				
			||||||
 | 
					                console.error(err);
 | 
				
			||||||
                initPromise = null
 | 
					                initPromise = null
 | 
				
			||||||
                noHID = true
 | 
					                useHID = false;
 | 
				
			||||||
                return Promise.reject(err)
 | 
					                useWebSerial = false;
 | 
				
			||||||
 | 
					                // cleanup
 | 
				
			||||||
 | 
					                let p = ev3  ? ev3.disconnectAsync().catch(e => {}) : Promise.resolve();
 | 
				
			||||||
 | 
					                return p.then(() => Promise.reject(err))
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        noHID = true
 | 
					        useHID = false
 | 
				
			||||||
 | 
					        useWebSerial = false;
 | 
				
			||||||
        initPromise = Promise.reject(new Error("no HID"))
 | 
					        initPromise = Promise.reject(new Error("no HID"))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    return initPromise;
 | 
				
			||||||
    return initPromise
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// this comes from aux/pxt.lms
 | 
					// this comes from aux/pxt.lms
 | 
				
			||||||
@@ -61,8 +202,6 @@ const rbfTemplate = `
 | 
				
			|||||||
74617274696e672e2e2e0084006080XX00448581644886488405018130813e80427965210084000a
 | 
					74617274696e672e2e2e0084006080XX00448581644886488405018130813e80427965210084000a
 | 
				
			||||||
`
 | 
					`
 | 
				
			||||||
export function deployCoreAsync(resp: pxtc.CompileResult) {
 | 
					export function deployCoreAsync(resp: pxtc.CompileResult) {
 | 
				
			||||||
    let w: pxt.editor.Ev3Wrapper
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let filename = resp.downloadFileBaseName || "pxt"
 | 
					    let filename = resp.downloadFileBaseName || "pxt"
 | 
				
			||||||
    filename = filename.replace(/^lego-/, "")
 | 
					    filename = filename.replace(/^lego-/, "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -107,27 +246,31 @@ export function deployCoreAsync(resp: pxtc.CompileResult) {
 | 
				
			|||||||
        return Promise.resolve();
 | 
					        return Promise.resolve();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (noHID) return saveUF2Async()
 | 
					    if (!useHID) return saveUF2Async()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return initAsync()
 | 
					    pxt.tickEvent("bluetooth.flash");
 | 
				
			||||||
 | 
					    let w: Ev3Wrapper;
 | 
				
			||||||
 | 
					    return initHidAsync()
 | 
				
			||||||
        .then(w_ => {
 | 
					        .then(w_ => {
 | 
				
			||||||
            w = w_
 | 
					            w = w_
 | 
				
			||||||
            if (w.isStreaming)
 | 
					            if (w.isStreaming)
 | 
				
			||||||
                pxt.U.userError("please stop the program first")
 | 
					                pxt.U.userError("please stop the program first")
 | 
				
			||||||
            return w.stopAsync()
 | 
					            return w.reconnectAsync(false)
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					        .then(() => w.stopAsync())
 | 
				
			||||||
        .then(() => w.rmAsync(elfPath))
 | 
					        .then(() => w.rmAsync(elfPath))
 | 
				
			||||||
        .then(() => w.flashAsync(elfPath, UF2.readBytes(origElfUF2, 0, origElfUF2.length * 256)))
 | 
					        .then(() => w.flashAsync(elfPath, UF2.readBytes(origElfUF2, 0, origElfUF2.length * 256)))
 | 
				
			||||||
        .then(() => w.flashAsync(rbfPath, rbfBIN))
 | 
					        .then(() => w.flashAsync(rbfPath, rbfBIN))
 | 
				
			||||||
        .then(() => w.runAsync(rbfPath))
 | 
					        .then(() => w.runAsync(rbfPath))
 | 
				
			||||||
        .then(() => {
 | 
					        .then(() => {
 | 
				
			||||||
 | 
					            pxt.tickEvent("bluetooth.success");
 | 
				
			||||||
            return w.disconnectAsync()
 | 
					            return w.disconnectAsync()
 | 
				
			||||||
            //return Promise.delay(1000).then(() => w.dmesgAsync())
 | 
					            //return Promise.delay(1000).then(() => w.dmesgAsync())
 | 
				
			||||||
        }).catch(e => {
 | 
					        }).catch(e => {
 | 
				
			||||||
            // if we failed to initalize, retry
 | 
					            pxt.tickEvent("bluetooth.fail");
 | 
				
			||||||
            if (noHID)
 | 
					            useHID = false;
 | 
				
			||||||
                return saveUF2Async()
 | 
					            useWebSerial = false;
 | 
				
			||||||
            else
 | 
					            // if we failed to initalize, tell the user to retry
 | 
				
			||||||
            return Promise.reject(e)
 | 
					            return Promise.reject(e)
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,28 +1,18 @@
 | 
				
			|||||||
/// <reference path="../node_modules/pxt-core/built/pxteditor.d.ts"/>
 | 
					/// <reference path="../node_modules/pxt-core/built/pxteditor.d.ts"/>
 | 
				
			||||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
 | 
					/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { deployCoreAsync, initAsync } from "./deploy";
 | 
					import { deployCoreAsync, initAsync, canUseWebSerial, enableWebSerial } from "./deploy";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): Promise<pxt.editor.ExtensionResult> {
 | 
					pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): Promise<pxt.editor.ExtensionResult> {
 | 
				
			||||||
    pxt.debug('loading pxt-ev3 target extensions...')
 | 
					    pxt.debug('loading pxt-ev3 target extensions...')
 | 
				
			||||||
    const res: pxt.editor.ExtensionResult = {
 | 
					    const res: pxt.editor.ExtensionResult = {
 | 
				
			||||||
        deployCoreAsync,
 | 
					        deployCoreAsync,
 | 
				
			||||||
        showUploadInstructionsAsync: (fn: string, url: string, confirmAsync: (options: any) => Promise<number>) => {
 | 
					        showUploadInstructionsAsync: (fn: string, url: string, confirmAsync: (options: any) => Promise<number>) => {
 | 
				
			||||||
            let resolve: (thenableOrResult?: void | PromiseLike<void>) => void;
 | 
					 | 
				
			||||||
            let reject: (error: any) => void;
 | 
					 | 
				
			||||||
            const deferred = new Promise<void>((res, rej) => {
 | 
					 | 
				
			||||||
                resolve = res;
 | 
					 | 
				
			||||||
                reject = rej;
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            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
 | 
					            // https://msdn.microsoft.com/en-us/library/cc848897.aspx
 | 
				
			||||||
            // "For security reasons, data URIs are restricted to downloaded resources. 
 | 
					            // "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"
 | 
					            // Data URIs cannot be used for navigation, for scripting, or to populate frame or iframe elements"
 | 
				
			||||||
            const downloadAgain = !pxt.BrowserUtils.isIE() && !pxt.BrowserUtils.isEdge();
 | 
					            const downloadAgain = !pxt.BrowserUtils.isIE() && !pxt.BrowserUtils.isEdge();
 | 
				
			||||||
            const docUrl = pxt.appTarget.appTheme.usbDocs;
 | 
					            const docUrl = pxt.appTarget.appTheme.usbDocs;
 | 
				
			||||||
            const saveAs = pxt.BrowserUtils.hasSaveAs();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const htmlBody = `
 | 
					            const htmlBody = `
 | 
				
			||||||
            <div class="ui grid stackable">
 | 
					            <div class="ui grid stackable">
 | 
				
			||||||
@@ -84,7 +74,36 @@ pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): P
 | 
				
			|||||||
                hideAgree: false,
 | 
					                hideAgree: false,
 | 
				
			||||||
                agreeLbl: lf("I got it"),
 | 
					                agreeLbl: lf("I got it"),
 | 
				
			||||||
                className: 'downloaddialog',
 | 
					                className: 'downloaddialog',
 | 
				
			||||||
                buttons: [downloadAgain ? {
 | 
					                buttons: [canUseWebSerial() ? {
 | 
				
			||||||
 | 
					                    label: lf("Bluetooth"),
 | 
				
			||||||
 | 
					                    icon: "bluetooth",
 | 
				
			||||||
 | 
					                    className: "bluetooth focused",
 | 
				
			||||||
 | 
					                    onclick: () => {
 | 
				
			||||||
 | 
					                        pxt.tickEvent("bluetooth.enable");
 | 
				
			||||||
 | 
					                        enableWebSerial();
 | 
				
			||||||
 | 
					                        confirmAsync({
 | 
				
			||||||
 | 
					                            header: lf("Bluetooth enabled"),
 | 
				
			||||||
 | 
					                            hasCloseIcon: true,
 | 
				
			||||||
 | 
					                            hideCancel: true,
 | 
				
			||||||
 | 
					                            buttons: [{
 | 
				
			||||||
 | 
					                                label: lf("Help"),
 | 
				
			||||||
 | 
					                                icon: "question circle",
 | 
				
			||||||
 | 
					                                className: "lightgrey",
 | 
				
			||||||
 | 
					                                url: "/bluetooth"
 | 
				
			||||||
 | 
					                            }],
 | 
				
			||||||
 | 
					                            htmlBody: `
 | 
				
			||||||
 | 
					<p>
 | 
				
			||||||
 | 
					${lf("Please download again to send your code to the EV3 over Bluetooth.")}
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
 | 
					<p>
 | 
				
			||||||
 | 
					${lf("You will be prompted to select a serial port.")}
 | 
				
			||||||
 | 
					${lf("On Windows, look for 'Standard Serial over Bluetooth link'.")}
 | 
				
			||||||
 | 
					${lf("If you have paired multiple EV3, you might have to try out multiple ports until you find the correct one.")}
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } : undefined, downloadAgain ? {
 | 
				
			||||||
                    label: fn,
 | 
					                    label: fn,
 | 
				
			||||||
                    icon: "download",
 | 
					                    icon: "download",
 | 
				
			||||||
                    className: "lightgrey focused",
 | 
					                    className: "lightgrey focused",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,8 @@
 | 
				
			|||||||
namespace pxt.editor {
 | 
					 | 
				
			||||||
import HF2 = pxt.HF2
 | 
					import HF2 = pxt.HF2
 | 
				
			||||||
import U = pxt.U
 | 
					import U = pxt.U
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function log(msg: string) {
 | 
					function log(msg: string) {
 | 
				
			||||||
        pxt.log("EWRAP: " + msg)
 | 
					    pxt.debug("EWRAP: " + msg)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface DirEntry {
 | 
					export interface DirEntry {
 | 
				
			||||||
@@ -30,8 +29,8 @@ namespace pxt.editor {
 | 
				
			|||||||
                let payload = buf.slice(8)
 | 
					                let payload = buf.slice(8)
 | 
				
			||||||
                if (code == 1) {
 | 
					                if (code == 1) {
 | 
				
			||||||
                    let str = U.uint8ArrayToString(payload)
 | 
					                    let str = U.uint8ArrayToString(payload)
 | 
				
			||||||
                        if (Util.isNodeJS)
 | 
					                    if (U.isNodeJS)
 | 
				
			||||||
                            console.log("SERIAL: " + str.replace(/\n+$/, ""))
 | 
					                        pxt.debug("SERIAL: " + str.replace(/\n+$/, ""))
 | 
				
			||||||
                    else
 | 
					                    else
 | 
				
			||||||
                        window.postMessage({
 | 
					                        window.postMessage({
 | 
				
			||||||
                            type: 'serial',
 | 
					                            type: 'serial',
 | 
				
			||||||
@@ -39,7 +38,7 @@ namespace pxt.editor {
 | 
				
			|||||||
                            data: str
 | 
					                            data: str
 | 
				
			||||||
                        }, "*")
 | 
					                        }, "*")
 | 
				
			||||||
                } else
 | 
					                } else
 | 
				
			||||||
                        console.log("Magic: " + code + ": " + U.toHex(payload))
 | 
					                    pxt.debug("Magic: " + code + ": " + U.toHex(payload))
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (this.dataDump)
 | 
					            if (this.dataDump)
 | 
				
			||||||
@@ -280,6 +279,3 @@ namespace pxt.editor {
 | 
				
			|||||||
        return this.io.disconnectAsync()
 | 
					        return this.io.disconnectAsync()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -185,3 +185,8 @@
 | 
				
			|||||||
    font-family: 'legoIcons' !important;
 | 
					    font-family: 'legoIcons' !important;
 | 
				
			||||||
    content: "\f119" !important;
 | 
					    content: "\f119" !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.bluetooth {
 | 
				
			||||||
 | 
					    background-color: #007EF4 !important;
 | 
				
			||||||
 | 
					    color: white !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user