Skip to content

Commit d3300c5

Browse files
authored
WebUSB cleanup (microsoft#6806)
* move interfaces * more refactoring * removed duplicate api * 5.37.11 * adding is connected indicator * adding events for connection * more wiring up * add log * more refactoring * more refactoring * refactoring * more wrapping and cleaning * refactoring * more logging * more cleanup * cleanup connection changed * more logging * more logging * more logging * refactor * better error message * more refactoring * fix data invalidation * updated disconnect * implement disconnect * always reconenct * handling of web usb connection events * done note supported * clean up of events * connect on pairing * add retry * reconnect after flashing * add more error checking * don't force reconnection * handle timeout in reconnection * notify of device conn/disconn * lint * lint * moved disconnect/connect into reflash * 5.37.17 * wait for pairing before conect * lint * update winrt runtime versions * lint * lint
1 parent efd2518 commit d3300c5

19 files changed

Lines changed: 524 additions & 318 deletions

File tree

cli/hid.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -137,20 +137,24 @@ export function hf2ConnectAsync(path: string, raw = false) {
137137
// in .then() to make sure we catch errors
138138
let h = new HF2.Wrapper(new HidIO(path))
139139
h.rawMode = raw
140-
return h.reconnectAsync(true).then(() => h)
140+
return h.reconnectAsync().then(() => h)
141141
}
142142

143-
export function mkPacketIOAsync() {
144-
if (useWebUSB())
143+
export function mkWebUSBOrHidPacketIOAsync(): Promise<pxt.packetio.PacketIO> {
144+
if (useWebUSB()) {
145+
pxt.log(`packetio: mk cli webusb`)
145146
return hf2ConnectAsync("")
147+
}
148+
149+
pxt.log(`packetio: mk cli hidio`)
146150
return Promise.resolve()
147151
.then(() => {
148152
// in .then() to make sure we catch errors
149153
return new HidIO(null)
150154
})
151155
}
152156

153-
pxt.HF2.mkPacketIOAsync = mkPacketIOAsync
157+
pxt.packetio.mkPacketIOAsync = mkWebUSBOrHidPacketIOAsync;
154158

155159
let hf2Dev: Promise<HF2.Wrapper>
156160
export function initAsync(path: string = null): Promise<HF2.Wrapper> {
@@ -178,10 +182,12 @@ export class HIDError extends Error {
178182
}
179183
}
180184

181-
export class HidIO implements HF2.PacketIO {
185+
export class HidIO implements pxt.packetio.PacketIO {
182186
dev: any;
183187
private path: string;
184188

189+
onDeviceConnectionChanged = (connect: boolean) => { };
190+
onConnectionChanged = () => { };
185191
onData = (v: Uint8Array) => { };
186192
onEvent = (v: Uint8Array) => { };
187193
onError = (e: Error) => { };
@@ -207,6 +213,16 @@ export class HidIO implements HF2.PacketIO {
207213
this.onData(new Uint8Array(v))
208214
})
209215
this.dev.on("error", (v: Error) => this.onError(v))
216+
if (this.onConnectionChanged)
217+
this.onConnectionChanged();
218+
}
219+
220+
disposeAsync(): Promise<void> {
221+
return Promise.resolve();
222+
}
223+
224+
isConnected(): boolean {
225+
return !!this.dev;
210226
}
211227

212228
sendPacketAsync(pkt: Uint8Array): Promise<void> {
@@ -232,14 +248,17 @@ export class HidIO implements HF2.PacketIO {
232248
// see https://github.com/node-hid/node-hid/issues/61
233249
this.dev.removeAllListeners("data");
234250
this.dev.removeAllListeners("error");
235-
let pkt = new Uint8Array([0x48])
251+
const pkt = new Uint8Array([0x48])
236252
this.sendPacketAsync(pkt).catch(e => { })
237253
return Promise.delay(100)
238254
.then(() => {
239255
if (this.dev) {
240-
this.dev.close()
241-
this.dev = null
256+
const d = this.dev;
257+
delete this.dev;
258+
d.close()
242259
}
260+
if (this.onConnectionChanged)
261+
this.onConnectionChanged();
243262
})
244263
}
245264

pxteditor/editor.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,6 @@ namespace pxt.editor {
283283
toggleGreenScreen(): void;
284284
toggleAccessibleBlocks(): void;
285285
setAccessibleBlocks(enabled: boolean): void;
286-
pair(): void;
287286
launchFullEditor(): void;
288287

289288
settings: EditorSettings;
@@ -318,7 +317,8 @@ namespace pxt.editor {
318317
showPackageDialog(): void;
319318
showBoardDialogAsync(features?: string[], closeIcon?: boolean): Promise<void>;
320319
checkForHwVariant(): boolean;
321-
pair(): Promise<void>;
320+
pairAsync(autoConnect: boolean): Promise<void>;
321+
disconnectAsync(): Promise<void>;
322322

323323
showModalDialogAsync(options: ModalDialogOptions): Promise<void>;
324324

@@ -379,6 +379,7 @@ namespace pxt.editor {
379379
toolboxOptions?: IToolboxOptions;
380380
blocklyPatch?: (pkgTargetVersion: string, dom: Element) => void;
381381
webUsbPairDialogAsync?: (confirmAsync: (options: any) => Promise<number>) => Promise<number>;
382+
mkPacketIOWrapper?: (io: pxt.packetio.PacketIO) => pxt.packetio.PacketIOWrapper;
382383

383384
// Used with the @tutorialCompleted macro. See docs/writing-docs/tutorials.md for more info
384385
onTutorialCompleted?: () => void;

pxteditor/editorcontroller.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,11 @@ namespace pxt.editor {
404404
}
405405
case "pair": {
406406
return Promise.resolve()
407-
.then(() => projectView.pair());
407+
.then(() => projectView.pairAsync(false));
408+
}
409+
case "connect": {
410+
return Promise.resolve()
411+
.then(() => projectView.pairAsync(true));
408412
}
409413
case "info": {
410414
return Promise.resolve()

pxtlib/cmds.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
namespace pxt.commands {
22
export interface DeployOptions {
3-
reportError?: (e: string) => void;
4-
showNotification?: (msg: string) => void;
5-
reportDeviceNotFoundAsync?: (docPath: string, resp?: ts.pxtc.CompileResult) => Promise<void>;
3+
reportError: (e: string) => void;
4+
showNotification: (msg: string) => void;
5+
reportDeviceNotFoundAsync: (docPath: string, resp?: ts.pxtc.CompileResult) => Promise<void>;
66
}
77

88
// overriden by targets

pxtlib/hf2.ts

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -32,30 +32,6 @@ namespace pxt.HF2 {
3232
NXP = 0x0D28, // aka Freescale, KL26 etc
3333
}
3434

35-
36-
export interface TalkArgs {
37-
cmd: number;
38-
data?: Uint8Array;
39-
}
40-
41-
export interface PacketIO {
42-
sendPacketAsync(pkt: Uint8Array): Promise<void>;
43-
onData: (v: Uint8Array) => void;
44-
onError: (e: Error) => void;
45-
onEvent: (v: Uint8Array) => void;
46-
error(msg: string): any;
47-
reconnectAsync(): Promise<void>;
48-
disconnectAsync(): Promise<void>;
49-
isSwitchingToBootloader?: () => void;
50-
51-
// these are implemneted by HID-bridge
52-
talksAsync?(cmds: TalkArgs[]): Promise<Uint8Array[]>;
53-
sendSerialAsync?(buf: Uint8Array, useStdErr: boolean): Promise<void>;
54-
onSerial?: (v: Uint8Array, isErr: boolean) => void;
55-
}
56-
57-
export let mkPacketIOAsync: () => Promise<pxt.HF2.PacketIO>
58-
5935
// see https://github.com/microsoft/uf2/blob/master/hf2.md for full spec
6036
export const HF2_CMD_BININFO = 0x0001 // no arguments
6137
export const HF2_MODE_BOOTLOADER = 0x01
@@ -174,8 +150,6 @@ namespace pxt.HF2 {
174150
return res
175151
}
176152

177-
178-
179153
export interface BootloaderInfo {
180154
Header: string;
181155
Parsed: {
@@ -198,10 +172,13 @@ namespace pxt.HF2 {
198172
pxt.debug("HF2: " + msg)
199173
}
200174

201-
export class Wrapper {
175+
export class Wrapper implements pxt.packetio.PacketIOWrapper {
202176
private cmdSeq = U.randomUint32();
203-
constructor(public io: PacketIO) {
177+
constructor(public readonly io: pxt.packetio.PacketIO) {
204178
let frames: Uint8Array[] = []
179+
io.onDeviceConnectionChanged = connect =>
180+
this.disconnectAsync()
181+
.then(() => connect && this.reconnectAsync());
205182
io.onSerial = (b, e) => this.onSerial(b, e)
206183
io.onData = buf => {
207184
let tp = buf[0] & HF2_FLAG_MASK
@@ -291,10 +268,10 @@ namespace pxt.HF2 {
291268
this.eventHandlers[id + ""] = f
292269
}
293270

294-
reconnectAsync(first = false): Promise<void> {
271+
reconnectAsync(): Promise<void> {
295272
this.resetState()
296-
if (first) return this.initAsync()
297273
log(`reconnect raw=${this.rawMode}`);
274+
298275
return this.io.reconnectAsync()
299276
.then(() => this.initAsync())
300277
.catch(e => {
@@ -426,9 +403,13 @@ namespace pxt.HF2 {
426403
})
427404
}
428405

429-
reflashAsync(blocks: pxtc.UF2.Block[]) {
406+
reflashAsync(resp: pxtc.CompileResult): Promise<void> {
430407
log(`reflash`)
431-
return this.flashAsync(blocks)
408+
U.assert(pxt.appTarget.compile.useUF2);
409+
const f = resp.outfiles[pxtc.BINARY_UF2]
410+
const blocks = pxtc.UF2.parseFile(pxt.Util.stringToUint8Array(atob(f)))
411+
return this.io.reconnectAsync()
412+
.then(() => this.flashAsync(blocks))
432413
.then(() => Promise.delay(100))
433414
.then(() => this.reconnectAsync())
434415
}
@@ -552,6 +533,11 @@ namespace pxt.HF2 {
552533

553534
}
554535

536+
export function mkPacketIOWrapper(io: pxt.packetio.PacketIO): pxt.packetio.PacketIOWrapper {
537+
pxt.log(`packetio: wrapper hf2`)
538+
return new Wrapper(io);
539+
}
540+
555541
export type ReadAsync = (addr: number, len: number) => Promise<ArrayLike<number>>
556542
function readChecksumBlockAsync(readWordsAsync: ReadAsync): Promise<pxtc.ChecksumBlock> {
557543
if (!pxt.appTarget.compile.flashChecksumAddr)

pxtlib/hwdbg.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,7 @@ namespace pxt.HWDBG {
307307
let f = lastCompileResult.outfiles[pxtc.BINARY_UF2]
308308
let blockBuf = U.stringToUint8Array(atob(f))
309309
lastFlash = pxtc.UF2.toBin(blockBuf)
310-
let blocks = pxtc.UF2.parseFile(blockBuf)
311-
return hid.reflashAsync(blocks) // this will reset into app at the end
310+
return hid.reflashAsync(lastCompileResult) // this will reset into app at the end
312311
})
313312
.then(() => hid.talkAsync(HF2_DBG_RESTART).catch(e => { }))
314313
.then(() => Promise.delay(200))

pxtlib/packetio.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
namespace pxt.packetio {
2+
export interface TalkArgs {
3+
cmd: number;
4+
data?: Uint8Array;
5+
}
6+
7+
export interface PacketIOWrapper {
8+
readonly io: PacketIO;
9+
10+
familyID: number;
11+
12+
onSerial: (buf: Uint8Array, isStderr: boolean) => void;
13+
14+
reconnectAsync(): Promise<void>;
15+
disconnectAsync(): Promise<void>;
16+
reflashAsync(resp: pxtc.CompileResult): Promise<void>;
17+
}
18+
19+
export interface PacketIO {
20+
sendPacketAsync(pkt: Uint8Array): Promise<void>;
21+
onDeviceConnectionChanged: (connect: boolean) => void;
22+
onConnectionChanged: () => void;
23+
onData: (v: Uint8Array) => void;
24+
onError: (e: Error) => void;
25+
onEvent: (v: Uint8Array) => void;
26+
error(msg: string): any;
27+
reconnectAsync(): Promise<void>;
28+
disconnectAsync(): Promise<void>;
29+
isConnected(): boolean;
30+
isSwitchingToBootloader?: () => void;
31+
// release any native resource before being released
32+
disposeAsync(): Promise<void>;
33+
34+
// these are implemneted by HID-bridge
35+
talksAsync?(cmds: TalkArgs[]): Promise<Uint8Array[]>;
36+
sendSerialAsync?(buf: Uint8Array, useStdErr: boolean): Promise<void>;
37+
38+
onSerial?: (v: Uint8Array, isErr: boolean) => void;
39+
}
40+
41+
export let mkPacketIOAsync: () => Promise<PacketIO>;
42+
export let mkPacketIOWrapper: (io: PacketIO) => PacketIOWrapper;
43+
44+
let wrapper: PacketIOWrapper;
45+
let initPromise: Promise<PacketIOWrapper>;
46+
let onConnectionChangedHandler: () => void = () => { };
47+
let onSerialHandler: (buf: Uint8Array, isStderr: boolean) => void;
48+
49+
export function isConnected() {
50+
return wrapper && wrapper.io.isConnected();
51+
}
52+
53+
export function disconnectAsync(): Promise<void> {
54+
log('disconnect')
55+
let p = Promise.resolve();
56+
if (wrapper) {
57+
p = p.then(() => wrapper.disconnectAsync())
58+
.then(() => wrapper.io.disposeAsync())
59+
.catch(e => {
60+
// swallow execeptions
61+
pxt.reportException(e);
62+
})
63+
.finally(() => {
64+
initPromise = undefined; // dubious
65+
wrapper = undefined;
66+
});
67+
}
68+
if (onConnectionChangedHandler)
69+
p = p.then(() => onConnectionChangedHandler());
70+
return p;
71+
}
72+
73+
export function configureEvents(
74+
onConnectionChanged: () => void,
75+
onSerial: (buf: Uint8Array, isStderr: boolean) => void
76+
): void {
77+
onConnectionChangedHandler = onConnectionChanged;
78+
onSerialHandler = onSerial;
79+
if (wrapper) {
80+
wrapper.io.onConnectionChanged = onConnectionChangedHandler;
81+
wrapper.onSerial = onSerialHandler;
82+
}
83+
}
84+
85+
function wrapperAsync(): Promise<PacketIOWrapper> {
86+
if (wrapper)
87+
return Promise.resolve(wrapper);
88+
89+
pxt.log(`packetio: new wrapper`)
90+
return mkPacketIOAsync()
91+
.then(io => {
92+
io.onConnectionChanged = onConnectionChangedHandler;
93+
wrapper = mkPacketIOWrapper(io);
94+
if (onSerialHandler)
95+
wrapper.onSerial = onSerialHandler;
96+
return wrapper;
97+
})
98+
}
99+
100+
export function initAsync(force = false): Promise<PacketIOWrapper> {
101+
pxt.log(`packetio: init ${force ? "(force)" : ""}`)
102+
if (!initPromise) {
103+
let p = Promise.resolve();
104+
if (force)
105+
p = p.then(() => disconnectAsync());
106+
initPromise = p.then(() => wrapperAsync())
107+
.finally(() => { initPromise = undefined })
108+
}
109+
return initPromise;
110+
}
111+
}

pxtlib/webble.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -822,7 +822,7 @@ namespace pxt.webBluetooth {
822822
})
823823
}
824824

825-
export function flashAsync(resp: pxtc.CompileResult, d: pxt.commands.DeployOptions = {}): Promise<void> {
825+
export function flashAsync(resp: pxtc.CompileResult, d?: pxt.commands.DeployOptions): Promise<void> {
826826
pxt.tickEvent("webble.flash");
827827
const hex = resp.outfiles[ts.pxtc.BINARY_HEX];
828828
return connectAsync()

0 commit comments

Comments
 (0)