/* USER CODE BEGIN Header */ /**@Doxygen风格注释 ****************************************************************************** * @file : kv_flash.c * @brief : ****************************************************************************** * @attention * * * * * * * * ****************************************************************************** */ /* USER CODE END Header */ /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "kv_flash.h" #include "uType.h" #include "Printf.h" #include #include #include #include "cmsis_os.h" #include // PRIu32 等宏 #include // for isprint #include // offsetof /* USER CODE END Includes */ /* Exported types ------------------------------------------------------------*/ /* USER CODE BEGIN ET */ #define KV_ROOT(root, name, type) { #name, offsetof(root, name), type, sizeof(((root*)0)->name), 0 } #define KV_WORK(root, name, type) { #name, offsetof(root, name), type, sizeof(((root*)0)->name), 1 } extern IWDG_HandleTypeDef hiwdg; size_t init_index = 0; static char db_cache[3][DB_FLASH_SIZE]; // 每个数据库的缓存 static uint16_t db_cache_len[3]; // 每个数据库内的缓存长度 /**@BEGIN ------------ 映射表 -------------------*/ static const uint32_t DB_ADDR[DB_ID_MAX] = { [DB_ID_SYSTEM] = 0x0803C000, [DB_ID_CALIB] = 0x0803C800, [DB_ID_SNAPSHOT] = 0x0803D000, }; static const InitEntry init_table[] = { // SystemConfig {DB_ID_SYSTEM, KV_KEY_MAC "=001800000000"}, {DB_ID_SYSTEM, KV_KEY_ROLE "=1"}, {DB_ID_SYSTEM, KV_KEY_CONSOLE "=1"}, {DB_ID_SYSTEM, KV_KEY_ADDR "=255"}, {DB_ID_SYSTEM, KV_KEY_BAUDRATE "=2"}, {DB_ID_SYSTEM, KV_KEY_VERSION "=0007C128"}, // CalibrationParams {DB_ID_CALIB, KV_KEY_ROD_VOL0 "=2.637"}, {DB_ID_CALIB, KV_KEY_ROD_VOL1 "=2.905"}, {DB_ID_CALIB, KV_KEY_ROD_VOL2 "=3.165"}, {DB_ID_CALIB, KV_KEY_ROD_VOL3 "=3.504"}, {DB_ID_CALIB, KV_KEY_ROD_VOL4 "=3.863"}, {DB_ID_CALIB, KV_KEY_ROD_VOL5 "=4.125"}, {DB_ID_CALIB, KV_KEY_ROD_VOL6 "=4.402"}, {DB_ID_CALIB, KV_KEY_OIL_CAP0 "=1.86"}, {DB_ID_CALIB, KV_KEY_OIL_CAP1 "=2.01"}, {DB_ID_CALIB, KV_KEY_OIL_CAP2 "=2.14"}, {DB_ID_CALIB, KV_KEY_OIL_CAP3 "=2.35"}, {DB_ID_CALIB, KV_KEY_OIL_CAP4 "=3.05"}, {DB_ID_CALIB, KV_KEY_OIL_CAP5 "=3.12"}, {DB_ID_CALIB, KV_KEY_OIL_CAP6 "=3.22"}, {DB_ID_CALIB, KV_KEY_OIL_CAP7 "=3.39"}, {DB_ID_CALIB, KV_KEY_OIL_CAP8 "=3.65"}, {DB_ID_CALIB, KV_KEY_OIL_CAP9 "=3.84"}, {DB_ID_CALIB, KV_KEY_OIL_CAP10 "=3.98"}, {DB_ID_CALIB, KV_KEY_RES_VOL0 "=2.713"}, {DB_ID_CALIB, KV_KEY_RES_VOL1 "=2.739"}, {DB_ID_CALIB, KV_KEY_RES_VOL2 "=2.766"}, {DB_ID_CALIB, KV_KEY_RES_VOL3 "=2.788"}, {DB_ID_CALIB, KV_KEY_RES_VOL4 "=2.821"}, {DB_ID_CALIB, KV_KEY_RES_VOL5 "=2.855"}, {DB_ID_CALIB, KV_KEY_RES_CAP0 "=2.678"}, {DB_ID_CALIB, KV_KEY_RES_CAP1 "=2.356"}, {DB_ID_CALIB, KV_KEY_RES_CAP2 "=1.863"}, {DB_ID_CALIB, KV_KEY_RES_CAP3 "=1.545"}, {DB_ID_CALIB, KV_KEY_RES_CAP4 "=9.87"}, {DB_ID_CALIB, KV_KEY_RES_CAP5 "=3.98"}, {DB_ID_CALIB, KV_KEY_ROD_VOL_OFFSET "=0.008"}, {DB_ID_CALIB, KV_KEY_ROD_DIALOG "=10"}, {DB_ID_CALIB, KV_KEY_ROD_COL_TIME "=10"}, {DB_ID_CALIB, KV_KEY_OIL_PARA_INDU "=18"}, {DB_ID_CALIB, KV_KEY_OIL_PARA_CAP "=33"}, {DB_ID_CALIB, KV_KEY_OIL_CAP_OFFSET "=0"}, {DB_ID_CALIB, KV_KEY_OIL_DIALOG "=10"}, {DB_ID_CALIB, KV_KEY_OIL_COL_TIME "=10"}, {DB_ID_CALIB, KV_KEY_RES_PARA_INDU "=18"}, {DB_ID_CALIB, KV_KEY_RES_PARA_CAP "=33"}, {DB_ID_CALIB, KV_KEY_RES_CAP_OFFSET "=0"}, {DB_ID_CALIB, KV_KEY_RES_VOL_OFFSET "=0.008"}, {DB_ID_CALIB, KV_KEY_RES_CAP_DIALOG "=10"}, {DB_ID_CALIB, KV_KEY_RES_VOL_DIALOG "=10"}, {DB_ID_CALIB, KV_KEY_RES_CAP_COL_TIME "=10"}, {DB_ID_CALIB, KV_KEY_RES_VOL_COL_TIME "=10"}, {DB_ID_CALIB, KV_KEY_TEMP_VOL_OFFSET "=0.008"}, {DB_ID_CALIB, KV_KEY_TEMP_DIALOG "=10"}, {DB_ID_CALIB, KV_KEY_TEMP_COL_TIME "=10"}, // SnapshotInfo {DB_ID_SNAPSHOT, KV_KEY_IAP_LOAD "=0"}, {DB_ID_SNAPSHOT, KV_KEY_IAP_SIZE "=40960"}, {DB_ID_SNAPSHOT, KV_KEY_IAP_MD5 "=00000000"}, {DB_ID_SNAPSHOT, KV_KEY_UPDATE_OK "=2"}, {DB_ID_SNAPSHOT, KV_KEY_UPDATE_ERR "=2"}, {DB_ID_SNAPSHOT, KV_KEY_UBOOTBACK "=400"} }; typedef enum { FIELD_INT, FIELD_UINT8, FIELD_UINT16, FIELD_UINT32, FIELD_FLOAT, FIELD_STRING } FieldType; typedef struct { const char *key; // KV 名 size_t offset; // 在 JsRoot/J sRoot.work 中的偏移 FieldType type; // 字段类型 size_t size; // 字段大小(字符串用) uint8_t in_work; // 0 表示在 JsRoot,1 表示在 JsRoot.work } KvFieldMap; const KvFieldMap kv_map[] = { // ===== JsRoot ===== {"mac", offsetof(JsonDat_Root, mac), FIELD_STRING, sizeof(JsRoot.mac), 0}, {"role", offsetof(JsonDat_Root, role), FIELD_INT, sizeof(JsRoot.role), 0}, {"console", offsetof(JsonDat_Root, console), FIELD_INT, sizeof(JsRoot.console), 0}, {"addr", offsetof(JsonDat_Root, addr), FIELD_UINT8, sizeof(JsRoot.addr), 0}, {"baudrate", offsetof(JsonDat_Root, baudrate), FIELD_INT, sizeof(JsRoot.baudrate), 0}, {"version", offsetof(JsonDat_Root, vers_id), FIELD_UINT32, sizeof(JsRoot.vers_id), 0}, {"iapLoadStatus", offsetof(JsonDat_Root, iapLoadStatus), FIELD_UINT32, sizeof(JsRoot.iapLoadStatus), 0}, {"iapsize", offsetof(JsonDat_Root, iapSize), FIELD_INT, sizeof(JsRoot.iapSize), 0}, {"iapmd5", offsetof(JsonDat_Root, iapMd5), FIELD_STRING, sizeof(JsRoot.iapMd5), 0}, {"subcode", offsetof(JsonDat_Root, subcode), FIELD_UINT16, sizeof(JsRoot.subcode), 0}, {"update_ok", offsetof(JsonDat_Root, update_ok), FIELD_INT, sizeof(JsRoot.update_ok), 0}, {"update_err",offsetof(JsonDat_Root, update_err),FIELD_INT, sizeof(JsRoot.update_err), 0}, {"ubootback", offsetof(JsonDat_Root, ubootback), FIELD_INT, sizeof(JsRoot.ubootback), 0}, // ===== JsWork ===== {"rod_vol0", offsetof(JsonDat_Work, calib_rod) + offsetof(Calib_rod_t, vol_values[0]), FIELD_FLOAT, sizeof(JsWork.calib_rod.vol_values[0]), 1}, {"rod_vol1", offsetof(JsonDat_Work, calib_rod) + offsetof(Calib_rod_t, vol_values[1]), FIELD_FLOAT, sizeof(JsWork.calib_rod.vol_values[1]), 1}, {"rod_vol2", offsetof(JsonDat_Work, calib_rod) + offsetof(Calib_rod_t, vol_values[2]), FIELD_FLOAT, sizeof(JsWork.calib_rod.vol_values[2]), 1}, {"rod_vol3", offsetof(JsonDat_Work, calib_rod) + offsetof(Calib_rod_t, vol_values[3]), FIELD_FLOAT, sizeof(JsWork.calib_rod.vol_values[3]), 1}, {"rod_vol4", offsetof(JsonDat_Work, calib_rod) + offsetof(Calib_rod_t, vol_values[4]), FIELD_FLOAT, sizeof(JsWork.calib_rod.vol_values[4]), 1}, {"rod_vol5", offsetof(JsonDat_Work, calib_rod) + offsetof(Calib_rod_t, vol_values[5]), FIELD_FLOAT, sizeof(JsWork.calib_rod.vol_values[5]), 1}, {"rod_vol6", offsetof(JsonDat_Work, calib_rod) + offsetof(Calib_rod_t, vol_values[6]), FIELD_FLOAT, sizeof(JsWork.calib_rod.vol_values[6]), 1}, {"oil_cap0", offsetof(JsonDat_Work, calib_oil_water) + offsetof(CalibPoints_t, cap_values[0]), FIELD_FLOAT, sizeof(JsWork.calib_oil_water.cap_values[0]), 1}, {"oil_cap1", offsetof(JsonDat_Work, calib_oil_water) + offsetof(CalibPoints_t, cap_values[1]), FIELD_FLOAT, sizeof(JsWork.calib_oil_water.cap_values[1]), 1}, {"oil_cap2", offsetof(JsonDat_Work, calib_oil_water) + offsetof(CalibPoints_t, cap_values[2]), FIELD_FLOAT, sizeof(JsWork.calib_oil_water.cap_values[2]), 1}, {"oil_cap3", offsetof(JsonDat_Work, calib_oil_water) + offsetof(CalibPoints_t, cap_values[3]), FIELD_FLOAT, sizeof(JsWork.calib_oil_water.cap_values[3]), 1}, {"oil_cap4", offsetof(JsonDat_Work, calib_oil_water) + offsetof(CalibPoints_t, cap_values[4]), FIELD_FLOAT, sizeof(JsWork.calib_oil_water.cap_values[4]), 1}, {"oil_cap5", offsetof(JsonDat_Work, calib_oil_water) + offsetof(CalibPoints_t, cap_values[5]), FIELD_FLOAT, sizeof(JsWork.calib_oil_water.cap_values[5]), 1}, {"oil_cap6", offsetof(JsonDat_Work, calib_oil_water) + offsetof(CalibPoints_t, cap_values[6]), FIELD_FLOAT, sizeof(JsWork.calib_oil_water.cap_values[6]), 1}, {"oil_cap7", offsetof(JsonDat_Work, calib_oil_water) + offsetof(CalibPoints_t, cap_values[7]), FIELD_FLOAT, sizeof(JsWork.calib_oil_water.cap_values[7]), 1}, {"oil_cap8", offsetof(JsonDat_Work, calib_oil_water) + offsetof(CalibPoints_t, cap_values[8]), FIELD_FLOAT, sizeof(JsWork.calib_oil_water.cap_values[8]), 1}, {"oil_cap9", offsetof(JsonDat_Work, calib_oil_water) + offsetof(CalibPoints_t, cap_values[9]), FIELD_FLOAT, sizeof(JsWork.calib_oil_water.cap_values[9]), 1}, {"oil_cap10", offsetof(JsonDat_Work, calib_oil_water) + offsetof(CalibPoints_t, cap_values[10]), FIELD_FLOAT, sizeof(JsWork.calib_oil_water.cap_values[10]), 1}, {"res_vol0", offsetof(JsonDat_Work, calib_residual_oil) + offsetof(Calib_residual_oil_t, vol_values[0]), FIELD_FLOAT, sizeof(JsWork.calib_residual_oil.vol_values[0]), 1}, {"res_vol1", offsetof(JsonDat_Work, calib_residual_oil) + offsetof(Calib_residual_oil_t, vol_values[1]), FIELD_FLOAT, sizeof(JsWork.calib_residual_oil.vol_values[1]), 1}, {"res_vol2", offsetof(JsonDat_Work, calib_residual_oil) + offsetof(Calib_residual_oil_t, vol_values[2]), FIELD_FLOAT, sizeof(JsWork.calib_residual_oil.vol_values[2]), 1}, {"res_vol3", offsetof(JsonDat_Work, calib_residual_oil) + offsetof(Calib_residual_oil_t, vol_values[3]), FIELD_FLOAT, sizeof(JsWork.calib_residual_oil.vol_values[3]), 1}, {"res_vol4", offsetof(JsonDat_Work, calib_residual_oil) + offsetof(Calib_residual_oil_t, vol_values[4]), FIELD_FLOAT, sizeof(JsWork.calib_residual_oil.vol_values[4]), 1}, {"res_vol5", offsetof(JsonDat_Work, calib_residual_oil) + offsetof(Calib_residual_oil_t, vol_values[5]), FIELD_FLOAT, sizeof(JsWork.calib_residual_oil.vol_values[5]), 1}, {"res_cap0", offsetof(JsonDat_Work, calib_residual_oil) + offsetof(Calib_residual_oil_t, cap_values[0]), FIELD_FLOAT, sizeof(JsWork.calib_residual_oil.cap_values[0]), 1}, {"res_cap1", offsetof(JsonDat_Work, calib_residual_oil) + offsetof(Calib_residual_oil_t, cap_values[1]), FIELD_FLOAT, sizeof(JsWork.calib_residual_oil.cap_values[1]), 1}, {"res_cap2", offsetof(JsonDat_Work, calib_residual_oil) + offsetof(Calib_residual_oil_t, cap_values[2]), FIELD_FLOAT, sizeof(JsWork.calib_residual_oil.cap_values[2]), 1}, {"res_cap3", offsetof(JsonDat_Work, calib_residual_oil) + offsetof(Calib_residual_oil_t, cap_values[3]), FIELD_FLOAT, sizeof(JsWork.calib_residual_oil.cap_values[3]), 1}, {"res_cap4", offsetof(JsonDat_Work, calib_residual_oil) + offsetof(Calib_residual_oil_t, cap_values[4]), FIELD_FLOAT, sizeof(JsWork.calib_residual_oil.cap_values[4]), 1}, {"res_cap5", offsetof(JsonDat_Work, calib_residual_oil) + offsetof(Calib_residual_oil_t, cap_values[5]), FIELD_FLOAT, sizeof(JsWork.calib_residual_oil.cap_values[5]), 1}, {"rod_vol_offset", offsetof(JsonDat_Work, rod_vol_offset), FIELD_FLOAT, sizeof(JsWork.rod_vol_offset), 1}, {"rod_dialog", offsetof(JsonDat_Work, rod_dialog), FIELD_FLOAT, sizeof(JsWork.rod_dialog), 1}, {"rod_coltime", offsetof(JsonDat_Work, rod_coltime), FIELD_UINT16, sizeof(JsWork.rod_coltime), 1}, {"oil_para_indu", offsetof(JsonDat_Work, oil_para_indu), FIELD_FLOAT, sizeof(JsWork.oil_para_indu), 1}, {"oil_para_cap", offsetof(JsonDat_Work, oil_para_cap), FIELD_FLOAT, sizeof(JsWork.oil_para_cap), 1}, {"oil_cap_offset", offsetof(JsonDat_Work, oil_cap_offset), FIELD_FLOAT, sizeof(JsWork.oil_cap_offset), 1}, {"oil_dialog", offsetof(JsonDat_Work, oil_dialog), FIELD_FLOAT, sizeof(JsWork.oil_dialog), 1}, {"rod_oil_coltime", offsetof(JsonDat_Work, rod_oil_coltime), FIELD_UINT16, sizeof(JsWork.rod_oil_coltime), 1}, {"res_para_indu", offsetof(JsonDat_Work, res_para_indu), FIELD_FLOAT, sizeof(JsWork.res_para_indu), 1}, {"res_para_cap", offsetof(JsonDat_Work, res_para_cap), FIELD_FLOAT, sizeof(JsWork.res_para_cap), 1}, {"res_cap_offset", offsetof(JsonDat_Work, res_cap_offset), FIELD_FLOAT, sizeof(JsWork.res_cap_offset), 1}, {"res_vol_offset", offsetof(JsonDat_Work, res_vol_offset), FIELD_FLOAT, sizeof(JsWork.res_vol_offset), 1}, {"res_cap_dialog", offsetof(JsonDat_Work, res_cap_dialog), FIELD_FLOAT, sizeof(JsWork.res_cap_dialog), 1}, {"res_vol_dialog", offsetof(JsonDat_Work, res_vol_dialog), FIELD_FLOAT, sizeof(JsWork.res_vol_dialog), 1}, {"res_oil_cap_coltime", offsetof(JsonDat_Work, res_oil_cap_coltime), FIELD_UINT16, sizeof(JsWork.res_oil_cap_coltime), 1}, {"res_oil_vol_coltime", offsetof(JsonDat_Work, res_oil_vol_coltime), FIELD_UINT16, sizeof(JsWork.res_oil_vol_coltime), 1}, {"temp_vol_offset",offsetof(JsonDat_Work, temp_vol_offset), FIELD_FLOAT, sizeof(JsWork.temp_vol_offset), 1}, {"temp_dialog", offsetof(JsonDat_Work, temp_dialog), FIELD_FLOAT, sizeof(JsWork.temp_dialog), 1}, {"temp_coltime", offsetof(JsonDat_Work, temp_coltime), FIELD_UINT16, sizeof(JsWork.temp_coltime), 1}, }; /**@END ---映射表 */ /* USER CODE END ET */ /* Exported functions prototypes ---------------------------------------------*/ /**@BEGIN ------------ 小工具函数 -------------------*/ static uint8_t is_valid_db_id(uint8_t id) { // 数据库编号是否过界 return id < DB_ID_MAX; } size_t get_db_used_len(uint8_t db_id){ // 倒着查找该数据库里写入了多少数据 uint32_t addr = DB_ADDR[db_id]; const uint8_t *p = (const uint8_t *)addr; for (int i = 2047; i >= 0; i--) { if (p[i] != 0xFF) { return i + 1; } } return 0; // 全空 } static inline float str_to_float(const char *s) { // char转float return (float)strtof(s, NULL); } const char* memmem_custom(const char *haystack, size_t haylen,const char *needle, size_t neelen) { if (!haystack || !needle || neelen == 0 || haylen < neelen) return NULL; for (size_t i = 0; i <= haylen - neelen; i++) { if (memcmp(haystack + i, needle, neelen) == 0) return haystack + i; } return NULL; } /**@END --- 小工具函数 */ /**@BEGIN ------------ FLASH擦除写入函数 -------------------*/ static uint8_t flash_erase_page(uint32_t addr) { // 擦除指定数据库区域(2KB) 20~40ms __disable_irq(); // 关闭全局中断 HAL_IWDG_Refresh(&hiwdg); HAL_FLASH_Unlock(); FLASH_EraseInitTypeDef erase; uint32_t PageError = 0; erase.TypeErase = FLASH_TYPEERASE_PAGES; erase.Page = (addr - FLASH_BASE) / DB_FLASH_PAGE_SIZE; erase.NbPages = 1; erase.Banks = FLASH_BANK_1; if (HAL_FLASHEx_Erase(&erase, &PageError) != HAL_OK) { HAL_FLASH_Lock(); return 0; } HAL_FLASH_Lock(); HAL_IWDG_Refresh(&hiwdg); __enable_irq(); // 开启全局中断 return 1; } uint8_t DB_Save(uint8_t id, const char *buf, uint16_t len) { // 写入Flash(整个数据库内容) if (!is_valid_db_id(id) || buf == NULL || len > DB_FLASH_SIZE) { printf("DB_Save par error\n"); return 0; } uint32_t addr = DB_ADDR[id]; // Step1: 擦除整页 if (!flash_erase_page(addr)) { printf("DB_Save: flash_erase fail 0x%08X\n", addr); return 0; } // Step2: 写入字符串数据(按64位字节写入) HAL_FLASH_Unlock(); for (uint32_t i = 0; i < len; i += 8) { uint64_t word64 = 0xFFFFFFFFFFFFFFFFULL; memcpy(&word64, &buf[i], (len - i >= 8) ? 8 : (len - i)); if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr + i, word64) != HAL_OK) { KV_PRINTF("DB_Save: flash_write fail 0x%08X\n", addr + i); HAL_FLASH_Lock(); return 0; } } HAL_FLASH_Lock(); KV_PRINTF("DB_Save: flash_write succ id=%d, len=%d\n", id, len); return 1; } /**@END --- FLASH擦除写入函数 */ /**@BEGIN ------------ 键值对处理函数 -------------------*/ uint8_t SaveKvCache(uint8_t id, const char *kv_str){ // 支持有则更新,无则新增,值为空则删除 static char db_buf[DB_FLASH_SIZE]; static char new_buf[DB_FLASH_SIZE]; if (!is_valid_db_id(id) || kv_str == NULL || strlen(kv_str) >= KV_ITEM_MAX_LEN) { printf("[KV] Invalid DB ID or kv_str too long/null\n"); return 0; } char key[KV_MAX_KEY_LEN + 1] = {0}; const char *eq = strchr(kv_str, '='); if (!eq || eq - kv_str > KV_MAX_KEY_LEN) { printf("[KV] '=' not found or key too long in '%s'\n", kv_str); return 0; } strncpy(key, kv_str, eq - kv_str); key[eq - kv_str] = '\0'; KV_PRINTF("[KV] Processing key='%s'\n", key); memset(db_buf, 0, DB_FLASH_SIZE); uint16_t old_len = DB_Dump(id, db_buf, DB_FLASH_SIZE); // 从 flash 对应 DB 区域读取数据到 db_buf KV_PRINTF("[KV] Existing DB length: %u bytes\n", old_len); memset(new_buf, 0, DB_FLASH_SIZE); int found = 0; // 是否找到相同 key const char *pos = db_buf; while ((pos = strstr(pos, "##")) != NULL) { const char *kv = pos + 2; const char *next = strstr(kv, "##"); size_t len = next ? (size_t)(next - kv) : strlen(kv); char temp[KV_ITEM_MAX_LEN] = {0}; strncpy(temp, kv, len); temp[len] = '\0'; const char *eq_in = strchr(temp, '='); if (eq_in) { char key_in[KV_MAX_KEY_LEN + 1] = {0}; strncpy(key_in, temp, eq_in - temp); key_in[eq_in - temp] = '\0'; if (strcmp(key_in, key) == 0) { // 找到相同 key found = 1; if (strlen(eq + 1) > 0) { // 找到相同 key , 更新值或删除 // 更新:保持位置不变 strcat(new_buf, "##"); strcat(new_buf, kv_str); KV_PRINTF("[KV] Updating key in place: %s\n", kv_str); } else { // 删除:跳过这个 key KV_PRINTF("[KV] Deleting key: %s\n", key_in); } } else { // 不是目标 key,原样拷贝 strcat(new_buf, "##"); strcat(new_buf, temp); // 找不到相同 key , 复制到 new_buf } } if (!next) break; pos = next; } // 如果没找到,且新值不为空 → 追加到末尾 if (!found && strlen(eq + 1) > 0) { strcat(new_buf, "##"); strcat(new_buf, kv_str); KV_PRINTF("[KV] Adding new kv at end: %s\n", kv_str); } strcat(new_buf, "##"); // 结尾 KV_PRINTF("[KV] Final DB content (len=%u):\n%s\n", (unsigned int)strlen(new_buf), new_buf); uint8_t result = DB_Save(id, new_buf, strlen(new_buf)); // 把更新后的数据存回 flash if (result) { // KV_PRINTF("[KV] Flash save success\n"); } else { // printf("[KV] Flash save failed\n"); } return result; } void handle_cfg_set_common(uint8_t db_id, const char *key, const char *value){ // 通用处理函数 删除 更新 新增 if (!is_valid_db_id(db_id)) { printf("error=invalid db id, rlt=411\r\n"); return; } if (strlen(value) == 0) { // value 为空,删除 key KV_HandleDelete(db_id, key); return; } char tmp[64] = {0}; if (pickup_string_from_key(db_id, key, tmp, sizeof(tmp))) { // key 存在 → 更新 KV_HandleUpdate(db_id, key, value); } else { // key 不存在 → 新建 KV_HandleInsert(db_id, key, value); } } void KV_HandleQuery(uint8_t db_id, const char* key){ // 读取 if (!is_valid_db_id(db_id) || key == NULL) { printf("error=invalid db id or null key, rlt=450\r\n"); return; } char val[KV_MAX_VAL_LEN] = {0}; if (pickup_string_from_key(db_id, key, val, sizeof(val))) { printf("fileid=%d, [%s=%s], ok\r\n", db_id, key, val); } else { printf("rlt=404, error=not exist\r\n"); } } void KV_HandleUpdate(uint8_t db_id, const char* key, const char* value){ // 更新 if (!is_valid_db_id(db_id) || key == NULL || value == NULL) { printf("error=invalid params, rlt=451\r\n"); return; } char kv_buf[KV_ITEM_MAX_LEN] = {0}; snprintf(kv_buf, sizeof(kv_buf), "%s=%s", key, value); uint8_t ok = SaveKvCache(db_id, kv_buf); if (ok) { printf("ok\r\n"); } else { printf("error=update fail, rlt=452\r\n"); } } void KV_HandleDelete(uint8_t db_id, const char* key){ // 删除 if (!is_valid_db_id(db_id) || key == NULL) { printf("error=invalid params, rlt=453\r\n"); return; } char kv_buf[KV_ITEM_MAX_LEN] = {0}; char value[1] = {0}; // 空串表示删除 snprintf(kv_buf, sizeof(kv_buf), "%s=%s", key, value); uint8_t ok = SaveKvCache(db_id, kv_buf); if (ok) { printf("ok\r\n"); } else { printf("error=delete fail, rlt=454\r\n"); } } void KV_HandleInsert(uint8_t db_id, const char* key, const char* value){ // 新增 if (!is_valid_db_id(db_id) || key == NULL || value == NULL) { printf("error=invalid params, rlt=455\r\n"); return; } char tmp[KV_MAX_VAL_LEN] = {0}; if (pickup_string_from_key(db_id, key, tmp, sizeof(tmp))) { printf("error=exists, rlt=456\r\n"); return; } char kv_buf[KV_ITEM_MAX_LEN] = {0}; snprintf(kv_buf, sizeof(kv_buf), "%s=%s", key, value); uint8_t ok = SaveKvCache(db_id, kv_buf); if (ok) { printf("ok\r\n"); } else { printf("error=insert fail, rlt=457\r\n"); } } void DB_SaveUInt(uint8_t dbId, const char *key, uint32_t value){ // 将数值存入 KV 数据库 char kv_str[KV_MAX_VAL_LEN]; // 足够存 "key=value" snprintf(kv_str, sizeof(kv_str), "%s=%" PRIu32, key, value); SaveKvCache(dbId, kv_str); } void DB_SaveChar(uint8_t db_id, const char *key, const char *value){ char buf[128]; snprintf(buf, sizeof(buf), "%s=%s", key, value); SaveKvCache(db_id, buf); } void DB_SaveInt(uint8_t dbId, const char *key, int32_t value){ char kv_str[KV_MAX_VAL_LEN]; snprintf(kv_str, sizeof(kv_str), "%s=%" PRId32, key, value); SaveKvCache(dbId, kv_str); } void DB_SaveFloat(uint8_t dbId, const char *key, float value){ char kv_str[KV_MAX_VAL_LEN]; snprintf(kv_str, sizeof(kv_str), "%s=%.6f", key, value); // 保留 6 位小数 SaveKvCache(dbId, kv_str); } /**@END --- 键值对处理函数 */ /**@BEGIN ---------- 提取数据库数据函数 ---------------------------*/ static int extract_kv(const char *src, const char *key, char *val_out, uint8_t max_len) { // 查找"##"分隔符,在分隔符之间查找提取指定key对应的value const char *pos = strstr(src, "##"); while (pos) { const char *kv = pos + 2; const char *eq = strchr(kv, '='); const char *end = strstr(kv, "##"); KV_PRINTF("[extract_kv] Searching key '%s' from segment: %.32s\n", key, kv); if (eq && end && eq < end) { char key_buf[KV_MAX_KEY_LEN + 1] = {0}; strncpy(key_buf, kv, eq - kv); key_buf[eq - kv] = '\0'; KV_PRINTF("[extract_kv] Found key: '%s'\n", key_buf); if (strcmp(key_buf, key) == 0) { size_t val_len = end - eq - 1; if (val_len >= max_len) val_len = max_len - 1; strncpy(val_out, eq + 1, val_len); val_out[val_len] = '\0'; KV_PRINTF("[extract_kv] Matched key! Value: '%s'\n", val_out); return 1; } }else { // printf("[extract_kv] No valid key=value between ##...\n"); } pos = strstr(pos + 2, "##"); } // printf("[extract_kv] Key '%s' not found.\n", key); return 0; } uint8_t pickup_string_from_key(uint8_t id, const char *key, char *val_out, uint8_t max_len) { // 从指定数据库ID的键值存储中提取字符串值 if (!is_valid_db_id(id) || key == NULL || val_out == NULL) { printf("[pickup_string_from_key] Invalid input\n"); return 0; } static char db_buf[DB_FLASH_SIZE]; memset(db_buf, 0, sizeof(db_buf)); uint16_t len = DB_Dump(id, db_buf, DB_FLASH_SIZE); KV_PRINTF("[pickup_string_from_key] Dumped len: %u\n", len); uint8_t res = extract_kv(db_buf, key, val_out, max_len); return res; } uint8_t pickup_int_from_key(uint8_t id, const char *key, int *val_out) { // 从指定数据库ID的键值存储中提取整数值 if (!is_valid_db_id(id) || key == NULL || val_out == NULL) return 0; char val_str[KV_MAX_VAL_LEN] = {0}; if (pickup_string_from_key(id, key, val_str, sizeof(val_str))) { *val_out = atoi(val_str); return 1; } return 0; } uint8_t pickup_uint32_from_key(uint8_t id, const char *key, uint32_t *val_out) { // 从数据库提取 uint32_t 类型 if (!is_valid_db_id(id) || key == NULL || val_out == NULL) return 0; char val_str[KV_MAX_VAL_LEN] = {0}; if (pickup_string_from_key(id, key, val_str, sizeof(val_str))) { *val_out = (uint32_t)strtoul(val_str, NULL, 10); return 1; } return 0; } uint8_t pickup_long_from_key(uint8_t id, const char *key, long *val_out) { // 从数据库提取 long 类型 if (!is_valid_db_id(id) || key == NULL || val_out == NULL) return 0; char val_str[KV_MAX_VAL_LEN] = {0}; if (pickup_string_from_key(id, key, val_str, sizeof(val_str))) { *val_out = strtol(val_str, NULL, 10); return 1; } return 0; } /**@END --- 提取数据库数据函数 */ /**@BEGIN ------------ 控制台命令 一层入口函数 -------------------*/ void handle_mac_set_cmd(const char *buf){ // 处理 ###::{"mac":"112233445566"} 设置设备SN号也就是MAC号 uint8_t db_id = 0; // 固定数据库 0 const char *prefix = "{\"mac\":\""; size_t prefix_len = strlen(prefix); // 必须匹配前缀 if (strncmp(buf, prefix, prefix_len) != 0) { printf("error=invalid mac cmd\r\n"); return; } // 找到结束的引号 const char *end = strchr(buf + prefix_len, '"'); if (!end) { printf("error=invalid mac format\r\n"); return; } // 提取 value char value[128] = {0}; size_t vlen = end - (buf + prefix_len); if (vlen >= sizeof(value)) { printf("error=mac too long\r\n"); return; } strncpy(value, buf + prefix_len, vlen); value[vlen] = '\0'; handle_cfg_set_common(db_id, "mac", value); // 更新数据库 strncpy(JsRoot.mac, value, sizeof(JsRoot.mac) - 1); // 同步更新结构体 JsRoot.mac[sizeof(JsRoot.mac) - 1] = '\0'; // 确保结尾有 '\0' } void handle_cfg_set_cmd(const char *buf){ // 处理 --cfg_set=${key1}=${val1} 系统配置-KV设置 专门处理数据库0的键值对 uint8_t db_id = 0; char key[KV_MAX_VAL_LEN]; char value[128]; if (parse_cfg_set_cmd(buf, &db_id, key, sizeof(key), value, sizeof(value)) != 0) { printf("error=invalid cfg_set format, rlt=410\r\n"); return; } handle_cfg_set_common(db_id, key, value); // 更新数据库 sync_JsRoot_field(key, value); // 同步更新 JsRoot 对应字段 } void handle_cfg_setx_cmd(const char *buf){ // 处理 --cfg_setx=X,${key}=${val} 通用配置-KV设置 uint8_t db_id; char key[KV_MAX_VAL_LEN]; char value[128]; if (parse_cfg_setx_cmd(buf, &db_id, key, sizeof(key), value, sizeof(value)) != 0) { printf("error=invalid cfg_setx format, rlt=410\r\n"); return; } handle_cfg_set_common(db_id, key, value); // 更新数据库 sync_JsRoot_field(key, value); // 同步更新 JsRoot 对应字段 } void handle_cfg_get_cmd(const char *buf){ // 处理 --cfg_get=${key} 读取键值内容 const char *prefix = "--cfg_get="; size_t prefix_len = strlen(prefix); if (!buf || strncmp(buf, prefix, prefix_len) != 0) { printf("error=invalid cfg_get format, rlt=420\r\n"); return; } const char *key = buf + prefix_len; if (strlen(key) == 0) { printf("error=empty key, rlt=421\r\n"); return; } char val[128] = {0}; for (uint8_t db_id = 0; db_id < DB_ID_MAX; db_id++) { if (pickup_string_from_key(db_id, key, val, sizeof(val))) { printf("fileid=%d, [%s=%s], ok\r\n", db_id, key, val); return; // 找到就退出 } } // 遍历完还没找到 printf("rlt=404, error=not exist.\r\n"); } void handle_fileid_cmd(const char *buf){ // 处理 --fileid=X,0 读取库存信息 const char *prefix = "--fileid="; size_t prefix_len = strlen(prefix); if (!buf || strncmp(buf, prefix, prefix_len) != 0) { printf("error=invalid fileid format, rlt=430\r\n"); return; } // 解析 X,0 uint8_t db_id = 0; int offset = 0; if (sscanf(buf + prefix_len, "%hhu,%d", &db_id, &offset) != 2) { printf("error=invalid fileid params, rlt=431\r\n"); return; } if (!is_valid_db_id(db_id)) { printf("error=invalid db id, rlt=432\r\n"); return; } // 读取数据库内容 uint32_t addr = DB_ADDR[db_id]; const uint8_t *db_addr = (const uint8_t *)addr; size_t db_len = get_db_used_len(db_id); if (!db_addr || db_len == 0) { printf("error=empty db, rlt=433\r\n"); return; } // 打印头信息 printf("DB_DUMP, fileid=%d, len=%u,\r\n", db_id, (unsigned)db_len); printf("addr=0x%08X+%d,content=\r\n", (unsigned int)db_addr, offset); // 数据库内容是以##key=value##key2=value2## 存储 printf("["); for (size_t i = 0; i < db_len; i++) { char c = db_addr[i]; if (c >= 32 && c <= 126) { putchar(c); } } printf("], ok\r\n"); } void handle_clear_fileid_cmd(const char *buf){ // 处理 --clear_fileid=X 清除内部配置库 const char *prefix = "--clear_fileid="; size_t prefix_len = strlen(prefix); if (!buf || strncmp(buf, prefix, prefix_len) != 0) { printf("error=invalid clear_fileid format, rlt=440\r\n"); return; } uint8_t db_id = (uint8_t)atoi(buf + prefix_len); if (!is_valid_db_id(db_id)) { printf("error=invalid db id, rlt=441\r\n"); return; } uint32_t addr = DB_ADDR[db_id]; if (flash_erase_page(addr)) { printf("ok\r\n"); } else { printf("error=erase fail, rlt=442\r\n"); } } /**@END --- 控制台命令 一层入口函数 */ /**@BEGIN ------------ 控制台命令 二层提取函数---------*/ int parse_cfg_set_cmd(const char *buf, uint8_t *db_id, char *key, size_t key_sz, char *val, size_t val_sz){ // --cfg_set (固定 db=0) 提取 key、value--------- if (!buf || !db_id || !key || !val) return -1; const char *prefix = "--cfg_set="; size_t prefix_len = strlen(prefix); if (strncmp(buf, prefix, prefix_len) != 0) return -1; *db_id = 0; // 固定 0 号库 const char *kv_str = buf + prefix_len; char *eq = strchr(kv_str, '='); if (!eq) return -1; // 提取 key size_t klen = eq - kv_str; if (klen >= key_sz) return -1; strncpy(key, kv_str, klen); key[klen] = '\0'; // 提取 value(可能为空串) const char *val_str = eq + 1; if (strlen(val_str) >= val_sz) return -1; strcpy(val, val_str); return 0; } int parse_cfg_setx_cmd(const char *buf, uint8_t *db_id, char *key, size_t key_sz, char *val, size_t val_sz){ // --cfg_setx 提取 db、key、value-------------- if (!buf || !db_id || !key || !val) return -1; // 格式: --cfg_setx=X,key=value // 先找到 '=' 号 const char *prefix = "--cfg_setx="; size_t prefix_len = strlen(prefix); if (strncmp(buf, prefix, prefix_len) != 0) return -1; // 解析 DB ID const char *p = buf + prefix_len; char *comma = strchr(p, ','); if (!comma) return -1; *comma = '\0'; *db_id = (uint8_t)atoi(p); *comma = ','; // 还原 // 解析 key 和 value const char *kv_str = comma + 1; // 指向 key=value char *eq = strchr(kv_str, '='); if (!eq) return -1; // 提取 key size_t klen = eq - kv_str; if (klen >= key_sz) return -1; strncpy(key, kv_str, klen); key[klen] = '\0'; // 提取 value(可能为空串) const char *val_str = eq + 1; if (strlen(val_str) >= val_sz) return -1; strcpy(val, val_str); return 0; } /**@END --- 控制台命令 二层提取函数 */ /**@BEGIN --------------- 初始化函数 ------------------*/ void DB_InitAllConfigs(void) { // 初始化数据库 size_t total = sizeof(init_table) / sizeof(init_table[0]); for (size_t i = 0; i < total; i++) { const char *kv_str = init_table[i].kv_str; char key_name[KV_MAX_KEY_LEN + 1] = {0}; const char *eq = strchr(kv_str, '='); if (eq) { strncpy(key_name, kv_str, eq - kv_str); key_name[eq - kv_str] = '\0'; char existing_val[KV_MAX_VAL_LEN] = {0}; uint8_t exists = pickup_string_from_key(init_table[i].db_id, key_name, existing_val, sizeof(existing_val)); if (!exists) { if (!SaveKvCache(init_table[i].db_id, kv_str)) { // printf("SaveKvCache fail: %s\n", kv_str); } else { // printf("SaveKvCache succ: %s\n", kv_str); } } } HAL_IWDG_Refresh(&hiwdg); // 喂狗,防止初始化太久被复位 osDelay(1); } JsRoot.vers_id = CURRENT_FIRMWARE_VERSION; DB_SaveUInt(DB_ID_SYSTEM, KV_KEY_VERSION , JsRoot.vers_id ); // 存入 VERSION 到 SYSTEM 数据库 printf("DB_InitAllConfigs complete.\n"); osDelay(100); DB_Dump_to_JsRoot(); printf("DB_Dump_to_JsRoot ok \r\n"); } uint16_t DB_Dump(uint8_t id, char *buf, uint16_t buf_len) { // 从Flash读取区域内容到缓存 if (!is_valid_db_id(id)) { printf("[DB_Dump] Invalid DB ID: %d\n", id); return 0; } if (buf == NULL) { printf("[DB_Dump] NULL buffer pointer\n"); return 0; } if (buf_len < DB_FLASH_SIZE) { printf("[DB_Dump] Buffer too small: buf_len = %d, required = %d\n", buf_len, DB_FLASH_SIZE); return 0; } uint32_t addr = DB_ADDR[id]; const uint8_t *flash_ptr = (const uint8_t *)addr; // printf("[DB_Dump] Start reading from address: 0x%08X (DB_ID: %d)\n", addr, id); uint16_t i; for (i = 0; i < DB_FLASH_SIZE && i < buf_len - 1; i++) { buf[i] = flash_ptr[i]; if (flash_ptr[i] == 0xFF) { // printf("[DB_Dump] Reached 0xFF at offset %d\n", i); break; } } buf[i] = '\0'; // null终止 return i; } uint8_t pickup_string_from_cache(uint8_t id, const char *key, char *val_out, uint8_t max_len) { // 从缓存提取字符串 if (!is_valid_db_id(id) || key == NULL || val_out == NULL) { return 0; } return extract_kv(db_cache[id], key, val_out, max_len); } uint8_t pickup_int_from_cache(uint8_t id, const char *key, int *val_out) { // 从缓存提取 int if (!is_valid_db_id(id) || key == NULL || val_out == NULL) { return 0; } char val_str[KV_MAX_VAL_LEN] = {0}; if (pickup_string_from_cache(id, key, val_str, sizeof(val_str))) { *val_out = atoi(val_str); return 1; } return 0; } uint8_t pickup_uint32_from_cache(uint8_t id, const char *key, uint32_t *val_out) { // 从缓存提取 uint32_t if (!is_valid_db_id(id) || key == NULL || val_out == NULL) { return 0; } char val_str[KV_MAX_VAL_LEN] = {0}; if (pickup_string_from_cache(id, key, val_str, sizeof(val_str))) { *val_out = (uint32_t)strtoul(val_str, NULL, 10); return 1; } return 0; } uint8_t pickup_long_from_cache(uint8_t id, const char *key, long *val_out) { // 从缓存提取 long if (!is_valid_db_id(id) || key == NULL || val_out == NULL) { return 0; } char val_str[KV_MAX_VAL_LEN] = {0}; if (pickup_string_from_cache(id, key, val_str, sizeof(val_str))) { *val_out = strtol(val_str, NULL, 10); return 1; } return 0; } void DB_Dump_to_JsRoot(void) { // 从数据库同步到 缓存结构体 JsRoot DB_Load_All(); // 一次性加载所有数据库到缓存里 DB_SyncAllToJsRoot(); // 使用缓存解析,不再访问 Flash } void DB_Load_All(void) { // 一次性读取所有数据库到 RAM for (uint8_t id = 0; id < 3; id++) { db_cache_len[id] = DB_Dump(id, db_cache[id], DB_FLASH_SIZE); // db_cache用来缓存每个库的数据 } } void DB_SyncAllToJsRoot(void) { char key[64]; char val[128]; if(JsRoot.work == NULL) JsRoot.work = &JsWork; // 自动赋值 for (uint8_t id = 0; id < 3; id++) { const char *p = db_cache[id]; const char *pend = p + db_cache_len[id]; // printf(">>> Start parsing DB[%d], len=%u\n", id, db_cache_len[id]); while (p < pend) { // 找到 "##" 开头 const char *start = memmem_custom(p, pend - p, "##", 2); if (!start) break; start += 2; // 跳过 ## const char *end = memmem_custom(start, pend - start, "##", 2); if (!end) end = pend; // 没找到结束标记 // 在 start 到 end 之间找 '=' const char *sep = memchr(start, '=', end - start); if (!sep) { // printf("DB[%d] stop: no '=' found at offset=%ld\n", id, start - db_cache[id]); p = end; continue; } size_t klen = sep - start; size_t vlen = end - (sep + 1); if (klen >= sizeof(key)) klen = sizeof(key) - 1; if (vlen >= sizeof(val)) vlen = sizeof(val) - 1; strncpy(key, start, klen); key[klen] = '\0'; strncpy(val, sep + 1, vlen); val[vlen] = '\0'; // printf("DB[%d] key=%s, val=%s, p_offset=%ld\n", id, key, val, start - db_cache[id]); sync_JsRoot_field(key, val); p = end; // 移动到下一个 "##" 开头或结尾 HAL_IWDG_Refresh(&hiwdg); // 刷新看门狗 } // printf("<<< End of DB[%d]\n\n", id); } } void sync_JsRoot_field(const char *key, const char *value) { // 查询更新某个缓存结构体参数 for (size_t i = 0; i < sizeof(kv_map) / sizeof(kv_map[0]); i++) { if (strcmp(key, kv_map[i].key) == 0) { // uint8_t *base = kv_map[i].in_work ? (uint8_t*)&JsRoot.work : (uint8_t*)&JsRoot; uint8_t *base = kv_map[i].in_work ? (uint8_t*)JsRoot.work : (uint8_t*)&JsRoot; void *field_ptr = base + kv_map[i].offset; switch (kv_map[i].type) { case FIELD_STRING: snprintf((char*)field_ptr, kv_map[i].size, "%s", value); break; case FIELD_INT: *(int*)field_ptr = strtol(value, NULL, 10); break; case FIELD_UINT8: *(uint8_t*)field_ptr = (uint8_t)strtoul(value, NULL, 10); break; case FIELD_UINT16: *(uint16_t*)field_ptr = (uint16_t)strtoul(value, NULL, 10); break; case FIELD_UINT32: *(uint32_t*)field_ptr = strtoul(value, NULL, 0); break; case FIELD_FLOAT: *(float*)field_ptr = str_to_float(value); break; } HAL_IWDG_Refresh(&hiwdg); // 刷新看门狗 return; // 找到并更新完成 } } printf("sync_JsRoot_field: unknown key = %s\n", key); } void DB__Dump_IAP_LOAD(void){ int iapload =0; pickup_int_from_key(2,KV_KEY_IAP_LOAD,&iapload); JsRoot.iapLoadStatus = iapload; printf("iapLoadStatus %d\r\n",JsRoot.iapLoadStatus); } /**@END --- 初始化函数 */ /* USER CODE BEGIN EFP */