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)
 | 
			
		||||
* [EV3 Manager](https://ev3manager.education.lego.com/)
 | 
			
		||||
* [Bluetooth](/bluetooth)
 | 
			
		||||
* [Forum](https://forum.makecode.com)
 | 
			
		||||
* [LEGO Support](https://www.lego.com/service/)
 | 
			
		||||
* [FIRST LEGO League](/fll)
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ program to a **.uf2** file, which you then copy to the **@drivename@** drive. Th
 | 
			
		||||
 | 
			
		||||
### ~ 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.
 | 
			
		||||
 | 
			
		||||
### 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?
 | 
			
		||||
 | 
			
		||||
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"/>
 | 
			
		||||
 | 
			
		||||
import UF2 = pxtc.UF2;
 | 
			
		||||
import { Ev3Wrapper } from "./wrap";
 | 
			
		||||
 | 
			
		||||
export let ev3: pxt.editor.Ev3Wrapper
 | 
			
		||||
export let ev3: Ev3Wrapper
 | 
			
		||||
 | 
			
		||||
export function debug() {
 | 
			
		||||
    return initAsync()
 | 
			
		||||
    return initHidAsync()
 | 
			
		||||
        .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() {
 | 
			
		||||
    return pxt.HF2.mkPacketIOAsync()
 | 
			
		||||
        .then(h => {
 | 
			
		||||
            let w = new pxt.editor.Ev3Wrapper(h)
 | 
			
		||||
    const pktIOAsync: Promise<pxt.HF2.PacketIO> = useWebSerial
 | 
			
		||||
        ? WebSerialPackageIO.mkPacketIOAsync() : pxt.HF2.mkPacketIOAsync()
 | 
			
		||||
    return pktIOAsync.then(h => {
 | 
			
		||||
        let w = new Ev3Wrapper(h)
 | 
			
		||||
        ev3 = w
 | 
			
		||||
        return w.reconnectAsync(true)
 | 
			
		||||
            .then(() => w)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let noHID = false
 | 
			
		||||
 | 
			
		||||
let initPromise: Promise<pxt.editor.Ev3Wrapper>
 | 
			
		||||
export function initAsync() {
 | 
			
		||||
    if (initPromise)
 | 
			
		||||
        return initPromise
 | 
			
		||||
 | 
			
		||||
    let canHID = false
 | 
			
		||||
let useHID = false;
 | 
			
		||||
let useWebSerial = false;
 | 
			
		||||
export function initAsync(): Promise<void> {
 | 
			
		||||
    if (pxt.U.isNodeJS) {
 | 
			
		||||
        // doesn't seem to work ATM
 | 
			
		||||
        canHID = false
 | 
			
		||||
        useHID = false
 | 
			
		||||
    } else {
 | 
			
		||||
        const forceHexDownload = /forceHexDownload/i.test(window.location.href);
 | 
			
		||||
        if (pxt.Cloud.isLocalHost() && pxt.Cloud.localToken && !forceHexDownload)
 | 
			
		||||
            canHID = true
 | 
			
		||||
        const nodehid = /nodehid/i.test(window.location.href);
 | 
			
		||||
        if (pxt.Cloud.isLocalHost() && pxt.Cloud.localToken && nodehid)
 | 
			
		||||
            useHID = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (noHID)
 | 
			
		||||
        canHID = false
 | 
			
		||||
    if(WebSerialPackageIO.isSupported())
 | 
			
		||||
        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()
 | 
			
		||||
            .catch(err => {
 | 
			
		||||
                console.error(err);
 | 
			
		||||
                initPromise = null
 | 
			
		||||
                noHID = true
 | 
			
		||||
                return Promise.reject(err)
 | 
			
		||||
                useHID = false;
 | 
			
		||||
                useWebSerial = false;
 | 
			
		||||
                // cleanup
 | 
			
		||||
                let p = ev3  ? ev3.disconnectAsync().catch(e => {}) : Promise.resolve();
 | 
			
		||||
                return p.then(() => Promise.reject(err))
 | 
			
		||||
            })
 | 
			
		||||
    } else {
 | 
			
		||||
        noHID = true
 | 
			
		||||
        useHID = false
 | 
			
		||||
        useWebSerial = false;
 | 
			
		||||
        initPromise = Promise.reject(new Error("no HID"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return initPromise
 | 
			
		||||
    return initPromise;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// this comes from aux/pxt.lms
 | 
			
		||||
@@ -61,8 +202,6 @@ const rbfTemplate = `
 | 
			
		||||
74617274696e672e2e2e0084006080XX00448581644886488405018130813e80427965210084000a
 | 
			
		||||
`
 | 
			
		||||
export function deployCoreAsync(resp: pxtc.CompileResult) {
 | 
			
		||||
    let w: pxt.editor.Ev3Wrapper
 | 
			
		||||
 | 
			
		||||
    let filename = resp.downloadFileBaseName || "pxt"
 | 
			
		||||
    filename = filename.replace(/^lego-/, "")
 | 
			
		||||
 | 
			
		||||
@@ -107,27 +246,31 @@ export function deployCoreAsync(resp: pxtc.CompileResult) {
 | 
			
		||||
        return Promise.resolve();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (noHID) return saveUF2Async()
 | 
			
		||||
    if (!useHID) return saveUF2Async()
 | 
			
		||||
 | 
			
		||||
    return initAsync()
 | 
			
		||||
    pxt.tickEvent("bluetooth.flash");
 | 
			
		||||
    let w: Ev3Wrapper;
 | 
			
		||||
    return initHidAsync()
 | 
			
		||||
        .then(w_ => {
 | 
			
		||||
            w = w_
 | 
			
		||||
            if (w.isStreaming)
 | 
			
		||||
                pxt.U.userError("please stop the program first")
 | 
			
		||||
            return w.stopAsync()
 | 
			
		||||
            return w.reconnectAsync(false)
 | 
			
		||||
        })
 | 
			
		||||
        .then(() => w.stopAsync())
 | 
			
		||||
        .then(() => w.rmAsync(elfPath))
 | 
			
		||||
        .then(() => w.flashAsync(elfPath, UF2.readBytes(origElfUF2, 0, origElfUF2.length * 256)))
 | 
			
		||||
        .then(() => w.flashAsync(rbfPath, rbfBIN))
 | 
			
		||||
        .then(() => w.runAsync(rbfPath))
 | 
			
		||||
        .then(() => {
 | 
			
		||||
            pxt.tickEvent("bluetooth.success");
 | 
			
		||||
            return w.disconnectAsync()
 | 
			
		||||
            //return Promise.delay(1000).then(() => w.dmesgAsync())
 | 
			
		||||
        }).catch(e => {
 | 
			
		||||
            // if we failed to initalize, retry
 | 
			
		||||
            if (noHID)
 | 
			
		||||
                return saveUF2Async()
 | 
			
		||||
            else
 | 
			
		||||
            pxt.tickEvent("bluetooth.fail");
 | 
			
		||||
            useHID = false;
 | 
			
		||||
            useWebSerial = false;
 | 
			
		||||
            // if we failed to initalize, tell the user to retry
 | 
			
		||||
            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/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.debug('loading pxt-ev3 target extensions...')
 | 
			
		||||
    const res: pxt.editor.ExtensionResult = {
 | 
			
		||||
        deployCoreAsync,
 | 
			
		||||
        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
 | 
			
		||||
            // "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 downloadAgain = !pxt.BrowserUtils.isIE() && !pxt.BrowserUtils.isEdge();
 | 
			
		||||
            const docUrl = pxt.appTarget.appTheme.usbDocs;
 | 
			
		||||
            const saveAs = pxt.BrowserUtils.hasSaveAs();
 | 
			
		||||
 | 
			
		||||
            const htmlBody = `
 | 
			
		||||
            <div class="ui grid stackable">
 | 
			
		||||
@@ -84,7 +74,36 @@ pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): P
 | 
			
		||||
                hideAgree: false,
 | 
			
		||||
                agreeLbl: lf("I got it"),
 | 
			
		||||
                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,
 | 
			
		||||
                    icon: "download",
 | 
			
		||||
                    className: "lightgrey focused",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
namespace pxt.editor {
 | 
			
		||||
import HF2 = pxt.HF2
 | 
			
		||||
import U = pxt.U
 | 
			
		||||
 | 
			
		||||
function log(msg: string) {
 | 
			
		||||
        pxt.log("EWRAP: " + msg)
 | 
			
		||||
    pxt.debug("EWRAP: " + msg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface DirEntry {
 | 
			
		||||
@@ -30,8 +29,8 @@ namespace pxt.editor {
 | 
			
		||||
                let payload = buf.slice(8)
 | 
			
		||||
                if (code == 1) {
 | 
			
		||||
                    let str = U.uint8ArrayToString(payload)
 | 
			
		||||
                        if (Util.isNodeJS)
 | 
			
		||||
                            console.log("SERIAL: " + str.replace(/\n+$/, ""))
 | 
			
		||||
                    if (U.isNodeJS)
 | 
			
		||||
                        pxt.debug("SERIAL: " + str.replace(/\n+$/, ""))
 | 
			
		||||
                    else
 | 
			
		||||
                        window.postMessage({
 | 
			
		||||
                            type: 'serial',
 | 
			
		||||
@@ -39,7 +38,7 @@ namespace pxt.editor {
 | 
			
		||||
                            data: str
 | 
			
		||||
                        }, "*")
 | 
			
		||||
                } else
 | 
			
		||||
                        console.log("Magic: " + code + ": " + U.toHex(payload))
 | 
			
		||||
                    pxt.debug("Magic: " + code + ": " + U.toHex(payload))
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            if (this.dataDump)
 | 
			
		||||
@@ -280,6 +279,3 @@ namespace pxt.editor {
 | 
			
		||||
        return this.io.disconnectAsync()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -185,3 +185,8 @@
 | 
			
		||||
    font-family: 'legoIcons' !important;
 | 
			
		||||
    content: "\f119" !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bluetooth {
 | 
			
		||||
    background-color: #007EF4 !important;
 | 
			
		||||
    color: white !important;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user