mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-18 10:59:45 +00:00
17 files changed
+748
-17
No files matched your search
+187
@@ -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"
|
||||
"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\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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 *);
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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 = .;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
+10
@@ -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) \
|
||||
Reference in new issue
Block a user