/** * @file app_params.c * @brief Application parameter record in the APP_DATA flash region. * * caiic_params_t lives at CAIIC_APP_DATA_ADDR with magic + CRC32 (same * pattern as the bootsetting record). "param set" modifies the RAM copy, * then erases the first APP_DATA sector and programs the record back with * read-back verification. No raw flash access is exposed: the only way to * touch the region is by named parameter. * * Qflash erase/write disables interrupts for tens of ms per sector (SDK * driver runs the flash algorithm from RAM); the BLE link survives via the * connection supervision timeout - same behaviour as the OTA path. */ #include "app_params.h" #include "cli_core.h" #include "dfu_layout.h" #include "boot_crc32.h" #include "infra_cjson.h" #include "n32wb03x.h" #include "n32wb03x_qflash.h" #include #include #include static caiic_params_t s_params; static uint8_t s_valid; /* flash record valid at init */ static uint8_t s_qflash_ready; /* ------------------------------------------------------------------ */ /* Helpers */ /* ------------------------------------------------------------------ */ static void params_ensure_qflash(void) { if (!s_qflash_ready) { Qflash_Init(); /* copy the flash algorithm to RAM (once) */ s_qflash_ready = 1; } } static void params_load_defaults(void) { memset(&s_params, 0, sizeof(s_params)); s_params.magic = CAIIC_PARAMS_MAGIC; s_params.layout_ver = CAIIC_PARAMS_LAYOUT_VER; s_params.led1_blink_ms = PARAM_LED1_BLINK_MS_DEF; s_params.pwm_duty_pct = 0; } static uint32_t params_crc(const caiic_params_t* p) { return caiic_crc32((const uint8_t*)p, (uint32_t)offsetof(caiic_params_t, crc32)); } /** * @brief Program the RAM parameter copy to flash and verify by read-back. * @return 0 on success, 1 on flash failure. */ static uint8_t params_save(void) { params_ensure_qflash(); s_params.crc32 = params_crc(&s_params); if (Qflash_Erase_Sector(CAIIC_APP_DATA_ADDR) != 0) { return 1; } /* sizeof(caiic_params_t) = 52, already 4-byte aligned */ if (Qflash_Write(CAIIC_APP_DATA_ADDR, (uint8_t*)&s_params, sizeof(s_params)) != 0) { return 1; } if (memcmp(&s_params, (const void*)CAIIC_APP_DATA_ADDR, sizeof(s_params)) != 0) { return 1; } s_valid = 1; return 0; } /** * @brief Parse a u32 argument, decimal or 0x-prefixed hex. * @return 0 on success. */ static uint8_t parse_u32(const char* s, uint32_t* out) { char* end = NULL; uint32_t v = strtoul(s, &end, 0); if (end == s || *end != '\0') { return 1; } *out = v; return 0; } /* ------------------------------------------------------------------ */ /* Public interface */ /* ------------------------------------------------------------------ */ void app_params_init(void) { memcpy(&s_params, (const void*)CAIIC_APP_DATA_ADDR, sizeof(s_params)); if (s_params.magic == CAIIC_PARAMS_MAGIC && s_params.layout_ver == CAIIC_PARAMS_LAYOUT_VER && s_params.crc32 == params_crc(&s_params)) { s_valid = 1; } else { params_load_defaults(); s_valid = 0; } } /** * @brief Restore factory defaults: load defaults into RAM and program them * to flash. * @return 0 on success, 1 on flash failure. */ uint8_t app_params_restore_defaults(void) { params_load_defaults(); return params_save(); } const caiic_params_t* app_params_get(void) { return &s_params; } uint8_t app_params_is_valid(void) { return s_valid; } /* ------------------------------------------------------------------ */ /* JSON interface (appget json / appset json {...}): self-describing */ /* output and forward-compatible input - unknown keys are ignored so */ /* newer firmware params do not break older clients. Storage stays */ /* the binary caiic_params_t record; JSON is an interface view only. */ /* ------------------------------------------------------------------ */ /** * @brief Print the params as a JSON object, e.g. {"led":500,"pwm":0,"valid":1} */ static void params_print_json(void) { cJSON* root; char* out; infra_cjson_init(); root = cJSON_CreateObject(); if (root == NULL) { cli_write("json: alloc fail\r\n"); return; } cJSON_AddNumberToObject(root, "led", s_params.led1_blink_ms); cJSON_AddNumberToObject(root, "pwm", s_params.pwm_duty_pct); cJSON_AddNumberToObject(root, "valid", s_valid); out = cJSON_PrintUnformatted(root); cJSON_Delete(root); if (out == NULL) { cli_write("json: alloc fail\r\n"); return; } cli_write(out); cli_write("\r\n"); cJSON_free(out); } /** * @brief Apply a JSON object like {"led":500,"pwm":50}. All known keys are * range-checked first; on any violation nothing is modified. * Unknown keys are ignored (forward compatibility). */ static void params_set_json(const char* json) { cJSON* root; cJSON* item; long led = -1; long pwm = -1; infra_cjson_init(); root = cJSON_Parse(json); if (root == NULL || !cJSON_IsObject(root)) { cli_write("appset json: parse fail\r\n"); if (root != NULL) { cJSON_Delete(root); } return; } item = cJSON_GetObjectItem(root, "led"); if (cJSON_IsNumber(item)) { led = item->valueint; } item = cJSON_GetObjectItem(root, "pwm"); if (cJSON_IsNumber(item)) { pwm = item->valueint; } if (led < 0 && pwm < 0) { cli_write("appset json: no known key (led, pwm)\r\n"); cJSON_Delete(root); return; } if (led >= 0 && (led < (long)PARAM_LED1_BLINK_MS_MIN || led > (long)PARAM_LED1_BLINK_MS_MAX)) { cli_printf("led range %u..%u\r\n", (unsigned int)PARAM_LED1_BLINK_MS_MIN, (unsigned int)PARAM_LED1_BLINK_MS_MAX); cJSON_Delete(root); return; } if (pwm >= 0 && pwm > (long)PARAM_PWM_DUTY_PCT_MAX) { cli_printf("pwm range 0..%u\r\n", (unsigned int)PARAM_PWM_DUTY_PCT_MAX); cJSON_Delete(root); return; } cJSON_Delete(root); if (led >= 0) { s_params.led1_blink_ms = (uint16_t)led; } if (pwm >= 0) { s_params.pwm_duty_pct = (uint32_t)pwm; } if (params_save() == 0) { params_print_json(); } else { cli_write("appset json: flash fail\r\n"); } } /* ------------------------------------------------------------------ */ /* CLI: appget / appset */ /* ------------------------------------------------------------------ */ void AppParams_CmdAppget(int argc, char* argv[]) { (void)argv; if (argc >= 2 && strcmp(argv[1], "json") == 0) { params_print_json(); return; } cli_printf("params: %s\r\n", s_valid ? "valid" : "default (not saved)"); cli_printf(" led = %u (led1 blink half period, ms)\r\n", (unsigned int)s_params.led1_blink_ms); cli_printf(" pwm = %lu (pwm duty, %%)\r\n", (unsigned long)s_params.pwm_duty_pct); } void AppParams_CmdAppset(int argc, char* argv[]) { uint32_t v; if (argc < 3) { cli_write("usage: appset | appset json {\"led\":500,\"pwm\":50}\r\n"); cli_write(" led = led1 blink half period 50..10000 ms\r\n"); cli_write(" pwm = pwm duty 0..100 %\r\n"); return; } if (strcmp(argv[1], "json") == 0) { /* cli_exec_line() splits on spaces; rejoin the JSON argument */ char json[80]; uint32_t pos = 0; int i; for (i = 2; i < argc; i++) { uint32_t n = (uint32_t)strlen(argv[i]); if (pos + n + 2u > sizeof(json)) { cli_write("appset json: line too long\r\n"); return; } if (pos != 0) { json[pos++] = ' '; } memcpy(json + pos, argv[i], n); pos += n; } json[pos] = '\0'; params_set_json(json); return; } if (parse_u32(argv[2], &v) != 0) { cli_write("appset: bad value\r\n"); return; } if (strcmp(argv[1], "led") == 0) { if (v < PARAM_LED1_BLINK_MS_MIN || v > PARAM_LED1_BLINK_MS_MAX) { cli_printf("led range %u..%u\r\n", (unsigned int)PARAM_LED1_BLINK_MS_MIN, (unsigned int)PARAM_LED1_BLINK_MS_MAX); return; } s_params.led1_blink_ms = (uint16_t)v; } else if (strcmp(argv[1], "pwm") == 0) { if (v > PARAM_PWM_DUTY_PCT_MAX) { cli_printf("pwm range 0..%u\r\n", (unsigned int)PARAM_PWM_DUTY_PCT_MAX); return; } s_params.pwm_duty_pct = v; } else { cli_write("unknown param (led, pwm)\r\n"); return; } if (params_save() == 0) { cli_printf("%s = %lu, saved\r\n", argv[1], (unsigned long)v); } else { cli_write("appset: flash fail\r\n"); } }