Web usb error handling (#1208)

* stash changes

* Progress using dapjs WebUSB example

* Try stuff

* more playing around

* Finish + clean up

* Small tweaks

* Undo some generated changes
This commit is contained in:
Guillaume Jenkins 2018-09-12 10:26:35 -04:00 committed by GitHub
parent fbb23feda7
commit a1a3ded4f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 190 additions and 1589 deletions

View File

@ -8,6 +8,7 @@ namespace pxt.editor {
const pageSize = 1024;
const numPages = 256;
const timeoutMessage = "timeout";
function murmur3_core(data: Uint8Array) {
let h0 = 0x2F9BE6CC;
@ -284,41 +285,53 @@ namespace pxt.editor {
return r;
};
function fullVendorCommandFlashAsync(resp: pxtc.CompileResult, wrap: DAPWrapper): Promise<void> {
const chunkSize = 62;
let aborted = false;
function getFlashChecksumsAsync(wrap: DAPWrapper) {
log("getting existing flash checksums")
let pages = numPages
return wrap.cortexM.runCode(computeChecksums2, loadAddr, loadAddr + 1, 0xffffffff, stackAddr, true,
dataAddr, 0, pageSize, pages)
.then(() => wrap.cortexM.memory.readBlock(dataAddr, pages * 2, pageSize))
return Promise.resolve()
.then(() => {
return wrap.cmsisdap.cmdNums(0x8A /* DAPLinkFlash.OPEN */, [1]);
})
.then((res) => {
const hexUint8 = U.stringToUint8Array(resp.outfiles[pxtc.BINARY_HEX]);
const hexArray: number[] = Array.prototype.slice.call(hexUint8);
const sendPages = (offset: number = 0): Promise<void> => {
const end = Math.min(hexArray.length, offset + chunkSize);
const nextPage = hexArray.slice(offset, end);
nextPage.unshift(nextPage.length);
return wrap.cmsisdap.cmdNums(0x8C /* DAPLinkFlash.WRITE */, nextPage)
.then(() => {
if (!aborted && end < hexArray.length) {
return sendPages(end);
}
return Promise.resolve();
});
}
return sendPages();
})
.then((res) => {
return wrap.cmsisdap.cmdNums(0x8B /* DAPLinkFlash.CLOSE */, []);
})
.timeout(60000, timeoutMessage)
.catch((e) => {
aborted = true;
return wrap.cmsisdap.cmdNums(0x89 /* DAPLinkFlash.RESET */, [])
.catch((e2: any) => {
// Best effort reset, no-op if there's an error
})
.then(() => {
return Promise.reject(e);
});
});
}
function onlyChanged(blocks: UF2.Block[], checksums: Uint8Array) {
return blocks.filter(b => {
let idx = b.targetAddr / pageSize
U.assert((idx | 0) == idx)
U.assert(b.data.length == pageSize)
if (idx * 8 + 8 > checksums.length)
return true // out of range?
let c0 = HF2.read32(checksums, idx * 8)
let c1 = HF2.read32(checksums, idx * 8 + 4)
let ch = murmur3_core(b.data)
if (c0 == ch[0] && c1 == ch[1])
return false
return true
})
}
export function deployCoreAsync(resp: pxtc.CompileResult): Promise<void> {
let saveHexAsync = () => {
return pxt.commands.saveOnlyAsync(resp)
}
startTime = 0
let wrap: DAPWrapper
log("init")
function quickHidFlashAsync(resp: pxtc.CompileResult, wrap: DAPWrapper): Promise<void> {
let logV = (msg: string) => { }
//let logV = log
let aborted = false;
const runFlash = (b: UF2.Block, dataAddr: number) => {
const cmd = wrap.cortexM.prepareCommand();
@ -345,111 +358,177 @@ namespace pxt.editor {
}
let checksums: Uint8Array
pxt.tickEvent("hid.flash.start");
return Promise.resolve()
.then(() => {
if (previousDapWrapper) {
previousDapWrapper.flashing = true
return Promise.delay(100)
}
return Promise.resolve()
})
.then(initAsync)
.then(w => {
wrap = w
log("reset")
return wrap.cortexM.init()
.then(() => wrap.cortexM.reset(true))
.catch(e => {
log("trying re-connect")
return wrap.reconnectAsync(false)
.then(() => wrap.cortexM.reset(true))
})
})
.then(() => wrap.cortexM.memory.readBlock(0x10001014, 1, pageSize))
.then(v => {
if (HF2.read32(v, 0) != 0x3C000) {
pxt.tickEvent("hid.flash.uicrfail");
U.userError(U.lf("Please flash any MakeCode hex file using drag and drop. Flashing from app will work afterwards."))
}
})
.then(() => getFlashChecksumsAsync(wrap))
return getFlashChecksumsAsync(wrap)
.then(buf => {
checksums = buf
log("write code")
return wrap.cortexM.memory.writeBlock(loadAddr, flashPageBIN)
checksums = buf;
log("write code");
return wrap.cortexM.memory.writeBlock(loadAddr, flashPageBIN);
})
.then(() => {
log("convert")
log("convert");
// TODO this is seriously inefficient (130ms on a fast machine)
let uf2 = UF2.newBlockFile()
UF2.writeHex(uf2, resp.outfiles[pxtc.BINARY_HEX].split(/\r?\n/))
let bytes = U.stringToUint8Array(UF2.serializeFile(uf2))
let parsed = UF2.parseFile(bytes)
let uf2 = UF2.newBlockFile();
UF2.writeHex(uf2, resp.outfiles[pxtc.BINARY_HEX].split(/\r?\n/));
let bytes = U.stringToUint8Array(UF2.serializeFile(uf2));
let parsed = UF2.parseFile(bytes);
let aligned = pageAlignBlocks(parsed, pageSize)
log(`initial: ${aligned.length} pages`)
aligned = onlyChanged(aligned, checksums)
log(`incremental: ${aligned.length} pages`)
let aligned = pageAlignBlocks(parsed, pageSize);
log(`initial: ${aligned.length} pages`);
aligned = onlyChanged(aligned, checksums);
log(`incremental: ${aligned.length} pages`);
return Promise.mapSeries(U.range(aligned.length),
i => {
let b = aligned[i]
if (aborted) return Promise.resolve();
let b = aligned[i];
if (b.targetAddr >= 0x10000000)
return Promise.resolve()
return Promise.resolve();
logV("about to write at 0x" + b.targetAddr.toString(16))
logV("about to write at 0x" + b.targetAddr.toString(16));
let writeBl = Promise.resolve()
let writeBl = Promise.resolve();
let thisAddr = (i & 1) ? dataAddr : dataAddr + pageSize
let nextAddr = (i & 1) ? dataAddr + pageSize : dataAddr
let thisAddr = (i & 1) ? dataAddr : dataAddr + pageSize;
let nextAddr = (i & 1) ? dataAddr + pageSize : dataAddr;
if (i == 0) {
let u32data = new Uint32Array(b.data.length / 4)
let u32data = new Uint32Array(b.data.length / 4);
for (let i = 0; i < b.data.length; i += 4)
u32data[i >> 2] = HF2.read32(b.data, i)
writeBl = wrap.cortexM.memory.writeBlock(thisAddr, u32data)
u32data[i >> 2] = HF2.read32(b.data, i);
writeBl = wrap.cortexM.memory.writeBlock(thisAddr, u32data);
}
return writeBl
.then(() => runFlash(b, thisAddr))
.then(() => {
let next = aligned[i + 1]
let next = aligned[i + 1];
if (!next)
return Promise.resolve()
logV("write next")
let buf = new Uint32Array(next.data.buffer)
return wrap.cortexM.memory.writeBlock(nextAddr, buf)
return Promise.resolve();
logV("write next");
let buf = new Uint32Array(next.data.buffer);
return wrap.cortexM.memory.writeBlock(nextAddr, buf);
})
.then(() => {
logV("wait")
return wrap.cortexM.waitForHalt(500)
logV("wait");
return wrap.cortexM.waitForHalt(500);
})
.then(() => {
logV("done block")
})
logV("done block");
});
})
.then(() => {
log("flash done")
log("flash done");
pxt.tickEvent("hid.flash.done");
return wrap.cortexM.reset(false)
return wrap.cortexM.reset(false);
})
.then(() => {
wrap.flashing = false;
})
});
})
.timeout(25000, timeoutMessage)
.catch((e) => {
aborted = true;
return Promise.reject(e);
});
}
function getFlashChecksumsAsync(wrap: DAPWrapper) {
log("getting existing flash checksums")
let pages = numPages
return wrap.cortexM.runCode(computeChecksums2, loadAddr, loadAddr + 1, 0xffffffff, stackAddr, true,
dataAddr, 0, pageSize, pages)
.then(() => wrap.cortexM.memory.readBlock(dataAddr, pages * 2, pageSize))
}
function onlyChanged(blocks: UF2.Block[], checksums: Uint8Array) {
return blocks.filter(b => {
let idx = b.targetAddr / pageSize
U.assert((idx | 0) == idx)
U.assert(b.data.length == pageSize)
if (idx * 8 + 8 > checksums.length)
return true // out of range?
let c0 = HF2.read32(checksums, idx * 8)
let c1 = HF2.read32(checksums, idx * 8 + 4)
let ch = murmur3_core(b.data)
if (c0 == ch[0] && c1 == ch[1])
return false
return true
})
}
export function deployCoreAsync(resp: pxtc.CompileResult, d: pxt.commands.DeployOptions = {}): Promise<void> {
let saveHexAsync = () => {
return pxt.commands.saveOnlyAsync(resp)
}
startTime = 0
let wrap: DAPWrapper
log("init")
pxt.tickEvent("hid.flash.start");
return Promise.resolve()
.then(() => {
if (previousDapWrapper) {
previousDapWrapper.flashing = true;
return Promise.delay(100);
}
return Promise.resolve();
})
.then(initAsync)
.then(w => {
wrap = w
log("reset");
return wrap.cortexM.init()
.then(() => wrap.cortexM.reset(true))
.catch(e => {
log("trying re-connect");
return wrap.reconnectAsync(false)
.then(() => wrap.cortexM.reset(true));
});
})
.then(() => wrap.cortexM.memory.readBlock(0x10001014, 1, pageSize))
.then(v => {
if (HF2.read32(v, 0) != 0x3C000) {
pxt.tickEvent("hid.flash.uicrfail");
return fullVendorCommandFlashAsync(resp, wrap);
}
return quickHidFlashAsync(resp, wrap);
})
.catch(e => {
// TODO: (microbit master)
if (e.type === "devicenotfound") { //&& d.reportDeviceNotFoundAsync) {
if (e.type === "devicenotfound" && d.reportDeviceNotFoundAsync) {
pxt.tickEvent("hid.flash.devicenotfound");
//return d.reportDeviceNotFoundAsync("/device/windows-app/troubleshoot", resp);
return undefined;
return d.reportDeviceNotFoundAsync("/device/windows-app/troubleshoot", resp);
} else if (e.message === timeoutMessage) {
pxt.tickEvent("hid.flash.timeout");
return previousDapWrapper.reconnectAsync(true)
.catch((e) => {
// Best effort disconnect; at this point we don't even know the state of the device
pxt.reportException(e);
})
.then(() => {
return resp.confirmAsync({
header: lf("Something went wrong..."),
body: lf("Flashing your {0} took too long. Please disconnect your {0} from your computer and reconnect it, then flash using drag and drop.", pxt.appTarget.appTheme.boardName || lf("device")),
disagreeLbl: lf("Ok"),
hideAgree: true
});
})
.then(() => {
return pxt.commands.saveOnlyAsync(resp);
});
} else {
return saveHexAsync()
pxt.tickEvent("hid.flash.unknownerror");
return resp.confirmAsync({
header: U.lf("We cannot flash your program..."),
body: U.lf("Please flash your device using drag and drop this time. Automatic flashing might work afterwards."),
disagreeLbl: lf("Ok"),
hideAgree: true
})
.then(() => {
return saveHexAsync();
});
}
})
});
}
/**
@ -715,28 +794,7 @@ namespace pxt.editor {
}])
if (canHID())
pxt.commands.deployCoreAsync = (r: pxtc.CompileResult, d: pxt.commands.DeployOptions): Promise<void> => {
return deployCoreAsync(r)
.timeout(18000)
.catch((e) => {
return previousDapWrapper.reconnectAsync(true)
.catch((e) => {
// Best effort disconnect; at this point we don't even know the state of the device
pxt.reportException(e);
})
.then(() => {
return r.confirmAsync({
header: lf("Something went wrong..."),
body: lf("Flashing your {0} took too long. Please disconnect your {0} from your computer and try reconnecting it.", pxt.appTarget.appTheme.boardName || lf("device")),
disagreeLbl: lf("Ok"),
hideAgree: true
});
})
.then(() => {
return pxt.commands.saveOnlyAsync(r);
});
});
}
pxt.commands.deployCoreAsync = deployCoreAsync;
res.blocklyPatch = patchBlocks;
res.showUploadInstructionsAsync = showUploadInstructionsAsync;

1479
package-lock.json generated

File diff suppressed because it is too large Load Diff