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:
parent
6d940a9ec7
commit
352c1ca5ec
@ -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**
|
||||||
|
|
||||||
|
![A screenshot of the flags page in chrome](/static/bluetooth/experimental.png)
|
||||||
|
|
||||||
|
## 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user