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:
parent
fbb23feda7
commit
a1a3ded4f3
@ -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
1479
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user