Skip to content

Commit 885d2cd

Browse files
committed
components/esp_matter: fix string attribute max length lost after NVS
restore
1 parent 0b91331 commit 885d2cd

4 files changed

Lines changed: 149 additions & 0 deletions

File tree

components/esp_matter/data_model/esp_matter_data_model.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,12 @@ attribute_t *create(cluster_t *cluster, uint32_t attribute_id, uint16_t flags, e
581581
// read from the NVS and store in the attribute's storage
582582
esp_matter_attr_val_t temp_val;
583583
temp_val.type = attribute->attribute_val_type;
584+
if (val.type == ESP_MATTER_VAL_TYPE_CHAR_STRING || val.type == ESP_MATTER_VAL_TYPE_LONG_CHAR_STRING ||
585+
val.type == ESP_MATTER_VAL_TYPE_OCTET_STRING || val.type == ESP_MATTER_VAL_TYPE_LONG_OCTET_STRING) {
586+
temp_val.val.a.max = max_val_size;
587+
temp_val.val.a.s = 0;
588+
temp_val.val.a.t = 0;
589+
}
584590
esp_err_t err =
585591
get_val_from_nvs(attribute->endpoint_id, attribute->cluster_id, attribute_id, temp_val);
586592
if (err == ESP_OK) {

components/esp_matter/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
list(APPEND srcs_list "attribute_get_val.cpp")
2+
list(APPEND srcs_list "attribute_create_value_persistence.cpp")
23
list(APPEND srcs_list "attribute_get_val_type.cpp")
34
list(APPEND srcs_list "attribute_report.cpp")
45
list(APPEND srcs_list "cluster_lifecycle_basic.cpp")
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*
6+
* Multi-stage tests (see ESP-IDF unit test guide):
7+
* https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/unit-tests.html#multi-stage-test-cases
8+
*
9+
* On device: run sub-test (1), wait for reboot, run the same menu entry and sub-test (2).
10+
* In CI/QEMU: pytest calls run_all_single_board_cases() which runs both stages automatically.
11+
*/
12+
13+
#include <string.h>
14+
#include <unity.h>
15+
#include <esp_system.h>
16+
#include <esp_matter.h>
17+
#include <esp_matter_core.h>
18+
#include <esp_matter_data_model.h>
19+
#include <esp_matter_cluster.h>
20+
#include <nvs_flash.h>
21+
22+
namespace esp_matter::attribute {
23+
esp_err_t get_val_internal(attribute_t *attribute, esp_matter_attr_val_t *val);
24+
esp_err_t store_val_in_nvs(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id,
25+
const esp_matter_attr_val_t &val);
26+
esp_err_t erase_val_in_nvs(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id);
27+
} // namespace esp_matter::attribute
28+
29+
using namespace esp_matter;
30+
31+
static constexpr uint32_t k_cluster_id = 0xFFF2;
32+
static constexpr uint32_t k_attribute_id = 0x10001;
33+
static constexpr uint16_t k_max_size = 32;
34+
35+
static esp_err_t init_nvs()
36+
{
37+
esp_err_t err = nvs_flash_init();
38+
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
39+
TEST_ASSERT_EQUAL(ESP_OK, nvs_flash_erase());
40+
err = nvs_flash_init();
41+
}
42+
TEST_ASSERT_EQUAL(ESP_OK, err);
43+
return esp_matter_nvs_init();
44+
}
45+
46+
static cluster_t *add_test_cluster(endpoint_t *endpoint)
47+
{
48+
cluster_t *cluster = cluster::create(endpoint, k_cluster_id, CLUSTER_FLAG_SERVER);
49+
TEST_ASSERT_NOT_NULL(cluster);
50+
cluster::global::attribute::create_feature_map(cluster, 0);
51+
cluster::global::attribute::create_cluster_revision(cluster, 1);
52+
return cluster;
53+
}
54+
55+
static void check_max_and_stored(attribute_t *attr)
56+
{
57+
esp_matter_attr_val_t val;
58+
TEST_ASSERT_EQUAL(ESP_OK, attribute::get_val_internal(attr, &val));
59+
TEST_ASSERT_EQUAL(k_max_size, val.val.a.max);
60+
TEST_ASSERT_EQUAL_STRING("stored", reinterpret_cast<char *>(val.val.a.b));
61+
}
62+
63+
static void attr_create_max_before_reboot()
64+
{
65+
TEST_ASSERT_EQUAL(ESP_OK, init_nvs());
66+
67+
if (node::get() != nullptr) {
68+
node::destroy_raw();
69+
}
70+
71+
node::config_t node_config;
72+
node_t *node = node::create(&node_config, nullptr, nullptr);
73+
TEST_ASSERT_NOT_NULL(node);
74+
75+
endpoint_t *ep = endpoint::create(node, ENDPOINT_FLAG_NONE, nullptr);
76+
TEST_ASSERT_NOT_NULL(ep);
77+
uint16_t ep_id = endpoint::get_id(ep);
78+
TEST_ASSERT_EQUAL(1, ep_id);
79+
80+
cluster_t *cluster = add_test_cluster(ep);
81+
attribute::erase_val_in_nvs(ep_id, k_cluster_id, k_attribute_id);
82+
83+
char stored[] = "stored";
84+
esp_matter_attr_val_t nvs_val = esp_matter_char_str(stored, strlen(stored));
85+
TEST_ASSERT_EQUAL(ESP_OK, attribute::store_val_in_nvs(ep_id, k_cluster_id, k_attribute_id, nvs_val));
86+
87+
char empty[] = "";
88+
esp_matter_attr_val_t def = esp_matter_char_str(empty, 0);
89+
attribute_t *attr = attribute::create(cluster, k_attribute_id, ATTRIBUTE_FLAG_NONVOLATILE, def, k_max_size);
90+
TEST_ASSERT_NOT_NULL(attr);
91+
check_max_and_stored(attr);
92+
93+
esp_restart();
94+
}
95+
96+
static void attr_create_max_after_reboot()
97+
{
98+
TEST_ASSERT_EQUAL(ESP_RST_SW, esp_reset_reason());
99+
TEST_ASSERT_EQUAL(ESP_OK, init_nvs());
100+
101+
if (node::get() != nullptr) {
102+
node::destroy_raw();
103+
}
104+
105+
node::config_t node_config;
106+
node_t *node = node::create(&node_config, nullptr, nullptr);
107+
TEST_ASSERT_NOT_NULL(node);
108+
109+
endpoint_t *ep = endpoint::create(node, ENDPOINT_FLAG_NONE, nullptr);
110+
TEST_ASSERT_NOT_NULL(ep);
111+
TEST_ASSERT_EQUAL(1, endpoint::get_id(ep));
112+
113+
cluster_t *cluster = add_test_cluster(ep);
114+
115+
char empty[] = "";
116+
esp_matter_attr_val_t def = esp_matter_char_str(empty, 0);
117+
attribute_t *attr = attribute::create(cluster, k_attribute_id, ATTRIBUTE_FLAG_NONVOLATILE, def, k_max_size);
118+
TEST_ASSERT_NOT_NULL(attr);
119+
check_max_and_stored(attr);
120+
121+
attribute::erase_val_in_nvs(1, k_cluster_id, k_attribute_id);
122+
cluster::destroy(cluster);
123+
}
124+
125+
TEST_CASE_MULTIPLE_STAGES("attribute::create preserves max after reboot",
126+
"[attribute][create][reset=SW_CPU_RESET]",
127+
attr_create_max_before_reboot, attr_create_max_after_reboot);

examples/unit_test_app/pytest_unit_test_app.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,18 @@ def test_jsontlv(dut: QemuDut) -> None:
5656
@pytest.mark.esp32c3
5757
def test_lifecycle(dut: QemuDut) -> None:
5858
run_group(dut, "cluster_lifecycle")
59+
60+
61+
@pytest.mark.host_test
62+
@pytest.mark.qemu
63+
@pytest.mark.esp32c3
64+
def test_attribute_create_value_persistence(dut: QemuDut) -> None:
65+
"""Runs TEST_CASE_MULTIPLE_STAGES cases (both stages, including across SW reset)."""
66+
dut.run_all_single_board_cases(
67+
name=["attribute::create preserves max value after reboot"],
68+
timeout=120,
69+
)
70+
failed = dut.testsuite.failed_cases
71+
if failed:
72+
names = [tc.name for tc in failed]
73+
pytest.fail(f"{len(failed)} failed: {', '.join(names)}")

0 commit comments

Comments
 (0)