Skip to content

NRFutil style DFU support. A bit flakey, but seemingly functional #101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion components/Flash.vue
Original file line number Diff line number Diff line change
@@ -17,7 +17,8 @@
</div>
<div id="flash-modal" tabindex="-1" aria-hidden="true"
class="dark hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full">
<TargetsUf2 v-if="['nrf52840', 'rp2040'].includes(deviceStore.selectedArchitecture)" />
<TargetsUf2 v-if="deviceStore.selectedArchitecture === 'rp2040'" />
<TargetsNrf52 v-if="deviceStore.selectedArchitecture.startsWith('nrf52')" />
<TargetsEsp32 v-if="deviceStore.selectedArchitecture.startsWith('esp32')" />
</div>
<div id="erase-modal" tabindex="-1" aria-hidden="true"
2 changes: 1 addition & 1 deletion components/targets/EraseUf2.vue
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@
<div class="p-4 mb-4 my-2 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-gray-800 dark:text-blue-400" role="alert">
<span class="font-medium">
<InformationCircleIcon class="h-4 w-4 inline" />
For firmware versions &lt; {{ deviceStore.enterDfuVersion }}, trigger DFU mode manually by {{ deviceStore.dfuStepAction }}
For firmware versions &lt; {{ deviceStore.enterDfuVersion }}, trigger DFU mode manually by {{ deviceStore.dfuStepAction }}.
<br />
After erasing flash, this operation will not be available again until new Meshtastic firmware is flashed on the device.
</span>
124 changes: 124 additions & 0 deletions components/targets/Nrf52.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<template>
<div class="relative w-full max-w-4xl max-h-full">
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
<FlashHeader />
<div class="p-4 md:p-5">
<ReleaseNotes />
<ol v-if="firmwareStore.canShowFlash" class="relative border-s border-gray-200 dark:border-gray-600 ms-3.5 mb-4 md:mb-5">
<li class="mb-10 ms-8">
<span class="absolute flex items-center justify-center w-6 h-6 bg-blue-100 rounded-full -start-3 ring-8 ring-white dark:ring-gray-900 dark:bg-blue-900">
1
</span>
<h3 class="flex items-start mb-1 text-lg font-semibold text-gray-900 dark:text-white">
Ensure device is plugged in via USB
</h3>
</li>
<li class="mb-10 ms-8">
<span class="absolute flex items-center justify-center w-6 h-6 bg-blue-100 rounded-full -start-3 ring-8 ring-white dark:ring-gray-900 dark:bg-blue-900">
2
</span>
<h3 class="flex items-start mb-1 text-lg font-semibold text-gray-900 dark:text-white">
Enter DFU Mode
</h3>
<div class="p-4 mb-4 my-2 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-gray-800 dark:text-blue-400" role="alert">
<span class="font-medium">
<!-- <InformationCircleIcon class="h-4 w-4 inline" /> -->
<button type="button"
class="inline-flex items-center py-2 px-3 text-sm font-medium focus:outline-none bg-meshtastic rounded-lg hover:bg-white focus:z-10 focus:ring-4 focus:ring-gray-200 text-black"
@click="deviceStore.enterDfuMode">
<FolderArrowDownIcon class="h-4 w-4 text-black" />
Enter DFU Mode
</button> or you can optionally enter DFU mode manually by {{ deviceStore.dfuStepAction }}.
</span>
</div>
</li>
<li class="ms-8">
<span class="absolute flex items-center justify-center w-6 h-6 bg-blue-100 rounded-full -start-3 ring-8 ring-white dark:ring-gray-900 dark:bg-blue-900">
3
</span>
<h3 class="mb-1 text-lg font-semibold text-gray-900 dark:text-white">
Flash firmware via DFU utility or
<a :href="downloadUf2FileUrl" v-if="firmwareStore.selectedFirmware?.id" class="inline-flex items-center py-2 px-3 text-sm font-medium focus:outline-none bg-meshtastic rounded-lg hover:bg-white focus:z-10 focus:ring-4 focus:ring-gray-200 text-black">
<ArrowDownTrayIcon class="h-4 w-4 text-black" />
Download UF2
</a>
<button @click="downloadUf2FileFs" v-else
class="inline-flex items-center py-2 px-3 text-sm font-medium focus:outline-none bg-meshtastic rounded-lg hover:bg-white focus:z-10 focus:ring-4 focus:ring-gray-200 text-black">
<ArrowDownTrayIcon class="h-4 w-4 text-black" />
Download UF2
</button>
onto the USB mass storage device
</h3>
<p>
This process could take a while.
</p>
</li>
</ol>
<div v-if="firmwareStore.canShowFlash">
<button v-if="showFlashButton"
class="text-black inline-flex w-full justify-center bg-meshtastic hover:bg-gray-200 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center" @click="flash">
Update via DFU Utility
</button>
<button v-if="firmwareStore.$state.flashPercentDone > 0 && !firmwareStore.$state.isFlashing"
class="mx-2 my-2 text-black inline-flex w-full justify-center bg-meshtastic hover:bg-gray-200 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center" @click="flash">
Start Over
</button>
<button v-if="firmwareStore.$state.flashPercentDone > 0 && !firmwareStore.$state.isFlashing"
class="mx-2 text-black inline-flex w-full justify-center bg-meshtastic hover:bg-gray-200 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center" @click="serialMonitor">
Open Serial Monitor
</button>
<div v-if="firmwareStore.$state.flashPercentDone > 0" class="mb-1 text-center font-medium text-white">Flashing {{ firmwareStore.percentDone }} complete</div>
<div class="w-fullrounded-full h-2.5 mb-4 bg-gray-700" v-if="firmwareStore.$state.flashPercentDone > 0">
<div class="bg-meshtastic h-2.5 rounded-full" :style=" { 'width': firmwareStore.percentDone }"></div>
</div>
</div>
</div>
</div>
</div>
</template>

<script lang="ts" setup>
import '@/node_modules/xterm/css/xterm.css';

import {
ArrowDownTrayIcon,
FolderArrowDownIcon,
} from '@heroicons/vue/24/solid';

import { useDeviceStore } from '../../stores/deviceStore';
import { useFirmwareStore } from '../../stores/firmwareStore';
import FlashHeader from './FlashHeader.vue';
import ReleaseNotes from './ReleaseNotes.vue';

const deviceStore = useDeviceStore();
const firmwareStore = useFirmwareStore();
const serialMonitorStore = useSerialMonitorStore();
const showFlashButton = computed(() => {
return !firmwareStore.$state.isFlashing && firmwareStore.$state.flashPercentDone < 1;
})

const downloadUf2FileFs = () => {
const searchRegex = new RegExp(`firmware-${deviceStore.$state.selectedTarget.platformioTarget}-.+.uf2`);
firmwareStore.downloadUf2FileSystem(searchRegex);
}

const downloadUf2FileUrl = computed(() => {
if (!firmwareStore.selectedFirmware?.id) return '';
const firmwareVersion = firmwareStore.selectedFirmware.id.replace('v', '')
const firmwareFile = `firmware-${deviceStore.$state.selectedTarget.platformioTarget}-${firmwareVersion}.uf2`
firmwareStore.trackDownload(deviceStore.$state.selectedTarget, false);
return firmwareStore.getReleaseFileUrl(firmwareFile);
})

const flash = async () => {
const otaZipFile = `firmware-${deviceStore.$state.selectedTarget.platformioTarget}-${firmwareStore.firmwareVersion}-ota.zip`
const port = await firmwareStore.flashNrf52(otaZipFile, deviceStore.$state.selectedTarget);
// await serialMonitorStore.monitorSerial(port);
}

const serialMonitor = async () => {
document.getElementById('flash-modal')?.click();
await serialMonitorStore.monitorSerial();
}

</script>
6 changes: 3 additions & 3 deletions components/targets/Uf2.vue
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@
<div class="p-4 mb-4 my-2 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-gray-800 dark:text-blue-400" role="alert">
<span class="font-medium">
<InformationCircleIcon class="h-4 w-4 inline" />
For firmware versions &lt; {{ deviceStore.enterDfuVersion }}, trigger DFU mode manually by {{ deviceStore.dfuStepAction }}
For firmware versions &lt; {{ deviceStore.enterDfuVersion }}, trigger DFU mode manually by {{ deviceStore.dfuStepAction }}.
</span>
</div>
<button type="button"
@@ -61,8 +61,8 @@
</ol>
<div v-if="firmwareStore.canShowFlash">
<a :href="downloadUf2FileUrl" v-if="firmwareStore.selectedFirmware?.id"
class="text-black inline-flex w-full justify-center bg-meshtastic hover:bg-gray-200 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center">
Download UF2
class="text-black inline-flex w-full justify-center bg-meshtastic hover:bg-gray-200 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center">
Download UF2
</a>
<button @click="downloadUf2FileFs" v-else
class="text-black inline-flex w-full justify-center bg-meshtastic hover:bg-gray-200 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center">
1 change: 0 additions & 1 deletion nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -51,6 +51,5 @@ export default defineNuxtConfig({
}
}
},

compatibilityDate: '2024-09-03',
});
14 changes: 10 additions & 4 deletions stores/deviceStore.ts
Original file line number Diff line number Diff line change
@@ -38,9 +38,9 @@ export const useDeviceStore = defineStore('device', {
},
dfuStepAction(): string {
if (this.isSelectedNrf) {
return 'double-clicking RST button.';
return 'double-clicking RST button';
} else {
return 'pressing and holding BOOTSEL button while plugging in USB cable.';
return 'pressing and holding BOOTSEL button while plugging in USB cable';
}
},
},
@@ -71,7 +71,8 @@ export const useDeviceStore = defineStore('device', {
});
return connection;
},
async enterDfuMode() {
async enterDfuModeLegacy() {
// Legacy API mode
const connection = await this.openDeviceConnection();
connection.events.onFromRadio.subscribe((packet: any) => {
if (packet?.payloadVariant?.case === "configCompleteId") {
@@ -80,6 +81,11 @@ export const useDeviceStore = defineStore('device', {
}
});
},
async enterDfuMode() {
const port: SerialPort = await navigator.serial.requestPort();
const nrfFlasher = new Nrf52DfuFlasher(port, () => {});
await nrfFlasher.enterDfuMode();
},
async baud1200() {
const port: SerialPort = await navigator.serial.requestPort();
await port.open({ baudRate: 1200 });
@@ -93,7 +99,7 @@ export const useDeviceStore = defineStore('device', {
}
return connection.disconnect();
});
await new Promise(_ => setTimeout(_, 4000));
await waitForMs(4000);
await connection.disconnect();
}
},
53 changes: 51 additions & 2 deletions stores/firmwareStore.ts
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import { mande } from 'mande';
import { defineStore } from 'pinia';
import type { Terminal } from 'xterm';

import { Nrf52DfuFlasher } from '@/utils/nrfUtil';
import { track } from '@vercel/analytics';
import { useSessionStorage } from '@vueuse/core';
import {
@@ -25,6 +26,7 @@ import {
} from '../types/api';
import { createUrl } from './store';


const previews = new Array<FirmwareResource>()// new Array<FirmwareResource>(currentPrerelease)

const firmwareApi = mande(createUrl('api/github/firmware/list'))
@@ -91,6 +93,32 @@ export const useFirmwareStore = defineStore('firmware', {
const baseUrl = getCorsFriendyReleaseUrl(this.selectedFirmware.zip_url);
return `${baseUrl}/${fileName}`;
},
async flashNrf52(fileName: string, selectedTarget: DeviceHardware): Promise<SerialPort> {
this.trackDownload(selectedTarget, false);
if (!this.port || !this.isConnected) {
this.port = await navigator.serial.requestPort({});
this.port.ondisconnect = () => {
this.isConnected = false;
};
await this.port.open({
baudRate: 115200,
});
}
this.isConnected = true;
this.isFlashing = true;
this.flashPercentDone = 1;
const nrfFlasher = new Nrf52DfuFlasher(this.port, (percent) => {
this.flashPercentDone = percent;
if (percent === 100) {
this.isFlashing = false;
console.log('Done flashing!');
}
});
const firmware = await this.fetchBlob(fileName);
await nrfFlasher.flash(firmware);
this.isFlashing = false;
return this.port;
},
async downloadUf2FileSystem(searchRegex: RegExp) {
if (!this.selectedFile) return;
const reader = new BlobReader(this.selectedFile);
@@ -155,7 +183,7 @@ export const useFirmwareStore = defineStore('firmware', {
},
async resetEsp32(transport: Transport) {
await transport.setRTS(true);
await new Promise((resolve) => setTimeout(resolve, 100));
await waitForMs(100);
await transport.setRTS(false);
},
trackDownload(selectedTarget: DeviceHardware, isCleanInstall: boolean) {
@@ -201,6 +229,27 @@ export const useFirmwareStore = defineStore('firmware', {
};
await this.startWrite(terminal, espLoader, transport, flashOptions);
},
async fetchBlob(fileName: string): Promise<Blob> {
if (this.selectedFirmware?.zip_url) {
const baseUrl = getCorsFriendyReleaseUrl(this.selectedFirmware!.zip_url!);
const response = await fetch(`${baseUrl}/${fileName}`);
return await response.blob();
} else if (this.selectedFile && this.isZipFile) {
const reader = new BlobReader(this.selectedFile!);
const zipReader = new ZipReader(reader);
const entries = await zipReader.getEntries()
console.log('Zip entries:', entries);
console.log('Looking for file matching pattern:', fileName);
const file = entries.find(entry => new RegExp(fileName).test(entry.filename))
if (file) {
console.log('Found file:', file.filename);
return await file.getData!(new BlobWriter());
}
} else if (this.selectedFile && !this.isZipFile) {
return this.selectedFile;
}
throw new Error('Cannot fetch blob without a file or firmware selected');
},
async fetchBinaryContent(fileName: string): Promise<string> {
if (this.selectedFirmware?.zip_url) {
const baseUrl = getCorsFriendyReleaseUrl(this.selectedFirmware.zip_url);
@@ -273,7 +322,7 @@ export const useFirmwareStore = defineStore('firmware', {
if (value) {
terminal.write(value);
}
await new Promise(resolve => setTimeout(resolve, 5));
await waitForMs(5);
}
},
},
12 changes: 8 additions & 4 deletions stores/serialMonitorStore.ts
Original file line number Diff line number Diff line change
@@ -65,12 +65,16 @@ export const useSerialMonitorStore = defineStore("serialMonitor", {
this.terminalBuffer[this.terminalBuffer.length - 1] = newLine;
}
}
await new Promise((resolve) => setTimeout(resolve, 5));
await waitForMs(1);
}
},
async monitorSerial() {
this.port = await navigator.serial.requestPort({});
await this.port.open({ baudRate: this.baudRate });
async monitorSerial(port: SerialPort | undefined = undefined) {
if (port) {
this.port = port;
} else {
this.port = await navigator.serial.requestPort({});
await this.port.open({ baudRate: this.baudRate });
}
this.isOpen = true;
this.isConnected = true;
this.port.ondisconnect = () => {
229 changes: 229 additions & 0 deletions utils/nrfUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import {
BlobReader,
BlobWriter,
type Entry,
TextWriter,
ZipReader,
} from '@zip.js/zip.js';

import { waitForMs } from './promiseUtils';
import {
crc16,
createSlipHeader,
int16ToBytes,
int32ToBytes,
slipEncodeEscChars,
} from './slipEncoding';

const Nrf52Constants = {
DFU_TOUCH_BAUD: 1200,
SERIAL_PORT_OPEN_WAIT_TIME: 0.1,
TOUCH_RESET_WAIT_TIME: 1.5,

HEX_TYPE_SOFTDEVICE: 1,
HEX_TYPE_BOOTLOADER: 2,
HEX_TYPE_SOFTDEVICE_BOOTLOADER: 3,
HEX_TYPE_APPLICATION: 4,

DFU_initPacket: 1,
DFU_START_PACKET: 3,
DFU_DATA_PACKET: 4,
DFU_STOP_DATA_PACKET: 5,

DATA_INTEGRITY_CHECK_PRESENT: 1,
RELIABLE_PACKET: 1,
HCI_PACKET_TYPE: 14,

FLASH_PAGE_SIZE: 4096,
FLASH_PAGE_ERASE_TIME: 0.0897,
FLASH_WORD_WRITE_TIME: 0.000100,
FLASH_PAGE_WRITE_TIME: (4096 / 4) * 0.000100,

DFU_PACKET_MAX_SIZE: 512,
}

// This is a port of the following Python from Adafruit NRFUtil: https://github.com/adafruit/Adafruit_nRF52_nrfutil/blob/master/nordicsemi/dfu/dfu_transport_serial.py
// Adapted from the following JS into TypeScript: https://github.com/liamcottle/rnode-flasher/blob/master/js/nrf52_dfu_flasher.js

export class Nrf52DfuFlasher {
constructor(serialPort: SerialPort, progressCallback: (progressPercent: number) => void) {
this.serialPort = serialPort;
this.progressCallback = progressCallback;
}
progressCallback: (progressPercent: number) => void;
serialPort: SerialPort
sequenceNumber: number = 0;
softDeviceSize: number = 0;
applicationSize: number = 0;
bootloaderSize: number = 0;
totalSize: number = 0;

async sendPacket(data: Uint8Array) {
const writer = this.serialPort.writable!.getWriter();
try {
await writer.write(data);
} finally {
writer.releaseLock();
}
}

async enterDfuMode() {
console.log("Entering DFU mode w/ 1200bps touch reset");
await this.serialPort.open({
baudRate: Nrf52Constants.DFU_TOUCH_BAUD,
});
await waitForMs(Nrf52Constants.SERIAL_PORT_OPEN_WAIT_TIME * 1000);
await this.serialPort.close();
await waitForMs(Nrf52Constants.TOUCH_RESET_WAIT_TIME * 1000);
}

async flash(firmwareZipBlob: Blob) {
const blobReader = new BlobReader(firmwareZipBlob);
const zipReader = new ZipReader(blobReader);
const zipEntries = await zipReader.getEntries();

// find manifest file
const manifestFile = zipEntries.find(zipEntry => zipEntry.filename === "manifest.json");
if (!manifestFile){
throw "manifest.json not found in ota zip!";
}

const json = JSON.parse(await manifestFile.getData!(new TextWriter()));
const manifest = json.manifest;

// TODO: Bootloader as well
if (manifest.bootloader) {
console.log("Flashing bootloader");
//await this.dfuSendBinary(Nrf52Constants.HEX_TYPE_BOOTLOADER, zipEntries, manifest.bootloader);
}

if (manifest.application){
console.log("Flashing application");
await this.dfuSendBinary(Nrf52Constants.HEX_TYPE_APPLICATION, zipEntries, manifest.application);
}
}

private async dfuSendBinary(programMode: number, zipEntries: Entry[], firmwareManifest: any) {
await waitForMs(Nrf52Constants.SERIAL_PORT_OPEN_WAIT_TIME * 1000);

const binFile = zipEntries.find(entry => entry.filename === firmwareManifest.bin_file);
const firmwareBlob = await binFile!.getData!(new BlobWriter());
const firmware = new Uint8Array(await firmwareBlob.arrayBuffer());

const datFile = zipEntries.find(entry => entry.filename === firmwareManifest.dat_file);
const datBlob = await datFile!.getData!(new BlobWriter());
const initPacket = new Uint8Array(await datBlob.arrayBuffer());

// determine application size
if (programMode === Nrf52Constants.HEX_TYPE_APPLICATION){
this.applicationSize = firmware.length;
}

console.log("Sending DFU start packet");
await this.sendDfuStartPacket(programMode);
console.log("Sending init packet");
await this.sendInitPacket(initPacket);
console.log("Sending firmware");
await this.sendFirmware(firmware);
console.log("Sending DFU stop packet");
await this.sendDfuStopPacket();
}

private createHciPacketFromFrame(frame: Uint8Array) : Uint8Array {
// Iterate sequence number, but wrap-around at 8
this.sequenceNumber = (this.sequenceNumber + 1) % 8;

const slipHeaderBytes = createSlipHeader(
this.sequenceNumber,
Nrf52Constants.DATA_INTEGRITY_CHECK_PRESENT,
Nrf52Constants.RELIABLE_PACKET,
Nrf52Constants.HCI_PACKET_TYPE,
frame.length,
);

const crc = crc16(new Uint8Array([
...slipHeaderBytes,
...frame,
]), 0xffff);

const packet = [
...slipHeaderBytes,
...frame,
crc & 0xFF,
(crc & 0xFF00) >> 8,
];

return new Uint8Array([
0xc0,
...slipEncodeEscChars(packet),
0xc0,
]);
}

private getEraseWaitTime() {
return Math.max(0.5, ((this.totalSize / Nrf52Constants.FLASH_PAGE_SIZE) + 1) * Nrf52Constants.FLASH_PAGE_ERASE_TIME);
}

private createImageSizePacket(): number[] {
return [
...int32ToBytes(this.softDeviceSize),
...int32ToBytes(this.bootloaderSize),
...int32ToBytes(this.applicationSize),
];
}

private async sendInitPacket(initPacket: Uint8Array) {
const frame = new Uint8Array([
...int32ToBytes(Nrf52Constants.DFU_initPacket),
...initPacket,
...int16ToBytes(0x0000), // padding required
]);

await this.sendPacket(this.createHciPacketFromFrame(frame));
}

private async sendFirmware(firmware: Uint8Array) {
let packetsSent = 0;

const packets = Array.from({ length: Math.ceil(firmware.length / Nrf52Constants.DFU_PACKET_MAX_SIZE) }, (_, i) => {
const chunk = new Uint8Array([
...int32ToBytes(Nrf52Constants.DFU_DATA_PACKET),
...firmware.slice(i * Nrf52Constants.DFU_PACKET_MAX_SIZE, (i + 1) * Nrf52Constants.DFU_PACKET_MAX_SIZE),
]);
return this.createHciPacketFromFrame(chunk);
});

if (this.progressCallback){
this.progressCallback(0);
}
for (let index = 0; index < packets.length; index++) {
await this.sendPacket(packets[index]);
await waitForMs(5);
//await waitForMs(Nrf52Constants.FLASH_PAGE_WRITE_TIME * 1000);
packetsSent++;
if (this.progressCallback) {
// console.log("Progress: ", packetsSent, packets.length);
const progress = Math.floor(((index + 1) / packets.length) * 100);
this.progressCallback(progress);
}
}
}

private async sendDfuStartPacket(mode: number){
const frame = new Uint8Array([
...int32ToBytes(Nrf52Constants.DFU_START_PACKET),
...int32ToBytes(mode),
...this.createImageSizePacket(),
]);
await this.sendPacket(this.createHciPacketFromFrame(frame));

this.softDeviceSize = this.softDeviceSize;
this.totalSize = this.softDeviceSize + this.bootloaderSize + this.applicationSize;

await waitForMs(this.getEraseWaitTime() * 1000);
}

private async sendDfuStopPacket() {
await this.sendPacket(this.createHciPacketFromFrame(int32ToBytes(Nrf52Constants.DFU_STOP_DATA_PACKET)));
}
}
5 changes: 5 additions & 0 deletions utils/promiseUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export async function waitForMs(milliseconds: number) {
await new Promise((resolve) => {
setTimeout(resolve, milliseconds);
});
}
48 changes: 48 additions & 0 deletions utils/slipEncoding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export function createSlipHeader(seq: number, dip: number, rp: number, pktType: number, pktLen: number) {
let ints = [0, 0, 0, 0];
ints[0] = seq | (((seq + 1) % 8) << 3) | (dip << 6) | (rp << 7);
ints[1] = pktType | ((pktLen & 0x000F) << 4);
ints[2] = (pktLen & 0x0FF0) >> 4;
ints[3] = (~(ints[0] + ints[1] + ints[2]) + 1) & 0xFF;
return new Uint8Array(ints);
}

export function crc16(binaryData: Uint8Array, crc = 0xffff) : number {
binaryData.forEach(b => {
crc = (crc >> 8 & 0x00FF) | (crc << 8 & 0xFF00);
crc ^= b;
crc ^= (crc & 0x00FF) >> 4;
crc ^= (crc << 8) << 4;
crc ^= ((crc & 0x00FF) << 4) << 1;
});

return crc & 0xFFFF;
}

export function slipEncodeEscChars(data: number[]) : Uint8Array {
return Uint8Array.from([...data].flatMap((b: number) => {
if (b === 0xC0) {
return [0xDB, 0xDC];
} else if (b === 0xDB) {
return [0xDB, 0xDD];
} else {
return [b];
}
}));
}

export function int16ToBytes(num: number): Uint8Array {
return new Uint8Array([
(num & 0x00ff),
(num & 0xff00) >> 8,
]);
}

export function int32ToBytes(num: number): Uint8Array {
return new Uint8Array([
(num & 0x000000ff),
(num & 0x0000ff00) >> 8,
(num & 0x00ff0000) >> 16,
(num & 0xff000000) >> 24,
]);
}