Add OTA to the device dashboard. Implement for H5

This commit is contained in:
cpq 2023-08-24 16:30:08 +01:00
parent d5b5cec797
commit 2f014237b2
19 changed files with 3332 additions and 7534 deletions

View File

@ -175,7 +175,7 @@ mongoose.c: Makefile $(wildcard src/*.c) $(wildcard src/drivers/*.c)
(cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/drivers/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@ (cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/drivers/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@
mongoose.h: $(HDRS) Makefile mongoose.h: $(HDRS) Makefile
(cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/json.h src/rpc.h src/net_builtin.h src/drivers/*.h src/certs.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ (cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/json.h src/rpc.h src/ota.h src/net_builtin.h src/drivers/*.h src/certs.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
clean: clean_examples clean_embedded clean: clean_examples clean_embedded

View File

@ -1,6 +1,7 @@
PROG ?= ./example # Program we are building PROG ?= ./example # Program we are building
PACK ?= ./pack # Packing executable PACK ?= ./pack # Packing executable
DELETE = rm -rf # Command to remove files DELETE = rm -rf # Command to remove files
GZIP ?= gzip # For compressing files in web_root/
OUT ?= -o $(PROG) # Compiler argument for output file OUT ?= -o $(PROG) # Compiler argument for output file
SOURCES = main.c mongoose.c net.c packed_fs.c # Source code files SOURCES = main.c mongoose.c net.c packed_fs.c # Source code files
CFLAGS = -W -Wall -Wextra -g -I. # Build options CFLAGS = -W -Wall -Wextra -g -I. # Build options
@ -14,6 +15,7 @@ ifeq ($(OS),Windows_NT) # Windows settings. Assume MinGW compiler. To us
CC = gcc # Use MinGW gcc compiler CC = gcc # Use MinGW gcc compiler
CFLAGS += -lws2_32 # Link against Winsock library CFLAGS += -lws2_32 # Link against Winsock library
DELETE = cmd /C del /Q /F /S # Command prompt command to delete files DELETE = cmd /C del /Q /F /S # Command prompt command to delete files
GZIP = echo # No gzip on Windows
endif endif
# Default target. Build and run program # Default target. Build and run program
@ -34,8 +36,10 @@ web_root/main.css: web_root/index.html $(wildcard web_root/*.js)
# Generate packed filesystem for serving Web UI # Generate packed filesystem for serving Web UI
packed_fs.c: $(wildcard web_root/*) $(wildcard certs/*) Makefile web_root/main.css web_root/bundle.js packed_fs.c: $(wildcard web_root/*) $(wildcard certs/*) Makefile web_root/main.css web_root/bundle.js
$(GZIP) web_root/*
$(CC) ../../test/pack.c -o $(PACK) $(CC) ../../test/pack.c -o $(PACK)
$(PACK) $(wildcard web_root/*) $(wildcard certs/*) > $@ $(PACK) web_root/* certs/* > $@
$(GZIP) -d web_root/*
mbedtls: mbedtls:
git clone --depth 1 -b v2.28.2 https://github.com/mbed-tls/mbedtls $@ git clone --depth 1 -b v2.28.2 https://github.com/mbed-tls/mbedtls $@

View File

@ -17,7 +17,6 @@ int main(void) {
signal(SIGINT, signal_handler); signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler); signal(SIGTERM, signal_handler);
srand(time(NULL));
mg_log_set(MG_LL_DEBUG); // Set debug log level mg_log_set(MG_LL_DEBUG); // Set debug log level
mg_mgr_init(&mgr); mg_mgr_init(&mgr);

View File

@ -83,7 +83,7 @@ static struct user *authenticate(struct mg_http_message *hm) {
char user[64], pass[64]; char user[64], pass[64];
struct user *u, *result = NULL; struct user *u, *result = NULL;
mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass)); mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass));
MG_INFO(("user [%s] pass [%s]", user, pass)); MG_VERBOSE(("user [%s] pass [%s]", user, pass));
if (user[0] != '\0' && pass[0] != '\0') { if (user[0] != '\0' && pass[0] != '\0') {
// Both user and password is set, search by user/password // Both user and password is set, search by user/password
@ -198,6 +198,67 @@ static void handle_settings_get(struct mg_connection *c) {
MG_ESC("device_name"), MG_ESC(s_settings.device_name)); MG_ESC("device_name"), MG_ESC(s_settings.device_name));
} }
static void handle_firmware_upload(struct mg_connection *c,
struct mg_http_message *hm) {
char name[64], offset[20], total[20];
struct mg_str data = hm->body;
long ofs = -1, tot = -1;
name[0] = offset[0] = '\0';
mg_http_get_var(&hm->query, "name", name, sizeof(name));
mg_http_get_var(&hm->query, "offset", offset, sizeof(offset));
mg_http_get_var(&hm->query, "total", total, sizeof(total));
MG_INFO(("File %s, offset %s, len %lu", name, offset, data.len));
if ((ofs = mg_json_get_long(mg_str(offset), "$", -1)) < 0 ||
(tot = mg_json_get_long(mg_str(total), "$", -1)) < 0) {
mg_http_reply(c, 500, "", "offset and total not set\n");
} else if (ofs == 0 && mg_ota_begin((size_t) tot) == false) {
mg_http_reply(c, 500, "", "mg_ota_begin(%ld) failed\n", tot);
} else if (data.len > 0 && mg_ota_write(data.ptr, data.len) == false) {
mg_http_reply(c, 500, "", "mg_ota_write(%lu) @%ld failed\n", data.len, ofs);
mg_ota_end();
} else if (data.len == 0 && mg_ota_end() == false) {
mg_http_reply(c, 500, "", "mg_ota_end() failed\n", tot);
} else {
mg_http_reply(c, 200, s_json_header, "true\n");
if (data.len == 0) {
// Successful mg_ota_end() called, schedule device reboot
mg_timer_add(c->mgr, 500, 0, (void (*)(void *)) mg_sys_reset, NULL);
}
}
}
static void handle_firmware_commit(struct mg_connection *c) {
mg_http_reply(c, 200, s_json_header, "%s\n",
mg_ota_commit() ? "true" : "false");
}
static void handle_firmware_rollback(struct mg_connection *c) {
mg_http_reply(c, 200, s_json_header, "%s\n",
mg_ota_rollback() ? "true" : "false");
}
static size_t print_status(void (*out)(char, void *), void *ptr, va_list *ap) {
struct mg_ota_data *os = va_arg(*ap, struct mg_ota_data *);
return mg_xprintf(
out, ptr, "{%m:%s,%m:%c%x%c,%m:%u,%m:%u,%m:%u,%m:%u,%m:%u}",
MG_ESC("valid"), os->magic == MG_OTA_MAGIC ? "true" : "false",
MG_ESC("magic"), '"', os->magic, '"', MG_ESC("crc32"), os->crc32,
MG_ESC("size"), os->size, MG_ESC("time"), os->time, MG_ESC("booted"),
os->booted, MG_ESC("golden"), os->golden);
}
static void handle_firmware_status(struct mg_connection *c) {
struct mg_ota_data od[2];
mg_ota_status(od);
mg_http_reply(c, 200, s_json_header, "[%M,%M]\n", print_status, &od[0],
print_status, &od[1]);
}
static void handle_sys_reset(struct mg_connection *c) {
mg_http_reply(c, 200, s_json_header, "true\n");
mg_timer_add(c->mgr, 500, 0, (void (*)(void *)) mg_sys_reset, NULL);
}
// HTTP request handler function // HTTP request handler function
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) { if (ev == MG_EV_HTTP_MSG) {
@ -220,6 +281,16 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
handle_settings_get(c); handle_settings_get(c);
} else if (mg_http_match_uri(hm, "/api/settings/set")) { } else if (mg_http_match_uri(hm, "/api/settings/set")) {
handle_settings_set(c, hm->body); handle_settings_set(c, hm->body);
} else if (mg_http_match_uri(hm, "/api/firmware/upload")) {
handle_firmware_upload(c, hm);
} else if (mg_http_match_uri(hm, "/api/firmware/commit")) {
handle_firmware_commit(c);
} else if (mg_http_match_uri(hm, "/api/firmware/rollback")) {
handle_firmware_rollback(c);
} else if (mg_http_match_uri(hm, "/api/firmware/status")) {
handle_firmware_status(c);
} else if (mg_http_match_uri(hm, "/api/sys/reset")) {
handle_sys_reset(c);
} else { } else {
struct mg_http_serve_opts opts; struct mg_http_serve_opts opts;
memset(&opts, 0, sizeof(opts)); memset(&opts, 0, sizeof(opts));
@ -249,7 +320,6 @@ void web_init(struct mg_mgr *mgr) {
mg_http_listen(mgr, HTTP_URL, fn, NULL); mg_http_listen(mgr, HTTP_URL, fn, NULL);
mg_http_listen(mgr, HTTPS_URL, fn, NULL); mg_http_listen(mgr, HTTPS_URL, fn, NULL);
// mg_timer_add(c->mgr, 1000, MG_TIMER_REPEAT, timer_mqtt_fn, c->mgr);
mg_timer_add(mgr, 3600 * 1000, MG_TIMER_RUN_NOW | MG_TIMER_REPEAT, mg_timer_add(mgr, 3600 * 1000, MG_TIMER_RUN_NOW | MG_TIMER_REPEAT,
timer_sntp_fn, mgr); timer_sntp_fn, mgr);
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,9 @@
'use strict'; 'use strict';
import { h, render, useState, useEffect, useRef, html, Router } from './bundle.js'; import { h, render, useState, useEffect, useRef, html, Router } from './bundle.js';
// Helper function that returns a promise that resolves after delay
const Delay = (ms, val) => new Promise(resolve => setTimeout(resolve, ms, val));
export const Icons = { export const Icons = {
heart: props => html`<svg class=${props.class} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"></path></svg>`, heart: props => html`<svg class=${props.class} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"></path></svg>`,
downArrowBox: 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="M9 8.25H7.5a2.25 2.25 0 00-2.25 2.25v9a2.25 2.25 0 002.25 2.25h9a2.25 2.25 0 002.25-2.25v-9a2.25 2.25 0 00-2.25-2.25H15M9 12l3 3m0 0l3-3m-3 3V2.25" /> </svg>`, downArrowBox: 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="M9 8.25H7.5a2.25 2.25 0 00-2.25 2.25v9a2.25 2.25 0 002.25 2.25h9a2.25 2.25 0 002.25-2.25v-9a2.25 2.25 0 00-2.25-2.25H15M9 12l3 3m0 0l3-3m-3 3V2.25" /> </svg>`,
@ -31,7 +34,9 @@ export const Icons = {
arrowup: 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="M12 19.5v-15m0 0l-6.75 6.75M12 4.5l6.75 6.75" /> </svg>`, arrowup: 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="M12 19.5v-15m0 0l-6.75 6.75M12 4.5l6.75 6.75" /> </svg>`,
warn: 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="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" /> </svg>`, warn: 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="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" /> </svg>`,
info: 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="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" /> </svg>`, info: 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="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" /> </svg>`,
exclamationTriangle: 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="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" /> </svg>` exclamationTriangle: 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="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" /> </svg>`,
thumbUp: 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.633 10.5c.806 0 1.533-.446 2.031-1.08a9.041 9.041 0 012.861-2.4c.723-.384 1.35-.956 1.653-1.715a4.498 4.498 0 00.322-1.672V3a.75.75 0 01.75-.75A2.25 2.25 0 0116.5 4.5c0 1.152-.26 2.243-.723 3.218-.266.558.107 1.282.725 1.282h3.126c1.026 0 1.945.694 2.054 1.715.045.422.068.85.068 1.285a11.95 11.95 0 01-2.649 7.521c-.388.482-.987.729-1.605.729H13.48c-.483 0-.964-.078-1.423-.23l-3.114-1.04a4.501 4.501 0 00-1.423-.23H5.904M14.25 9h2.25M5.904 18.75c.083.205.173.405.27.602.197.4-.078.898-.523.898h-.908c-.889 0-1.713-.518-1.972-1.368a12 12 0 01-.521-3.507c0-1.553.295-3.036.831-4.398C3.387 10.203 4.167 9.75 5 9.75h1.053c.472 0 .745.556.5.96a8.958 8.958 0 00-1.302 4.665c0 1.194.232 2.333.654 3.375z" /> </svg>`,
backward: 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="M21 16.811c0 .864-.933 1.405-1.683.977l-7.108-4.062a1.125 1.125 0 010-1.953l7.108-4.062A1.125 1.125 0 0121 8.688v8.123zM11.25 16.811c0 .864-.933 1.405-1.683.977l-7.108-4.062a1.125 1.125 0 010-1.953L9.567 7.71a1.125 1.125 0 011.683.977v8.123z" /> </svg>`,
}; };
export const tipColors = { export const tipColors = {
@ -51,7 +56,7 @@ export function Button({title, onclick, disabled, cls, icon, ref, colors, hoverc
}; };
if (!colors) colors = 'bg-blue-600 hover:bg-blue-500 disabled:bg-blue-400'; if (!colors) colors = 'bg-blue-600 hover:bg-blue-500 disabled:bg-blue-400';
return html` return html`
<button type="button" class="inline-flex justify-center items-center gap-1 rounded px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm ${colors} ${cls}" <button type="button" class="inline-flex justify-center items-center gap-2 rounded px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm ${colors} ${cls}"
ref=${ref} onclick=${cb} disabled=${disabled || spin} > ref=${ref} onclick=${cb} disabled=${disabled || spin} >
${title} ${title}
<${spin ? Icons.refresh : icon} class="w-4 ${spin ? 'animate-spin' : ''}" /> <${spin ? Icons.refresh : icon} class="w-4 ${spin ? 'animate-spin' : ''}" />
@ -253,3 +258,74 @@ export function Pagination({ totalItems, itemsPerPage, currentPage, setPageFn })
</div> </div>
</div>`; </div>`;
}; };
export function UploadFileButton(props) {
const [upload, setUpload] = useState(null); // Upload promise
const [status, setStatus] = useState(''); // Current upload status
const btn = useRef(null);
const input = useRef(null);
// Send a large file chunk by chunk
const sendFileData = function(url, fileName, fileData, chunkSize) {
return new Promise(function(resolve, reject) {
const finish = ok => {
setUpload(null);
const res = props.onupload ? props.onupload(ok, fileName, fileData.length) : null;
if (res && typeof (res.catch) === 'function') {
res.catch(() => false).then(() => ok ? resolve() : reject());
} else {
ok ? resolve() : reject();
}
};
const sendChunk = function(offset) {
var chunk = fileData.subarray(offset, offset + chunkSize) || '';
var opts = {method: 'POST', body: chunk};
var fullUrl = url + '?offset=' + offset +
'&total=' + fileData.length +
'&name=' + encodeURIComponent(fileName);
var ok;
setStatus('Uploading ' + fileName + ', bytes ' + offset + '..' +
(offset + chunk.length) + ' of ' + fileData.length);
fetch(fullUrl, opts)
.then(function(res) {
if (res.ok && chunk.length > 0) sendChunk(offset + chunk.length);
ok = res.ok;
return res.text();
})
.then(function(text) {
if (!ok) setStatus('Error: ' + text), finish(ok); // Fail
if (chunk.length > 0) return; // More chunks to send
setStatus(x => x + '. Done, resetting device...');
finish(ok); // All chunks sent
});
};
//setFailed(false);
sendChunk(0);
});
};
const onchange = function(ev) {
if (!ev.target.files[0]) return;
let r = new FileReader(), f = ev.target.files[0];
r.readAsArrayBuffer(f);
r.onload = function() {
setUpload(sendFileData(props.url, f.name, new Uint8Array(r.result), 2048));
ev.target.value = '';
ev.preventDefault();
btn && btn.current.base.click();
};
};
const onclick = function(ev) {
let fn; setUpload(x => fn = x);
if (!fn) input.current.click(); // No upload in progress, show file dialog
return fn;
};
return html`
<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} />
<div class="py-2 text-sm text-slate-400 ${status || 'hidden'}">${status}<//>
<//>`;
};

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
'use strict'; 'use strict';
import { h, render, useState, useEffect, useRef, html, Router } from './bundle.js'; import { h, render, useState, useEffect, useRef, html, Router } from './bundle.js';
import { Icons, Login, Setting, Button, Stat, tipColors, Colored, Notification, Pagination } from './components.js'; import { Icons, Login, Setting, Button, Stat, tipColors, Colored, Notification, Pagination, UploadFileButton } from './components.js';
const Logo = props => html`<svg class=${props.class} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12.87 12.85"><defs><style>.ll-cls-1{fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:0.5px;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="ll-cls-1" d="M12.62,1.82V8.91A1.58,1.58,0,0,1,11,10.48H4a1.44,1.44,0,0,1-1-.37A.69.69,0,0,1,2.84,10l-.1-.12a.81.81,0,0,1-.15-.48V5.57a.87.87,0,0,1,.86-.86H4.73V7.28a.86.86,0,0,0,.86.85H9.42a.85.85,0,0,0,.85-.85V3.45A.86.86,0,0,0,10.13,3,.76.76,0,0,0,10,2.84a.29.29,0,0,0-.12-.1,1.49,1.49,0,0,0-1-.37H2.39V1.82A1.57,1.57,0,0,1,4,.25H11A1.57,1.57,0,0,1,12.62,1.82Z"/><path class="ll-cls-1" d="M10.48,10.48V11A1.58,1.58,0,0,1,8.9,12.6H1.82A1.57,1.57,0,0,1,.25,11V3.94A1.57,1.57,0,0,1,1.82,2.37H8.9a1.49,1.49,0,0,1,1,.37l.12.1a.76.76,0,0,1,.11.14.86.86,0,0,1,.14.47V7.28a.85.85,0,0,1-.85.85H8.13V5.57a.86.86,0,0,0-.85-.86H3.45a.87.87,0,0,0-.86.86V9.4a.81.81,0,0,0,.15.48l.1.12a.69.69,0,0,0,.13.11,1.44,1.44,0,0,0,1,.37Z"/></g></g></svg>`; const Logo = props => html`<svg class=${props.class} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12.87 12.85"><defs><style>.ll-cls-1{fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:0.5px;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="ll-cls-1" d="M12.62,1.82V8.91A1.58,1.58,0,0,1,11,10.48H4a1.44,1.44,0,0,1-1-.37A.69.69,0,0,1,2.84,10l-.1-.12a.81.81,0,0,1-.15-.48V5.57a.87.87,0,0,1,.86-.86H4.73V7.28a.86.86,0,0,0,.86.85H9.42a.85.85,0,0,0,.85-.85V3.45A.86.86,0,0,0,10.13,3,.76.76,0,0,0,10,2.84a.29.29,0,0,0-.12-.1,1.49,1.49,0,0,0-1-.37H2.39V1.82A1.57,1.57,0,0,1,4,.25H11A1.57,1.57,0,0,1,12.62,1.82Z"/><path class="ll-cls-1" d="M10.48,10.48V11A1.58,1.58,0,0,1,8.9,12.6H1.82A1.57,1.57,0,0,1,.25,11V3.94A1.57,1.57,0,0,1,1.82,2.37H8.9a1.49,1.49,0,0,1,1,.37l.12.1a.76.76,0,0,1,.11.14.86.86,0,0,1,.14.47V7.28a.85.85,0,0,1-.85.85H8.13V5.57a.86.86,0,0,0-.85-.86H3.45a.87.87,0,0,0-.86.86V9.4a.81.81,0,0,0,.15.48l.1.12a.69.69,0,0,0,.13.11,1.44,1.44,0,0,0,1,.37Z"/></g></g></svg>`;
@ -46,6 +46,7 @@ function Sidebar({url, show}) {
<div class="flex flex-1 flex-col"> <div class="flex flex-1 flex-col">
<${NavLink} title="Dashboard" icon=${Icons.home} href="/" url=${url} /> <${NavLink} title="Dashboard" icon=${Icons.home} href="/" url=${url} />
<${NavLink} title="Settings" icon=${Icons.settings} href="/settings" url=${url} /> <${NavLink} title="Settings" icon=${Icons.settings} href="/settings" url=${url} />
<${NavLink} title="Firmware Update" icon=${Icons.download} href="/update" url=${url} />
<${NavLink} title="Events" icon=${Icons.alert} href="/events" url=${url} /> <${NavLink} title="Events" icon=${Icons.alert} href="/events" url=${url} />
<//> <//>
<//> <//>
@ -143,13 +144,16 @@ function Chart({data}) {
<//>`; <//>`;
}; };
function DeveloperNote({text}) { function DeveloperNote({text, children}) {
return html` return html`
<div class="flex p-4 gap-2"> <div class="flex p-4 gap-2">
<${Icons.info} class="self-start basis-[30px] grow-0 shrink-0 text-green-600" /> <div class="text-sm text-slate-500">
<div class="text-sm"> <div class="flex items-center">
<div class="font-semibold mt-1">Developer Note<//> <${Icons.info} class="self-start basis-[30px] grow-0 shrink-0 text-green-600 mr-2" />
${text.split('.').map(v => html` <p class="my-2 text-slate-500">${v}<//>`)} <div class="font-semibold">Developer Note<//>
<//>
${(text || '').split('.').map(v => html` <p class="my-2 ">${v}<//>`)}
${children}
<//> <//>
<//>`; <//>`;
}; };
@ -181,6 +185,95 @@ function Main({}) {
<//>`; <//>`;
}; };
function FirmwareStatus({title, info, children}) {
return html`
<div class="bg-white py-1 divide-y border rounded">
<div class="font-light uppercase flex items-center text-gray-600 px-4 py-2">
${title}
<//>
<div class="px-4 py-2 relative">
<div class="my-1">CRC32: ${info.valid ? info.crc32.toString(16) : 'n/a'}<//>
<div class="my-1">Size: ${info.valid ? info.size : 'n/a'}<//>
<div class="my-1">Flashed at: ${info.valid ? new Date(info.time * 1000).toLocaleString() : 'n/a'}<//>
<div class="my-1">State: ${info.valid ? (info.golden == 0 ? 'commtited' : 'NOT committed') : 'n/a'}<//>
${children}
<//>
<//>`;
};
function FirmwareUpdate({}) {
const [info, setInfo] = useState([{}, {}]);
const refresh = () => fetch('api/firmware/status').then(r => r.json()).then(r => setInfo(r));
useEffect(refresh, []);
const state = ['new', 'dirty', 'clean'][(info.state || 0) % 3];
const oncommit = ev => fetch('api/firmware/commit')
.then(r => r.json())
.then(refresh);
const onreboot = ev => fetch('api/sys/reset')
.then(r => r.json())
.then(r => new Promise(r => setTimeout(ev => { refresh(); r(); }, 3000)));
const onrollback = ev => fetch('api/firmware/rollback')
.then(onreboot);
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">
<${FirmwareStatus} title="Current firmware image" info=${info[0]}>
<${Button} cls="mr-2" 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} class="mt-2"
title="Upload new firmware: choose .bin file:" onupload=${onupload}
url="api/firmware/upload" accept=".bin,.uf2" />
<//>
<${FirmwareStatus} title="Previous firmware image" info=${info[1]}>
<${Button} title="Rollback to this firmware" onclick=${onrollback}
icon=${Icons.backward} disabled=${info[1].valid == false} />
<//>
<div class="bg-white border shadow-lg">
<${DeveloperNote}>
<div class="my-2">
When new firmware gets flashed, its status is unreliable, "not
committed". In order to become "committed" (verified), a firmware must
be committed. If a firmware is not committed, then the next boot
reverts back to the previous firmware.
<//>
<div class="my-2">
This GUI loads a firmware file and sends it chunk by chunk to the
device, passing current chunk offset, total firmware size and a file name:
api/firmware/upload?offset=X&total=Y&name=Z
<//>
<//>
<//>
<div class="bg-white border shadow-lg">
<${DeveloperNote}>
<div>
Firmware udpdate mechanism defines 3 API functions that the
target device must implement: ota_begin(), ota_write() and ota_end()
<//>
<div class="my-2">
RESTful API handlers use ota_xxx() API to save firmware to flash.
The last 0-length chunk triggers ota_end() which performs firmware
update using saved firmware image
<//>
<div class="my-2">
<a class="link text-blue-600 underline"
href="https://mongoose.ws/webinars/">Subscribe to our free webinar</a> to
get detailed explanations about possible firmware updates strategies
and implementation demo
<//>
<//>
<//>
<//>`;
};
function Settings({}) { function Settings({}) {
const [settings, setSettings] = useState(null); const [settings, setSettings] = useState(null);
const [saveResult, setSaveResult] = useState(null); const [saveResult, setSaveResult] = useState(null);
@ -256,6 +349,7 @@ const App = function({}) {
<${Router} onChange=${ev => setUrl(ev.url)} history=${History.createHashHistory()} > <${Router} onChange=${ev => setUrl(ev.url)} history=${History.createHashHistory()} >
<${Main} default=${true} /> <${Main} default=${true} />
<${Settings} path="settings" /> <${Settings} path="settings" />
<${FirmwareUpdate} path="update" />
<${Events} path="events" /> <${Events} path="events" />
<//> <//>
<//> <//>

View File

@ -12,8 +12,7 @@ SOURCES += cmsis_h5/Source/Templates/gcc/startup_stm32h563xx.s # ST startup file
SOURCES += mongoose.c net.c packed_fs.c SOURCES += mongoose.c net.c packed_fs.c
CFLAGS += -DMG_ENABLE_TCPIP=1 -DMG_ARCH=MG_ARCH_NEWLIB -DMG_ENABLE_CUSTOM_MILLIS=1 CFLAGS += -DMG_ENABLE_TCPIP=1 -DMG_ARCH=MG_ARCH_NEWLIB -DMG_ENABLE_CUSTOM_MILLIS=1
CFLAGS += -DMG_ENABLE_CUSTOM_RANDOM=1 -DMG_ENABLE_PACKED_FS=1 CFLAGS += -DMG_ENABLE_CUSTOM_RANDOM=1 -DMG_ENABLE_PACKED_FS=1
CFLAGS += -DMG_ENABLE_DRIVER_STM32H=1 $(CFLAGS_EXTRA) CFLAGS += -DMG_ENABLE_DRIVER_STM32H=1 -DMG_OTA=MG_OTA_STM32H5 $(CFLAGS_EXTRA)
CFLAGS += $(CFLAGS_EXTRA)
# Example specific build options. See README.md # Example specific build options. See README.md
CFLAGS += -DHTTP_URL=\"http://0.0.0.0/\" -DHTTPS_URL=\"https://0.0.0.0/\" CFLAGS += -DHTTP_URL=\"http://0.0.0.0/\" -DHTTPS_URL=\"https://0.0.0.0/\"

View File

@ -39,8 +39,8 @@ enum {
}; };
// Make sure your chip package uses the internal LDO, otherwise set PLL1_N = 200 // Make sure your chip package uses the internal LDO, otherwise set PLL1_N = 200
enum { PLL1_HSI = 64, PLL1_M = 32, PLL1_N = 250, PLL1_P = 2 }; enum { PLL1_HSI = 64, PLL1_M = 32, PLL1_N = 250, PLL1_P = 2 };
#define FLASH_LATENCY 0x25 // WRHIGHFREQ LATENCY #define CPU_FREQUENCY \
#define CPU_FREQUENCY ((PLL1_HSI * PLL1_N / PLL1_M / PLL1_P / (BIT(HPRE - 7))) * 1000000) ((PLL1_HSI * PLL1_N / PLL1_M / PLL1_P / (BIT(HPRE - 7))) * 1000000)
#define AHB_FREQUENCY CPU_FREQUENCY #define AHB_FREQUENCY CPU_FREQUENCY
#define APB2_FREQUENCY (AHB_FREQUENCY / (BIT(PPRE2 - 3))) #define APB2_FREQUENCY (AHB_FREQUENCY / (BIT(PPRE2 - 3)))
#define APB1_FREQUENCY (AHB_FREQUENCY / (BIT(PPRE1 - 3))) #define APB1_FREQUENCY (AHB_FREQUENCY / (BIT(PPRE1 - 3)))

View File

@ -3,7 +3,7 @@ MEMORY {
flash(rx) : ORIGIN = 0x08000000, LENGTH = 2048k flash(rx) : ORIGIN = 0x08000000, LENGTH = 2048k
sram(rwx) : ORIGIN = 0x20000000, LENGTH = 640k sram(rwx) : ORIGIN = 0x20000000, LENGTH = 640k
} }
_estack = ORIGIN(sram) + LENGTH(sram); /* stack points to end of SRAM */ _estack = ORIGIN(sram) + LENGTH(sram); /* End of RAM. stack points here */
SECTIONS { SECTIONS {
.vectors : { KEEP(*(.isr_vector)) } > flash .vectors : { KEEP(*(.isr_vector)) } > flash
@ -11,19 +11,20 @@ SECTIONS {
.rodata : { *(.rodata*) } > flash .rodata : { *(.rodata*) } > flash
.data : { .data : {
_sdata = .; /* for init_ram() */ _sdata = .;
*(.first_data) *(.first_data)
*(.ram)
*(.data SORT(.data.*)) *(.data SORT(.data.*))
_edata = .; /* for init_ram() */ _edata = .;
} > sram AT > flash } > sram AT > flash
_sidata = LOADADDR(.data); _sidata = LOADADDR(.data);
.bss : { .bss : {
_sbss = .; /* for init_ram() */ _sbss = .;
*(.bss SORT(.bss.*) COMMON) *(.bss SORT(.bss.*) COMMON)
_ebss = .; /* for init_ram() */ _ebss = .;
} > sram } > sram
. = ALIGN(8); . = ALIGN(8);
_end = .; /* for cmsis_gcc.h and init_ram() */ _end = .;
} }

View File

@ -35,7 +35,6 @@ static void timer_fn(void *arg) {
int main(void) { int main(void) {
gpio_output(LED); // Setup green LED gpio_output(LED); // Setup green LED
uart_init(UART_DEBUG, 115200); // Initialise debug printf uart_init(UART_DEBUG, 115200); // Initialise debug printf
ethernet_init(); // Initialise ethernet pins
MG_INFO(("Starting, CPU freq %g MHz", (double) SystemCoreClock / 1000000)); MG_INFO(("Starting, CPU freq %g MHz", (double) SystemCoreClock / 1000000));
struct mg_mgr mgr; // Initialise struct mg_mgr mgr; // Initialise
@ -43,6 +42,7 @@ int main(void) {
mg_log_set(MG_LL_DEBUG); // Set log level mg_log_set(MG_LL_DEBUG); // Set log level
// Initialise Mongoose network stack // Initialise Mongoose network stack
ethernet_init(); // Initialise ethernet pins
struct mg_tcpip_driver_stm32h_data driver_data = {.mdc_cr = 4}; struct mg_tcpip_driver_stm32h_data driver_data = {.mdc_cr = 4};
struct mg_tcpip_if mif = {.mac = GENERATE_LOCALLY_ADMINISTERED_MAC(), struct mg_tcpip_if mif = {.mac = GENERATE_LOCALLY_ADMINISTERED_MAC(),
// Uncomment below for static configuration: // Uncomment below for static configuration:

View File

@ -13,6 +13,11 @@ void SystemInit(void) { // Called automatically by startup code
SCB->CPACR |= ((3UL << 20U) | (3UL << 22U)); // Enable FPU SCB->CPACR |= ((3UL << 20U) | (3UL << 22U)); // Enable FPU
asm("DSB"); asm("DSB");
asm("ISB"); asm("ISB");
// Set flash latency. RM0481, section 7.11.1, section 7.3.4 table 37
SETBITS(FLASH->ACR, (FLASH_ACR_WRHIGHFREQ_Msk | FLASH_ACR_LATENCY_Msk),
FLASH_ACR_LATENCY_5WS | FLASH_ACR_WRHIGHFREQ_1);
if (ldo_is_on()) { if (ldo_is_on()) {
PWR->VOSCR = PWR_VOSCR_VOS_0 | PWR_VOSCR_VOS_1; // Select VOS0 PWR->VOSCR = PWR_VOSCR_VOS_0 | PWR_VOSCR_VOS_1; // Select VOS0
} else { } else {
@ -21,7 +26,6 @@ void SystemInit(void) { // Called automatically by startup code
uint32_t f = PWR->VOSCR; // fake read to wait for bus clocking uint32_t f = PWR->VOSCR; // fake read to wait for bus clocking
while ((PWR->VOSSR & PWR_VOSSR_ACTVOSRDY) == 0) spin(1); while ((PWR->VOSSR & PWR_VOSSR_ACTVOSRDY) == 0) spin(1);
(void) f; (void) f;
FLASH->ACR |= FLASH_LATENCY;
RCC->CR = RCC_CR_HSION; // Clear HSI clock divisor RCC->CR = RCC_CR_HSION; // Clear HSI clock divisor
while ((RCC->CR & RCC_CR_HSIRDY) == 0) spin(1); // Wait until done while ((RCC->CR & RCC_CR_HSIRDY) == 0) spin(1); // Wait until done
RCC->CFGR2 = (PPRE3 << 12) | (PPRE2 << 8) | (PPRE1 << 4) | (HPRE << 0); RCC->CFGR2 = (PPRE3 << 12) | (PPRE2 << 8) | (PPRE1 << 4) | (HPRE << 0);

View File

@ -4913,6 +4913,242 @@ bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
} }
#endif // MG_ENABLE_TCPIP #endif // MG_ENABLE_TCPIP
#ifdef MG_ENABLE_LINES
#line 1 "src/ota_dummy.c"
#endif
#if MG_OTA == MG_OTA_NONE
static struct mg_ota_data s_od[2] = {
{MG_OTA_MAGIC, (uint32_t) -1, (uint32_t) -1, (uint32_t) -1, (uint32_t) -1,
(uint32_t) -1},
{MG_OTA_MAGIC, (uint32_t) -1, (uint32_t) -1, (uint32_t) -1, (uint32_t) -1,
(uint32_t) -1},
};
bool mg_ota_begin(size_t new_firmware_size) {
MG_DEBUG(("Starting firmware update, size %lu", new_firmware_size));
return true;
}
bool mg_ota_write(const void *buf, size_t len) {
MG_DEBUG(("%p %lu", buf, len));
return true;
}
bool mg_ota_end(void) {
return true;
}
bool mg_ota_status(struct mg_ota_data od[2]) {
od[0] = s_od[0];
od[1] = s_od[1];
return true;
}
bool mg_ota_commit(void) {
s_od[0].golden = 0;
return true;
}
bool mg_ota_rollback(void) {
return true;
}
void mg_sys_reset(void) {
MG_DEBUG(("Resetting device..."));
}
#endif
#ifdef MG_ENABLE_LINES
#line 1 "src/ota_stm32h5.c"
#endif
#if MG_OTA == MG_OTA_STM32H5
#define FLASH_BANK1 0x08000000
#define FLASH_SIZE1 0x00100000
#define FLASH_BANK2 0x08100000
#define FLASH_SIZE2 0x00100000
#define FLASH_SSIZE 8192 // Sector (page) size, 8k
// Keep OTA data at the beginning of the last sector of the flash bank
#define FLASH_BANK1_OTA_DATA (FLASH_BANK1 + FLASH_SIZE1 - FLASH_SSIZE)
#define FLASH_BANK2_OTA_DATA (FLASH_BANK2 + FLASH_SIZE2 - FLASH_SSIZE)
#define FLASH_BASE 0x40022000 // Base address of the flash controller
#define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11
#define FLASH_OPTKEYR (FLASH_BASE + 0xc)
#define FLASH_OPTCR (FLASH_BASE + 0x1c)
#define FLASH_NSSR (FLASH_BASE + 0x20)
#define FLASH_NSCR (FLASH_BASE + 0x28)
#define FLASH_NSCCR (FLASH_BASE + 0x30)
#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50)
#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54)
#undef REG
#define REG(x) ((volatile uint32_t *) (x))[0]
#undef BIT
#define BIT(x) (((uint32_t) 1U) << (x))
#undef SETBITS
#define SETBITS(R, CLEARMASK, SETMASK) (R) = ((R) & ~(CLEARMASK)) | (SETMASK)
static uint32_t s_addr; // Current address to write to
static uint32_t s_crc32; // Firmware checksum
static void flash_unlock(void) {
static bool unlocked = false;
if (unlocked == false) {
REG(FLASH_KEYR) = 0x45670123;
REG(FLASH_KEYR) = 0Xcdef89ab;
REG(FLASH_OPTKEYR) = 0x08192a3b;
REG(FLASH_OPTKEYR) = 0x4c5d6e7f;
unlocked = true;
}
}
static int flash_page_start(volatile uint32_t *dst) {
uint32_t addr = (uint32_t) (uintptr_t) dst;
return (addr & (FLASH_SSIZE - 1)) == 0;
}
static bool flash_is_err(void) {
return REG(FLASH_NSSR) & ((BIT(8) - 1) << 17); // RM0481 7.11.9
}
static void flash_wait(void) {
while ((REG(FLASH_NSSR) & BIT(0)) && (REG(FLASH_NSSR) & BIT(16)) == 0) {
(void) 0;
}
}
static void flash_prep(void) {
flash_wait(); // Wait until ready
REG(FLASH_NSCR) = 0UL; // Clear control reg
REG(FLASH_NSCCR) = ((BIT(9) - 1) << 16U); // Clear all errors
}
static bool flash_bank_is_swapped(void) {
return REG(FLASH_OPTCR) & BIT(31); // RM0481 7.11.8
}
static void flash_erase(uint32_t sector) {
flash_prep();
if ((sector < 128 && flash_bank_is_swapped()) ||
(sector > 127 && !flash_bank_is_swapped())) {
REG(FLASH_NSCR) |= BIT(31); // Set FLASH_CR_BKSEL
}
if (sector > 127) sector -= 128;
REG(FLASH_NSCR) |= BIT(2) | (sector << 6); // SectorErase | sector_num
REG(FLASH_NSCR) |= BIT(5); // Start erasing
// MG_INFO(("ERASE %lu, CR %#lx SR %#lx", sector, REG(FLASH_NSCR),
// REG(FLASH_NSSR)));
flash_prep();
}
static bool flash_swap_bank(void) {
uint32_t desired = flash_bank_is_swapped() ? 0 : BIT(31);
flash_unlock();
flash_prep();
// printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG);
SETBITS(REG(FLASH_OPTSR_PRG), BIT(31), desired);
// printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
REG(FLASH_OPTCR) |= BIT(1); // OPTSTART
while ((REG(FLASH_OPTSR_CUR) & BIT(31)) != desired) (void) 0;
return true;
}
static bool flash_write(uint32_t addr, const void *buf, size_t len) {
volatile uint32_t *dst = (uint32_t *) addr;
uint32_t *src = (uint32_t *) buf;
uint32_t *end = (uint32_t *) ((char *) buf + len);
bool success = true;
flash_unlock();
flash_prep();
while (success && src < end) {
uint32_t pageno = ((uint32_t) dst - FLASH_BANK1) / FLASH_SSIZE;
if (flash_page_start(dst)) flash_erase(pageno);
REG(FLASH_NSCR) = BIT(1); // Set programming flag
do {
*dst++ = *src++;
flash_wait();
} while (src < end && !flash_page_start(dst) && !flash_is_err());
#if 0
do {
*dst++ = *src++;
printf("WRITE %p, CR %#lx SR %#lx OCR %#lx %#lx %#lx\n", dst, FLASH->NSCR,
FLASH->NSSR, FLASH->OPTCR, src[-1], dst[-1]);
} while (src < end && !flash_page_start(dst));
#endif
// if (FLASH->NSSR & FLASH_SR_WBNE) FLASH->NSCR |= FLASH_CR_FW;
if (REG(FLASH_NSSR) & BIT(1)) REG(FLASH_NSCR) |= BIT(4);
MG_INFO(("W %p, CR %#lx SR %#lx OCR %#lx %#lx %#lx", dst, REG(FLASH_NSCR),
REG(FLASH_NSSR), REG(FLASH_OPTCR), src[-1], dst[-1]));
flash_wait();
if (flash_is_err() || (src[-1] != dst[-1])) {
// printf(" E %lx CR %#lx SR %#lx, %#lx %#lx\n", (unsigned long) dst,
// FLASH->NSCR, FLASH->NSSR, src[-1], dst[-1]);
success = false;
}
}
return success;
}
void mg_sys_reset(void) {
// SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk);
*(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
}
bool mg_ota_begin(size_t new_firmware_size) {
s_crc32 = 0;
s_addr = FLASH_BANK2;
MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, FLASH_SIZE2));
return new_firmware_size < FLASH_SIZE2;
}
bool mg_ota_write(const void *buf, size_t len) {
bool ok = flash_write(s_addr, buf, len); // Write chunk to flash
s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC
MG_DEBUG(("%#x %p %lu -> %d", s_addr, buf, len, ok));
s_addr += len;
return ok;
}
static bool w32(uint32_t addr, uint32_t value) {
return flash_write(addr, &value, sizeof(value));
}
bool mg_ota_end(void) {
bool ok = false;
size_t size = s_addr - FLASH_BANK2;
uint32_t crc = mg_crc32(0, (char *) FLASH_BANK2, size);
if (crc == s_crc32) {
uint32_t now = (uint32_t) (mg_now() / 1000);
struct mg_ota_data od = {MG_OTA_MAGIC, crc, size, now, -1, -1};
ok = flash_write(FLASH_BANK2_OTA_DATA, &od, sizeof(od));
}
MG_DEBUG(("CRC check: %x %x", s_crc32, crc));
return ok && flash_swap_bank();
}
bool mg_ota_status(struct mg_ota_data od[2]) {
od[0] = *(struct mg_ota_data *) FLASH_BANK1_OTA_DATA;
od[1] = *(struct mg_ota_data *) FLASH_BANK2_OTA_DATA;
return od[0].magic == MG_OTA_MAGIC;
}
bool mg_ota_commit(void) {
struct mg_ota_data *od = (struct mg_ota_data *) FLASH_BANK1_OTA_DATA;
bool ok = false;
if (od->magic == MG_OTA_MAGIC && od->golden == 0) {
ok = true; // Already clean, do nothing
} else if (od->magic == MG_OTA_MAGIC) { // Dirty!
ok = w32((uint32_t) &od->golden, 0);
}
return ok;
}
bool mg_ota_rollback(void) {
return flash_swap_bank();
}
#endif
#ifdef MG_ENABLE_LINES #ifdef MG_ENABLE_LINES
#line 1 "src/printf.c" #line 1 "src/printf.c"
#endif #endif

View File

@ -1021,7 +1021,8 @@ char *mg_random_str(char *buf, size_t len);
uint16_t mg_ntohs(uint16_t net); uint16_t mg_ntohs(uint16_t net);
uint32_t mg_ntohl(uint32_t net); uint32_t mg_ntohl(uint32_t net);
uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len); uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len);
uint64_t mg_millis(void); uint64_t mg_millis(void); // Return milliseconds since boot
uint64_t mg_now(void); // Return milliseconds since Epoch
#define mg_htons(x) mg_ntohs(x) #define mg_htons(x) mg_ntohs(x)
#define mg_htonl(x) mg_ntohl(x) #define mg_htonl(x) mg_ntohl(x)
@ -1646,6 +1647,41 @@ void mg_rpc_vok(struct mg_rpc_req *, const char *fmt, va_list *ap);
void mg_rpc_err(struct mg_rpc_req *, int code, const char *fmt, ...); void mg_rpc_err(struct mg_rpc_req *, int code, const char *fmt, ...);
void mg_rpc_verr(struct mg_rpc_req *, int code, const char *fmt, va_list *); void mg_rpc_verr(struct mg_rpc_req *, int code, const char *fmt, va_list *);
void mg_rpc_list(struct mg_rpc_req *r); void mg_rpc_list(struct mg_rpc_req *r);
// Copyright (c) 2023 Cesanta Software Limited
// All rights reserved
#define MG_OTA_NONE 0 // No OTA support
#define MG_OTA_STM32H5 1 // STM32 H5 series
#define MG_OTA_CUSTOM 100 // Custom implementation
#define MG_OTA_MAGIC 0xb07afeed
#ifndef MG_OTA
#define MG_OTA MG_OTA_NONE
#endif
// Firmware update API
bool mg_ota_begin(size_t new_firmware_size); // Start writing
bool mg_ota_write(const void *buf, size_t len); // Write firmware chunk
bool mg_ota_end(void); // Stop writing
void mg_sys_reset(void); // Reboot device
struct mg_ota_data {
uint32_t magic; // Must be MG_OTA_MAGIC
uint32_t crc32; // Checksum of the current firmware
uint32_t size; // Firware size
uint32_t time; // Flashing timestamp. Unix epoch, seconds since 1970
uint32_t booted; // -1: not yet booted before, otherwise booted
uint32_t golden; // -1: not yet comitted, otherwise clean, committed
};
bool mg_ota_status(struct mg_ota_data[2]); // Get status for curr and prev fw
bool mg_ota_commit(void); // Commit current firmware
bool mg_ota_rollback(void); // Rollback to prev firmware

35
src/ota.h Normal file
View File

@ -0,0 +1,35 @@
// Copyright (c) 2023 Cesanta Software Limited
// All rights reserved
#pragma once
#include "arch.h"
#define MG_OTA_NONE 0 // No OTA support
#define MG_OTA_STM32H5 1 // STM32 H5 series
#define MG_OTA_CUSTOM 100 // Custom implementation
#define MG_OTA_MAGIC 0xb07afeed
#ifndef MG_OTA
#define MG_OTA MG_OTA_NONE
#endif
// Firmware update API
bool mg_ota_begin(size_t new_firmware_size); // Start writing
bool mg_ota_write(const void *buf, size_t len); // Write firmware chunk
bool mg_ota_end(void); // Stop writing
void mg_sys_reset(void); // Reboot device
struct mg_ota_data {
uint32_t magic; // Must be MG_OTA_MAGIC
uint32_t crc32; // Checksum of the current firmware
uint32_t size; // Firware size
uint32_t time; // Flashing timestamp. Unix epoch, seconds since 1970
uint32_t booted; // -1: not yet booted before, otherwise booted
uint32_t golden; // -1: not yet comitted, otherwise clean, committed
};
bool mg_ota_status(struct mg_ota_data[2]); // Get status for curr and prev fw
bool mg_ota_commit(void); // Commit current firmware
bool mg_ota_rollback(void); // Rollback to prev firmware

37
src/ota_dummy.c Normal file
View File

@ -0,0 +1,37 @@
#include "log.h"
#include "ota.h"
#if MG_OTA == MG_OTA_NONE
static struct mg_ota_data s_od[2] = {
{MG_OTA_MAGIC, (uint32_t) -1, (uint32_t) -1, (uint32_t) -1, (uint32_t) -1,
(uint32_t) -1},
{MG_OTA_MAGIC, (uint32_t) -1, (uint32_t) -1, (uint32_t) -1, (uint32_t) -1,
(uint32_t) -1},
};
bool mg_ota_begin(size_t new_firmware_size) {
MG_DEBUG(("Starting firmware update, size %lu", new_firmware_size));
return true;
}
bool mg_ota_write(const void *buf, size_t len) {
MG_DEBUG(("%p %lu", buf, len));
return true;
}
bool mg_ota_end(void) {
return true;
}
bool mg_ota_status(struct mg_ota_data od[2]) {
od[0] = s_od[0];
od[1] = s_od[1];
return true;
}
bool mg_ota_commit(void) {
s_od[0].golden = 0;
return true;
}
bool mg_ota_rollback(void) {
return true;
}
void mg_sys_reset(void) {
MG_DEBUG(("Resetting device..."));
}
#endif

191
src/ota_stm32h5.c Normal file
View File

@ -0,0 +1,191 @@
#include "arch.h"
#include "log.h"
#include "ota.h"
#if MG_OTA == MG_OTA_STM32H5
#define FLASH_BANK1 0x08000000
#define FLASH_SIZE1 0x00100000
#define FLASH_BANK2 0x08100000
#define FLASH_SIZE2 0x00100000
#define FLASH_SSIZE 8192 // Sector (page) size, 8k
// Keep OTA data at the beginning of the last sector of the flash bank
#define FLASH_BANK1_OTA_DATA (FLASH_BANK1 + FLASH_SIZE1 - FLASH_SSIZE)
#define FLASH_BANK2_OTA_DATA (FLASH_BANK2 + FLASH_SIZE2 - FLASH_SSIZE)
#define FLASH_BASE 0x40022000 // Base address of the flash controller
#define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11
#define FLASH_OPTKEYR (FLASH_BASE + 0xc)
#define FLASH_OPTCR (FLASH_BASE + 0x1c)
#define FLASH_NSSR (FLASH_BASE + 0x20)
#define FLASH_NSCR (FLASH_BASE + 0x28)
#define FLASH_NSCCR (FLASH_BASE + 0x30)
#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50)
#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54)
#undef REG
#define REG(x) ((volatile uint32_t *) (x))[0]
#undef BIT
#define BIT(x) (((uint32_t) 1U) << (x))
#undef SETBITS
#define SETBITS(R, CLEARMASK, SETMASK) (R) = ((R) & ~(CLEARMASK)) | (SETMASK)
static uint32_t s_addr; // Current address to write to
static uint32_t s_crc32; // Firmware checksum
static void flash_unlock(void) {
static bool unlocked = false;
if (unlocked == false) {
REG(FLASH_KEYR) = 0x45670123;
REG(FLASH_KEYR) = 0Xcdef89ab;
REG(FLASH_OPTKEYR) = 0x08192a3b;
REG(FLASH_OPTKEYR) = 0x4c5d6e7f;
unlocked = true;
}
}
static int flash_page_start(volatile uint32_t *dst) {
uint32_t addr = (uint32_t) (uintptr_t) dst;
return (addr & (FLASH_SSIZE - 1)) == 0;
}
static bool flash_is_err(void) {
return REG(FLASH_NSSR) & ((BIT(8) - 1) << 17); // RM0481 7.11.9
}
static void flash_wait(void) {
while ((REG(FLASH_NSSR) & BIT(0)) && (REG(FLASH_NSSR) & BIT(16)) == 0) {
(void) 0;
}
}
static void flash_prep(void) {
flash_wait(); // Wait until ready
REG(FLASH_NSCR) = 0UL; // Clear control reg
REG(FLASH_NSCCR) = ((BIT(9) - 1) << 16U); // Clear all errors
}
static bool flash_bank_is_swapped(void) {
return REG(FLASH_OPTCR) & BIT(31); // RM0481 7.11.8
}
static void flash_erase(uint32_t sector) {
flash_prep();
if ((sector < 128 && flash_bank_is_swapped()) ||
(sector > 127 && !flash_bank_is_swapped())) {
REG(FLASH_NSCR) |= BIT(31); // Set FLASH_CR_BKSEL
}
if (sector > 127) sector -= 128;
REG(FLASH_NSCR) |= BIT(2) | (sector << 6); // SectorErase | sector_num
REG(FLASH_NSCR) |= BIT(5); // Start erasing
// MG_INFO(("ERASE %lu, CR %#lx SR %#lx", sector, REG(FLASH_NSCR),
// REG(FLASH_NSSR)));
flash_prep();
}
static bool flash_swap_bank(void) {
uint32_t desired = flash_bank_is_swapped() ? 0 : BIT(31);
flash_unlock();
flash_prep();
// printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG);
SETBITS(REG(FLASH_OPTSR_PRG), BIT(31), desired);
// printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
REG(FLASH_OPTCR) |= BIT(1); // OPTSTART
while ((REG(FLASH_OPTSR_CUR) & BIT(31)) != desired) (void) 0;
return true;
}
static bool flash_write(uint32_t addr, const void *buf, size_t len) {
volatile uint32_t *dst = (uint32_t *) addr;
uint32_t *src = (uint32_t *) buf;
uint32_t *end = (uint32_t *) ((char *) buf + len);
bool success = true;
flash_unlock();
flash_prep();
while (success && src < end) {
uint32_t pageno = ((uint32_t) dst - FLASH_BANK1) / FLASH_SSIZE;
if (flash_page_start(dst)) flash_erase(pageno);
REG(FLASH_NSCR) = BIT(1); // Set programming flag
do {
*dst++ = *src++;
flash_wait();
} while (src < end && !flash_page_start(dst) && !flash_is_err());
#if 0
do {
*dst++ = *src++;
printf("WRITE %p, CR %#lx SR %#lx OCR %#lx %#lx %#lx\n", dst, FLASH->NSCR,
FLASH->NSSR, FLASH->OPTCR, src[-1], dst[-1]);
} while (src < end && !flash_page_start(dst));
#endif
// if (FLASH->NSSR & FLASH_SR_WBNE) FLASH->NSCR |= FLASH_CR_FW;
if (REG(FLASH_NSSR) & BIT(1)) REG(FLASH_NSCR) |= BIT(4);
MG_INFO(("W %p, CR %#lx SR %#lx OCR %#lx %#lx %#lx", dst, REG(FLASH_NSCR),
REG(FLASH_NSSR), REG(FLASH_OPTCR), src[-1], dst[-1]));
flash_wait();
if (flash_is_err() || (src[-1] != dst[-1])) {
// printf(" E %lx CR %#lx SR %#lx, %#lx %#lx\n", (unsigned long) dst,
// FLASH->NSCR, FLASH->NSSR, src[-1], dst[-1]);
success = false;
}
}
return success;
}
void mg_sys_reset(void) {
// SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk);
*(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
}
bool mg_ota_begin(size_t new_firmware_size) {
s_crc32 = 0;
s_addr = FLASH_BANK2;
MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, FLASH_SIZE2));
return new_firmware_size < FLASH_SIZE2;
}
bool mg_ota_write(const void *buf, size_t len) {
bool ok = flash_write(s_addr, buf, len); // Write chunk to flash
s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC
MG_DEBUG(("%#x %p %lu -> %d", s_addr, buf, len, ok));
s_addr += len;
return ok;
}
static bool w32(uint32_t addr, uint32_t value) {
return flash_write(addr, &value, sizeof(value));
}
bool mg_ota_end(void) {
bool ok = false;
size_t size = s_addr - FLASH_BANK2;
uint32_t crc = mg_crc32(0, (char *) FLASH_BANK2, size);
if (crc == s_crc32) {
uint32_t now = (uint32_t) (mg_now() / 1000);
struct mg_ota_data od = {MG_OTA_MAGIC, crc, size, now, -1, -1};
ok = flash_write(FLASH_BANK2_OTA_DATA, &od, sizeof(od));
}
MG_DEBUG(("CRC check: %x %x", s_crc32, crc));
return ok && flash_swap_bank();
}
bool mg_ota_status(struct mg_ota_data od[2]) {
od[0] = *(struct mg_ota_data *) FLASH_BANK1_OTA_DATA;
od[1] = *(struct mg_ota_data *) FLASH_BANK2_OTA_DATA;
return od[0].magic == MG_OTA_MAGIC;
}
bool mg_ota_commit(void) {
struct mg_ota_data *od = (struct mg_ota_data *) FLASH_BANK1_OTA_DATA;
bool ok = false;
if (od->magic == MG_OTA_MAGIC && od->golden == 0) {
ok = true; // Already clean, do nothing
} else if (od->magic == MG_OTA_MAGIC) { // Dirty!
ok = w32((uint32_t) &od->golden, 0);
}
return ok;
}
bool mg_ota_rollback(void) {
return flash_swap_bank();
}
#endif

View File

@ -16,7 +16,8 @@ char *mg_random_str(char *buf, size_t len);
uint16_t mg_ntohs(uint16_t net); uint16_t mg_ntohs(uint16_t net);
uint32_t mg_ntohl(uint32_t net); uint32_t mg_ntohl(uint32_t net);
uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len); uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len);
uint64_t mg_millis(void); uint64_t mg_millis(void); // Return milliseconds since boot
uint64_t mg_now(void); // Return milliseconds since Epoch
#define mg_htons(x) mg_ntohs(x) #define mg_htons(x) mg_ntohs(x)
#define mg_htonl(x) mg_ntohl(x) #define mg_htonl(x) mg_ntohl(x)