Skip to content

Commit

Permalink
Merge pull request #90 from particle-iot/feature/add_progress_cb
Browse files Browse the repository at this point in the history
Add support for a progress callback
  • Loading branch information
monkbroc committed Aug 21, 2023
2 parents b1fc2c0 + e5c3c50 commit 069a568
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 15 deletions.
16 changes: 15 additions & 1 deletion src/device.js
Original file line number Diff line number Diff line change
Expand Up @@ -364,21 +364,35 @@ class Device extends DeviceBase {
* @param {Buffer} data Firmware data.
* @param {Object} [options] Options.
* @param {Number} [options.timeout] Timeout (milliseconds).
* @param {Function} [options.progress] User's callback function to log progress of the flashing process.
* @return {Promise}
*/
async updateFirmware(data, { timeout = DEFAULT_FIRMWARE_UPDATE_TIMEOUT } = {}) {
async updateFirmware(data, { timeout = DEFAULT_FIRMWARE_UPDATE_TIMEOUT, progress } = {}) {
if (!data.length) {
throw new RangeError('Invalid firmware size');
}
return this.timeout(timeout, async (s) => {
if (progress) {
progress({ event: 'start-erase', bytes: data.length });
}
const { chunkSize } = await s.sendRequest(Request.START_FIRMWARE_UPDATE, { size: data.length });
if (progress) {
progress({ event: 'erased', bytes: data.length });
progress({ event: 'start-download', bytes: data.length });
}
let offs = 0;
while (offs < data.length) {
const n = Math.min(chunkSize, data.length - offs);
await s.sendRequest(Request.FIRMWARE_UPDATE_DATA, { data: data.slice(offs, offs + n) });
if (progress) {
progress({ event: 'downloaded', bytes: n });
}
offs += n;
}
await s.sendRequest(Request.FINISH_FIRMWARE_UPDATE, { validateOnly: false });
if (progress) {
progress({ event: 'complete-download', bytes: data.length });
}
});
}

Expand Down
15 changes: 9 additions & 6 deletions src/dfu-device.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ const DfuDevice = (base) => class extends base {
/**
* Flashes the firmware over DFU interface.
*
* @param {Number} altSetting The interface alternate setting.
* @param {Buffer} buffer The binary firmware data to be flashed.
* @param {Number} addr The starting address where the firmware will be written.
* @param {Object} options Optional options for the flashing process.
* @param {Buffer} data The binary firmware data to be flashed.
* @param {Object} options Options.
* @param {Number} options.altSetting The interface alternate setting.
* @param {Number} options.startAddr The starting address where the firmware will be written.
* @param {boolean} [options.noErase] - Skip erasing the device memory.
* @param {boolean} [options.leave] - Leave DFU mode after download.
* @param {Function} [options.progress] User's callback function to log progress of the flashing process.
* @returns {Promise<void>} A Promise that resolves when the firmware is successfully flashed.
*/
async writeOverDfu(altSetting, buffer, addr, options) {
async writeOverDfu(data, { altSetting, startAddr, noErase, leave, progress }) {
await this._dfu.setAltSetting(altSetting);
await this._dfu.doDownload(addr, buffer, options);
await this._dfu.doDownload({ startAddr, data, noErase, leave, progress });
}
};

Expand Down
41 changes: 33 additions & 8 deletions src/dfu.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,12 +247,15 @@ class Dfu {
/**
* Perform DFU download of binary data to the device.
*
* @param {number} startAddr - The starting address to write the data.
* @param {Buffer} data - The binary data to write.
* @param {object} options - Options for the download process.
* @param {Object} options Options.
* @param {number} options.startAddr - The starting address to write the data.
* @param {Buffer} options.data - The binary data to write.
* @param {boolean} [options.noErase] - Skip erasing the device memory.
* @param {boolean} [options.leave] - Leave DFU mode after download.
* @param {function} [options.progress] - Callback function used to log progress.
* @return {Promise}
*/
async doDownload(startAddr, data, options = { noErase: false, leave: false }) {
async doDownload({ startAddr, data, noErase, leave, progress }) {
if (!this._memoryInfo || !this._memoryInfo.segments) {
throw new Error('No memory map available');
}
Expand All @@ -263,13 +266,16 @@ class Dfu {
}
const expectedSize = data.byteLength;

if (!options.noErase) {
if (!noErase) {
this._log.info('Erasing DFU device memory');
await this._erase(startAddress, expectedSize);
await this._erase(startAddress, expectedSize, progress);
}

this._log.info('Copying binary data to DFU device startAddress=' + startAddress + ' total_expected_size=' + expectedSize);

if (progress) {
progress({ event: 'start-download', bytes: expectedSize });
}
let bytesSent = 0;
let address = startAddress;
while (bytesSent < expectedSize) {
Expand All @@ -289,15 +295,24 @@ class Dfu {
}

if (dfuStatus.status !== DfuDeviceStatus.OK) {
if (progress) {
progress({ event: 'failed-download' });
}
throw new Error(`DFU DOWNLOAD failed state=${dfuStatus.state}, status=${dfuStatus.status}`);
}

this._log.trace('Wrote ' + chunkSize + ' bytes');
bytesSent += chunkSize;
if (progress) {
progress({ event: 'downloaded', bytes: chunkSize });
}
}
this._log.info(`Wrote ${bytesSent} bytes total`);
if (progress) {
progress({ event: 'complete-download', bytes: bytesSent });
}

if (options.leave) {
if (leave) {
this._log.info('Manifesting new firmware');
try {
await this.leave();
Expand Down Expand Up @@ -581,21 +596,28 @@ class Dfu {
* @param {number} length The length of the memory range to be erased in bytes.
* @throws {Error} If the start address or the length is outside the memory map bounds, or if erasing fails.
*/
async _erase(startAddr, length) {
async _erase(startAddr, length, progress) {
let segment = this._getSegment(startAddr);
let addr = this._getSectorStart(startAddr, segment);
const endAddr = this._getSectorEnd(startAddr + length - 1);

let bytesErased = 0;
const bytesToErase = endAddr - addr;

if (progress) {
progress({ event: 'start-erase', bytes: bytesToErase });
}
while (addr < endAddr) {
if (segment.end <= addr) {
segment = this._getSegment(addr);
}
if (!segment.erasable) {
// Skip over the non-erasable section
bytesErased = Math.min(bytesErased + segment.end - addr, bytesToErase);
if (progress) {
// include a progress event for the skipped section to ensure total matches
progress({ event: 'erased', bytes: segment.end - addr });
}
addr = segment.end;
continue;
}
Expand All @@ -605,6 +627,9 @@ class Dfu {
await this._dfuseCommand(DfuseCommand.DFUSE_COMMAND_ERASE, sectorAddr);
addr = sectorAddr + segment.sectorSize;
bytesErased += segment.sectorSize;
if (progress) {
progress({ event: 'erased', bytes: segment.sectorSize });
}
}
}

Expand Down

0 comments on commit 069a568

Please sign in to comment.