mirror of
https://github.com/cesanta/mongoose.git
synced 2024-12-04 01:39:01 +08:00
Add OTA to the device dashboard. Implement for H5
This commit is contained in:
parent
d5b5cec797
commit
2f014237b2
2
Makefile
2
Makefile
@ -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))> $@
|
||||
|
||||
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
|
||||
|
@ -1,6 +1,7 @@
|
||||
PROG ?= ./example # Program we are building
|
||||
PACK ?= ./pack # Packing executable
|
||||
DELETE = rm -rf # Command to remove files
|
||||
GZIP ?= gzip # For compressing files in web_root/
|
||||
OUT ?= -o $(PROG) # Compiler argument for output file
|
||||
SOURCES = main.c mongoose.c net.c packed_fs.c # Source code files
|
||||
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
|
||||
CFLAGS += -lws2_32 # Link against Winsock library
|
||||
DELETE = cmd /C del /Q /F /S # Command prompt command to delete files
|
||||
GZIP = echo # No gzip on Windows
|
||||
endif
|
||||
|
||||
# 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
|
||||
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)
|
||||
$(PACK) $(wildcard web_root/*) $(wildcard certs/*) > $@
|
||||
$(PACK) web_root/* certs/* > $@
|
||||
$(GZIP) -d web_root/*
|
||||
|
||||
mbedtls:
|
||||
git clone --depth 1 -b v2.28.2 https://github.com/mbed-tls/mbedtls $@
|
||||
|
@ -17,7 +17,6 @@ int main(void) {
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
|
||||
srand(time(NULL));
|
||||
mg_log_set(MG_LL_DEBUG); // Set debug log level
|
||||
mg_mgr_init(&mgr);
|
||||
|
||||
|
@ -83,7 +83,7 @@ static struct user *authenticate(struct mg_http_message *hm) {
|
||||
char user[64], pass[64];
|
||||
struct user *u, *result = NULL;
|
||||
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') {
|
||||
// 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));
|
||||
}
|
||||
|
||||
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
|
||||
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
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);
|
||||
} else if (mg_http_match_uri(hm, "/api/settings/set")) {
|
||||
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 {
|
||||
struct mg_http_serve_opts 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, 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,
|
||||
timer_sntp_fn, mgr);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,9 @@
|
||||
'use strict';
|
||||
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 = {
|
||||
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>`,
|
||||
@ -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>`,
|
||||
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>`,
|
||||
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 = {
|
||||
@ -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';
|
||||
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} >
|
||||
${title}
|
||||
<${spin ? Icons.refresh : icon} class="w-4 ${spin ? 'animate-spin' : ''}" />
|
||||
@ -253,3 +258,74 @@ export function Pagination({ totalItems, itemsPerPage, currentPage, setPageFn })
|
||||
</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
@ -2,7 +2,7 @@
|
||||
|
||||
'use strict';
|
||||
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>`;
|
||||
|
||||
@ -46,6 +46,7 @@ function Sidebar({url, show}) {
|
||||
<div class="flex flex-1 flex-col">
|
||||
<${NavLink} title="Dashboard" icon=${Icons.home} href="/" 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} />
|
||||
<//>
|
||||
<//>
|
||||
@ -143,13 +144,16 @@ function Chart({data}) {
|
||||
<//>`;
|
||||
};
|
||||
|
||||
function DeveloperNote({text}) {
|
||||
function DeveloperNote({text, children}) {
|
||||
return html`
|
||||
<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">
|
||||
<div class="font-semibold mt-1">Developer Note<//>
|
||||
${text.split('.').map(v => html` <p class="my-2 text-slate-500">${v}<//>`)}
|
||||
<div class="text-sm text-slate-500">
|
||||
<div class="flex items-center">
|
||||
<${Icons.info} class="self-start basis-[30px] grow-0 shrink-0 text-green-600 mr-2" />
|
||||
<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({}) {
|
||||
const [settings, setSettings] = useState(null);
|
||||
const [saveResult, setSaveResult] = useState(null);
|
||||
@ -256,6 +349,7 @@ const App = function({}) {
|
||||
<${Router} onChange=${ev => setUrl(ev.url)} history=${History.createHashHistory()} >
|
||||
<${Main} default=${true} />
|
||||
<${Settings} path="settings" />
|
||||
<${FirmwareUpdate} path="update" />
|
||||
<${Events} path="events" />
|
||||
<//>
|
||||
<//>
|
||||
|
@ -12,8 +12,7 @@ SOURCES += cmsis_h5/Source/Templates/gcc/startup_stm32h563xx.s # ST startup file
|
||||
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_CUSTOM_RANDOM=1 -DMG_ENABLE_PACKED_FS=1
|
||||
CFLAGS += -DMG_ENABLE_DRIVER_STM32H=1 $(CFLAGS_EXTRA)
|
||||
CFLAGS += $(CFLAGS_EXTRA)
|
||||
CFLAGS += -DMG_ENABLE_DRIVER_STM32H=1 -DMG_OTA=MG_OTA_STM32H5 $(CFLAGS_EXTRA)
|
||||
|
||||
# Example specific build options. See README.md
|
||||
CFLAGS += -DHTTP_URL=\"http://0.0.0.0/\" -DHTTPS_URL=\"https://0.0.0.0/\"
|
||||
|
@ -39,8 +39,8 @@ enum {
|
||||
};
|
||||
// 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 };
|
||||
#define FLASH_LATENCY 0x25 // WRHIGHFREQ LATENCY
|
||||
#define CPU_FREQUENCY ((PLL1_HSI * PLL1_N / PLL1_M / PLL1_P / (BIT(HPRE - 7))) * 1000000)
|
||||
#define CPU_FREQUENCY \
|
||||
((PLL1_HSI * PLL1_N / PLL1_M / PLL1_P / (BIT(HPRE - 7))) * 1000000)
|
||||
#define AHB_FREQUENCY CPU_FREQUENCY
|
||||
#define APB2_FREQUENCY (AHB_FREQUENCY / (BIT(PPRE2 - 3)))
|
||||
#define APB1_FREQUENCY (AHB_FREQUENCY / (BIT(PPRE1 - 3)))
|
||||
|
@ -3,7 +3,7 @@ MEMORY {
|
||||
flash(rx) : ORIGIN = 0x08000000, LENGTH = 2048k
|
||||
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 {
|
||||
.vectors : { KEEP(*(.isr_vector)) } > flash
|
||||
@ -11,19 +11,20 @@ SECTIONS {
|
||||
.rodata : { *(.rodata*) } > flash
|
||||
|
||||
.data : {
|
||||
_sdata = .; /* for init_ram() */
|
||||
_sdata = .;
|
||||
*(.first_data)
|
||||
*(.ram)
|
||||
*(.data SORT(.data.*))
|
||||
_edata = .; /* for init_ram() */
|
||||
_edata = .;
|
||||
} > sram AT > flash
|
||||
_sidata = LOADADDR(.data);
|
||||
|
||||
.bss : {
|
||||
_sbss = .; /* for init_ram() */
|
||||
_sbss = .;
|
||||
*(.bss SORT(.bss.*) COMMON)
|
||||
_ebss = .; /* for init_ram() */
|
||||
_ebss = .;
|
||||
} > sram
|
||||
|
||||
. = ALIGN(8);
|
||||
_end = .; /* for cmsis_gcc.h and init_ram() */
|
||||
_end = .;
|
||||
}
|
||||
|
@ -35,7 +35,6 @@ static void timer_fn(void *arg) {
|
||||
int main(void) {
|
||||
gpio_output(LED); // Setup green LED
|
||||
uart_init(UART_DEBUG, 115200); // Initialise debug printf
|
||||
ethernet_init(); // Initialise ethernet pins
|
||||
|
||||
MG_INFO(("Starting, CPU freq %g MHz", (double) SystemCoreClock / 1000000));
|
||||
struct mg_mgr mgr; // Initialise
|
||||
@ -43,6 +42,7 @@ int main(void) {
|
||||
mg_log_set(MG_LL_DEBUG); // Set log level
|
||||
|
||||
// Initialise Mongoose network stack
|
||||
ethernet_init(); // Initialise ethernet pins
|
||||
struct mg_tcpip_driver_stm32h_data driver_data = {.mdc_cr = 4};
|
||||
struct mg_tcpip_if mif = {.mac = GENERATE_LOCALLY_ADMINISTERED_MAC(),
|
||||
// Uncomment below for static configuration:
|
||||
|
@ -13,6 +13,11 @@ void SystemInit(void) { // Called automatically by startup code
|
||||
SCB->CPACR |= ((3UL << 20U) | (3UL << 22U)); // Enable FPU
|
||||
asm("DSB");
|
||||
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()) {
|
||||
PWR->VOSCR = PWR_VOSCR_VOS_0 | PWR_VOSCR_VOS_1; // Select VOS0
|
||||
} 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
|
||||
while ((PWR->VOSSR & PWR_VOSSR_ACTVOSRDY) == 0) spin(1);
|
||||
(void) f;
|
||||
FLASH->ACR |= FLASH_LATENCY;
|
||||
RCC->CR = RCC_CR_HSION; // Clear HSI clock divisor
|
||||
while ((RCC->CR & RCC_CR_HSIRDY) == 0) spin(1); // Wait until done
|
||||
RCC->CFGR2 = (PPRE3 << 12) | (PPRE2 << 8) | (PPRE1 << 4) | (HPRE << 0);
|
||||
|
236
mongoose.c
236
mongoose.c
@ -4913,6 +4913,242 @@ bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
|
||||
}
|
||||
#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
|
||||
#line 1 "src/printf.c"
|
||||
#endif
|
||||
|
38
mongoose.h
38
mongoose.h
@ -1021,7 +1021,8 @@ char *mg_random_str(char *buf, size_t len);
|
||||
uint16_t mg_ntohs(uint16_t net);
|
||||
uint32_t mg_ntohl(uint32_t net);
|
||||
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_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_verr(struct mg_rpc_req *, int code, const char *fmt, va_list *);
|
||||
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
35
src/ota.h
Normal 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
37
src/ota_dummy.c
Normal 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
191
src/ota_stm32h5.c
Normal 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
|
@ -16,7 +16,8 @@ char *mg_random_str(char *buf, size_t len);
|
||||
uint16_t mg_ntohs(uint16_t net);
|
||||
uint32_t mg_ntohl(uint32_t net);
|
||||
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_htonl(x) mg_ntohl(x)
|
||||
|
Loading…
Reference in New Issue
Block a user