戰(zhàn):ESP32 Persistent Storage 示例 —— 基于 NVS 的 Key Value Store 測試與 API 使用指南)
Matter connectedhomeip 實(shí)戰(zhàn)ESP32 Persistent Storage 示例 —— 基于 NVS 的 Key Value Store 測試與 API 使用指南【免費(fèi)下載鏈接】connectedhomeipMatter (formerly Project CHIP) creates more connections between more objects, simplifying development for manufacturers and increasing compatibility for consumers, guided by the Connectivity Standards Alliance.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/co/connectedhomeip本指南圍繞 connectedhomeipMatter/Project CHIP倉庫中的persistent-storageESP32 示例展開完整介紹該示例的設(shè)計目標(biāo)、入口代碼、KeyValueStoreManager公共 API 與平臺實(shí)現(xiàn)、8 個 KVS 測試用例的含義以及從環(huán)境準(zhǔn)備、構(gòu)建、燒錄到串口監(jiān)控的全流程操作。讀者完成閱讀后將能夠在自己的 ESP32 Matter 應(yīng)用中正確調(diào)用持久化鍵值存儲接口并理解 NVS 底層實(shí)現(xiàn)的能力邊界與當(dāng)前限制。示例概覽與設(shè)計目標(biāo)示例 README 明確指出這是一個用于測試和演示 key value storageKVSAPI 的示例程序。它的價值體現(xiàn)在兩個層面平臺驗(yàn)證當(dāng) KVS 實(shí)現(xiàn)在不同平臺上適配bring-up時用這套統(tǒng)一測試用例檢驗(yàn)各平臺實(shí)現(xiàn)的正確性API 教學(xué)為開發(fā)者提供一個如何調(diào)用KeyValueStoreMgr()接口讀寫持久化數(shù)據(jù)的完整參考實(shí)現(xiàn)。README 同時說明了一個重要的演進(jìn)方向未來當(dāng)所有平臺都具備可用的 KVS 后這個示例可以被遷移為單元測試unit test從示例工程演變?yōu)槠脚_回歸測試。注意該文檔還明確標(biāo)注了當(dāng)前平臺的已知限制——ESP32 平臺的 KVS 尚未完全實(shí)現(xiàn)特別是不支持 offset 讀取與 partial部分讀取。這一限制與源碼實(shí)現(xiàn)完全吻合后文會從實(shí)現(xiàn)層面詳細(xì)解釋。環(huán)境準(zhǔn)備與前置文檔示例 README 指引讀者先完成兩件事對應(yīng)的完整操作分別位于ESP-IDF 與 CHIP/Matter 環(huán)境搭建構(gòu)建與配對commissioning指南其中構(gòu)建指南docs/platforms/esp32/build_app_and_commission.md給出了完整的環(huán)境變量配置流程這是所有 ESP32 示例包括本示例共用的基礎(chǔ)步驟# 1) 激活 ESP-IDF 工具鏈 $ cd path/to/esp-idf $ source export.sh # 2) 激活 Matter 環(huán)境需在 export.sh 之后執(zhí)行 $ cd path/to/connectedhomeip $ source scripts/activate.sh # 3) 可選啟用 Ccache 加速 IDF 構(gòu)建 $ export IDF_CCACHE_ENABLE1示例代碼結(jié)構(gòu)與入口分析persistent-storage目錄在倉庫中是一個跨平臺共享示例ESP32 只是其中一個平臺實(shí)現(xiàn)。整體結(jié)構(gòu)如下examples/persistent-storage/KeyValueStorageTest.h —— 跨平臺共享的測試聲明定義RunKvsTest()入口和TestConfigurations枚舉examples/persistent-storage/KeyValueStorageTest.cpp —— 跨平臺共享的測試實(shí)現(xiàn)共 7 個測試函數(shù)examples/persistent-storage/esp32/main/main.cpp —— ESP32 平臺入口app_main()examples/persistent-storage/esp32/CMakeLists.txt 與 main/CMakeLists.txt —— ESP-IDF 構(gòu)建配置examples/persistent-storage/esp32/sdkconfig.defaults 與 partitions.csv —— 工程默認(rèn)配置與分區(qū)表。app_main 入口初始化 NVS 后循環(huán)跑測試ESP32 入口代碼main.cpp的邏輯非常清晰extern C void app_main() { esp_err_t err nvs_flash_init(); if (err ! ESP_OK) { ESP_LOGE(TAG, nvs_flash_init() failed: %s, esp_err_to_name(err)); return; } ESP_LOGI(TAG, ); ESP_LOGI(TAG, chip-esp32-persitent-storage-example starting); ESP_LOGI(TAG, ); // Run tests while (true) { ESP_LOGI(TAG, Running Tests:); // Partial and offset reads are not currently supported on the ESP32 // platform, skip these tests, chip::RunKvsTest(chip::SKIP_MULTI_READ_TEST); vTaskDelay(60000); // Run every minute } }關(guān)鍵點(diǎn)必須先nvs_flash_init()ESP32 的 KVS 底層基于 ESP-IDF 的 NVSNon-Volatile Storage子系統(tǒng)使用前必須初始化 flash 上的 NVS 分區(qū)。初始化失敗直接退出不做任何后續(xù)操作以SKIP_MULTI_READ_TEST參數(shù)運(yùn)行測試這與 README 所述的平臺限制一一對應(yīng)——ESP32 尚不支持 offset/partial 讀取因此跳過了專門驗(yàn)證多段讀取的TestMultiRead用例測試每 60 秒循環(huán)執(zhí)行一次vTaskDelay(60000)讓設(shè)備可以反復(fù)對同一片 flash 進(jìn)行寫入/讀取/擦除壓力測試便于觀察 NVS 的磨損與穩(wěn)定性表現(xiàn)。共享測試的入口與配置枚舉KeyValueStorageTest.h 定義了測試配置枚舉enum TestConfigurations { RUN_ALL_TESTS, // 運(yùn)行全部 7 個測試含 TestMultiRead SKIP_MULTI_READ_TEST // 跳過多段讀取測試ESP32 當(dāng)前使用 }; void RunKvsTest(TestConfigurations test_config RUN_ALL_TESTS);支持多段讀取的平臺可以傳入RUN_ALL_TESTS運(yùn)行全部用例不支持的平臺如當(dāng)前的 ESP32則傳入SKIP_MULTI_READ_TEST。KeyValueStoreManager統(tǒng)一的 KVS 公共 API所有平臺共享的公共 API 定義在 src/include/platform/KeyValueStoreManager.h 中位于命名空間chip::DeviceLayer::PersistedStorage。應(yīng)用側(cè)通過單例訪問接口KeyValueStoreMgr()獲取平臺實(shí)現(xiàn)。三個核心操作API說明關(guān)鍵返回碼Put(key, value, value_size)寫入鍵值對key 已存在則覆蓋CHIP_NO_ERRORCHIP_ERROR_INVALID_ARGUMENTkey 為空/過長或 value 過大CHIP_ERROR_PERSISTED_STORAGE_FAILEDGet(key, buffer, buffer_size, read_bytes_size, offset_bytes)讀取鍵對應(yīng)的值到緩沖區(qū)可指定起始偏移CHIP_ERROR_BUFFER_TOO_SMALL緩沖區(qū)放不下全部數(shù)據(jù)時返回并返回已拷貝字節(jié)數(shù)CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUNDkey 不存在CHIP_ERROR_INTEGRITY_CHECK_FAILED數(shù)據(jù)損壞Delete(key)刪除鍵值對CHIP_NO_ERRORCHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUNDGet的一個特別用法是探測 key 是否存在傳入nullptr緩沖區(qū)與0大小此時返回CHIP_NO_ERROR或CHIP_ERROR_BUFFER_TOO_SMALL都說明 key 存在詳見測試用例TestKeyExistence。模板重載對任意平凡可拷貝類型透明讀寫除了原始指針版本KeyValueStoreManager還提供了基于模板的重載KeyValueStoreManager.h讓uint32_t、數(shù)組、結(jié)構(gòu)體等**平凡可拷貝類型trivially copyable**可以直接讀寫template typename T CHIP_ERROR Get(const char * key, T * value) { static_assert(std::is_trivially_copyableT(), KVS values must copyable); static_assert(!std::is_pointerT(), KVS values cannot be pointers); static_assert(CHAR_BIT 8, Current implementation assumes 8 bit.); return Get(key, value, sizeof(T)); } template typename T CHIP_ERROR Put(const char * key, const T value) { static_assert(std::is_trivially_copyableT(), KVS values must copyable); static_assert(!std::is_pointerT(), KVS values cannot be pointers); static_assert(CHAR_BIT 8, Current implementation assumes 8 bit.); return Put(key, value, sizeof(T)); }三個static_assert約束了類型邊界必須平凡可拷貝、不能是指針、假定字節(jié)寬度為 8 位。對象大小由編譯器從類型自動推導(dǎo)調(diào)用方無需手工指定。平臺實(shí)現(xiàn)的分發(fā)機(jī)制公共類通過靜態(tài)分發(fā)把調(diào)用轉(zhuǎn)發(fā)給平臺實(shí)現(xiàn)KeyValueStoreManager.hinline CHIP_ERROR KeyValueStoreManager::Put(const char * key, const void * value, size_t value_size) { return static_castImplClass *(this)-_Put(key, value, value_size); }平臺實(shí)現(xiàn)類KeyValueStoreManagerImpl以friend class KeyValueStoreManager的方式聲明提供_Put/_Get/_Delete前綴方法。ImplClass由CHIP_DEVICE_LAYER_TARGET宏決定構(gòu)建時通過KeyValueStoreManagerImpl.h按目標(biāo)平臺引入對應(yīng)實(shí)現(xiàn)。這正是本示例能在 Linux、QPG、Infineon PSOC6、ESP32 等多個平臺復(fù)用的架構(gòu)基礎(chǔ)。七大 KVS 測試用例逐項(xiàng)解析KeyValueStorageTest.cpp 中共定義了 7 個測試函數(shù)RunKvsTest()逐個執(zhí)行并通過RUN_TEST宏打印PASSED/FAILED失敗時附帶FormatCHIPError格式化的錯誤信息。每個用例的模式都是寫入 → 讀取 → 校驗(yàn) → 刪除因此可以反復(fù)運(yùn)行而不污染存儲。測試函數(shù)測試的 key驗(yàn)證內(nèi)容TestEmptyString()str_key寫入空字符串長度為 0 的值后再讀回校驗(yàn)讀回內(nèi)容與長度覆蓋空值邊界TestKeyExistence()str_key用Get(key, nullptr, 0)探測 key 是否存在允許返回CHIP_NO_ERROR或CHIP_ERROR_BUFFER_TOO_SMALLTestString()str_key字符串test_value的完整寫入/讀回/刪除往返TestUint32()uint32_key通過模板重載讀寫單個uint32_t數(shù)值TestArray()array_key通過模板重載讀寫uint32_t[5]數(shù)組用memcmp逐字節(jié)比對TestStruct()struct_key自定義結(jié)構(gòu)體{uint8_t value1; uint32_t value2;}的序列化讀寫逐字段校驗(yàn)TestUpdateValue()update_key連續(xù) 10 次更新同一個 key值 0~9每次寫后立即讀回校驗(yàn)驗(yàn)證覆蓋更新語義TestMultiRead()multi_key以i * sizeof(uint32_t)為 offset 分 5 次讀取數(shù)組元素驗(yàn)證偏移讀取前 4 次應(yīng)返回CHIP_ERROR_BUFFER_TOO_SMALL最后一次返回CHIP_NO_ERROR其中TestMultiRead()是唯一被 ESP32 跳過的用例KeyValueStorageTest.cpp其注釋與實(shí)現(xiàn)都依賴offset_bytes參數(shù)——而該參數(shù)正是 ESP32 當(dāng)前未實(shí)現(xiàn)的能力。ESP32 平臺實(shí)現(xiàn)NVS 之上的 KVS 適配層ESP32 的實(shí)現(xiàn)位于 src/platform/ESP32/KeyValueStoreManagerImpl.cpp 與 KeyValueStoreManagerImpl.h它把 Matter 的KeyValueStoreManager抽象直接映射到 ESP-IDF 的 NVS API 之上。命名空間與單例實(shí)現(xiàn)類持有靜態(tài)單例sInstance并使用固定的 NVS 命名空間class KeyValueStoreManagerImpl final : public KeyValueStoreManager { private: static inline const char kNamespace[] CHIP_KVS; static KeyValueStoreManagerImpl sInstance; };即所有 Matter KVS 數(shù)據(jù)都存放在 NVS 命名空間CHIP_KVS下。同時提供兩個訪問函數(shù)KeyValueStoreManagerImpl.hKeyValueStoreMgr()—— 返回公共接口單例應(yīng)用通用代碼使用KeyValueStoreMgrImpl()—— 返回平臺專屬接口單例需要訪問 ESP32 特有能力時使用。寫/讀/刪與 NVS API 的對應(yīng)關(guān)系Matter 方法底層 NVS 調(diào)用說明_Putnvs_set_blob()nvs_commit()以 blob 形式寫入原始字節(jié)nvs_commit確保落盤持久化_Getnvs_get_blob()讀取 blobvalue可為nullptr以探測 key 是否存在_Deletenvs_erase_key()nvs_commit()擦除指定 keyEraseAllnvs_erase_all()nvs_commit()清空CHIP_KVS命名空間下全部數(shù)據(jù)所有 NVS 句柄通過 RAII 類Internal::ScopedNvsHandle管理見 src/platform/ESP32/ScopedNvsHandle.h以只讀NVS_READONLY或讀寫NVS_READWRITE模式打開作用域退出自動釋放。錯誤碼通過ReturnMappedErrorOnFailure從esp_err_t映射為CHIP_ERROR。長 key 的 SHA1 哈希處理實(shí)現(xiàn)中最具工程細(xì)節(jié)的部分是HashIfLongKey()KeyValueStoreManagerImpl.cpp。ESP-IDF NVS 對 key 名稱長度有硬性上限NVS_KEY_NAME_MAX_SIZE即 15 字符而 Matter 的 KVS 抽象并不限制 key 長度。為解決該矛盾當(dāng) key 長度 ≥ 15 時對 key 做SHA1 哈希取前 7.5 字節(jié)轉(zhuǎn)換為十六進(jìn)制字符串作為實(shí)際 NVS key哈希結(jié)果只包含十六進(jìn)制字符0-9a-f而正常的 Matter KVS key 前綴通常包含/因此哈希生成的 key 不會與普通 key 沖突函數(shù)通過返回值區(qū)分是否發(fā)生了哈希true表示已哈希調(diào)用方隨后用哈希結(jié)果替換原始 key。代碼注釋中明確記錄了設(shè)計權(quán)衡雖然 SHA1 取 8 字節(jié)存在理論上的沖突概率但在實(shí)際使用場景中可能性很低。偏移讀取明確的未實(shí)現(xiàn)邊界_Get的第一行就給出了 README 所述限制的代碼級證據(jù)KeyValueStoreManagerImpl.cpp// Offset and partial reads are not supported in nvs, for now just return NOT_IMPLEMENTED. Support can be added in the // future if this is needed. VerifyOrReturnError(offset_bytes 0, CHIP_ERROR_NOT_IMPLEMENTED);任何offset_bytes ! 0的讀取都會直接返回CHIP_ERROR_NOT_IMPLEMENTED。頭文件同樣在注釋中說明Currently this platform does not support partial and offset reads, these will returnCHIP_ERROR_NOT_IMPLEMENTED。這就是示例入口必須傳SKIP_MULTI_READ_TEST的根本原因。工程構(gòu)建配置解析頂層 CMakeListsexamples/persistent-storage/esp32/CMakeLists.txtcmake_minimum_required(VERSION 3.20) set(PROJECT_VER v1.0) set(PROJECT_VER_NUMBER 1) include($ENV{IDF_PATH}/tools/cmake/project.cmake) include(${CMAKE_CURRENT_LIST_DIR}/third_party/connectedhomeip/examples/common/cmake/idf_flashing.cmake) set(EXTRA_COMPONENT_DIRS ${CMAKE_CURRENT_LIST_DIR}/third_party/connectedhomeip/config/esp32/components ) project(chip-persistent-storage) idf_build_set_property(CXX_COMPILE_OPTIONS -stdgnu17;-Os;-DCHIP_HAVE_CONFIG_H APPEND) idf_build_set_property(C_COMPILE_OPTIONS -Os APPEND) # For the C3, project_include.cmake sets -Wno-format, but does not clear various # flags that depend on -Wformat idf_build_set_property(COMPILE_OPTIONS -Wno-format-nonliteral;-Wno-format-security APPEND) # -Wmaybe-uninitialized has too many false positives, including on std::optional # and chip::Optional. Make it nonfatal. idf_build_set_property(COMPILE_OPTIONS -Wno-errormaybe-uninitialized APPEND) flashing_script()要點(diǎn)工程名chip-persistent-storage要求CMake ≥ 3.20C 編譯采用-stdgnu17與-Os尺寸優(yōu)化適合 flash 受限的嵌入式環(huán)境并定義-DCHIP_HAVE_CONFIG_H通過EXTRA_COMPONENT_DIRS引入 connectedhomeip 的 ESP32 公共組件config/esp32/components這是所有 ESP32 示例復(fù)用 SDK 代碼的通用手法-Wno-errormaybe-uninitialized用于規(guī)避 GCC 對std::optional/chip::Optional的誤報源碼注釋引用了 gcc.gnu.org bug 80635flashing_script()會生成chip-persistent-storage.flash.py燒錄腳本供后續(xù)一鍵燒錄。main 組件examples/persistent-storage/esp32/main/CMakeLists.txtmain 組件把跨平臺共享的persistent-storage目錄含KeyValueStorageTest.cpp一并納入編譯idf_component_register(PRIV_INCLUDE_DIRS ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/examples/persistent-storage SRC_DIRS ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/examples/persistent-storage) target_compile_options(${COMPONENT_LIB} PRIVATE -DCHIP_HAVE_CONFIG_H)注意此處引用的third_party/connectedhomeip是該示例目錄內(nèi)部的 vendored SDK 副本examples/persistent-storage/esp32/third_party/connectedhomeip它保持了 SDK 的標(biāo)準(zhǔn)目錄布局。sdkconfig 默認(rèn)配置examples/persistent-storage/esp32/sdkconfig.defaults# Use a custom partition table CONFIG_PARTITION_TABLE_CUSTOMy CONFIG_PARTITION_TABLE_FILENAMEpartitions.csv # Vendor and product id CONFIG_DEVICE_VENDOR_ID0xFFF1 CONFIG_DEVICE_PRODUCT_ID0x8009 # Enable HKDF in mbedtls CONFIG_MBEDTLS_HKDF_Cy使用自定義分區(qū)表見下方partitions.csv預(yù)設(shè)廠商 ID0xFFF1與產(chǎn)品 ID0x8009開發(fā)用途的測試 VID/PID啟用 mbedtls 的 HKDF 支持Matter 加密密鑰派生所需。分區(qū)表examples/persistent-storage/esp32/partitions.csv# Name, Type, SubType, Offset, Size, Flags # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap nvs, data, nvs, , 0xC000, phy_init, data, phy, , 0x1000, # Factory partition size about 1.9MB factory, app, factory, , 1920K,三個分區(qū)各司其職nvs分區(qū)48 KB存放鍵值數(shù)據(jù)Matter KVS 的CHIP_KVS命名空間就落在這里phy_init存放 WiFi/藍(lán)牙射頻校準(zhǔn)數(shù)據(jù)factory為應(yīng)用固件分區(qū)約 1.9 MB。由于 KVS 測試會持續(xù)寫入 NVS該分區(qū)大小直接影響可容納的鍵值條目數(shù)與擦寫壽命。構(gòu)建、燒錄與運(yùn)行根據(jù) docs/platforms/esp32/build_app_and_commission.md 的標(biāo)準(zhǔn)流程本示例的構(gòu)建運(yùn)行步驟如下1. 進(jìn)入示例目錄并設(shè)定目標(biāo)芯片$ cd examples/persistent-storage/esp32 $ idf.py set-target esp32 # 或 esp32c3 / esp32s3 等目標(biāo)該文檔指出所有 Matter demo 應(yīng)用支持 ESP32、ESP32C3、ESP32S3 芯片變體ESP32H2/ESP32C6 目前僅針對 lighting-app、lit-icd-app、all-clusters-app 驗(yàn)證過本示例請以倉庫實(shí)際支持為準(zhǔn)。2. 構(gòu)建默認(rèn)使用sdkconfig.defaults直接執(zhí)行$ idf.py build如需自定義配置可先運(yùn)行idf.py menuconfig調(diào)整或通過idf.py -D SDKCONFIG_DEFAULTS... build指定其他 defaults 文件。3. 燒錄與監(jiān)控$ idf.py -p (PORT) erase_flash $ idf.py -p (PORT) flash monitor將(PORT)替換為實(shí)際串口設(shè)備名Linux 下通常是/dev/ttyUSB0。首次燒錄前建議erase_flash清空整片 flash避免殘留數(shù)據(jù)干擾測試。監(jiān)控模式下按Ctrl]退出。4. 查看測試輸出啟動后串口會周期性打印 chip-esp32-persitent-storage-example starting Running Tests: KeyValueStoreMgr().Put(...): PASSED ...每 60 秒循環(huán)一輪全部用例除被跳過的TestMultiRead輸出PASSED即說明 ESP32 平臺的 KVS 基礎(chǔ)讀寫/更新/刪除能力正常。5. 使用生成腳本燒錄可選構(gòu)建過程中flashing_script()已生成chip-persistent-storage.flash.py可一鍵燒錄$ export ESPPORT/dev/tty.SLAB_USBtoUART $ idf.py build $ idf.py flashing_script $ python chip-persistent-storage.flash.py當(dāng)前限制與后續(xù)演進(jìn)綜合 README 與源碼當(dāng)前版本的要點(diǎn)總結(jié)如下offset / partial 讀取未實(shí)現(xiàn)KeyValueStoreManagerImpl::_Get對非零偏移直接返回CHIP_ERROR_NOT_IMPLEMENTED示例因此以SKIP_MULTI_READ_TEST跳過TestMultiReadkey 長度限制的適配NVS key 最長 15 字符Matter 層通過 SHA1 哈希 十六進(jìn)制截斷來支持長 key持久化語義寫入與刪除均顯式調(diào)用nvs_commit保證數(shù)據(jù)真正落盤演進(jìn)方向README 明確該示例未來在平臺條件成熟后可轉(zhuǎn)為單元測試成為 CI 中校驗(yàn)各平臺 KVS 實(shí)現(xiàn)一致性的回歸用例。對于需要在 ESP32 上使用持久化存儲的 Matter 開發(fā)者可以直接借鑒本示例的調(diào)用模式nvs_flash_init()初始化 →KeyValueStoreMgr().Put/Get/Delete讀寫 → 通過返回碼判斷結(jié)果同時在設(shè)計業(yè)務(wù)時避開對偏移讀取的依賴或關(guān)注 SDK 后續(xù)版本對該能力的支持。延伸閱讀KeyValueStoreManager 公共 API 頭文件ESP32 KVS 平臺實(shí)現(xiàn)跨平臺共享測試用例ESP32 平臺配置選項(xiàng)總覽ESP32 構(gòu)建與配對完整指南【免費(fèi)下載鏈接】connectedhomeipMatter (formerly Project CHIP) creates more connections between more objects, simplifying development for manufacturers and increasing compatibility for consumers, guided by the Connectivity Standards Alliance.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/co/connectedhomeip創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考