Skip to content

Commit

Permalink
Merge pull request #2446 from cesanta/o2
Browse files Browse the repository at this point in the history
Single bank OTA
  • Loading branch information
cpq authored Nov 2, 2023
2 parents b357485 + 376e5cc commit 22f7609
Show file tree
Hide file tree
Showing 26 changed files with 2,065 additions and 1,808 deletions.
10 changes: 10 additions & 0 deletions examples/device-dashboard/net.c
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,14 @@ static void handle_device_reset(struct mg_connection *c) {
mg_timer_add(c->mgr, 500, 0, (void (*)(void *)) mg_device_reset, NULL);
}

static void handle_device_eraselast(struct mg_connection *c) {
size_t ss = mg_flash_sector_size(), size = mg_flash_size();
char *base = (char *) mg_flash_start(), *last = base + size - ss;
if (mg_flash_bank() == 2) last -= size / 2;
mg_flash_erase(last);
mg_http_reply(c, 200, s_json_header, "true\n");
}

// HTTP request handler function
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_ACCEPT) {
Expand Down Expand Up @@ -294,6 +302,8 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
handle_firmware_status(c);
} else if (mg_http_match_uri(hm, "/api/device/reset")) {
handle_device_reset(c);
} else if (mg_http_match_uri(hm, "/api/device/eraselast")) {
handle_device_eraselast(c);
} else {
struct mg_http_serve_opts opts;
memset(&opts, 0, sizeof(opts));
Expand Down
3,033 changes: 1,525 additions & 1,508 deletions examples/device-dashboard/packed_fs.c

Large diffs are not rendered by default.

34 changes: 21 additions & 13 deletions examples/device-dashboard/web_root/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const Icons = {
chip: props => html`<svg class=${props.class} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 002.25-2.25V6.75a2.25 2.25 0 00-2.25-2.25H6.75A2.25 2.25 0 004.5 6.75v10.5a2.25 2.25 0 002.25 2.25zm.75-12h9v9h-9v-9z" /> </svg>`,
camera: props => html`<svg class=${props.class} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M6.827 6.175A2.31 2.31 0 015.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 00-1.134-.175 2.31 2.31 0 01-1.64-1.055l-.822-1.316a2.192 2.192 0 00-1.736-1.039 48.774 48.774 0 00-5.232 0 2.192 2.192 0 00-1.736 1.039l-.821 1.316z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M16.5 12.75a4.5 4.5 0 11-9 0 4.5 4.5 0 019 0zM18.75 10.5h.008v.008h-.008V10.5z" /> </svg>`,
arrows: props => html`<svg class=${props.class} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" /> </svg>`,
doc: props => html`<svg class=${props.class} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>`,
};

export const tipColors = {
Expand Down Expand Up @@ -157,7 +158,7 @@ export function Colored({icon, text, colors}) {
export function Stat({title, text, tipText, tipIcon, tipColors, colors}) {
return html`
<div class="flex flex-col bg-white border shadow-sm rounded-xl dark:bg-slate-900 dark:border-gray-800">
<div class="overflow-auto rounded-lg bg-white px-4 py-2 shadow">
<div class="overflow-auto rounded-lg bg-white px-4 py-2 ">
<div class="flex items-center gap-x-2">
<p class="text-sm truncate text-gray-500 font-medium"> ${title} </p>
<//>
Expand All @@ -173,27 +174,34 @@ export function Stat({title, text, tipText, tipIcon, tipColors, colors}) {
<//>`;
};

export function TextValue({value, setfn, disabled, placeholder, type, addonRight, addonLeft, attr, min, max, step}) {
export function TextValue({value, setfn, disabled, placeholder, type, addonRight, addonLeft, attr, min, max, step, mult}) {
const [bg, setBg] = useState('bg-white');
useEffect(() => { checkval(value); }, []);
const checkval = function(v) {
useEffect(() => { if (type == 'number') checkval(+min, +max, +value); }, []);
step ||= '1', mult ||= 1;
const checkval = function(min, max, v) {
setBg('bg-white');
if (min && v < min) setBg('bg-red-100 border-red-200');
if (max && v > max) setBg('bg-red-100 border-red-200');
};
const oninput = ev => {
const m = step.match(/^.+\.(.+)/);
const digits = m ? m[1].length : 0;
const onchange = ev => {
let v = ev.target.value;
if (type == 'number') v = parseInt(v);
if (type == 'number') {
checkval(+min, +max, +v);
v = +(parseFloat(v) / mult).toFixed(digits);
}
setfn(v);
checkval(v);
};
if (type == 'number') value = +(value * mult).toFixed(digits);
return html`
<div class="flex w-full items-center rounded border shadow-sm ${bg}">
${ addonLeft && html`<span class="inline-flex font-normal truncate py-1 border-r bg-slate-100 items-center border-gray-300 px-2 text-gray-500 text-xs">${addonLeft}<//>` }
<input type=${type || 'text'} disabled=${disabled}
oninput=${oninput} ...${attr}
class="${bg} font-normal text-sm rounded w-full flex-1 py-0.5 px-2 text-gray-700 placeholder:text-gray-400 focus:outline-none disabled:cursor-not-allowed disabled:bg-gray-100 disabled:text-gray-500" placeholder=${placeholder} value=${value} />
${ addonRight && html`<span class="inline-flex font-normal truncate py-1 border-l bg-slate-100 items-center border-gray-300 px-2 text-gray-500 text-xs overflow-scroll" style="min-width: 50%;">${addonRight}<//>` }
${addonLeft && html`<span class="inline-flex font-normal truncate py-1 border-r bg-slate-100 items-center border-gray-300 px-2 text-gray-500 text-xs">${addonLeft}<//>` }
<input type=${type || 'text'} disabled=${disabled} value=${value}
step=${step} min=${min} max=${max}
onchange=${onchange} ...${attr}
class="${bg} font-normal text-sm rounded w-full flex-1 py-0.5 px-2 text-gray-700 placeholder:text-gray-400 focus:outline-none disabled:cursor-not-allowed disabled:bg-gray-100 disabled:text-gray-500" placeholder=${placeholder} />
${addonRight && html`<span class="inline-flex font-normal truncate py-1 border-l bg-slate-100 items-center border-gray-300 px-2 text-gray-500 text-xs overflow-scroll" style="min-width: 50%;">${addonRight}<//>` }
<//>`;
};

Expand Down Expand Up @@ -344,6 +352,6 @@ export function UploadFileButton(props) {
<div class="inline-flex flex-col ${props.class}">
<input class="hidden" type="file" ref=${input} onchange=${onchange} accept=${props.accept} />
<${Button} title=${props.title} icon=${Icons.download} onclick=${onclick} ref=${btn} colors=${props.colors} />
<div class="py-2 text-sm text-slate-400 ${status || 'hidden'}">${status}<//>
<div class="pt-2 text-sm text-slate-400 ${status || 'hidden'}">${status}<//>
<//>`;
};
2 changes: 1 addition & 1 deletion examples/device-dashboard/web_root/main.css

Large diffs are not rendered by default.

41 changes: 27 additions & 14 deletions examples/device-dashboard/web_root/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,36 +216,49 @@ function FirmwareUpdate({}) {
.then(r => new Promise(r => setTimeout(ev => { refresh(); r(); }, 3000)));
const onrollback = ev => fetch('api/firmware/rollback')
.then(onreboot);
const onerase = ev => fetch('api/device/eraselast').then(refresh);
const onupload = function(ok, name, size) {
if (!ok) return false;
return new Promise(r => setTimeout(ev => { refresh(); r(); }, 3000));
};
const clean = info[0].valid && info[0].golden == 0;
return html`
<div class="m-4 gap-4 grid grid-cols-1 lg:grid-cols-2">
<div class="m-4 gap-4 grid grid-cols-1 lg:grid-cols-3">
<${FirmwareStatus} title="Current firmware image" info=${info[0]}>
<div class="flex flex-wrap gap-2">
<${Button} title="Commit this firmware"
onclick=${oncommit} icon=${Icons.thumbUp} disabled=${clean} />
<${Button} title="Reboot device" onclick=${onreboot} icon=${Icons.refresh} clsx="absolute top-4 right-4" />
<${UploadFileButton}
title="Upload new firmware: choose .bin file:" onupload=${onupload}
url="api/firmware/upload" accept=".bin,.uf2" />
<${Button} title="Commit this firmware" onclick=${oncommit}
icon=${Icons.thumbUp} disabled=${info[0].status == 3} cls="w-full" />
<//>
<//>
<${FirmwareStatus} title="Previous firmware image" info=${info[1]}>
<${Button} title="Rollback to this firmware" onclick=${onrollback}
icon=${Icons.backward} disabled=${info[1].valid == false} />
icon=${Icons.backward} disabled=${info[1].status == 0} cls="w-full" />
<//>
<div class="bg-white xm-4 divide-y border rounded flex flex-col">
<div class="font-light uppercase flex items-center text-gray-600 px-4 py-2">
Device control
<//>
<div class="px-4 py-3 flex flex-col gap-2 grow">
<${UploadFileButton}
title="Upload new firmware .bin file" onupload=${onupload}
url="api/firmware/upload" accept=".bin,.uf2" />
<div class="grow"><//>
<${Button} title="Reboot device" onclick=${onreboot} icon=${Icons.refresh} cls="w-full" />
<${Button} title="Erase last sector" onclick=${onerase} icon=${Icons.doc} cls="w-full hidden" />
<//>
<//>
<//>
<div class="m-4 gap-4 grid grid-cols-1 lg:grid-cols-2">
<div class="bg-white border shadow-lg">
<${DeveloperNote}>
<div class="my-2">
When a new firmware gets flashed, its status is marked as, "first_boot".
That is an unreliable (uncommitted) firmware. A user may choose
to revert back to the previous committed firmware on the subsequent
boots. Clicking on the "commit" button calls "mg_ota_commit()" function
which commits the firmware.
Firmware status and other information is stored in the last sector
of flash
<//>
<div class="my-2">
Firmware status can be FIRST_BOOT, UNCOMMITTED or COMMITTED. If no
information is available, it is UNAVAILABLE.
<//>
<div class="my-2">
This GUI loads a firmware file and sends it chunk by chunk to the
Expand Down
18 changes: 8 additions & 10 deletions examples/stm32/nucleo-h563zi-make-baremetal-builtin/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,21 @@ int main(void) {
mg_mgr_init(&mgr); // Mongoose event manager
mg_log_set(MG_LL_DEBUG); // Set log level

#if MG_OTA == MG_OTA_FLASH
// If we don't have any OTA info saved, e.g. we're pre-flashed, then
// call mg_ota_commit() to mark this firmware as reliable
if (mg_ota_status(MG_FIRMWARE_CURRENT) == MG_OTA_UNAVAILABLE) mg_ota_commit();
mg_ota_boot(); // Call bootloader: continue to load, or boot another FW

// Demonstrate the use of mg_flash_{load/save} functions for keeping device
#if MG_OTA == MG_OTA_FLASH
// Demonstrate the use of mg_flash_{load/save} functions for storing device
// configuration data on flash. Increment boot count on every boot.
struct deviceconfig {
uint32_t boot_count;
int some_other_data;
char some_other_data[40];
};
uint32_t key = 0x12345678; // A unique key, one per data type
struct deviceconfig dc = {}; // Initialise to some default values
mg_flash_load(NULL, key, &dc, sizeof(dc)); // Load from flash
dc.boot_count++; // Increment boot count
mg_flash_save(NULL, key, &dc, sizeof(dc)); // And save back
MG_INFO(("Boot count: %u", dc.boot_count));
mg_flash_load(NULL, key, &dc, sizeof(dc)); // Load from flash
dc.boot_count++; // Increment boot count
mg_flash_save(NULL, key, &dc, sizeof(dc)); // And save back
MG_INFO(("Boot count: %u", dc.boot_count)); // Print boot count
#endif

// Initialise Mongoose network stack
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ firmware.bin: firmware.elf

firmware.elf: cmsis_core cmsis_h7 $(SOURCES) hal.h link.ld Makefile
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@
arm-none-eabi-size $@

flash: firmware.bin
st-flash --reset write $< 0x8000000
Expand Down
20 changes: 10 additions & 10 deletions examples/stm32/nucleo-h723zg-make-baremetal-builtin/hal.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@

#pragma once

#define BTN PIN('C', 13) // User push button
#define LED1 PIN('B', 0) // On-board LED pin (green)
#define LED2 PIN('E', 1) // On-board LED pin (yellow)
#define LED3 PIN('B', 14) // On-board LED pin (red)
#define LED LED2 // Use yellow LED for blinking

#ifndef UART_DEBUG
#define UART_DEBUG USART3
#endif

#include <stm32h723xx.h>

#include <stdbool.h>
Expand All @@ -21,12 +31,6 @@
#define PINNO(pin) (pin & 255)
#define PINBANK(pin) (pin >> 8)

#define LED1 PIN('B', 0) // On-board LED pin (green)
#define LED2 PIN('E', 1) // On-board LED pin (yellow)
#define LED3 PIN('B', 14) // On-board LED pin (red)

#define LED LED2 // Use yellow LED for blinking

// System clock (2.1, Figure 1; 8.5, Figure 45; 8.5.5, Figure 47; 8.5.6, Figure
// 49; 8.5.8 Table 56; datasheet) CPU_FREQUENCY <= 550 MHz; hclk = CPU_FREQUENCY
// / HPRE ; hclk <= 275 MHz; APB clocks <= 137.5 MHz. D1 domain bus matrix (and
Expand Down Expand Up @@ -96,10 +100,6 @@ static inline void gpio_output(uint16_t pin) {
GPIO_PULL_NONE, 0);
}

#ifndef UART_DEBUG
#define UART_DEBUG USART3
#endif

// D2 Kernel clock (8.7.21) USART1 defaults to pclk2 (APB2), while USART2,3
// default to pclk1 (APB1). Even if using other kernel clocks, the APBx clocks
// must be enabled for CPU access, as the kernel clock drives the BRR, not the
Expand Down
14 changes: 5 additions & 9 deletions examples/stm32/nucleo-h723zg-make-baremetal-builtin/link.ld
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,15 @@ SECTIONS {
.rodata : { *(.rodata*) } > flash

.data : {
_sdata = .; /* for init_ram() */
_sdata = .;
*(.first_data)
*(.data SORT(.data.*))
_edata = .; /* for init_ram() */
*(.iram .iram* .iram.*)
_edata = .;
} > sram AT > flash
_sidata = LOADADDR(.data);

.bss : {
_sbss = .; /* for init_ram() */
*(.bss SORT(.bss.*) COMMON)
_ebss = .; /* for init_ram() */
} > sram

.bss : { _sbss = .; *(.bss SORT(.bss.*) COMMON) _ebss = .; } > sram
. = ALIGN(8);
_end = .; /* for cmsis_gcc.h and init_ram() */
_end = .;
}
19 changes: 16 additions & 3 deletions examples/stm32/nucleo-h723zg-make-baremetal-builtin/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,22 @@ int main(void) {
mg_mgr_init(&mgr); // Mongoose event manager
mg_log_set(MG_LL_DEBUG); // Set log level

// If we don't have any OTA info saved, e.g. we're pre-flashed, then
// call mg_ota_commit() to mark this firmware as reliable
if (mg_ota_status(MG_FIRMWARE_CURRENT) == MG_OTA_UNAVAILABLE) mg_ota_commit();
mg_ota_boot(); // Call bootloader: continue to load, or boot another FW

#if MG_OTA == MG_OTA_FLASH
// Demonstrate the use of mg_flash_{load/save} functions for keeping device
// configuration data on flash. Increment boot count on every boot.
struct deviceconfig {
uint32_t boot_count;
char some_other_data[40];
};
uint32_t key = 0x12345678; // A unique key, one per data type
struct deviceconfig dc = {}; // Initialise to some default values
mg_flash_load(NULL, key, &dc, sizeof(dc)); // Load from flash
dc.boot_count++; // Increment boot count
mg_flash_save(NULL, key, &dc, sizeof(dc)); // And save back
MG_INFO(("Boot count: %u", dc.boot_count));
#endif

// Initialise Mongoose network stack
struct mg_tcpip_driver_stm32h_data driver_data = {.mdc_cr = 4};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#define MG_ARCH MG_ARCH_NEWLIB
#define MG_OTA MG_OTA_FLASH
#define MG_DEVICE MG_DEVICE_STM32H7

#define MG_ENABLE_TCPIP 1
#define MG_ENABLE_CUSTOM_MILLIS 1
Expand Down
19 changes: 16 additions & 3 deletions examples/stm32/nucleo-h743zi-make-baremetal-builtin/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,22 @@ int main(void) {
mg_mgr_init(&mgr); // Mongoose event manager
mg_log_set(MG_LL_DEBUG); // Set log level

// If we don't have any OTA info saved, e.g. we're pre-flashed, then
// call mg_ota_commit() to mark this firmware as reliable
if (mg_ota_status(MG_FIRMWARE_CURRENT) == MG_OTA_UNAVAILABLE) mg_ota_commit();
mg_ota_boot(); // Call bootloader: continue to load, or boot another FW

#if MG_OTA == MG_OTA_FLASH
// Demonstrate the use of mg_flash_{load/save} functions for keeping device
// configuration data on flash. Increment boot count on every boot.
struct deviceconfig {
uint32_t boot_count;
char some_other_data[40];
};
uint32_t key = 0x12345678; // A unique key, one per data type
struct deviceconfig dc = {}; // Initialise to some default values
mg_flash_load(NULL, key, &dc, sizeof(dc)); // Load from flash
dc.boot_count++; // Increment boot count
mg_flash_save(NULL, key, &dc, sizeof(dc)); // And save back
MG_INFO(("Boot count: %u", dc.boot_count));
#endif

// Initialise Mongoose network stack
struct mg_tcpip_driver_stm32h_data driver_data = {.mdc_cr = 4};
Expand Down
Loading

0 comments on commit 22f7609

Please sign in to comment.