Skip to content

add uf2 convert and download #158

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
10 changes: 8 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
FROM node:22-alpine AS base

ENV ARDUINO_CLI_VERSION=1.1.0
ENV ARDUINO_CLI_VERSION=1.2.0
ENV SENSEBOXCORE_VERSION=2.0.0
ENV ARDUINO_SAMD_VERSION=1.8.13
ENV ARDUINO_SAMD_VERSION=1.8.12
ENV ARDUINO_AVR_VERSION=1.8.5
ENV ESP32_VERSION=2.0.17
ENV SENSEBOXCORE_URL=https://raw.githubusercontent.com/mariopesch/senseBoxMCU-core/master/package_sensebox_index.json
Expand Down Expand Up @@ -45,6 +45,12 @@ RUN cp /tmp/OTAFiles/boards.txt /root/.arduino15/packages/esp32/hardware/esp32/$
cp /tmp/OTAFiles/APOTA.bin /root/.arduino15/packages/esp32/hardware/esp32/${ESP32_VERSION}/variants/sensebox_mcu_esp32s2/ && \
cp /tmp/OTAFiles/variant.cpp /root/.arduino15/packages/esp32/hardware/esp32/${ESP32_VERSION}/variants/sensebox_mcu_esp32s2/

COPY ./uf2/ /tmp/uf2/

# Add uf2conv.py
RUN cp /tmp/uf2/uf2conv.py /usr/local/bin/uf2conv.py && \
cp /tmp/uf2/uf2families.json /usr/local/bin/uf2families.json && \
cp /tmp/uf2/uf2conv.c /usr/local/bin/uf2conv.c

# install Libraries with arduino-cli
RUN arduino-cli lib install "Ethernet"
Expand Down
41 changes: 36 additions & 5 deletions src/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const boardBinaryFileextensions = {
"sensebox-esp32s2": "bin",
};

const baseArgs = ["--build-cache-path", `/app/src/build-cache`];
const baseArgs = ["--build-path", `/app/src/build-cache`];

export const payloadValidator = function payloadValidator(req, res, next) {
// reject all non application/json requests
Expand All @@ -37,7 +37,7 @@ export const payloadValidator = function payloadValidator(req, res, next) {
}

// check if parameters sketch and board are specified and valid
let { sketch, board } = req.body;
let { sketch, board, uf2 } = req.body;

if (!sketch || !board) {
return next(
Expand All @@ -50,6 +50,7 @@ export const payloadValidator = function payloadValidator(req, res, next) {

sketch = sketch.toString().trim();
board = board.toString().trim();
uf2 = uf2 ? uf2.toString().trim() : false;

if (!sketch || !board) {
return next(
Expand All @@ -71,11 +72,16 @@ export const payloadValidator = function payloadValidator(req, res, next) {
);
}

req._builderParams = { sketch, board };
req._builderParams = { sketch, board, uf2 };
next();
};

const execBuilder = async function execBuilder({ board, sketch, buildDir }) {
const execBuilder = async function execBuilder({
board,
sketch,
buildDir,
uf2,
}) {
// const tmpSketchPath = await tempWrite(sketch);
const sketchDir = `${temporaryDirectory()}/sketch`;
mkdirSync(sketchDir);
Expand All @@ -93,6 +99,31 @@ const execBuilder = async function execBuilder({ board, sketch, buildDir }) {
sketchDir,
]);

const ext = boardBinaryFileextensions[board];
const binaryFile = `${buildDir}/sketch.ino.${ext}`;

// Optional: Convert to UF2 if requested
if (uf2 && ext === "bin") {
const uf2File = `${buildDir}/sketch.uf2`;
try {
await spawn("python3", [
"/usr/local/bin/uf2conv.py",
binaryFile,
"-c",
"-b",
"0x00",
"-f",
"ESP32S2",
"--output",
uf2File,
]);
console.log(`UF2 file created: ${uf2File}`);
} catch (err) {
console.warn("UF2 conversion failed:", err.message);
// You may choose to throw here depending on how critical UF2 output is
}
}

try {
const dirname = _dirname(tmpSketchPath);
await rimraf(`${dirname}`);
Expand Down Expand Up @@ -130,7 +161,7 @@ export const compileHandler = async function compileHandler(req, res, next) {
);
} catch (err) {
if (process.env.NODE_ENV === "test") {
console.error(err.message)
console.error(err.message);
}
return next(new HTTPError({ error: err.message }));
}
Expand Down
43 changes: 26 additions & 17 deletions src/download.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
import { createReadStream } from "fs";
import { createReadStream, existsSync } from "fs";
import { join } from "path";
import { rimraf } from "rimraf";
import { boardBinaryFileextensions } from "./builder.js";
import { HTTPError } from "./utils.js";

const readFile = async function readFile({ id, board }) {
return Promise.resolve(
createReadStream(
`/tmp/${id}/sketch.ino.${boardBinaryFileextensions[board]}`
)
const readFile = async function readFile({ id, board, format }) {
const ext = format === "uf2" ? "uf2" : boardBinaryFileextensions[board];
const filePath = join(
"/tmp",
id,
`sketch.${format !== "uf2" ? "ino." : ""}${ext}`
);
console.log(`Reading file: ${filePath}`);

if (!existsSync(filePath)) {
throw new HTTPError({
code: 404,
error: `Compiled file not found: sketch.${ext}`,
});
}

return Promise.resolve(createReadStream(filePath));
};

export const downloadHandler = async function downloadHandler(req, res, next) {
Expand All @@ -21,7 +33,7 @@ export const downloadHandler = async function downloadHandler(req, res, next) {
);
}

const { id, board } = req._url.query;
const { id, board, format } = req._url.query;

if (!id || !board) {
return next(
Expand All @@ -32,30 +44,27 @@ export const downloadHandler = async function downloadHandler(req, res, next) {
);
}

// execute builder with parameters from user
try {
const stream = await readFile(req._url.query);
const ext = format === "uf2" ? "uf2" : boardBinaryFileextensions[board];
const stream = await readFile({ id, board, format });
const filename = req._url.query.filename || "sketch";
console.log(`Downloading ${filename}.${ext} for board ${board}`);
stream.on("error", function (err) {
return next(err);
});

stream.on("end", async () => {
try {
await rimraf(`/tmp/${req._url.query.id}`);
await rimraf(`/tmp/${id}`);
} catch (error) {
console.log(
`Error deleting compile sketch folder with ${req._url.query.id}: `,
error
);
console.log(`Error deleting compile sketch folder with ${id}: `, error);
}
});

res.setHeader("Content-Type", "application/octet-stream");
res.setHeader(
"Content-Disposition",
`attachment; filename=${filename}.${
boardBinaryFileextensions[req._url.query.board]
}`
`attachment; filename=${filename}.${ext}`
);
stream.pipe(res);
} catch (err) {
Expand Down
45 changes: 45 additions & 0 deletions uf2/uf2conv.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include <stdio.h>
#include <string.h>
#include "uf2format.h"

int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "USAGE: %s file.bin [file.uf2]\n", argv[0]);
return 1;
}
FILE *f = fopen(argv[1], "rb");
if (!f) {
fprintf(stderr, "No such file: %s\n", argv[1]);
return 1;
}

fseek(f, 0L, SEEK_END);
uint32_t sz = ftell(f);
fseek(f, 0L, SEEK_SET);

const char *outname = argc > 2 ? argv[2] : "flash.uf2";

FILE *fout = fopen(outname, "wb");

UF2_Block bl;
memset(&bl, 0, sizeof(bl));

bl.magicStart0 = UF2_MAGIC_START0;
bl.magicStart1 = UF2_MAGIC_START1;
bl.magicEnd = UF2_MAGIC_END;
bl.targetAddr = APP_START_ADDRESS;
bl.numBlocks = (sz + 255) / 256;
bl.payloadSize = 256;
int numbl = 0;
while (fread(bl.data, 1, bl.payloadSize, f)) {
bl.blockNo = numbl++;
fwrite(&bl, 1, sizeof(bl), fout);
bl.targetAddr += bl.payloadSize;
// clear for next iteration, in case we get a short read
memset(bl.data, 0, sizeof(bl.data));
}
fclose(fout);
fclose(f);
printf("Wrote %d blocks to %s\n", numbl, outname);
return 0;
}
68 changes: 68 additions & 0 deletions uf2/uf2conv.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# uf2conv -- Packing and unpacking UF2 files

## SYNOPSIS

**uf2conv.py** [-h] [-l]

**uf2conv.py** [-b BASE] [-f FAMILY] [-o FILE] [-d DEVICE_PATH] [-l] [-c] [-D]
[-w] [-C]
[HEX or BIN FILE]

**uf2conv.py** [-c] [-D] [-w] [-i] [UF2 FILE]

## DESCRIPTION

## EXAMPLES

### Pack a .bin/.hex to .uf2

```uf2conv.py cpx/firmware.bin --convert --output cpx/firmware.uf2```

```uf2conv.py metro_m4/firmware.bin --base 0x4000 --convert --output metro_m4/firmware.uf2```

```uf2conv.py nrf52840_xxaa.hex --family 0xADA52840 --convert --output nrf52840_xxaa.uf2```

### Unpack a .uf2 to .bin

```uf2conv.py current.uf2 --convert --output current.bin```

## OPTIONS
`-b`
`--base`
: set base address of application for BIN format (default: 0x2000)

`-f`
`--family`
: specify familyID - number or name (default: 0x0)

`-o`
`--output`
: write output to named file (defaults to "flash.uf2" or "flash.bin" where sensible)

`-d`
`--device`
: select a device path to flash

`-l`
`--list`
: list connected devices

`-c`
`--convert`
: do not flash, just convert

`-D`
`--deploy`
: just flash, do not convert

`-w`
`--wait`
: wait for device to flash

`-C`
`--carray`
: convert binary file to a C array, not UF2

`-i`
`--info`
: display header information from UF2, do not convert
Loading
Loading