mothercup/mcm-ddc-ble/src/app_params.c

355 lines
9.3 KiB
C

/**
* @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 <string.h>
#include <stddef.h>
#include <stdlib.h>
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 <led|pwm> <val> | 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");
}
}