graceful handling of bluetooth connectivity issues (#922)

* more cleanup

* cleanup life cycle

* more error checking
This commit is contained in:
Peli de Halleux 2019-09-27 11:15:10 -07:00 committed by GitHub
parent 2563fd6031
commit 42454ecd57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 96 additions and 53 deletions

View File

@ -4,7 +4,12 @@
import UF2 = pxtc.UF2;
import { Ev3Wrapper } from "./wrap";
export let ev3: Ev3Wrapper
export let ev3: Ev3Wrapper;
let confirmAsync: (options: any) => Promise<number>;
export function setConfirmAsync(cf: (options: any) => Promise<number>) {
confirmAsync = cf;
}
export function debug() {
return initHidAsync()
@ -59,14 +64,10 @@ class WebSerialPackageIO implements pxt.HF2.PacketIO {
private _writer: any;
constructor(private port: SerialPort, private options: SerialOptions) {
// start reading
this.readSerialAsync();
this.openAsync();
}
async readSerialAsync() {
if (this._reader) return;
this._reader = this.port.readable.getReader();
let buffer: Uint8Array;
const reader = this._reader;
@ -100,9 +101,7 @@ class WebSerialPackageIO implements pxt.HF2.PacketIO {
baudrate: 460800,
buffersize: 4096
};
await port.open(options);
if (port)
return new WebSerialPackageIO(port, options);
return new WebSerialPackageIO(port, options);
} catch (e) {
console.log(`connection error`, e)
}
@ -115,27 +114,32 @@ class WebSerialPackageIO implements pxt.HF2.PacketIO {
throw new Error(lf("error on brick ({0})", msg))
}
private close() {
if (this.port.readable) {// it's open
this.port.close();
this._reader = undefined;
this._writer = undefined;
}
private openAsync() {
console.log(`serial: opening port`)
if (!!this._reader) return Promise.resolve();
this._reader = undefined;
this._writer = undefined;
return this.port.open(this.options)
.then(() => {
this.readSerialAsync();
return Promise.resolve();
});
}
async reconnectAsync(): Promise<void> {
if (!this.port.readable) {
this._reader = undefined;
this._writer = undefined;
await this.port.open(this.options);
this.readSerialAsync();
}
return Promise.resolve();
private closeAsync() {
console.log(`serial: closing port`);
this.port.close();
this._reader = undefined;
this._writer = undefined;
return Promise.delay(500);
}
async disconnectAsync(): Promise<void> {
this.close();
return Promise.resolve();
reconnectAsync(): Promise<void> {
return this.openAsync();
}
disconnectAsync(): Promise<void> {
return this.closeAsync();
}
sendPacketAsync(pkt: Uint8Array): Promise<void> {
@ -178,12 +182,23 @@ export function canUseWebSerial() {
return WebSerialPackageIO.isSupported();
}
export function enableWebSerial() {
export function enableWebSerialAsync() {
initPromise = undefined;
useWebSerial = WebSerialPackageIO.isSupported();
useHID = useWebSerial;
if (useWebSerial)
initHidAsync().done();
return initHidAsync().then(() => { });
else return Promise.resolve();
}
function cleanupAsync() {
if (ev3) {
console.log('cleanup previous port')
return ev3.disconnectAsync()
.catch(e => { })
.finally(() => { ev3 = undefined; });
}
return Promise.resolve();
}
let initPromise: Promise<Ev3Wrapper>
@ -191,15 +206,14 @@ function initHidAsync() { // needs to run within a click handler
if (initPromise)
return initPromise
if (useHID) {
initPromise = hf2Async()
initPromise = cleanupAsync()
.then(() => hf2Async())
.catch(err => {
console.error(err);
initPromise = null
useHID = false;
useWebSerial = false;
// cleanup
let p = ev3 ? ev3.disconnectAsync().catch(e => { }) : Promise.resolve();
return p.then(() => Promise.reject(err))
return Promise.reject(err);
})
} else {
useHID = false
@ -210,6 +224,7 @@ function initHidAsync() { // needs to run within a click handler
}
// this comes from aux/pxt.lms
const fspath = "../prjs/BrkProg_SAVE/"
const rbfTemplate = `
4c45474f580000006d000100000000001c000000000000000e000000821b038405018130813e8053
74617274696e672e2e2e0084006080XX00448581644886488405018130813e80427965210084000a
@ -218,8 +233,6 @@ export function deployCoreAsync(resp: pxtc.CompileResult) {
let filename = resp.downloadFileBaseName || "pxt"
filename = filename.replace(/^lego-/, "")
let fspath = "../prjs/BrkProg_SAVE/"
let elfPath = fspath + filename + ".elf"
let rbfPath = fspath + filename + ".rbf"
@ -269,6 +282,26 @@ export function deployCoreAsync(resp: pxtc.CompileResult) {
if (w.isStreaming)
pxt.U.userError("please stop the program first")
return w.reconnectAsync(false)
.catch(e => {
// user easily forgets to stop robot
if (confirmAsync)
return confirmAsync({
header: lf("Bluetooth download failed..."),
htmlBody:
`<ul>
<li>${lf("Make sure to stop your program on the EV3.")}</li>
<li>${lf("Check your battery level.")}</li>
</ul>`,
hasCloseIcon: true,
hideCancel: true,
hideAgree: false,
agreeLbl: lf("Try again"),
}).then(() => w.disconnectAsync())
.then(() => w.reconnectAsync());
// nothing we can do
return Promise.reject(e);
})
})
.then(() => w.stopAsync())
.then(() => w.rmAsync(elfPath))

View File

@ -1,13 +1,15 @@
/// <reference path="../node_modules/pxt-core/built/pxteditor.d.ts"/>
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
import { deployCoreAsync, initAsync, canUseWebSerial, enableWebSerial } from "./deploy";
import { deployCoreAsync, initAsync, canUseWebSerial, enableWebSerialAsync, setConfirmAsync } from "./deploy";
let bluetoothDialogShown = false;
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>) => {
setConfirmAsync(confirmAsync);
// 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"
@ -80,29 +82,37 @@ pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): P
className: "bluetooth focused",
onclick: () => {
pxt.tickEvent("bluetooth.enable");
confirmAsync({
header: lf("Bluetooth enabled"),
hasCloseIcon: true,
hideCancel: true,
buttons: [{
label: lf("Help"),
icon: "question circle",
className: "lightgrey",
url: "/bluetooth"
}],
htmlBody: `
<p>
${lf("Download again to send your code to the EV3 over Bluetooth. Make sure to stop your program!")}
</p>
<p>
if (bluetoothDialogShown) {
enableWebSerialAsync().done();
} else {
bluetoothDialogShown = true;
confirmAsync({
header: lf("Bluetooth pairing"),
hasCloseIcon: true,
hideCancel: true,
buttons: [{
label: lf("Help"),
icon: "question circle",
className: "lightgrey",
url: "/bluetooth"
}],
htmlBody: `<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>
`
}).then(() => {
enableWebSerial();
})
}).then(() => enableWebSerialAsync())
.then(() => confirmAsync({
header: lf("Bluetooth is ready!"),
hasCloseIcon: true,
hideCancel: true,
htmlBody: `<p>
${lf("Click Download again to send your code to the EV3 over Bluetooth.")}
</p>
`
}))
}
}
} : undefined, downloadAgain ? {
label: fn,