Merge pull request #3692 from cesanta/health

health record support
This commit is contained in:
Sergey Lyubka authored and GitHub committed 2026-09-12 08:12:04 +01:00
commit ed5ef09d1c
17 files changed
+748 -17

No files matched your search

+187
View File
@@ -3597,6 +3597,15 @@ static bool p_mkdir(const char *path) {
struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read,
p_write, p_seek, p_rename, p_remove, p_mkdir};
#ifdef MG_ENABLE_LINES
#line 1 "src/health.c"
#endif
// The one health record. Lives in RAM that survives a warm reset, see
// MG_HEALTH_RAM and the .mg_health region in the linker script
struct mg_health mg_health_record MG_HEALTH_RAM;
#ifdef MG_ENABLE_LINES
#line 1 "src/http.c"
#endif
@@ -7044,6 +7053,179 @@ void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) {
}
#endif
#ifdef MG_ENABLE_LINES
#line 1 "src/mdash.c"
#endif
#if MG_ENABLE_MDASH
static const char *mg_mdash_ca_pem =
"-----BEGIN CERTIFICATE-----\n"
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n"
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n"
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n"
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n"
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n"
"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n"
"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n"
"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n"
"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n"
"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n"
"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n"
"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n"
"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n"
"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n"
"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n"
"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n"
"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n"
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n"
"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n"
"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n"
"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n"
"NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n"
"ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n"
"TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n"
"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n"
"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n"
"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n"
"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n"
"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n"
"-----END CERTIFICATE-----\n";
static struct mg_connection *mg_mdash_conn = NULL;
// static struct mg_str mg_mdash_key;
// static void mg_connection *mg_mdash_conn = NULL;
static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_CONNECT) {
// c->is_hexdumping = true;
if (mg_url_is_ssl(MG_MDASH_URL)) {
struct mg_str host = mg_url_host(MG_MDASH_URL);
struct mg_tls_opts opts = {.ca = mg_str(mg_mdash_ca_pem), .name = host};
mg_tls_init(c, &opts);
}
}
if (ev == MG_EV_WS_OPEN) {
MG_INFO(("Connected to %s", MG_MDASH_URL));
// s_mDashState = MDASH_STATE_CLOUD_CONNECTED;
// mDashTriggerEvent(s_mDashState, NULL);
} else if (ev == MG_EV_WS_MSG) {
struct mg_iobuf io = {0, 0, 0, 512};
struct mg_ws_message *wm = ev_data;
struct mg_str s = wm->data;
while (s.len > 0 && isspace((uint8_t) s.buf[s.len - 1])) s.len--;
MG_INFO(("RPC: %.*s", (int) s.len, s.buf));
struct mg_rpc_req r = {.head = &c->mgr->rpcs,
.rpc = NULL,
.pfn = mg_pfn_iobuf,
.pfn_data = &io,
.req_data = NULL,
.frame = s};
mg_rpc_process(&r);
if (io.buf != NULL) {
MG_INFO((" %s", io.buf));
mg_ws_send(c, (const char *) io.buf, io.len, WEBSOCKET_OP_TEXT);
} else {
MG_DEBUG(("%s", "RPC unhandled"));
}
mg_iobuf_free(&io);
} else if (ev == MG_EV_CLOSE) {
mg_mdash_conn = NULL;
MG_INFO(("disconnected, ram %lu", 0));
}
(void) c;
}
static void mg_mdash_rpc_get_info(struct mg_rpc_req *r) {
mg_rpc_ok(r, "{%m:%m,%m:%llu,%m:%m,%m:\"mws.%d\"}", MG_ESC("fw_version"),
MG_ESC(MG_MDASH_FIRMWARE_VERSION), MG_ESC("uptime"),
(uint64_t) (mg_millis() / 1000), MG_ESC("reboot_reason"),
MG_ESC(mg_health_reason_str(mg_health_reason())), MG_ESC("arch"),
MG_ARCH);
}
static void mg_mdash_rpc_ota_begin(struct mg_rpc_req *r) {
size_t total = (size_t) mg_json_get_long(r->frame, "$.params.size", -1);
if (mg_ota_begin(total)) {
mg_rpc_ok(r, "true");
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
}
}
static void mg_mdash_rpc_ota_write(struct mg_rpc_req *r) {
int len = 0, ofs = 0;
if ((ofs = mg_json_get(r->frame, "$.params.data", &len)) <= 0 ||
r->frame.buf[ofs] != '"') {
mg_rpc_err(r, 500, "%m", MG_ESC("data required"));
} else {
struct mg_str s = mg_str_n(&r->frame.buf[ofs] + 1, (size_t) (len - 2));
size_t n = mg_base64_decode(s.buf, s.len, s.buf, s.len); // Decode in-place
if (n == 0) {
mg_rpc_ok(r, "%m", MG_ESC("finished"));
mg_ota_end();
} else if (mg_ota_write(s.buf, n)) {
mg_rpc_ok(r, "{%m:%d}", MG_ESC("queued"), n);
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("write failed"));
mg_ota_end();
}
// mDashNotify("State.Set", "{%m:{%m:%d}}", MG_ESC("ota"),
// MG_ESC("percent"), (int) (s_ota_written * 100 / s_ota_size));
}
}
static void mg_mdash_rpc_ota_end(struct mg_rpc_req *r) {
if (mg_ota_end()) {
mg_rpc_ok(r, "true");
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
}
// mDashNotify("State.Set", "{%m:{%m:null}}", MG_ESC("ota"),
// MG_ESC("percent"));
}
void mg_mdash_init(struct mg_mgr *mgr) {
MG_INFO(("Initialising mdash agent, key [%s]", MG_MDASH_KEY));
mg_rpc_add(&mgr->rpcs, mg_str("RPC.List"), mg_rpc_list, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("Sys.GetInfo"), mg_mdash_rpc_get_info, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Begin"), mg_mdash_rpc_ota_begin, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Write"), mg_mdash_rpc_ota_write, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("OTA.End"), mg_mdash_rpc_ota_end, NULL);
}
void mg_mdash_poll(struct mg_mgr *mgr) {
static uint64_t timer = 0;
if (mg_mdash_conn == NULL && mg_timer_expired(&timer, 1000, mg_millis())) {
MG_INFO(("Connecting to %s", MG_MDASH_URL));
mg_mdash_conn = mg_ws_connect(mgr, MG_MDASH_URL, mg_mdash_fn, NULL,
"Origin: %s\r\n"
"Authorization: Bearer %s\r\n",
MG_MDASH_URL, MG_MDASH_KEY);
}
}
#else
void mg_mdash_init(struct mg_mgr *mgr) {
(void) mgr;
}
void mg_mdash_poll(struct mg_mgr *mgr) {
(void) mgr;
}
#endif
#ifdef MG_ENABLE_LINES
#line 1 "src/modbus.c"
#endif
@@ -8241,6 +8423,9 @@ void mg_mgr_init(struct mg_mgr *mgr) {
: MG_TLS == MG_TLS_BUILTIN ? "builtin"
: MG_TLS == MG_TLS_WOLFSSL ? "WolfSSL"
: "custom"));
#if MG_ENABLE_MDASH
mg_mdash_init(mgr);
#endif
}
#if MG_ENABLE_TCPIP
@@ -10661,6 +10846,7 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
uint64_t now = mg_millis();
mg_timer_poll(&mgr->timers, now);
mg_ota_poll(mgr);
mg_mdash_poll(mgr);
if (mgr->ifp == NULL || mgr->ifp->driver == NULL) return;
mg_tcpip_poll(mgr->ifp, now);
for (c = mgr->conns; c != NULL; c = tmp) {
@@ -15151,6 +15337,7 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
now = mg_millis();
mg_timer_poll(&mgr->timers, now);
mg_ota_poll(mgr);
mg_mdash_poll(mgr);
for (c = mgr->conns; c != NULL; c = tmp) {
long flush = 0;
+146
View File
@@ -2443,6 +2443,7 @@ struct mg_mgr {
struct mg_tcpip_if *ifp; // Builtin TCP/IP stack: network interface pointer
size_t extraconnsize; // Builtin TCP/IP stack: extra bytes allocated per connection
union mg_pipe pipe; // Socketpair write-end / queue, used by mg_wakeup()
struct mg_rpc *rpcs; // Linked list of registered RPC handlers
#if MG_ENABLE_FREERTOS_TCP
SocketSet_t ss; // FreeRTOS-TCP socket set
#endif
@@ -4919,6 +4920,151 @@ void mg_ota_device_id(char *buf, size_t len);
// mg_mgr_poll() when MG_OTA_URL is set in mongoose_config.h. Do not call
// directly.
void mg_ota_poll(struct mg_mgr *);
// Device health monitoring
//
// Keeps a small record in a RAM region that survives a warm reset, so a device
// can tell why it restarted. The record holds a boot counter, the uptime this
// boot has reached, and the reset reason reported by the hardware. A crash
// loop, a hang loop and a marginal power supply each leave a different mark.
//
// Put MG_HEALTH_INIT() in main(), right after clock and memory init:
//
// ```c
// int main(void) {
// hal_clock_init();
// MG_HEALTH_INIT();
// ...
// }
// ```
//
// The record must live in RAM that startup code neither copies nor zeroes.
// See tutorials/stm32/nucleo-h723zg/minimal/link.ld for the .mg_health region,
// and tutorials/stm32/nucleo-h723zg/minimal/mongoose_config.h for the
// reset-reason hooks.
//
// On Cortex-M, a fault handler can record the crash: it sets reset_reason to
// MG_HEALTH_RESET_FAULT and walks the stack into backtrace[]. See
// tutorials/stm32/nucleo-h723zg/minimal/main.c for a sample HardFault_Handler.
#ifndef MG_HEALTH_MAGIC
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
#endif
// Places the record in RAM that survives a reset. Define in mongoose_config.h
// to match the linker script, e.g. __attribute__((section(".mg_health")))
#ifndef MG_HEALTH_RAM
#define MG_HEALTH_RAM
#endif
// Why the device restarted. Hardware usually reports several flags at once,
// so these are ordered by how much they tell you: the most specific cause a
// device reports wins
enum mg_health_reason {
MG_HEALTH_RESET_UNKNOWN = 0, // Nothing reported, or no masks configured
MG_HEALTH_RESET_POWER, // Power-on reset, RAM contents are lost
MG_HEALTH_RESET_BROWNOUT, // Supply dipped below the brown-out level
MG_HEALTH_RESET_PIN, // External reset pin, e.g. the reset button
MG_HEALTH_RESET_SOFTWARE, // Firmware asked for it, e.g. NVIC_SystemReset
MG_HEALTH_RESET_LOWPOWER, // Illegal or failed low-power transition
MG_HEALTH_RESET_WATCHDOG, // Watchdog fired: the application stopped
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
};
#define MG_HEALTH_BACKTRACE 20 // Return addresses in mg_health::backtrace
struct mg_health {
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
uint32_t reset_reason; // enum mg_health_reason that started this boot
uint32_t backtrace[MG_HEALTH_BACKTRACE]; // Crash backtrace, frame 0 first
};
extern struct mg_health mg_health_record; // Defined in health.c
// Return true if the record holds valid data, i.e. it survived the reset and
// was written by this firmware layout
static inline bool mg_health_valid(void) {
static const char magic[] = MG_HEALTH_MAGIC;
return memcmp(mg_health_record.magic, magic,
sizeof(mg_health_record.magic)) == 0;
}
static inline int mg_health_reason(void) {
return mg_health_valid() ? (int) mg_health_record.reset_reason
: MG_HEALTH_RESET_UNKNOWN;
}
static inline const char *mg_health_reason_str(int reason) {
switch (reason) {
case MG_HEALTH_RESET_POWER: return "power";
case MG_HEALTH_RESET_BROWNOUT: return "brownout";
case MG_HEALTH_RESET_PIN: return "pin";
case MG_HEALTH_RESET_SOFTWARE: return "software";
case MG_HEALTH_RESET_LOWPOWER: return "lowpower";
case MG_HEALTH_RESET_WATCHDOG: return "watchdog";
case MG_HEALTH_RESET_FAULT: return "fault";
default: return "unknown";
}
}
// Start a new boot: validate the record, count the boot, store the reset
// reason. The current boot moves to prev_* slots first, so the previous
// boot's uptime and reason stay readable. Called by MG_HEALTH_INIT()
static inline void mg_health_init(void) {
char magic[] = MG_HEALTH_MAGIC;
// struct mg_health *h = mg_health_get();
if (!mg_health_valid()) { // First boot, or RAM lost its contents
memset(&mg_health_record, 0, sizeof(mg_health_record));
memcpy(mg_health_record.magic, magic, sizeof(mg_health_record.magic));
}
// h->prev_uptime = h->uptime;
// h->prev_reason = h->reason;
// h->counter++;
// h->uptime = 0;
// h->reason = mg_health_decode_reason();
// MG_HEALTH_RESET_CLEAR();
}
// // Record that the main loop is still alive. Called from mg_mgr_poll(), so a
// // hung application leaves uptime frozen at the hang
// extern uint64_t mg_health_next_ms; // Defined in health.c
// static inline void mg_health_uptime(uint64_t now_ms) {
// struct mg_health *h = mg_health_get();
// if (now_ms >= mg_health_next_ms && mg_health_valid()) {
// mg_health_next_ms = now_ms + 1000;
// h->uptime++;
// }
// }
#define MG_HEALTH_INIT() mg_health_init()
// Cloud connector API to https://mdash.net - device management cloud
#ifndef MG_MDASH_URL
#define MG_MDASH_URL "wss://mdash.net/api/v2/rpc"
#endif
#ifndef MG_MDASH_KEY
#define MG_MDASH_KEY ""
#endif
#ifndef MG_MDASH_FIRMWARE_VERSION
#define MG_MDASH_FIRMWARE_VERSION "1.0.0"
#endif
#ifndef MG_ENABLE_MDASH
#define MG_ENABLE_MDASH 0
#endif
void mg_mdash_init(struct mg_mgr *);
void mg_mdash_poll(struct mg_mgr *);
@@ -39,8 +39,7 @@ patch_linker_script() {
HUART=`perl -nle 'print \$1 if /^UART_HandleTypeDef (.+);/' $MAIN_C`
grep -q mongoose.h $MAIN_C || perl -i -ne 'print; print "#include \"mongoose.h\"\n" if /BEGIN Includes/' $MAIN_C
grep -q '^static void log_fn' $MAIN_C || perl -i -ne "print; print \"static void log_fn(char ch, void *param) {\n HAL_UART_Transmit(param, (unsigned char *) &ch, 1, HAL_MAX_DELAY);\n}\n\" if /USER CODE BEGIN 0/" $MAIN_C
grep -q 'mg_mgr_init' $MAIN_C || perl -i -ne 'print; print " struct mg_mgr mgr;\n mg_mgr_init(&mgr);\n mg_log_set_fn(log_fn, &'"${HUART}"');\n\n" if /USER CODE BEGIN WHILE/' $MAIN_C
grep -q 'mg_mgr_init' $MAIN_C && grep -q 'mg_log_set_fn' $MAIN_C || perl -i -ne 'print; print " mg_log_set_fn(log_fn, &'"${HUART}"');\n" if /mg_mgr_init/' $MAIN_C
grep -q 'mg_mgr_init' $MAIN_C || perl -i -ne 'print; print " struct mg_mgr mgr;\n mg_log_set_fn(log_fn, &'"${HUART}"');\n mg_mgr_init(&mgr);\n mg_log_set_fn(log_fn, &'"${HUART}"');\n\n" if /USER CODE BEGIN WHILE/' $MAIN_C
grep -q 'mg_mgr_poll' $MAIN_C || perl -i -ne 'print " mg_mgr_poll(&mgr, 0);\n" if /USER CODE END WHILE/; print;' $MAIN_C
patch_linker_script $DIR/STM32H723XG_FLASH.ld
patch_linker_script $DIR/STM32H743XX_FLASH.ld
+5
View File
@@ -0,0 +1,5 @@
#include "health.h"
// The one health record. Lives in RAM that survives a warm reset, see
// MG_HEALTH_RAM and the .mg_health region in the linker script
struct mg_health mg_health_record MG_HEALTH_RAM;
+121
View File
@@ -0,0 +1,121 @@
// Device health monitoring
//
// Keeps a small record in a RAM region that survives a warm reset, so a device
// can tell why it restarted. The record holds a boot counter, the uptime this
// boot has reached, and the reset reason reported by the hardware. A crash
// loop, a hang loop and a marginal power supply each leave a different mark.
//
// Put MG_HEALTH_INIT() in main(), right after clock and memory init:
//
// ```c
// int main(void) {
// hal_clock_init();
// MG_HEALTH_INIT();
// ...
// }
// ```
//
// The record must live in RAM that startup code neither copies nor zeroes.
// See tutorials/stm32/nucleo-h723zg/minimal/link.ld for the .mg_health region,
// and tutorials/stm32/nucleo-h723zg/minimal/mongoose_config.h for the
// reset-reason hooks.
//
// On Cortex-M, a fault handler can record the crash: it sets reset_reason to
// MG_HEALTH_RESET_FAULT and walks the stack into backtrace[]. See
// tutorials/stm32/nucleo-h723zg/minimal/main.c for a sample HardFault_Handler.
#pragma once
#include "arch.h"
#ifndef MG_HEALTH_MAGIC
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
#endif
// Places the record in RAM that survives a reset. Define in mongoose_config.h
// to match the linker script, e.g. __attribute__((section(".mg_health")))
#ifndef MG_HEALTH_RAM
#define MG_HEALTH_RAM
#endif
// Why the device restarted. Hardware usually reports several flags at once,
// so these are ordered by how much they tell you: the most specific cause a
// device reports wins
enum mg_health_reason {
MG_HEALTH_RESET_UNKNOWN = 0, // Nothing reported, or no masks configured
MG_HEALTH_RESET_POWER, // Power-on reset, RAM contents are lost
MG_HEALTH_RESET_BROWNOUT, // Supply dipped below the brown-out level
MG_HEALTH_RESET_PIN, // External reset pin, e.g. the reset button
MG_HEALTH_RESET_SOFTWARE, // Firmware asked for it, e.g. NVIC_SystemReset
MG_HEALTH_RESET_LOWPOWER, // Illegal or failed low-power transition
MG_HEALTH_RESET_WATCHDOG, // Watchdog fired: the application stopped
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
};
#define MG_HEALTH_BACKTRACE 20 // Return addresses in mg_health::backtrace
struct mg_health {
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
uint32_t reset_reason; // enum mg_health_reason that started this boot
uint32_t backtrace[MG_HEALTH_BACKTRACE]; // Crash backtrace, frame 0 first
};
extern struct mg_health mg_health_record; // Defined in health.c
// Return true if the record holds valid data, i.e. it survived the reset and
// was written by this firmware layout
static inline bool mg_health_valid(void) {
static const char magic[] = MG_HEALTH_MAGIC;
return memcmp(mg_health_record.magic, magic,
sizeof(mg_health_record.magic)) == 0;
}
static inline int mg_health_reason(void) {
return mg_health_valid() ? (int) mg_health_record.reset_reason
: MG_HEALTH_RESET_UNKNOWN;
}
static inline const char *mg_health_reason_str(int reason) {
switch (reason) {
case MG_HEALTH_RESET_POWER: return "power";
case MG_HEALTH_RESET_BROWNOUT: return "brownout";
case MG_HEALTH_RESET_PIN: return "pin";
case MG_HEALTH_RESET_SOFTWARE: return "software";
case MG_HEALTH_RESET_LOWPOWER: return "lowpower";
case MG_HEALTH_RESET_WATCHDOG: return "watchdog";
case MG_HEALTH_RESET_FAULT: return "fault";
default: return "unknown";
}
}
// Start a new boot: validate the record, count the boot, store the reset
// reason. The current boot moves to prev_* slots first, so the previous
// boot's uptime and reason stay readable. Called by MG_HEALTH_INIT()
static inline void mg_health_init(void) {
char magic[] = MG_HEALTH_MAGIC;
// struct mg_health *h = mg_health_get();
if (!mg_health_valid()) { // First boot, or RAM lost its contents
memset(&mg_health_record, 0, sizeof(mg_health_record));
memcpy(mg_health_record.magic, magic, sizeof(mg_health_record.magic));
}
// h->prev_uptime = h->uptime;
// h->prev_reason = h->reason;
// h->counter++;
// h->uptime = 0;
// h->reason = mg_health_decode_reason();
// MG_HEALTH_RESET_CLEAR();
}
// // Record that the main loop is still alive. Called from mg_mgr_poll(), so a
// // hung application leaves uptime frozen at the hang
// extern uint64_t mg_health_next_ms; // Defined in health.c
// static inline void mg_health_uptime(uint64_t now_ms) {
// struct mg_health *h = mg_health_get();
// if (now_ms >= mg_health_next_ms && mg_health_valid()) {
// mg_health_next_ms = now_ms + 1000;
// h->uptime++;
// }
// }
#define MG_HEALTH_INIT() mg_health_init()
+169
View File
@@ -0,0 +1,169 @@
#include "mdash.h"
#if MG_ENABLE_MDASH
#include "base64.h"
#include "health.h"
#include "log.h"
#include "ota.h"
#include "printf.h"
#include "rpc.h"
#include "sntp.h"
#include "tls.h"
#include "url.h"
#include "ws.h"
static const char *mg_mdash_ca_pem =
"-----BEGIN CERTIFICATE-----\n"
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n"
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n"
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n"
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n"
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n"
"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n"
"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n"
"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n"
"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n"
"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n"
"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n"
"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n"
"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n"
"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n"
"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n"
"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n"
"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n"
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n"
"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n"
"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n"
"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n"
"NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n"
"ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n"
"TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n"
"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n"
"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n"
"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n"
"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n"
"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n"
"-----END CERTIFICATE-----\n";
static struct mg_connection *mg_mdash_conn = NULL;
// static struct mg_str mg_mdash_key;
// static void mg_connection *mg_mdash_conn = NULL;
static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_CONNECT) {
// c->is_hexdumping = true;
if (mg_url_is_ssl(MG_MDASH_URL)) {
struct mg_str host = mg_url_host(MG_MDASH_URL);
struct mg_tls_opts opts = {.ca = mg_str(mg_mdash_ca_pem), .name = host};
mg_tls_init(c, &opts);
}
}
if (ev == MG_EV_WS_OPEN) {
MG_INFO(("Connected to %s", MG_MDASH_URL));
// s_mDashState = MDASH_STATE_CLOUD_CONNECTED;
// mDashTriggerEvent(s_mDashState, NULL);
} else if (ev == MG_EV_WS_MSG) {
struct mg_iobuf io = {0, 0, 0, 512};
struct mg_ws_message *wm = ev_data;
struct mg_str s = wm->data;
while (s.len > 0 && isspace((uint8_t) s.buf[s.len - 1])) s.len--;
MG_INFO(("RPC: %.*s", (int) s.len, s.buf));
struct mg_rpc_req r = {.head = &c->mgr->rpcs,
.rpc = NULL,
.pfn = mg_pfn_iobuf,
.pfn_data = &io,
.req_data = NULL,
.frame = s};
mg_rpc_process(&r);
if (io.buf != NULL) {
MG_INFO((" %s", io.buf));
mg_ws_send(c, (const char *) io.buf, io.len, WEBSOCKET_OP_TEXT);
} else {
MG_DEBUG(("%s", "RPC unhandled"));
}
mg_iobuf_free(&io);
} else if (ev == MG_EV_CLOSE) {
mg_mdash_conn = NULL;
MG_INFO(("disconnected, ram %lu", 0));
}
(void) c;
}
static void mg_mdash_rpc_get_info(struct mg_rpc_req *r) {
mg_rpc_ok(r, "{%m:%m,%m:%llu,%m:%m,%m:\"mws.%d\"}", MG_ESC("fw_version"),
MG_ESC(MG_MDASH_FIRMWARE_VERSION), MG_ESC("uptime"),
(uint64_t) (mg_millis() / 1000), MG_ESC("reboot_reason"),
MG_ESC(mg_health_reason_str(mg_health_reason())), MG_ESC("arch"),
MG_ARCH);
}
static void mg_mdash_rpc_ota_begin(struct mg_rpc_req *r) {
size_t total = (size_t) mg_json_get_long(r->frame, "$.params.size", -1);
if (mg_ota_begin(total)) {
mg_rpc_ok(r, "true");
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
}
}
static void mg_mdash_rpc_ota_write(struct mg_rpc_req *r) {
int len = 0, ofs = 0;
if ((ofs = mg_json_get(r->frame, "$.params.data", &len)) <= 0 ||
r->frame.buf[ofs] != '"') {
mg_rpc_err(r, 500, "%m", MG_ESC("data required"));
} else {
struct mg_str s = mg_str_n(&r->frame.buf[ofs] + 1, (size_t) (len - 2));
size_t n = mg_base64_decode(s.buf, s.len, s.buf, s.len); // Decode in-place
if (n == 0) {
mg_rpc_ok(r, "%m", MG_ESC("finished"));
mg_ota_end();
} else if (mg_ota_write(s.buf, n)) {
mg_rpc_ok(r, "{%m:%d}", MG_ESC("queued"), n);
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("write failed"));
mg_ota_end();
}
// mDashNotify("State.Set", "{%m:{%m:%d}}", MG_ESC("ota"),
// MG_ESC("percent"), (int) (s_ota_written * 100 / s_ota_size));
}
}
static void mg_mdash_rpc_ota_end(struct mg_rpc_req *r) {
if (mg_ota_end()) {
mg_rpc_ok(r, "true");
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
}
// mDashNotify("State.Set", "{%m:{%m:null}}", MG_ESC("ota"),
// MG_ESC("percent"));
}
void mg_mdash_init(struct mg_mgr *mgr) {
MG_INFO(("Initialising mdash agent, key [%s]", MG_MDASH_KEY));
mg_rpc_add(&mgr->rpcs, mg_str("RPC.List"), mg_rpc_list, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("Sys.GetInfo"), mg_mdash_rpc_get_info, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Begin"), mg_mdash_rpc_ota_begin, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Write"), mg_mdash_rpc_ota_write, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("OTA.End"), mg_mdash_rpc_ota_end, NULL);
}
void mg_mdash_poll(struct mg_mgr *mgr) {
static uint64_t timer = 0;
if (mg_mdash_conn == NULL && mg_timer_expired(&timer, 1000, mg_millis())) {
MG_INFO(("Connecting to %s", MG_MDASH_URL));
mg_mdash_conn = mg_ws_connect(mgr, MG_MDASH_URL, mg_mdash_fn, NULL,
"Origin: %s\r\n"
"Authorization: Bearer %s\r\n",
MG_MDASH_URL, MG_MDASH_KEY);
}
}
#else
void mg_mdash_init(struct mg_mgr *mgr) {
(void) mgr;
}
void mg_mdash_poll(struct mg_mgr *mgr) {
(void) mgr;
}
#endif
+24
View File
@@ -0,0 +1,24 @@
// Cloud connector API to https://mdash.net - device management cloud
#pragma once
#include "net.h"
#ifndef MG_MDASH_URL
#define MG_MDASH_URL "wss://mdash.net/api/v2/rpc"
#endif
#ifndef MG_MDASH_KEY
#define MG_MDASH_KEY ""
#endif
#ifndef MG_MDASH_FIRMWARE_VERSION
#define MG_MDASH_FIRMWARE_VERSION "1.0.0"
#endif
#ifndef MG_ENABLE_MDASH
#define MG_ENABLE_MDASH 0
#endif
void mg_mdash_init(struct mg_mgr *);
void mg_mdash_poll(struct mg_mgr *);
+4 -1
View File
@@ -2,7 +2,7 @@
#include "dns.h"
#include "fmt.h"
#include "log.h"
#include "ota.h"
#include "mdash.h"
#include "printf.h"
#include "profile.h"
#include "timer.h"
@@ -350,6 +350,9 @@ void mg_mgr_init(struct mg_mgr *mgr) {
: MG_TLS == MG_TLS_BUILTIN ? "builtin"
: MG_TLS == MG_TLS_WOLFSSL ? "WolfSSL"
: "custom"));
#if MG_ENABLE_MDASH
mg_mdash_init(mgr);
#endif
}
#if MG_ENABLE_TCPIP
+1
View File
@@ -50,6 +50,7 @@ struct mg_mgr {
struct mg_tcpip_if *ifp; // Builtin TCP/IP stack: network interface pointer
size_t extraconnsize; // Builtin TCP/IP stack: extra bytes allocated per connection
union mg_pipe pipe; // Socketpair write-end / queue, used by mg_wakeup()
struct mg_rpc *rpcs; // Linked list of registered RPC handlers
#if MG_ENABLE_FREERTOS_TCP
SocketSet_t ss; // FreeRTOS-TCP socket set
#endif
+1
View File
@@ -2403,6 +2403,7 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
uint64_t now = mg_millis();
mg_timer_poll(&mgr->timers, now);
mg_ota_poll(mgr);
mg_mdash_poll(mgr);
if (mgr->ifp == NULL || mgr->ifp->driver == NULL) return;
mg_tcpip_poll(mgr->ifp, now);
for (c = mgr->conns; c != NULL; c = tmp) {
+2 -1
View File
@@ -1,6 +1,6 @@
#include "dns.h"
#include "event.h"
#include "log.h"
#include "mdash.h"
#include "net.h"
#include "printf.h"
#include "str.h"
@@ -768,6 +768,7 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
now = mg_millis();
mg_timer_poll(&mgr->timers, now);
mg_ota_poll(mgr);
mg_mdash_poll(mgr);
for (c = mgr->conns; c != NULL; c = tmp) {
long flush = 0;
+1 -1
View File
@@ -106,7 +106,7 @@ mongoose.c: Makefile $(wildcard ../src/*.c) $(wildcard ../src/drivers/*.c)
cd .. && (export LC_ALL=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
cd .. && (export LC_ALL=C ; 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/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.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/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/jwt.h src/rpc.h src/dash.h src/ota.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
cd .. && (export LC_ALL=C ; 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/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.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/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/jwt.h src/rpc.h src/dash.h src/ota.h src/health.h src/mdash.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
# Check that all external (exported) symbols have "mg_" prefix
mg_prefix: mongoose.c mongoose.h
@@ -133,7 +133,7 @@ int main(void)
/* Initialize all configured peripherals */
MX_GPIO_Init();
(void) MX_ETH_Init; // Don't use that stuff, Mongoose handle Ethernet
MX_ETH_Init();
MX_RNG_Init();
MX_USART3_UART_Init();
/* USER CODE BEGIN 2 */
@@ -1,24 +1,22 @@
# Copyright (c) 2026 Cesanta Software Limited
# Environment setup: https://mongoose.ws/docs/getting-started/build-environment/
MGDIR = ../../../..
CFLAGS = -W -Wall -Wextra -Wundef -Wshadow -Wdouble-promotion
CFLAGS += -Wformat-truncation -fno-common -Wconversion -Wno-sign-conversion
CFLAGS += -g3 -Os -ffunction-sections -fdata-sections
CFLAGS += -I. -Icmsis_core/CMSIS/Core/Include -Icmsis_h7/Include -Imongoose
CFLAGS += -I. -I$(MGDIR) -Icmsis_core/CMSIS/Core/Include -Icmsis_h7/Include
CFLAGS += -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16 $(CFLAGS_EXTRA)
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nosys.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map
SOURCES = main.c hal.c
SOURCES += cmsis_h7/Source/Templates/gcc/startup_stm32h723xx.s
SOURCES += mongoose/mongoose.c
SOURCES += $(MGDIR)/mongoose.c
all build example: firmware.bin
mongoose/mongoose.c mongoose/mongoose.h:
cp ../../../../mongoose.[ch] mongoose/
firmware.elf: cmsis_core cmsis_h7 hal.h link.ld Makefile $(SOURCES) mongoose/mongoose.h
firmware.elf: cmsis_core cmsis_h7 hal.h link.ld Makefile $(SOURCES) $(MGDIR)/mongoose.h mongoose_config.h
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(CFLAGS_EXTRA) $(LDFLAGS) -o $@
firmware.bin: firmware.elf
@@ -36,7 +34,7 @@ cmsis_h7:
git clone -q -c advice.detachedHead=false --depth 1 -b v1.10.6 https://github.com/STMicroelectronics/cmsis_device_h7 $@
clean:
rm -rf firmware.* cmsis_* mongoose/mongoose.*
rm -rf firmware.* cmsis_*
# Automated remote test. Requires env variable VCON_API_KEY set. See https://vcon.io/automated-firmware-tests/
@@ -23,6 +23,10 @@ SECTIONS {
.eth_ram : { *(.eth_ram .eth_ram*) } > ram_d1 AT > flash
/* Health record. NOLOAD and in unused DTCM, so startup code neither copies
nor zeroes it and the contents survive a warm reset */
.mg_health (NOLOAD) : { KEEP(*(.mg_health .mg_health*)) } > dtcmram
. = ALIGN(8);
_end = .;
}
+65 -3
View File
@@ -24,10 +24,54 @@ static void log_fn(char ch, void *param) {
static void blink_task(void) {
static uint64_t blink_timer = 0;
if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) {
hal_gpio_toggle(LED1);
hal_gpio_toggle(LED2);
}
}
// Fault handler body. Runs in exception context: no printf, no malloc, no
// blocking calls. Records the crash reason and a backtrace into the health
// record, then resets.
// "used" keeps the linker from garbage-collecting this section: the only
// reference is the "b fault_c" branch in the naked handler below
__attribute__((used, noinline)) static void fault_c(uint32_t *sp) {
extern uint8_t _estack; // End of the main RAM region, defined in link.ld
size_t n = 0;
mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT;
// Frame 0 is the faulting PC, frame 1 the caller's LR. Deeper frames come
// from a heuristic stack walk: BL pushes an odd return address that lives
// in flash. A stack word that merely looks like an address shows up as a
// bogus frame when symbolised, which is easy to filter by eye
mg_health_record.backtrace[n++] = sp[6] & ~1U; // Stacked PC
mg_health_record.backtrace[n++] = sp[5] & ~1U; // Stacked LR
for (uint32_t *p = sp + 8;
n < MG_HEALTH_BACKTRACE &&
(uintptr_t) p < (uintptr_t) sp + 4096U && // Bound the scan
(uintptr_t) p < (uintptr_t) &_estack; // Stay in RAM
p++) {
uint32_t v = *p;
if ((v & 1U) && v >= 0x08000000U && v < 0x08000000U + 1024U * 1024U) {
mg_health_record.backtrace[n++] = v & ~1U;
}
}
NVIC_SystemReset();
}
// Common fault entry. EXC_RETURN bit 2 tells which stack was in use:
// 0 = MSP, 1 = PSP. Load the faulting SP into r0 and hand it to fault_c()
__attribute__((naked)) void HardFault_Handler(void) {
__asm volatile(
"tst lr, #4\n\t" // Test EXC_RETURN bit 2
"ite eq\n\t" // If zero, use MSP; else PSP
"mrseq r0, msp\n\t"
"mrsne r0, psp\n\t"
"b fault_c\n\t");
}
// Route the other fault types through the same entry point
void MemManage_Handler(void) __attribute__((alias("HardFault_Handler")));
void BusFault_Handler(void) __attribute__((alias("HardFault_Handler")));
void UsageFault_Handler(void) __attribute__((alias("HardFault_Handler")));
uint64_t mg_millis(void) {
return hal_get_tick();
}
@@ -70,6 +114,15 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
int main(void) {
hal_clock_init();
// Enable MemManage, BusFault and UsageFault so faults are precise and
// routed to their own handlers instead of escalating to HardFault
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk | SCB_SHCSR_BUSFAULTENA_Msk |
SCB_SHCSR_USGFAULTENA_Msk;
MG_OTA_BOOT_CHECK(); // Must be called after clock init
MG_HEALTH_INIT(); // Must be called after clock init
hal_uart_init(UART_DEBUG, UART_DEBUG_TX_PIN, UART_DEBUG_RX_PIN, 115200);
mg_log_set_fn(log_fn, UART_DEBUG);
hal_rng_init();
@@ -79,8 +132,17 @@ int main(void) {
hal_gpio_output(LED3);
MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000));
MG_OTA_BOOT_CHECK(); // Must be called after clock init
// Report the previous boot's crash backtrace, if any
if (mg_health_reason() == MG_HEALTH_RESET_FAULT) {
MG_INFO(("Previous boot faulted. Backtrace:"));
for (int i = 0; i < MG_HEALTH_BACKTRACE; i++) {
if (mg_health_record.backtrace[i] == 0) break;
MG_INFO((" #%d 0x%08lx", i,
(unsigned long) mg_health_record.backtrace[i]));
}
// * (volatile uint32_t *) 0 = 123; // Deliberate fault: write to unmapped memory
}
// for (;;) (void) 0;
struct mg_mgr mgr;
mg_mgr_init(&mgr);
@@ -44,6 +44,16 @@
#define MG_OTA_STATE_GET() (RTC->BKP0R)
#define MG_OTA_STATE_SET(v) (RTC->BKP0R = (uint32_t) (v))
// Health record lives in the .mg_health region, see link.ld
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
// mdash device token
#define MG_ENABLE_MDASH 1
#ifndef MG_MDASH_KEY
#define MG_MDASH_KEY "" // Set this
#endif
// Construct MAC address from the MCU unique ID
#define MGUID ((uint32_t *) UID_BASE)
#define MG_SET_MAC_ADDRESS(mac) \