mirror of
https://github.com/espressif/esp-matter.git
synced 2026-04-27 19:13:13 +00:00
[example] Add example to tests optional attributes of migrated clusters along with the script to verify.
Add pytest to CI Fix CI
This commit is contained in:
@@ -146,3 +146,9 @@ examples/unit_test_app:
|
||||
- if: IDF_TARGET in ["esp32c3"]
|
||||
temporary: true
|
||||
reason: the other targets are not tested yet
|
||||
|
||||
examples/test_apps/test_optional_attributes:
|
||||
enable:
|
||||
- if: IDF_TARGET in ["esp32c3"]
|
||||
temporary: true
|
||||
reason: the other targets are not tested yet
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pathlib
|
||||
import pytest
|
||||
import time
|
||||
import re
|
||||
import subprocess
|
||||
from pytest_embedded import Dut
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../../tools/ci')))
|
||||
from gitlab_api import GitLabAPI
|
||||
|
||||
CURRENT_DIR = str(pathlib.Path(__file__).parent) + '/test_optional_attributes'
|
||||
ESP_MATTER_PATH = str(pathlib.Path(__file__).parent.parent.parent)
|
||||
TEST_SCRIPT_PATH = str(pathlib.Path(__file__).parent.parent.parent / 'tools' / 'test_optional_attributes' / 'test_optional_attributes_framework.py')
|
||||
PAA_CERTS_PATH = str(pathlib.Path(__file__).parent.parent.parent / 'connectedhomeip' / 'connectedhomeip' / 'credentials' / 'development' / 'paa-root-certs')
|
||||
|
||||
pytest_build_dir = CURRENT_DIR
|
||||
|
||||
gitlab_api = GitLabAPI()
|
||||
PYTEST_SSID = gitlab_api.ci_gitlab_pytest_ssid
|
||||
PYTEST_PASSPHRASE = gitlab_api.ci_gitlab_pytest_passphrase
|
||||
|
||||
|
||||
@pytest.mark.esp32c3
|
||||
@pytest.mark.esp_matter_dut
|
||||
@pytest.mark.parametrize(
|
||||
'count, app_path, target, erase_all', [
|
||||
(1, pytest_build_dir, 'esp32c3', 'y'),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_optional_attributes_c3(dut: Dut) -> None:
|
||||
dut.expect(r'Configuring CHIPoBLE advertising', timeout=20)
|
||||
time.sleep(5)
|
||||
|
||||
command = (
|
||||
f"python3 {TEST_SCRIPT_PATH}"
|
||||
f" -n 1"
|
||||
f" --commissioning-method ble-wifi"
|
||||
f" --wifi-ssid {PYTEST_SSID}"
|
||||
f" --wifi-passphrase {PYTEST_PASSPHRASE}"
|
||||
f" --passcode 20202021"
|
||||
f" --discriminator 3840"
|
||||
f" --paa-trust-store-path {PAA_CERTS_PATH}"
|
||||
)
|
||||
|
||||
out_str = subprocess.getoutput(command)
|
||||
print(out_str)
|
||||
|
||||
passed = re.search(r'Passed:\s+(\d+)', out_str)
|
||||
failed = re.search(r'Failed:\s+(\d+)', out_str)
|
||||
|
||||
if failed and int(failed.group(1)) > 0:
|
||||
assert False, f"Optional attributes test failed. {failed.group(1)} failures detected."
|
||||
|
||||
if not passed or int(passed.group(1)) == 0:
|
||||
assert False, "Optional attributes test did not report any passed results."
|
||||
@@ -0,0 +1,35 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
if(NOT DEFINED ENV{ESP_MATTER_PATH})
|
||||
message(FATAL_ERROR "Please set ESP_MATTER_PATH to the path of esp-matter repo")
|
||||
endif(NOT DEFINED ENV{ESP_MATTER_PATH})
|
||||
|
||||
if(NOT DEFINED CLI_PROJECT_VER)
|
||||
set(PROJECT_VER "1.0")
|
||||
else()
|
||||
set(PROJECT_VER "${CLI_PROJECT_VER}")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED CLI_PROJECT_VER_NUMBER)
|
||||
set(PROJECT_VER_NUMBER 1)
|
||||
else()
|
||||
set(PROJECT_VER_NUMBER "${CLI_PROJECT_VER_NUMBER}")
|
||||
endif()
|
||||
|
||||
set(ESP_MATTER_PATH $ENV{ESP_MATTER_PATH})
|
||||
set(MATTER_SDK_PATH ${ESP_MATTER_PATH}/connectedhomeip/connectedhomeip)
|
||||
|
||||
# This should be done before using the IDF_TARGET variable.
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS
|
||||
"${MATTER_SDK_PATH}/config/esp32/components"
|
||||
"${ESP_MATTER_PATH}/components"
|
||||
${extra_components_dirs_append})
|
||||
|
||||
project(test_optional_attributes)
|
||||
|
||||
idf_build_set_property(CXX_COMPILE_OPTIONS "-std=gnu++17;-Os;-DCHIP_HAVE_CONFIG_H;-Wno-overloaded-virtual" APPEND)
|
||||
idf_build_set_property(C_COMPILE_OPTIONS "-Os" APPEND)
|
||||
@@ -0,0 +1,32 @@
|
||||
# Optional Attributes Test Application
|
||||
|
||||
This example application creates a Matter node with multiple clusters on the root endpoint, each with all their optional attributes enabled. It serves as the device-side companion to the optional attributes test framework.
|
||||
|
||||
## Overview
|
||||
|
||||
The application:
|
||||
- Creates a Matter Root Node on endpoint 0
|
||||
- Adds optional attributes to existing clusters (Basic Information, General Diagnostics)
|
||||
- Creates additional clusters (Boolean State Configuration, Electrical Energy Measurement, Electrical Power Measurement, Ethernet Network Diagnostics, Occupancy Sensing, HEPA Filter Monitoring, Software Diagnostics, Time Synchronization, WiFi Network Diagnostics) with their optional attributes enabled
|
||||
- Enables the Matter console shell for diagnostics and attribute inspection
|
||||
|
||||
## Hardware Required
|
||||
|
||||
ESP32 board.
|
||||
|
||||
## Build and Flash
|
||||
|
||||
```bash
|
||||
cd examples/test_apps/test_optional_attributes
|
||||
idf.py set-target <target>
|
||||
idf.py build flash monitor
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The key sdkconfig option for this example is:
|
||||
- `CONFIG_ESP_MATTER_ENABLE_OPTIONAL_ATTRIBUTES=y` — enables the optional attribute helper APIs
|
||||
|
||||
## Running Tests
|
||||
|
||||
This application is meant to be used with the optional attributes test framework. See [Optional Attributes Test Suite](../../../tools/test_optional_attributes/README.md) for instructions on commissioning the device and running the test scripts.
|
||||
@@ -0,0 +1,10 @@
|
||||
set(SRC_DIRS_LIST "." )
|
||||
set(INCLUDE_DIRS_LIST ".")
|
||||
list(APPEND PRIV_INCLUDE_DIRS_LIST "${ESP_MATTER_PATH}/examples/common/utils")
|
||||
|
||||
|
||||
idf_component_register(SRC_DIRS ${SRC_DIRS_LIST}
|
||||
PRIV_INCLUDE_DIRS "." ${PRIV_INCLUDE_DIRS_LIST}
|
||||
INCLUDE_DIRS ${INCLUDE_DIRS_LIST})
|
||||
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE "-DCHIP_HAVE_CONFIG_H")
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <esp_err.h>
|
||||
#include <esp_log.h>
|
||||
#include <nvs_flash.h>
|
||||
|
||||
#include <esp_matter.h>
|
||||
#include <common_macros.h>
|
||||
#include <esp_matter_console.h>
|
||||
#include <esp_matter_ota.h>
|
||||
#include <esp_matter_optional_attribute.h>
|
||||
|
||||
#include <app/server/CommissioningWindowManager.h>
|
||||
#include <app/server/Server.h>
|
||||
|
||||
static const char *TAG = "app_main";
|
||||
uint16_t test_endpoint_id = 0;
|
||||
|
||||
using namespace esp_matter;
|
||||
using namespace esp_matter::attribute;
|
||||
using namespace esp_matter::endpoint;
|
||||
using namespace chip::app::Clusters;
|
||||
|
||||
constexpr auto k_timeout_seconds = 300;
|
||||
|
||||
static void app_event_cb(const ChipDeviceEvent *event, intptr_t arg)
|
||||
{
|
||||
switch (event->Type) {
|
||||
case chip::DeviceLayer::DeviceEventType::kInterfaceIpAddressChanged:
|
||||
ESP_LOGI(TAG, "Interface IP Address changed");
|
||||
break;
|
||||
|
||||
case chip::DeviceLayer::DeviceEventType::kCommissioningComplete:
|
||||
ESP_LOGI(TAG, "Commissioning complete");
|
||||
break;
|
||||
|
||||
case chip::DeviceLayer::DeviceEventType::kFailSafeTimerExpired:
|
||||
ESP_LOGI(TAG, "Commissioning failed, fail safe timer expired");
|
||||
break;
|
||||
|
||||
case chip::DeviceLayer::DeviceEventType::kCommissioningSessionStarted:
|
||||
ESP_LOGI(TAG, "Commissioning session started");
|
||||
break;
|
||||
|
||||
case chip::DeviceLayer::DeviceEventType::kCommissioningSessionStopped:
|
||||
ESP_LOGI(TAG, "Commissioning session stopped");
|
||||
break;
|
||||
|
||||
case chip::DeviceLayer::DeviceEventType::kCommissioningWindowOpened:
|
||||
ESP_LOGI(TAG, "Commissioning window opened");
|
||||
break;
|
||||
|
||||
case chip::DeviceLayer::DeviceEventType::kCommissioningWindowClosed:
|
||||
ESP_LOGI(TAG, "Commissioning window closed");
|
||||
break;
|
||||
|
||||
case chip::DeviceLayer::DeviceEventType::kFabricRemoved: {
|
||||
ESP_LOGI(TAG, "Fabric removed successfully");
|
||||
if (chip::Server::GetInstance().GetFabricTable().FabricCount() == 0) {
|
||||
chip::CommissioningWindowManager &commissionMgr = chip::Server::GetInstance().GetCommissioningWindowManager();
|
||||
constexpr auto kTimeoutSeconds = chip::System::Clock::Seconds16(k_timeout_seconds);
|
||||
if (!commissionMgr.IsCommissioningWindowOpen()) {
|
||||
/* After removing last fabric, this example does not remove the Wi-Fi credentials
|
||||
* and still has IP connectivity so, only advertising on DNS-SD.
|
||||
*/
|
||||
CHIP_ERROR err = commissionMgr.OpenBasicCommissioningWindow(kTimeoutSeconds,
|
||||
chip::CommissioningWindowAdvertisement::kDnssdOnly);
|
||||
if (err != CHIP_NO_ERROR) {
|
||||
ESP_LOGE(TAG, "Failed to open commissioning window, err:%" CHIP_ERROR_FORMAT, err.Format());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case chip::DeviceLayer::DeviceEventType::kFabricWillBeRemoved:
|
||||
ESP_LOGI(TAG, "Fabric will be removed");
|
||||
break;
|
||||
|
||||
case chip::DeviceLayer::DeviceEventType::kFabricUpdated:
|
||||
ESP_LOGI(TAG, "Fabric is updated");
|
||||
break;
|
||||
|
||||
case chip::DeviceLayer::DeviceEventType::kFabricCommitted:
|
||||
ESP_LOGI(TAG, "Fabric is committed");
|
||||
break;
|
||||
|
||||
case chip::DeviceLayer::DeviceEventType::kBLEDeinitialized:
|
||||
ESP_LOGI(TAG, "BLE deinitialized and memory reclaimed");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This callback is invoked when clients interact with the Identify Cluster.
|
||||
// In the callback implementation, an endpoint can identify itself. (e.g., by flashing an LED or light).
|
||||
static esp_err_t app_identification_cb(identification::callback_type_t type, uint16_t endpoint_id, uint8_t effect_id,
|
||||
uint8_t effect_variant, void *priv_data)
|
||||
{
|
||||
ESP_LOGI(TAG, "Identification callback: type: %u, effect: %u, variant: %u", type, effect_id, effect_variant);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// This callback is called for every attribute update. The callback implementation shall
|
||||
// handle the desired attributes and return an appropriate error code. If the attribute
|
||||
// is not of your interest, please do not return an error code and strictly return ESP_OK.
|
||||
static esp_err_t app_attribute_update_cb(attribute::callback_type_t type, uint16_t endpoint_id, uint32_t cluster_id,
|
||||
uint32_t attribute_id, esp_matter_attr_val_t *val, void *priv_data)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
|
||||
if (type == POST_UPDATE) {
|
||||
ESP_LOGI(TAG, "Attribute update for Endpoint 0x%04" PRIX16 "'s Cluster 0x%08" PRIX32 "'s Attribute 0x%08" PRIX32, endpoint_id, cluster_id, attribute_id);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
extern "C" void app_main()
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
|
||||
/* Initialize the ESP NVS layer */
|
||||
nvs_flash_init();
|
||||
|
||||
/* Create a Matter node and add the mandatory Root Node device type on endpoint 0 */
|
||||
node::config_t node_config;
|
||||
|
||||
// node handle can be used to add/modify other endpoints.
|
||||
node_t *node = node::create(&node_config, app_attribute_update_cb, app_identification_cb);
|
||||
ABORT_APP_ON_FAILURE(node != nullptr, ESP_LOGE(TAG, "Failed to create Matter node"));
|
||||
|
||||
// Add optional attributes for clusters on Root Node (Endpoint 0)
|
||||
endpoint_t *root_endpoint = endpoint::get(node, 0);
|
||||
|
||||
// Existing clusters on Root Node - just add optional attributes
|
||||
cluster::basic_information::create_optional_attributes(cluster::get(root_endpoint, BasicInformation::Id));
|
||||
cluster::general_diagnostics::create_optional_attributes(cluster::get(root_endpoint, GeneralDiagnostics::Id));
|
||||
|
||||
// Create new clusters and their optional attributes
|
||||
// 1. Boolean State Configuration
|
||||
cluster::boolean_state_configuration::config_t bool_config;
|
||||
cluster::boolean_state_configuration::create(root_endpoint, &bool_config, CLUSTER_FLAG_SERVER);
|
||||
cluster::boolean_state_configuration::create_optional_attributes(cluster::get(root_endpoint, BooleanStateConfiguration::Id));
|
||||
|
||||
// 2. Electrical Energy Measurement
|
||||
cluster::electrical_energy_measurement::config_t energy_config;
|
||||
energy_config.feature_flags = cluster::electrical_energy_measurement::feature::imported_energy::get_id() | cluster::electrical_energy_measurement::feature::cumulative_energy::get_id();
|
||||
cluster::electrical_energy_measurement::create(root_endpoint, &energy_config, CLUSTER_FLAG_SERVER);
|
||||
cluster::electrical_energy_measurement::create_optional_attributes(cluster::get(root_endpoint, ElectricalEnergyMeasurement::Id));
|
||||
|
||||
// 3. Electrical Power Measurement
|
||||
cluster::electrical_power_measurement::config_t power_config;
|
||||
power_config.feature_flags = cluster::electrical_power_measurement::feature::direct_current::get_id();
|
||||
cluster::electrical_power_measurement::create(root_endpoint, &power_config, CLUSTER_FLAG_SERVER);
|
||||
cluster::electrical_power_measurement::create_optional_attributes(cluster::get(root_endpoint, ElectricalPowerMeasurement::Id));
|
||||
|
||||
// 4. Ethernet Network Diagnostics
|
||||
// Note: Usually only one of Ethernet or WiFi diagnostics should be present, but for test purpose we try adding.
|
||||
cluster::ethernet_network_diagnostics::create(root_endpoint, nullptr, CLUSTER_FLAG_SERVER);
|
||||
cluster::ethernet_network_diagnostics::create_optional_attributes(cluster::get(root_endpoint, EthernetNetworkDiagnostics::Id));
|
||||
|
||||
// 5. Occupancy Sensing
|
||||
cluster::occupancy_sensing::config_t occupancy_config;
|
||||
occupancy_config.feature_flags = cluster::occupancy_sensing::feature::other::get_id();
|
||||
cluster::occupancy_sensing::create(root_endpoint, &occupancy_config, CLUSTER_FLAG_SERVER);
|
||||
cluster::occupancy_sensing::create_optional_attributes(cluster::get(root_endpoint, OccupancySensing::Id));
|
||||
|
||||
// 6. Resource Monitoring
|
||||
cluster::resource_monitoring::config_t resource_config; // Assuming generic config or specific like HepaFilter
|
||||
// Resource Monitoring is usually a base for specific clusters like HEPA Filter Monitoring.
|
||||
// We'll create HepaFilterMonitoring as an example of ResourceMonitoring
|
||||
cluster::hepa_filter_monitoring::create(root_endpoint, &resource_config, CLUSTER_FLAG_SERVER);
|
||||
cluster::resource_monitoring::create_optional_attributes(cluster::get(root_endpoint, HepaFilterMonitoring::Id));
|
||||
|
||||
// 7. Software Diagnostics
|
||||
cluster::software_diagnostics::config_t sw_diag_config;
|
||||
cluster::software_diagnostics::create(root_endpoint, &sw_diag_config, CLUSTER_FLAG_SERVER);
|
||||
cluster::software_diagnostics::create_optional_attributes(cluster::get(root_endpoint, SoftwareDiagnostics::Id));
|
||||
|
||||
// 8. Time Synchronization
|
||||
cluster::time_synchronization::config_t time_sync_config;
|
||||
cluster::time_synchronization::create(root_endpoint, &time_sync_config, CLUSTER_FLAG_SERVER);
|
||||
cluster::time_synchronization::create_optional_attributes(cluster::get(root_endpoint, TimeSynchronization::Id));
|
||||
|
||||
// 9. Wifi Network Diagnostics
|
||||
// Usually created by root_node::create if config enabled, but we ensure it's here and add optionals
|
||||
if (!cluster::get(root_endpoint, WiFiNetworkDiagnostics::Id)) {
|
||||
cluster::wifi_network_diagnostics::create(root_endpoint, nullptr, CLUSTER_FLAG_SERVER);
|
||||
}
|
||||
cluster::wifi_network_diagnostics::create_optional_attributes(cluster::get(root_endpoint, WiFiNetworkDiagnostics::Id));
|
||||
|
||||
/* Matter start */
|
||||
err = esp_matter::start(app_event_cb);
|
||||
ABORT_APP_ON_FAILURE(err == ESP_OK, ESP_LOGE(TAG, "Failed to start Matter, err:%d", err));
|
||||
|
||||
#if CONFIG_ENABLE_CHIP_SHELL
|
||||
esp_matter::console::diagnostics_register_commands();
|
||||
esp_matter::console::wifi_register_commands();
|
||||
esp_matter::console::factoryreset_register_commands();
|
||||
esp_matter::console::attribute_register_commands();
|
||||
#if CONFIG_OPENTHREAD_CLI
|
||||
esp_matter::console::otcli_register_commands();
|
||||
#endif
|
||||
esp_matter::console::init();
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
# Note: Firmware partition offset needs to be 64K aligned, initial 36K (9 sectors) are reserved for bootloader and partition table
|
||||
esp_secure_cert, 0x3F, ,0xd000, 0x2000, encrypted
|
||||
nvs, data, nvs, 0x10000, 0xC000,
|
||||
nvs_keys, data, nvs_keys,, 0x1000, encrypted
|
||||
otadata, data, ota, , 0x2000
|
||||
phy_init, data, phy, , 0x1000,
|
||||
ota_0, app, ota_0, 0x20000, 0x1E0000,
|
||||
ota_1, app, ota_1, 0x200000, 0x1E0000,
|
||||
fctry, data, nvs, 0x3E0000, 0x6000
|
||||
coredump, data, coredump,0x3F0000, 0x10000
|
||||
|
@@ -0,0 +1,46 @@
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
|
||||
#enable BT
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
|
||||
#disable BT connection reattempt
|
||||
CONFIG_BT_NIMBLE_ENABLE_CONN_REATTEMPT=n
|
||||
|
||||
#enable lwip ipv6 autoconfig
|
||||
CONFIG_LWIP_IPV6_AUTOCONFIG=y
|
||||
|
||||
# Use a custom partition table
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0xC000
|
||||
|
||||
# Enable chip shell
|
||||
CONFIG_ENABLE_CHIP_SHELL=y
|
||||
|
||||
#enable lwIP route hooks
|
||||
CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT=y
|
||||
CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT=y
|
||||
|
||||
# Button
|
||||
CONFIG_BUTTON_PERIOD_TIME_MS=20
|
||||
CONFIG_BUTTON_LONG_PRESS_TIME_MS=5000
|
||||
|
||||
# disable softap by default
|
||||
CONFIG_ESP_WIFI_SOFTAP_SUPPORT=n
|
||||
|
||||
# Remove this after `ENABLE_WIFI_AP` config has been deleted from chip component
|
||||
CONFIG_ENABLE_WIFI_AP=n
|
||||
|
||||
# Enable OTA Requestor
|
||||
CONFIG_ENABLE_OTA_REQUESTOR=y
|
||||
|
||||
# Enable optional attributes helper APIs
|
||||
CONFIG_ESP_MATTER_ENABLE_OPTIONAL_ATTRIBUTES=y
|
||||
|
||||
# Enable HKDF in mbedtls
|
||||
CONFIG_MBEDTLS_HKDF_C=y
|
||||
|
||||
# Increase LwIP IPv6 address number to 6 (MAX_FABRIC + 1)
|
||||
# unique local addresses for fabrics(MAX_FABRIC), a link local address(1)
|
||||
CONFIG_LWIP_IPV6_NUM_ADDRESSES=6
|
||||
Reference in New Issue
Block a user