355 lines
9.3 KiB
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");
|
|
}
|
|
}
|