Implement Matter Endpoint Plan Logic and Unit Tests

- Added `gateway_matter_plan.cpp` to define the logic for building Matter endpoint plans, including functions for inferring capabilities and managing allocations based on channel configurations.
- Introduced helper functions to determine the type of Matter endpoints and color methods based on device capabilities.
- Created `gateway_matter_plan_test.cpp` to validate the functionality of the endpoint plan logic with various test cases, ensuring correct behavior for different configurations and address handling.

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-08-08 08:36:42 +08:00
parent bb3e9178fb
commit 8505958b65
20 changed files with 1683 additions and 179 deletions
+50 -6
View File
@@ -123,17 +123,46 @@ gateway group matches the target.
## Matter-to-DALI development bridge
`GATEWAY_MATTER_SUPPORTED` enables the Matter bridge by default. It creates one
aggregator and resumes or discovers up to
`GATEWAY_MATTER_MAX_BRIDGED_DEVICES` direct-address DALI control gear. The
adapter queries all DALI device types `0` through `8`: DT8 becomes an extended
color light, DT7 becomes an on/off light, and the remaining types become
dimmable lights. Matter On/Off, Level Control, Color Control (mirek, XY, and
hue/saturation converted to DALI RGB), Groups, Scenes, and Identify are the
aggregator and owns a pool of 32 bridged DALI endpoints. ESP-Matter is configured
for 34 dynamic entries: the root, the aggregator, and the 32-slot DALI pool.
Every enabled channel receives a broadcast endpoint first, then cached non-empty
groups (including manually retained empty groups), explicitly included short
addresses, and automatic online short addresses in ascending order. Allocation
stops at 32 and reports every dropped target; this means the highest automatic
short addresses are removed first. Broadcast and group targets cannot be
disabled. On a single DALI channel, the pool can therefore retain broadcast,
all 16 groups, and 15 direct addresses when every group is present.
The portable DALI cache persists device-type masks and DT8 color-feature bits in
addition to group, scene, settings, and runtime state. The planner infers the
highest required multi-target capability in the order color, color temperature,
dimmer, then switch. DT8 feature bits select XY, RGBCW, or both. Per-channel
versioned configuration is stored in normal NVS by fixed channel index and can
override the automatic short-address policy, sparse direct-address inclusion,
and each broadcast/group type and color method. Existing direct-only bindings
are migrated to stable channel-index plus target-kind bindings. Reconciliation
retains unchanged endpoints and recreates one only when its Matter device type
changes.
Matter On/Off, Level Control, Color Control (mirek, XY, and hue/saturation
converted to equivalent DALI writes), Groups, Scenes, and Identify are the
only application clusters retained. Root-node commissioning, credentials,
descriptor, access-control, diagnostics, and the ESP-Matter internal Binding
manager are retained because the server requires them; unrelated device-type
clusters, Thread, OTA, and the Matter shell are excluded.
The checked-in ESP32-S3 profile uses only ESP-IDF 5.5.4-supported PSRAM paths:
CHIP, ESP-Matter, mDNS, TLS, NVS cache, Wi-Fi/lwIP allocations, and eligible BSS
prefer external RAM. The mDNS task and gateway-owned BACnet and on-demand DALI
operation stacks can use PSRAM with internal-RAM fallback. Tasks that write NVS,
change network settings, run the DALI PHY, or have timing/DMA constraints remain
in internal RAM; in particular, the Matter DALI scan stack stays internal because
the scan commits its discovered capabilities. Eligible non-ISR FreeRTOS code is
placed in flash to leave internal RAM headroom. This selective policy does not depend on
the unreleased global `FREERTOS_PLACE_TASK_STACKS_IN_EXT_RAM` option. Wi-Fi IRAM
throughput optimizations are disabled because Ethernet is the preferred data
path and Wi-Fi is retained as a provisioning and fallback path.
CHIP owns the single NimBLE controller and host while Matter is enabled. The
legacy `gateway_ble` transport registers its distinct FFF7 service in CHIP's
GATT database through ESP-Matter's extra-service hook, and adds the FFF7 UUID plus
@@ -173,6 +202,21 @@ private key/certificate, PAI, certification declaration, passcode `20202021`,
and discriminator `3840`. Flashing that image replaces all existing normal NVS
data and must never be used for production hardware.
DaliMaster's Matter Gateway page uses the transport-independent bridge action
surface to read live Matter state, open or close the five-minute commissioning
window, configure endpoints, and rescan DALI control gear. `matter_status`
returns schema version, effective 32-slot capacity, candidates, saved
configuration, active and dropped allocations, target kind/address, inferred or
manual source, and the last partial-apply error. `matter_onboarding` returns the
live serial number, VID/PID, QR payload, and manual pairing code from the active
ESP-Matter factory providers; controllers must treat this response as sensitive
and DaliMaster keeps it only in page memory. `matter_config` applies a selected
channel patch, while `matter_config_reset` restores automatic selection.
`matter_open_commissioning`, `matter_close_commissioning`, `matter_rescan`,
`matter_config`, and `matter_config_reset` are POST actions. They are available through the active
BLE/USB/IP/KNX/cloud bridge transport as well as the local HTTP `/bridge`
endpoint, so Matter setup does not require a working IP profile.
## Gateway operation protocol
Opcode `0x67` starts, aborts, and polls gateway-executed high-level DALI
+22 -7
View File
@@ -1594,12 +1594,22 @@ config GATEWAY_DALI_BAUDRATE
config GATEWAY_CONTROLLER_TASK_STACK_SIZE
int "Gateway controller task stack bytes"
range 6144 24576
default 12288
default 10240
help
Stack used by the gateway command controller. BLE bridge transport
requests are decoded in this task and may execute JSON-heavy bridge
management actions such as KNX programming-mode changes.
config GATEWAY_PREFER_PSRAM_TASK_STACKS
bool "Prefer PSRAM for supported gateway task stacks"
depends on SPIRAM && FREERTOS_TASK_CREATE_ALLOW_EXT_MEM
default y
help
Moves only gateway tasks that do not write flash or require DMA-capable
stacks to PSRAM, with an internal-RAM fallback if PSRAM allocation
fails. Cache, settings, Matter scan, network configuration, DALI PHY,
and other flash- or timing-sensitive tasks remain in internal RAM.
endmenu
menu "Connectivity Startup"
@@ -1669,13 +1679,15 @@ config GATEWAY_MATTER_SUPPORTED
so the legacy gateway BLE GATT transport is not started in this build.
config GATEWAY_MATTER_MAX_BRIDGED_DEVICES
int "Maximum bridged DALI devices"
int "Maximum bridged DALI endpoints"
depends on GATEWAY_MATTER_SUPPORTED
range 1 16
default 16
range 1 82
default 32
help
Limits dynamic Matter endpoints and DALI startup discovery work. Every
DALI device type 0 through 8 remains supported regardless of this count.
Limits bridged DALI broadcast, group, and short-address endpoints.
Matter root and aggregator endpoints are additional to this count. The
32-slot default fits broadcast plus all 16 DALI groups and leaves 15
automatic or explicitly included short-address endpoints.
config GATEWAY_MATTER_SCAN_START_DELAY_MS
int "DALI discovery startup delay ms"
@@ -1692,8 +1704,11 @@ config GATEWAY_MATTER_SCAN_ADDRESS_DELAY_MS
config GATEWAY_MATTER_SCAN_TASK_STACK_SIZE
int "Matter DALI scan task stack bytes"
depends on GATEWAY_MATTER_SUPPORTED
range 6144 16384
range 8192 32768
default 8192
help
This task performs NVS commits and therefore intentionally remains in
internal RAM even when supported gateway task stacks prefer PSRAM.
config GATEWAY_MATTER_WIFI_PROVISION_BUTTON_GPIO
int "Matter Wi-Fi provisioning button GPIO"
+78 -2
View File
@@ -11,6 +11,7 @@
#include "gateway_usb_setup.hpp"
#include "gateway_tridonic_hid.hpp"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "sdkconfig.h"
@@ -80,7 +81,7 @@
#endif
#ifndef CONFIG_GATEWAY_MATTER_MAX_BRIDGED_DEVICES
#define CONFIG_GATEWAY_MATTER_MAX_BRIDGED_DEVICES 16
#define CONFIG_GATEWAY_MATTER_MAX_BRIDGED_DEVICES 32
#endif
#ifndef CONFIG_GATEWAY_MATTER_SCAN_START_DELAY_MS
@@ -392,6 +393,12 @@ constexpr bool kMatterSupported = true;
constexpr bool kMatterSupported = false;
#endif
#ifdef CONFIG_GATEWAY_PREFER_PSRAM_TASK_STACKS
constexpr bool kPreferPsramTaskStacks = true;
#else
constexpr bool kPreferPsramTaskStacks = false;
#endif
#ifdef CONFIG_GATEWAY_START_BLE_ENABLED
constexpr bool kBleStartupEnabled = true;
#else
@@ -1038,6 +1045,20 @@ bool HasConfiguredDaliChannel(const std::vector<gateway::DaliChannelInfo>& chann
});
}
void LogHeapSnapshot(const char* phase) {
ESP_LOGI(kTag,
"heap %s internal_free=%u internal_largest=%u psram_free=%u psram_largest=%u",
phase,
static_cast<unsigned>(heap_caps_get_free_size(MALLOC_CAP_INTERNAL |
MALLOC_CAP_8BIT)),
static_cast<unsigned>(heap_caps_get_largest_free_block(
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)),
static_cast<unsigned>(heap_caps_get_free_size(MALLOC_CAP_SPIRAM |
MALLOC_CAP_8BIT)),
static_cast<unsigned>(heap_caps_get_largest_free_block(
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)));
}
} // namespace
extern "C" void app_main(void) {
@@ -1147,6 +1168,7 @@ extern "C" void app_main(void) {
gateway::GatewayControllerConfig controller_config;
controller_config.task_stack_size =
static_cast<uint32_t>(CONFIG_GATEWAY_CONTROLLER_TASK_STACK_SIZE);
controller_config.operation_task_stack_in_psram = kPreferPsramTaskStacks;
const bool network_transport_supported = profile.enable_wifi || profile.enable_eth;
controller_config.setup_supported = true;
controller_config.ble_supported = profile.enable_ble;
@@ -1234,6 +1256,7 @@ extern "C" void app_main(void) {
static_cast<uint32_t>(CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE);
bridge_config.bacnet_task_priority =
static_cast<UBaseType_t>(CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY);
bridge_config.bacnet_task_stack_in_psram = kPreferPsramTaskStacks;
if (kKnxBridgeSupported) {
gateway::GatewayKnxConfig default_knx;
default_knx.dali_router_enabled = true;
@@ -1326,6 +1349,56 @@ extern "C" void app_main(void) {
}
return s_network->gatewaySettingsCloudResponse(request);
};
bridge_config.matter_status_getter = [](std::optional<uint8_t> gateway_id) {
if (!kMatterSupported) {
return gateway::GatewayBridgeHttpResponse{
ESP_OK, "{\"matter\":{\"supported\":false,\"enabled\":false,\"started\":false}}"};
}
if (s_matter_bridge == nullptr) {
return gateway::GatewayBridgeHttpResponse{
ESP_ERR_INVALID_STATE,
"{\"status\":\"error\",\"error\":\"Matter bridge is not ready\"}"};
}
return gateway::GatewayBridgeHttpResponse{ESP_OK,
s_matter_bridge->statusJson(gateway_id)};
};
bridge_config.matter_onboarding_getter = []() {
if (!kMatterSupported || s_matter_bridge == nullptr) {
return gateway::GatewayBridgeHttpResponse{
ESP_ERR_NOT_SUPPORTED,
"{\"status\":\"error\",\"error\":\"Matter bridge is not available\"}"};
}
return gateway::GatewayBridgeHttpResponse{ESP_OK,
s_matter_bridge->onboardingJson()};
};
bridge_config.matter_action_handler = [](std::string_view action,
std::optional<uint8_t> gateway_id,
std::string_view body) {
if (!kMatterSupported || s_matter_bridge == nullptr) {
return gateway::GatewayBridgeHttpResponse{
ESP_ERR_NOT_SUPPORTED,
"{\"status\":\"error\",\"error\":\"Matter bridge is not available\"}"};
}
esp_err_t err = ESP_ERR_INVALID_ARG;
if (action == "matter_open_commissioning") {
err = s_matter_bridge->openCommissioningWindow();
} else if (action == "matter_close_commissioning") {
err = s_matter_bridge->closeCommissioningWindow();
} else if (action == "matter_rescan") {
err = s_matter_bridge->rescanDaliDevices();
} else if (action == "matter_config" && gateway_id.has_value()) {
err = s_matter_bridge->applyConfiguration(*gateway_id, body);
} else if (action == "matter_config_reset" && gateway_id.has_value()) {
err = s_matter_bridge->resetConfiguration(*gateway_id);
}
if (err != ESP_OK) {
const std::string message = std::string("{\"status\":\"error\",\"error\":\"") +
esp_err_to_name(err) + "\"}";
return gateway::GatewayBridgeHttpResponse{err, message};
}
return gateway::GatewayBridgeHttpResponse{ESP_OK,
s_matter_bridge->statusJson(gateway_id)};
};
bridge_config.knx_gateway_snapshot_provider = []() {
gateway::GatewayKnxGatewaySnapshot out;
if (s_controller == nullptr) {
@@ -1532,7 +1605,10 @@ extern "C" void app_main(void) {
}
s_matter_bridge =
std::make_unique<gateway::GatewayMatterBridge>(*s_dali_domain, matter_config);
ESP_ERROR_CHECK(s_matter_bridge->start());
LogHeapSnapshot("before Matter");
const esp_err_t matter_err = s_matter_bridge->start();
LogHeapSnapshot("after Matter");
ESP_ERROR_CHECK(matter_err);
}
// ESP-Matter needs several contiguous internal allocations for the CHIP
+30 -27
View File
@@ -711,7 +711,8 @@ CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y
# end of Gateway Cache
# CONFIG_GATEWAY_ENABLE_DALI_BUS is not set
CONFIG_GATEWAY_CONTROLLER_TASK_STACK_SIZE=12288
CONFIG_GATEWAY_CONTROLLER_TASK_STACK_SIZE=10240
CONFIG_GATEWAY_PREFER_PSRAM_TASK_STACKS=y
# end of DALI Settings
#
@@ -730,7 +731,7 @@ CONFIG_GATEWAY_SMARTCONFIG_SUPPORTED=y
# Matter-to-DALI Bridge
#
CONFIG_GATEWAY_MATTER_SUPPORTED=y
CONFIG_GATEWAY_MATTER_MAX_BRIDGED_DEVICES=16
CONFIG_GATEWAY_MATTER_MAX_BRIDGED_DEVICES=82
CONFIG_GATEWAY_MATTER_SCAN_START_DELAY_MS=2000
CONFIG_GATEWAY_MATTER_SCAN_ADDRESS_DELAY_MS=20
CONFIG_GATEWAY_MATTER_SCAN_TASK_STACK_SIZE=8192
@@ -1463,6 +1464,7 @@ CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y
# Common ESP-related
#
CONFIG_ESP_ERR_TO_NAME_LOOKUP=y
CONFIG_ESP_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
# end of Common ESP-related
#
@@ -1551,7 +1553,6 @@ CONFIG_RMT_OBJ_CACHE_SAFE=y
#
# ESP-Driver:SPI Configurations
#
# CONFIG_SPI_MASTER_IN_IRAM is not set
CONFIG_SPI_MASTER_ISR_IN_IRAM=y
# CONFIG_SPI_SLAVE_IN_IRAM is not set
CONFIG_SPI_SLAVE_ISR_IN_IRAM=y
@@ -1884,15 +1885,14 @@ CONFIG_SPIRAM_SPEED=80
CONFIG_SPIRAM_BOOT_HW_INIT=y
CONFIG_SPIRAM_BOOT_INIT=y
CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION=y
CONFIG_SPIRAM_IGNORE_NOTFOUND=y
# CONFIG_SPIRAM_USE_MEMMAP is not set
# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set
CONFIG_SPIRAM_USE_MALLOC=y
CONFIG_SPIRAM_MEMTEST=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=512
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65535
# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set
CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set
# end of SPI RAM config
# end of ESP PSRAM
@@ -2042,10 +2042,10 @@ CONFIG_ESP_TIMER_IMPL_SYSTIMER=y
CONFIG_ESP_WIFI_ENABLED=y
CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=10
CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32
# CONFIG_ESP_WIFI_STATIC_TX_BUFFER is not set
CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER=y
CONFIG_ESP_WIFI_TX_BUFFER_TYPE=1
CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=32
CONFIG_ESP_WIFI_STATIC_TX_BUFFER=y
CONFIG_ESP_WIFI_TX_BUFFER_TYPE=0
CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM=16
CONFIG_ESP_WIFI_CACHE_TX_BUFFER_NUM=32
# CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER is not set
CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUFFER=y
CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=1
@@ -2055,14 +2055,15 @@ CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y
CONFIG_ESP_WIFI_TX_BA_WIN=6
CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y
CONFIG_ESP_WIFI_RX_BA_WIN=6
# CONFIG_ESP_WIFI_AMSDU_TX_ENABLED is not set
# CONFIG_ESP_WIFI_NVS_ENABLED is not set
# CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0 is not set
CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1=y
CONFIG_ESP_WIFI_SOFTAP_BEACON_MAX_LEN=752
CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32
CONFIG_ESP_WIFI_IRAM_OPT=y
# CONFIG_ESP_WIFI_IRAM_OPT is not set
# CONFIG_ESP_WIFI_EXTRA_IRAM_OPT is not set
CONFIG_ESP_WIFI_RX_IRAM_OPT=y
# CONFIG_ESP_WIFI_RX_IRAM_OPT is not set
CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=y
CONFIG_ESP_WIFI_ENABLE_SAE_PK=y
CONFIG_ESP_WIFI_ENABLE_SAE_H2E=y
@@ -2215,7 +2216,7 @@ CONFIG_FREERTOS_TICK_SUPPORT_SYSTIMER=y
CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL1=y
# CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL3 is not set
CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y
# CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set
CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y
# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set
# end of Port
@@ -2406,6 +2407,7 @@ CONFIG_LWIP_TCP_OOSEQ_MAX_PBUFS=4
CONFIG_LWIP_TCP_OVERSIZE_MSS=y
# CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set
# CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set
# CONFIG_LWIP_WND_SCALE is not set
CONFIG_LWIP_TCP_RTO_TIME=1500
# end of TCP
@@ -2510,8 +2512,8 @@ CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y
# mbedTLS
#
# CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC is not set
# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC is not set
CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC=y
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set
# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set
CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y
CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384
@@ -3287,7 +3289,7 @@ CONFIG_MRP_MAX_RETRANS=4
#
# ESP Matter
#
CONFIG_ESP_MATTER_MAX_DEVICE_TYPE_COUNT=16
CONFIG_ESP_MATTER_MAX_DEVICE_TYPE_COUNT=5
CONFIG_ESP_MATTER_ATTRIBUTE_BUFFER_LARGEST=259
CONFIG_ESP_MATTER_NVS_PART_NAME="nvs"
CONFIG_ESP_MATTER_DEFERRED_ATTR_PERSISTENCE_TIME_MS=3000
@@ -3303,7 +3305,7 @@ CONFIG_FACTORY_DEVICE_INSTANCE_INFO_PROVIDER=y
# CONFIG_CUSTOM_DEVICE_INSTANCE_INFO_PROVIDER is not set
CONFIG_NONE_DEVICE_INFO_PROVIDER=y
# CONFIG_CUSTOM_DEVICE_INFO_PROVIDER is not set
CONFIG_ESP_MATTER_MAX_DYNAMIC_ENDPOINT_COUNT=18
CONFIG_ESP_MATTER_MAX_DYNAMIC_ENDPOINT_COUNT=84
CONFIG_ESP_MATTER_MODE_SELECT_CLUSTER_ENDPOINT_COUNT=0
CONFIG_ESP_MATTER_TEMPERATURE_CONTROL_CLUSTER_ENDPOINT_COUNT=0
CONFIG_ESP_MATTER_SCENES_TABLE_SIZE=16
@@ -3605,10 +3607,10 @@ CONFIG_MDNS_TASK_AFFINITY=0x0
#
# MDNS Memory Configuration
#
# CONFIG_MDNS_TASK_CREATE_FROM_SPIRAM is not set
CONFIG_MDNS_TASK_CREATE_FROM_INTERNAL=y
# CONFIG_MDNS_MEMORY_ALLOC_SPIRAM is not set
CONFIG_MDNS_MEMORY_ALLOC_INTERNAL=y
CONFIG_MDNS_TASK_CREATE_FROM_SPIRAM=y
# CONFIG_MDNS_TASK_CREATE_FROM_INTERNAL is not set
CONFIG_MDNS_MEMORY_ALLOC_SPIRAM=y
# CONFIG_MDNS_MEMORY_ALLOC_INTERNAL is not set
# CONFIG_MDNS_MEMORY_CUSTOM_IMPL is not set
# end of MDNS Memory Configuration
@@ -3853,22 +3855,23 @@ CONFIG_TIMER_TASK_STACK_SIZE=3584
CONFIG_ESP32_WIFI_ENABLED=y
CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10
CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32
# CONFIG_ESP32_WIFI_STATIC_TX_BUFFER is not set
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y
CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER=y
CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=16
CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=32
# CONFIG_ESP32_WIFI_CSI_ENABLED is not set
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y
CONFIG_ESP32_WIFI_TX_BA_WIN=6
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y
CONFIG_ESP32_WIFI_RX_BA_WIN=6
# CONFIG_ESP32_WIFI_AMSDU_TX_ENABLED is not set
# CONFIG_ESP32_WIFI_NVS_ENABLED is not set
# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0 is not set
CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1=y
CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752
CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32
CONFIG_ESP32_WIFI_IRAM_OPT=y
CONFIG_ESP32_WIFI_RX_IRAM_OPT=y
# CONFIG_ESP32_WIFI_IRAM_OPT is not set
# CONFIG_ESP32_WIFI_RX_IRAM_OPT is not set
CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y
CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y
CONFIG_WPA_MBEDTLS_CRYPTO=y
+22 -5
View File
@@ -25,7 +25,7 @@ CONFIG_TINYUSB_HID_COUNT=1
# Matter commissioning and legacy gateway GATT services share its database.
# Wired Ethernet remains the default route and Wi-Fi starts only when provisioned.
CONFIG_GATEWAY_MATTER_SUPPORTED=y
CONFIG_GATEWAY_MATTER_MAX_BRIDGED_DEVICES=16
CONFIG_GATEWAY_MATTER_MAX_BRIDGED_DEVICES=32
CONFIG_GATEWAY_MATTER_SCAN_START_DELAY_MS=2000
CONFIG_GATEWAY_MATTER_SCAN_ADDRESS_DELAY_MS=20
CONFIG_GATEWAY_MATTER_SCAN_TASK_STACK_SIZE=8192
@@ -33,16 +33,33 @@ CONFIG_GATEWAY_MATTER_WIFI_PROVISION_BUTTON_GPIO=-1
CONFIG_ESP_MATTER_ENABLE_MATTER_SERVER=y
CONFIG_ESP_MATTER_ENABLE_DATA_MODEL=y
CONFIG_ESP_MATTER_ENABLE_OPENTHREAD=n
# Keep the large CHIP core and ESP-Matter data model in PSRAM so the internal
# heap retains a contiguous block for the FreeRTOS event queue and radio/DMA
# allocations. The queue itself keeps Espressif's Wi-Fi-safe default length.
# Keep the large CHIP core, ESP-Matter data model, mDNS state, TLS allocations,
# and NVS cache in PSRAM so the internal heap retains contiguous blocks for
# event queues and radio/DMA allocations. Only stable, explicitly supported
# task stacks are moved; flash-writing tasks remain in internal RAM.
CONFIG_CHIP_MEM_ALLOC_MODE_EXTERNAL=y
CONFIG_ESP_MATTER_MEM_ALLOC_MODE_EXTERNAL=y
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
CONFIG_MDNS_TASK_CREATE_FROM_SPIRAM=y
CONFIG_MDNS_MEMORY_ALLOC_SPIRAM=y
CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM=y
CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=512
CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y
CONFIG_GATEWAY_PREFER_PSRAM_TASK_STACKS=y
# Ethernet is the normal data path and Wi-Fi is a provisioning/fallback path,
# so trade Wi-Fi peak throughput for substantial internal instruction RAM.
CONFIG_ESP_WIFI_IRAM_OPT=n
CONFIG_ESP_WIFI_RX_IRAM_OPT=n
# This stable IDF 5.5 option moves eligible FreeRTOS code out of IRAM. ISR
# functions remain in IRAM because the separate ISR option is not enabled.
CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y
# Preserve enough internal/DMA-capable RAM for gateway Wi-Fi RX buffers when
# ESP-Touch is requested; app_main starts Matter before the allocation-heavy
# KNX bridge so CHIP can obtain its queue and event-loop stack first.
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65535
CONFIG_ESP_MATTER_MAX_DYNAMIC_ENDPOINT_COUNT=18
CONFIG_ESP_MATTER_MAX_DYNAMIC_ENDPOINT_COUNT=34
CONFIG_ESP_MATTER_AGGREGATOR_ENDPOINT_COUNT=1
CONFIG_ESP_MATTER_NVS_PART_NAME="nvs"
CONFIG_ESP_MATTER_BRIDGE_INFO_PART_NAME="nvs"
+42 -43
View File
@@ -711,7 +711,8 @@ CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y
# end of Gateway Cache
# CONFIG_GATEWAY_ENABLE_DALI_BUS is not set
CONFIG_GATEWAY_CONTROLLER_TASK_STACK_SIZE=12288
CONFIG_GATEWAY_CONTROLLER_TASK_STACK_SIZE=10240
CONFIG_GATEWAY_PREFER_PSRAM_TASK_STACKS=y
# end of DALI Settings
#
@@ -730,7 +731,7 @@ CONFIG_GATEWAY_SMARTCONFIG_SUPPORTED=y
# Matter-to-DALI Bridge
#
CONFIG_GATEWAY_MATTER_SUPPORTED=y
CONFIG_GATEWAY_MATTER_MAX_BRIDGED_DEVICES=16
CONFIG_GATEWAY_MATTER_MAX_BRIDGED_DEVICES=32
CONFIG_GATEWAY_MATTER_SCAN_START_DELAY_MS=2000
CONFIG_GATEWAY_MATTER_SCAN_ADDRESS_DELAY_MS=20
CONFIG_GATEWAY_MATTER_SCAN_TASK_STACK_SIZE=8192
@@ -1022,8 +1023,15 @@ CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=256
CONFIG_BT_NIMBLE_ATT_MAX_PREP_ENTRIES=64
CONFIG_BT_NIMBLE_GATT_MAX_PROCS=4
# CONFIG_BT_NIMBLE_BLE_GATT_BLOB_TRANSFER is not set
# CONFIG_BT_NIMBLE_GATT_CACHING is not set
# CONFIG_BT_NIMBLE_INCL_SVC_DISCOVERY is not set
CONFIG_BT_NIMBLE_GATT_CACHING=y
# CONFIG_BT_NIMBLE_GATT_CACHING_INCLUDE_SERVICES is not set
CONFIG_BT_NIMBLE_GATT_CACHING_MAX_CONNS=4
CONFIG_BT_NIMBLE_GATT_CACHING_MAX_SVCS=8
CONFIG_BT_NIMBLE_GATT_CACHING_MAX_INCL_SVCS=4
CONFIG_BT_NIMBLE_GATT_CACHING_MAX_CHRS=16
CONFIG_BT_NIMBLE_GATT_CACHING_MAX_DSCS=16
# CONFIG_BT_NIMBLE_GATT_CACHING_DISABLE_AUTO is not set
# CONFIG_BT_NIMBLE_GATT_CACHING_ASSOC_ENABLE is not set
# end of GATT / ATT
#
@@ -1130,7 +1138,7 @@ CONFIG_BT_NIMBLE_SVC_GAP_PPCP_SUPERVISION_TMO=0
#
# Extra Features
#
# CONFIG_BT_NIMBLE_DYNAMIC_SERVICE is not set
CONFIG_BT_NIMBLE_DYNAMIC_SERVICE=y
# CONFIG_BT_NIMBLE_BLUFI_ENABLE is not set
# CONFIG_BT_NIMBLE_ENC_ADV_DATA is not set
# CONFIG_BT_NIMBLE_ADV_UUID_CONCAT is not set
@@ -1456,6 +1464,7 @@ CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y
# Common ESP-related
#
CONFIG_ESP_ERR_TO_NAME_LOOKUP=y
CONFIG_ESP_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
# end of Common ESP-related
#
@@ -1544,7 +1553,6 @@ CONFIG_RMT_OBJ_CACHE_SAFE=y
#
# ESP-Driver:SPI Configurations
#
# CONFIG_SPI_MASTER_IN_IRAM is not set
CONFIG_SPI_MASTER_ISR_IN_IRAM=y
# CONFIG_SPI_SLAVE_IN_IRAM is not set
CONFIG_SPI_SLAVE_ISR_IN_IRAM=y
@@ -1877,15 +1885,14 @@ CONFIG_SPIRAM_SPEED=80
CONFIG_SPIRAM_BOOT_HW_INIT=y
CONFIG_SPIRAM_BOOT_INIT=y
CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION=y
CONFIG_SPIRAM_IGNORE_NOTFOUND=y
# CONFIG_SPIRAM_USE_MEMMAP is not set
# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set
CONFIG_SPIRAM_USE_MALLOC=y
CONFIG_SPIRAM_MEMTEST=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=512
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65535
# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set
CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set
# end of SPI RAM config
# end of ESP PSRAM
@@ -2035,10 +2042,10 @@ CONFIG_ESP_TIMER_IMPL_SYSTIMER=y
CONFIG_ESP_WIFI_ENABLED=y
CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=10
CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32
# CONFIG_ESP_WIFI_STATIC_TX_BUFFER is not set
CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER=y
CONFIG_ESP_WIFI_TX_BUFFER_TYPE=1
CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=32
CONFIG_ESP_WIFI_STATIC_TX_BUFFER=y
CONFIG_ESP_WIFI_TX_BUFFER_TYPE=0
CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM=16
CONFIG_ESP_WIFI_CACHE_TX_BUFFER_NUM=32
# CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER is not set
CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUFFER=y
CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=1
@@ -2048,18 +2055,18 @@ CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y
CONFIG_ESP_WIFI_TX_BA_WIN=6
CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y
CONFIG_ESP_WIFI_RX_BA_WIN=6
CONFIG_ESP_WIFI_NVS_ENABLED=y
# CONFIG_ESP_WIFI_AMSDU_TX_ENABLED is not set
# CONFIG_ESP_WIFI_NVS_ENABLED is not set
# CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0 is not set
CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1=y
CONFIG_ESP_WIFI_SOFTAP_BEACON_MAX_LEN=752
CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32
CONFIG_ESP_WIFI_IRAM_OPT=y
# CONFIG_ESP_WIFI_IRAM_OPT is not set
# CONFIG_ESP_WIFI_EXTRA_IRAM_OPT is not set
CONFIG_ESP_WIFI_RX_IRAM_OPT=y
# CONFIG_ESP_WIFI_RX_IRAM_OPT is not set
CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=y
CONFIG_ESP_WIFI_ENABLE_SAE_PK=y
CONFIG_ESP_WIFI_ENABLE_SAE_H2E=y
CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT=y
CONFIG_ESP_WIFI_ENABLE_WPA3_OWE_STA=y
# CONFIG_ESP_WIFI_SLP_IRAM_OPT is not set
CONFIG_ESP_WIFI_SLP_DEFAULT_MIN_ACTIVE_TIME=50
@@ -2070,7 +2077,7 @@ CONFIG_ESP_WIFI_SLP_DEFAULT_WAIT_BROADCAST_DATA_TIME=15
CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE=y
# CONFIG_ESP_WIFI_GCMP_SUPPORT is not set
CONFIG_ESP_WIFI_GMAC_SUPPORT=y
CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y
# CONFIG_ESP_WIFI_SOFTAP_SUPPORT is not set
# CONFIG_ESP_WIFI_SLP_BEACON_LOST_OPT is not set
CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=7
CONFIG_ESP_WIFI_MBEDTLS_CRYPTO=y
@@ -2081,7 +2088,6 @@ CONFIG_ESP_WIFI_MBEDTLS_TLS_CLIENT=y
# CONFIG_ESP_WIFI_MBO_SUPPORT is not set
# CONFIG_ESP_WIFI_DPP_SUPPORT is not set
# CONFIG_ESP_WIFI_11R_SUPPORT is not set
# CONFIG_ESP_WIFI_WPS_SOFTAP_REGISTRAR is not set
#
# WPS Configuration Options
@@ -2210,7 +2216,7 @@ CONFIG_FREERTOS_TICK_SUPPORT_SYSTIMER=y
CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL1=y
# CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL3 is not set
CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y
# CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set
CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y
# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set
# end of Port
@@ -2401,6 +2407,7 @@ CONFIG_LWIP_TCP_OOSEQ_MAX_PBUFS=4
CONFIG_LWIP_TCP_OVERSIZE_MSS=y
# CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set
# CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set
# CONFIG_LWIP_WND_SCALE is not set
CONFIG_LWIP_TCP_RTO_TIME=1500
# end of TCP
@@ -2505,8 +2512,8 @@ CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y
# mbedTLS
#
# CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC is not set
# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC is not set
CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC=y
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set
# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set
CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y
CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384
@@ -3066,8 +3073,8 @@ CONFIG_MAX_GROUPS_PER_FABRIC_PER_ENDPOINT=4
CONFIG_MAX_GROUP_KEYS_PER_FABRIC=3
# CONFIG_CHIP_DEVICE_ENABLE_DYNAMIC_SERVER is not set
# CONFIG_CHIP_MEM_ALLOC_MODE_INTERNAL is not set
# CONFIG_CHIP_MEM_ALLOC_MODE_EXTERNAL is not set
CONFIG_CHIP_MEM_ALLOC_MODE_DEFAULT=y
CONFIG_CHIP_MEM_ALLOC_MODE_EXTERNAL=y
# CONFIG_CHIP_MEM_ALLOC_MODE_DEFAULT is not set
# end of General Options
#
@@ -3130,13 +3137,7 @@ CONFIG_DEVICE_TYPE=0
#
# WiFi Station Options
#
CONFIG_ENABLE_WIFI_STATION=y
CONFIG_DEFAULT_WIFI_SSID=""
CONFIG_DEFAULT_WIFI_PASSWORD=""
CONFIG_WIFI_STATION_RECONNECT_INTERVAL=100
CONFIG_MAX_SCAN_NETWORKS_RESULTS=10
CONFIG_WIFI_SCAN_COMPLETION_TIMEOUT=10000
CONFIG_WIFI_CONNECTIVITY_TIMEOUT=30000
# CONFIG_ENABLE_WIFI_STATION is not set
# end of WiFi Station Options
#
@@ -3148,9 +3149,9 @@ CONFIG_BLE_FAST_ADVERTISING_INTERVAL_MIN=40
CONFIG_BLE_FAST_ADVERTISING_INTERVAL_MAX=40
CONFIG_BLE_SLOW_ADVERTISING_INTERVAL_MIN=800
CONFIG_BLE_SLOW_ADVERTISING_INTERVAL_MAX=800
CONFIG_CHIPOBLE_SINGLE_CONNECTION=y
# CONFIG_CHIPOBLE_SINGLE_CONNECTION is not set
CONFIG_CHIPOBLE_ENABLE_ADVERTISING_AUTOSTART=0
CONFIG_USE_BLE_ONLY_FOR_COMMISSIONING=y
# CONFIG_USE_BLE_ONLY_FOR_COMMISSIONING is not set
# end of BLE Options
#
@@ -3275,8 +3276,6 @@ CONFIG_MRP_MAX_RETRANS=4
#
# Network Commissioning Driver Endpoint Id
#
CONFIG_WIFI_NETWORK_COMMISSIONING_DRIVER=y
CONFIG_WIFI_NETWORK_ENDPOINT_ID=0
# CONFIG_ETHERNET_NETWORK_COMMISSIONING_DRIVER is not set
# end of Network Commissioning Driver Endpoint Id
@@ -3290,7 +3289,7 @@ CONFIG_WIFI_NETWORK_ENDPOINT_ID=0
#
# ESP Matter
#
CONFIG_ESP_MATTER_MAX_DEVICE_TYPE_COUNT=16
CONFIG_ESP_MATTER_MAX_DEVICE_TYPE_COUNT=5
CONFIG_ESP_MATTER_ATTRIBUTE_BUFFER_LARGEST=259
CONFIG_ESP_MATTER_NVS_PART_NAME="nvs"
CONFIG_ESP_MATTER_DEFERRED_ATTR_PERSISTENCE_TIME_MS=3000
@@ -3306,13 +3305,13 @@ CONFIG_FACTORY_DEVICE_INSTANCE_INFO_PROVIDER=y
# CONFIG_CUSTOM_DEVICE_INSTANCE_INFO_PROVIDER is not set
CONFIG_NONE_DEVICE_INFO_PROVIDER=y
# CONFIG_CUSTOM_DEVICE_INFO_PROVIDER is not set
CONFIG_ESP_MATTER_MAX_DYNAMIC_ENDPOINT_COUNT=18
CONFIG_ESP_MATTER_MAX_DYNAMIC_ENDPOINT_COUNT=84
CONFIG_ESP_MATTER_MODE_SELECT_CLUSTER_ENDPOINT_COUNT=0
CONFIG_ESP_MATTER_TEMPERATURE_CONTROL_CLUSTER_ENDPOINT_COUNT=0
CONFIG_ESP_MATTER_SCENES_TABLE_SIZE=16
CONFIG_ESP_MATTER_BINDING_TABLE_SIZE=10
CONFIG_ESP_MATTER_MEM_ALLOC_MODE_INTERNAL=y
# CONFIG_ESP_MATTER_MEM_ALLOC_MODE_EXTERNAL is not set
# CONFIG_ESP_MATTER_MEM_ALLOC_MODE_INTERNAL is not set
CONFIG_ESP_MATTER_MEM_ALLOC_MODE_EXTERNAL=y
# CONFIG_ESP_MATTER_MEM_ALLOC_MODE_DEFAULT is not set
CONFIG_ESP_MATTER_ENABLE_DATA_MODEL=y
CONFIG_ESP_MATTER_ENABLE_MATTER_SERVER=y
@@ -3608,10 +3607,10 @@ CONFIG_MDNS_TASK_AFFINITY=0x0
#
# MDNS Memory Configuration
#
# CONFIG_MDNS_TASK_CREATE_FROM_SPIRAM is not set
CONFIG_MDNS_TASK_CREATE_FROM_INTERNAL=y
# CONFIG_MDNS_MEMORY_ALLOC_SPIRAM is not set
CONFIG_MDNS_MEMORY_ALLOC_INTERNAL=y
CONFIG_MDNS_TASK_CREATE_FROM_SPIRAM=y
# CONFIG_MDNS_TASK_CREATE_FROM_INTERNAL is not set
CONFIG_MDNS_MEMORY_ALLOC_SPIRAM=y
# CONFIG_MDNS_MEMORY_ALLOC_INTERNAL is not set
# CONFIG_MDNS_MEMORY_CUSTOM_IMPL is not set
# end of MDNS Memory Configuration
@@ -190,6 +190,9 @@ class DaliDomainService {
bool logarithmic_curve = false) const;
bool setColTempRaw(uint8_t gateway_id, int short_address, int mirek) const;
bool setColTemp(uint8_t gateway_id, int short_address, int kelvin) const;
// Accepts a DALI logical target (short address 0-63, group 64-79, or
// broadcast 127). Callers must not construct encoded command addresses.
bool setColourXY(uint8_t gateway_id, int logical_target, int x, int y) const;
bool setColourRaw(uint8_t gateway_id, int raw_addr, int x, int y) const;
bool setColourRGB(uint8_t gateway_id, int short_address, int r, int g, int b) const;
bool setColourRGBW(uint8_t gateway_id, int short_address, int r, int g, int b, int w) const;
@@ -1528,6 +1528,19 @@ bool DaliDomainService::setColTemp(uint8_t gateway_id, int short_address, int ke
return channel->dali->dt8.setColorTemperature(short_address, kelvin);
}
bool DaliDomainService::setColourXY(uint8_t gateway_id, int logical_target, int x,
int y) const {
const auto* channel = findChannelByGateway(gateway_id);
if (channel == nullptr || channel->dali == nullptr || logical_target < 0 ||
!((logical_target <= 79) || logical_target == 127)) {
return false;
}
markBusActivity(gateway_id);
const double normalized_x = std::clamp(x, 0, 65535) / 65535.0;
const double normalized_y = std::clamp(y, 0, 65535) / 65535.0;
return channel->dali->dt8.setColour(logical_target, normalized_x, normalized_y);
}
bool DaliDomainService::setColourRaw(uint8_t gateway_id, int raw_addr, int x, int y) const {
const auto* channel = findChannelByGateway(gateway_id);
if (channel == nullptr || channel->dali == nullptr) {
@@ -23,6 +23,7 @@ struct GatewayBacnetServerConfig {
uint16_t udp_port{47808};
uint32_t task_stack_size{8192};
UBaseType_t task_priority{5};
bool task_stack_in_psram{false};
};
struct GatewayBacnetObjectBinding {
@@ -2,7 +2,9 @@
#include "gateway_bacnet_stack_port.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "freertos/idf_additions.h"
#include "freertos/semphr.h"
#include <algorithm>
@@ -318,9 +320,21 @@ esp_err_t GatewayBacnetServer::startStackLocked(const GatewayBacnetServerConfig&
return ESP_FAIL;
}
const BaseType_t created = xTaskCreate(&GatewayBacnetServer::TaskEntry, "gw_bacnet_ip",
active_config_.task_stack_size, this,
active_config_.task_priority, &task_handle_);
BaseType_t created = pdFAIL;
if (active_config_.task_stack_in_psram) {
created = xTaskCreateWithCaps(
&GatewayBacnetServer::TaskEntry, "gw_bacnet_ip",
active_config_.task_stack_size, this, active_config_.task_priority,
&task_handle_, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (created != pdPASS) {
ESP_LOGW(kTag, "PSRAM BACnet stack allocation failed; using internal RAM");
}
}
if (created != pdPASS) {
created = xTaskCreate(&GatewayBacnetServer::TaskEntry, "gw_bacnet_ip",
active_config_.task_stack_size, this,
active_config_.task_priority, &task_handle_);
}
if (created != pdPASS) {
task_handle_ = nullptr;
gateway_bacnet_stack_cleanup();
@@ -45,6 +45,7 @@ struct GatewayBridgeServiceConfig {
std::vector<int> reserved_uart_ports;
uint32_t bacnet_task_stack_size{8192};
UBaseType_t bacnet_task_priority{5};
bool bacnet_task_stack_in_psram{false};
uint32_t knx_task_stack_size{12288};
UBaseType_t knx_task_priority{5};
std::optional<GatewayKnxConfig> default_knx_config;
@@ -52,6 +53,10 @@ struct GatewayBridgeServiceConfig {
std::function<GatewayBridgeHttpResponse()> gateway_settings_getter;
std::function<GatewayBridgeHttpResponse(std::string_view)> gateway_settings_setter;
std::function<std::string(std::string_view)> gateway_settings_cloud_handler;
std::function<GatewayBridgeHttpResponse(std::optional<uint8_t>)> matter_status_getter;
std::function<GatewayBridgeHttpResponse()> matter_onboarding_getter;
std::function<GatewayBridgeHttpResponse(std::string_view, std::optional<uint8_t>,
std::string_view)> matter_action_handler;
GatewayKnxGatewaySnapshotProvider knx_gateway_snapshot_provider;
GatewayKnxGatewayCommandTransactor knx_gateway_command_transactor;
};
@@ -2066,6 +2066,7 @@ struct GatewayBridgeService::ChannelRuntime {
}
config.task_stack_size = service_config.bacnet_task_stack_size;
config.task_priority = service_config.bacnet_task_priority;
config.task_stack_in_psram = service_config.bacnet_task_stack_in_psram;
if (bacnet_server_config.has_value()) {
config.device_instance = bacnet_server_config->deviceInstance;
config.local_address = bacnet_server_config->localAddress;
@@ -4636,6 +4637,18 @@ GatewayBridgeHttpResponse GatewayBridgeService::handleGet(
}
return config_.gateway_settings_getter();
}
if (action == "matter_status") {
if (!config_.matter_status_getter) {
return ErrorResponse(ESP_ERR_NOT_SUPPORTED, "Matter status is not available");
}
return config_.matter_status_getter(gateway_id);
}
if (action == "matter_onboarding") {
if (!config_.matter_onboarding_getter) {
return ErrorResponse(ESP_ERR_NOT_SUPPORTED, "Matter onboarding is not available");
}
return config_.matter_onboarding_getter();
}
if (action == "status" && !gateway_id.has_value()) {
cJSON* root = cJSON_CreateObject();
@@ -4814,6 +4827,14 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
}
return config_.gateway_settings_setter(body);
}
if (action == "matter_open_commissioning" ||
action == "matter_close_commissioning" || action == "matter_rescan" ||
action == "matter_config" || action == "matter_config_reset") {
if (!config_.matter_action_handler) {
return ErrorResponse(ESP_ERR_NOT_SUPPORTED, "Matter control is not available");
}
return config_.matter_action_handler(action, gateway_id, body);
}
if (!gateway_id.has_value()) {
return ErrorResponse(ESP_ERR_INVALID_ARG, "gateway id is required");
}
@@ -27,6 +27,7 @@ class GatewayRuntime;
struct GatewayControllerConfig {
uint32_t task_stack_size{12288};
UBaseType_t task_priority{5};
bool operation_task_stack_in_psram{false};
int color_temperature_min{2000};
int color_temperature_max{6500};
bool setup_supported{true};
@@ -187,6 +188,7 @@ class GatewayController {
struct GatewayOperationTaskContext {
GatewayController* controller{nullptr};
bool external_stack{false};
uint8_t gateway_id{0};
uint8_t request_id{0};
uint16_t operation_id{0};
@@ -1,8 +1,10 @@
#include "gateway_controller.hpp"
#include "dali_domain.hpp"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "esp_system.h"
#include "freertos/idf_additions.h"
#include "gateway_bridge.hpp"
#include "gateway_runtime.hpp"
@@ -924,14 +926,20 @@ void GatewayController::TaskEntry(void* arg) {
void GatewayController::OperationTaskEntry(void* arg) {
auto* context = static_cast<GatewayOperationTaskContext*>(arg);
if (context == nullptr || context->controller == nullptr) {
delete context;
if (context == nullptr) {
vTaskDelete(nullptr);
return;
}
context->controller->runOperationTask(context);
const bool external_stack = context->external_stack;
if (context->controller != nullptr) {
context->controller->runOperationTask(context);
}
delete context;
vTaskDelete(nullptr);
if (external_stack) {
vTaskDeleteWithCaps(nullptr);
} else {
vTaskDelete(nullptr);
}
}
void GatewayController::taskLoop() {
@@ -2495,9 +2503,21 @@ bool GatewayController::startOperation(uint8_t gateway_id, uint8_t request_id,
context->request_id = request_id;
context->operation_id = operation_id;
context->fields = std::move(fields);
const BaseType_t created = xTaskCreate(&GatewayController::OperationTaskEntry, "gateway_op",
config_.task_stack_size, context,
config_.task_priority, nullptr);
BaseType_t created = pdFAIL;
if (config_.operation_task_stack_in_psram) {
created = xTaskCreateWithCaps(
&GatewayController::OperationTaskEntry, "gateway_op", config_.task_stack_size,
context, config_.task_priority, nullptr, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
context->external_stack = created == pdPASS;
if (created != pdPASS) {
ESP_LOGW(kTag, "PSRAM operation stack allocation failed; using internal RAM");
}
}
if (created != pdPASS) {
created = xTaskCreate(&GatewayController::OperationTaskEntry, "gateway_op",
config_.task_stack_size, context,
config_.task_priority, nullptr);
}
if (created != pdPASS) {
delete context;
{
+2 -2
View File
@@ -1,7 +1,7 @@
idf_component_register(
SRCS "src/gateway_matter.cpp"
SRCS "src/gateway_matter.cpp" "src/gateway_matter_plan.cpp"
INCLUDE_DIRS "include"
REQUIRES dali_domain esp_driver_gpio esp_matter freertos log nvs_flash
REQUIRES dali_domain esp_driver_gpio esp_matter freertos json log nvs_flash
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
@@ -1,13 +1,18 @@
#pragma once
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "dali_domain.hpp"
#include "gateway_matter_plan.hpp"
#include "esp_err.h"
#include "esp_matter.h"
#include "esp_matter_bridge.h"
@@ -18,7 +23,7 @@ namespace gateway {
struct GatewayMatterBridgeConfig {
bool enabled{false};
size_t max_bridged_devices{16};
size_t max_bridged_devices{32};
uint32_t scan_start_delay_ms{2000};
uint32_t scan_address_delay_ms{20};
uint32_t scan_task_stack_size{8192};
@@ -43,12 +48,23 @@ class GatewayMatterBridge {
~GatewayMatterBridge() = default;
esp_err_t start();
esp_err_t openCommissioningWindow();
esp_err_t closeCommissioningWindow();
esp_err_t rescanDaliDevices();
esp_err_t applyConfiguration(uint8_t gateway_id, std::string_view patch);
esp_err_t resetConfiguration(uint8_t gateway_id);
std::string statusJson(std::optional<uint8_t> gateway_id = std::nullopt) const;
std::string onboardingJson() const;
private:
struct Binding {
GatewayMatterBridge* owner{nullptr};
uint8_t channel_index{0};
uint8_t gateway_id{0};
uint8_t short_address{0};
MatterTargetKind target_kind{MatterTargetKind::shortAddress};
uint8_t target_address{0};
MatterEndpointType endpoint_type{MatterEndpointType::switchLight};
std::optional<MatterColorMethod> color_method;
uint16_t dali_device_type_mask{0};
uint32_t matter_device_type{0};
uint16_t endpoint_id{0};
@@ -77,21 +93,25 @@ class GatewayMatterBridge {
static void ButtonTaskEntry(void* argument);
static void ApplyStatusWork(intptr_t argument);
static void OpenCommissioningWindowWork(intptr_t argument);
static void CloseCommissioningWindowWork(intptr_t argument);
esp_err_t handleAttributeUpdate(Binding& binding, uint32_t cluster_id,
uint32_t attribute_id,
const esp_matter_attr_val_t& value);
esp_err_t initializeBridgeStorage();
esp_err_t resumeBindings();
esp_err_t loadConfigurations();
esp_err_t storeConfiguration(const MatterChannelConfiguration& configuration) const;
esp_err_t configureProvisioningButton();
void scanDaliDevices();
esp_err_t reconcileEndpoints();
void buttonTaskLoop();
void handleStatusUpdate(const DaliGatewayStatusUpdate& update);
Binding* findBinding(uint8_t gateway_id, uint8_t short_address) const;
Binding* findBinding(const MatterTarget& target) const;
Binding* findBinding(uint16_t endpoint_id) const;
esp_err_t createBinding(uint8_t gateway_id, uint8_t short_address,
uint16_t device_type_mask,
uint8_t initial_level = 0);
esp_err_t createBinding(const MatterEndpointAllocation& allocation,
uint16_t device_type_mask, uint8_t initial_level = 0);
esp_err_t removeBinding(size_t index);
esp_err_t storeBinding(const Binding& binding) const;
std::unique_ptr<Binding> readBinding(uint16_t endpoint_id) const;
void updateMatterLevel(Binding& binding, uint8_t level);
@@ -103,9 +123,16 @@ class GatewayMatterBridge {
uint16_t aggregator_endpoint_id_{0};
mutable std::mutex bindings_mutex_;
std::vector<std::unique_ptr<Binding>> bindings_;
std::vector<MatterChannelConfiguration> configurations_;
MatterEndpointPlan endpoint_plan_;
std::string last_apply_error_;
TaskHandle_t scan_task_{nullptr};
TaskHandle_t button_task_{nullptr};
bool started_{false};
std::atomic_bool scan_in_progress_{false};
std::atomic_bool ble_advertising_{false};
std::atomic_bool commissioning_window_open_{false};
std::atomic_uint8_t fabric_count_{0};
};
} // namespace gateway
@@ -0,0 +1,86 @@
#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
namespace gateway {
enum class MatterTargetKind : uint8_t { shortAddress = 0, group = 1, broadcast = 2 };
enum class MatterEndpointType : uint8_t { switchLight = 0, dimmer = 1, colorTemperature = 2, color = 3 };
enum class MatterColorMethod : uint8_t { xy = 0, rgbcw = 1, both = 2 };
enum class MatterAddressOverride : uint8_t { automatic = 0, include = 1, exclude = 2 };
struct MatterTarget {
uint8_t channel_index{0};
uint8_t gateway_id{0};
MatterTargetKind kind{MatterTargetKind::shortAddress};
uint8_t address{0};
bool sameIdentity(const MatterTarget& other) const {
return channel_index == other.channel_index && kind == other.kind && address == other.address;
}
};
struct MatterEndpointOverride {
std::optional<MatterEndpointType> type;
std::optional<MatterColorMethod> color_method;
bool manual() const { return type.has_value() || color_method.has_value(); }
};
struct MatterChannelConfiguration {
uint8_t channel_index{0};
bool automatic_short_addresses{true};
std::array<MatterAddressOverride, 64> short_address_overrides{};
MatterEndpointOverride broadcast;
std::array<MatterEndpointOverride, 16> groups{};
};
struct MatterAddressCandidate {
bool online{false};
bool group_mask_known{false};
uint16_t group_mask{0};
std::optional<uint16_t> dali_device_type_mask;
std::optional<uint8_t> dt8_color_type_features;
};
struct MatterChannelCandidates {
uint8_t channel_index{0};
uint8_t gateway_id{0};
std::array<MatterAddressCandidate, 64> addresses{};
};
struct MatterEndpointAllocation {
MatterTarget target;
MatterEndpointType type{MatterEndpointType::switchLight};
std::optional<MatterColorMethod> color_method;
bool type_is_manual{false};
bool color_method_is_manual{false};
bool explicitly_included{false};
};
struct MatterDroppedAllocation {
MatterEndpointAllocation allocation;
std::string reason;
};
struct MatterEndpointPlan {
size_t capacity{0};
std::vector<MatterEndpointAllocation> active;
std::vector<MatterDroppedAllocation> dropped;
};
MatterEndpointPlan BuildMatterEndpointPlan(
const std::vector<MatterChannelCandidates>& candidates,
const std::vector<MatterChannelConfiguration>& configurations,
size_t capacity);
const char* MatterTargetKindName(MatterTargetKind kind);
const char* MatterEndpointTypeName(MatterEndpointType type);
const char* MatterColorMethodName(MatterColorMethod method);
} // namespace gateway
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,212 @@
#include "gateway_matter_plan.hpp"
#include <algorithm>
namespace gateway {
namespace {
constexpr uint16_t kDt8Mask = 1U << 8;
constexpr uint16_t kDt7Mask = 1U << 7;
struct InferredCapability {
MatterEndpointType type{MatterEndpointType::switchLight};
bool has_xy{false};
bool has_rgbcw{false};
bool usable{false};
};
const MatterChannelConfiguration* FindConfiguration(
const std::vector<MatterChannelConfiguration>& configurations,
uint8_t channel_index) {
const auto found = std::find_if(
configurations.begin(), configurations.end(),
[channel_index](const auto& item) { return item.channel_index == channel_index; });
return found == configurations.end() ? nullptr : &*found;
}
InferredCapability InferAddress(const MatterAddressCandidate& address) {
InferredCapability result;
if (!address.dali_device_type_mask.has_value()) return result;
result.usable = true;
const uint16_t mask = *address.dali_device_type_mask;
if ((mask & kDt8Mask) != 0) {
const uint8_t features = address.dt8_color_type_features.value_or(0);
result.has_xy = (features & 0x01) != 0;
result.has_rgbcw = ((features >> 5) & 0x07) != 0;
if (result.has_xy || result.has_rgbcw) {
result.type = MatterEndpointType::color;
} else if ((features & 0x02) != 0) {
result.type = MatterEndpointType::colorTemperature;
} else {
result.type = MatterEndpointType::dimmer;
}
} else if ((mask & ~kDt7Mask) != 0) {
result.type = MatterEndpointType::dimmer;
}
return result;
}
MatterEndpointAllocation InferMultiTarget(
const MatterChannelCandidates& channel, MatterTargetKind kind, uint8_t address,
const MatterEndpointOverride& override) {
MatterEndpointAllocation allocation;
allocation.target = {channel.channel_index, channel.gateway_id, kind, address};
bool any_member = false;
bool has_xy = false;
bool has_rgbcw = false;
for (const auto& member : channel.addresses) {
const uint8_t group = kind == MatterTargetKind::group
? static_cast<uint8_t>(address - 64)
: 0;
const bool included = kind == MatterTargetKind::broadcast ||
(member.group_mask_known &&
(member.group_mask & (1U << group)) != 0);
if (!included) continue;
any_member = true;
const auto inferred = InferAddress(member);
if (static_cast<uint8_t>(inferred.type) > static_cast<uint8_t>(allocation.type)) {
allocation.type = inferred.type;
}
has_xy = has_xy || inferred.has_xy;
has_rgbcw = has_rgbcw || inferred.has_rgbcw;
}
if (override.type.has_value()) {
allocation.type = *override.type;
allocation.type_is_manual = true;
}
if (allocation.type == MatterEndpointType::color) {
if (override.color_method.has_value()) {
allocation.color_method = *override.color_method;
allocation.color_method_is_manual = true;
} else if (has_xy && has_rgbcw) {
allocation.color_method = MatterColorMethod::both;
} else if (has_xy) {
allocation.color_method = MatterColorMethod::xy;
} else if (has_rgbcw) {
allocation.color_method = MatterColorMethod::rgbcw;
} else if (!any_member || override.manual()) {
allocation.color_method = MatterColorMethod::both;
} else {
allocation.color_method = MatterColorMethod::both;
}
}
return allocation;
}
MatterEndpointAllocation InferShortAddress(
const MatterChannelCandidates& channel, uint8_t address, bool explicitly_included) {
MatterEndpointAllocation allocation;
allocation.target = {channel.channel_index, channel.gateway_id,
MatterTargetKind::shortAddress, address};
allocation.explicitly_included = explicitly_included;
const auto inferred = InferAddress(channel.addresses[address]);
allocation.type = inferred.type;
if (inferred.type == MatterEndpointType::color) {
allocation.color_method = inferred.has_xy && inferred.has_rgbcw
? MatterColorMethod::both
: (inferred.has_xy ? MatterColorMethod::xy
: MatterColorMethod::rgbcw);
}
return allocation;
}
void AppendWithinCapacity(MatterEndpointPlan* plan,
const MatterEndpointAllocation& allocation) {
if (plan->active.size() < plan->capacity) {
plan->active.push_back(allocation);
} else {
plan->dropped.push_back({allocation, "capacity"});
}
}
} // namespace
MatterEndpointPlan BuildMatterEndpointPlan(
const std::vector<MatterChannelCandidates>& raw_candidates,
const std::vector<MatterChannelConfiguration>& configurations,
size_t capacity) {
auto candidates = raw_candidates;
std::sort(candidates.begin(), candidates.end(), [](const auto& a, const auto& b) {
return a.channel_index < b.channel_index;
});
MatterEndpointPlan plan;
plan.capacity = capacity;
// Broadcasts and retained groups are deliberately allocated across every
// channel before any short address can consume the shared pool.
for (const auto& channel : candidates) {
const auto* config = FindConfiguration(configurations, channel.channel_index);
const MatterEndpointOverride empty;
AppendWithinCapacity(&plan, InferMultiTarget(
channel, MatterTargetKind::broadcast, 127,
config == nullptr ? empty : config->broadcast));
}
for (const auto& channel : candidates) {
const auto* config = FindConfiguration(configurations, channel.channel_index);
for (uint8_t group = 0; group < 16; ++group) {
const bool cached_non_empty = std::any_of(
channel.addresses.begin(), channel.addresses.end(), [group](const auto& address) {
return address.group_mask_known && (address.group_mask & (1U << group)) != 0;
});
const MatterEndpointOverride empty;
const auto& override = config == nullptr ? empty : config->groups[group];
if (!cached_non_empty && !override.manual()) continue;
AppendWithinCapacity(&plan,
InferMultiTarget(channel, MatterTargetKind::group,
static_cast<uint8_t>(64 + group), override));
}
}
for (const auto& channel : candidates) {
const auto* config = FindConfiguration(configurations, channel.channel_index);
if (config == nullptr) continue;
for (uint8_t address = 0; address < 64; ++address) {
if (config->short_address_overrides[address] == MatterAddressOverride::include) {
AppendWithinCapacity(&plan, InferShortAddress(channel, address, true));
}
}
}
for (const auto& channel : candidates) {
const auto* config = FindConfiguration(configurations, channel.channel_index);
const bool automatic_enabled = config == nullptr || config->automatic_short_addresses;
if (!automatic_enabled) continue;
for (uint8_t address = 0; address < 64; ++address) {
const auto mode = config == nullptr ? MatterAddressOverride::automatic
: config->short_address_overrides[address];
if (mode != MatterAddressOverride::automatic || !channel.addresses[address].online) {
continue;
}
AppendWithinCapacity(&plan, InferShortAddress(channel, address, false));
}
}
return plan;
}
const char* MatterTargetKindName(MatterTargetKind kind) {
switch (kind) {
case MatterTargetKind::shortAddress: return "shortAddress";
case MatterTargetKind::group: return "group";
case MatterTargetKind::broadcast: return "broadcast";
}
return "shortAddress";
}
const char* MatterEndpointTypeName(MatterEndpointType type) {
switch (type) {
case MatterEndpointType::switchLight: return "switch";
case MatterEndpointType::dimmer: return "dimmer";
case MatterEndpointType::colorTemperature: return "colorTemperature";
case MatterEndpointType::color: return "color";
}
return "switch";
}
const char* MatterColorMethodName(MatterColorMethod method) {
switch (method) {
case MatterColorMethod::xy: return "xy";
case MatterColorMethod::rgbcw: return "rgbcw";
case MatterColorMethod::both: return "both";
}
return "both";
}
} // namespace gateway
@@ -0,0 +1,72 @@
#include "gateway_matter_plan.hpp"
#include <cassert>
using namespace gateway;
MatterChannelCandidates Channel(uint8_t index) {
MatterChannelCandidates channel;
channel.channel_index = index;
channel.gateway_id = static_cast<uint8_t>(10 + index);
return channel;
}
int main() {
{
auto channel = Channel(0);
channel.addresses[1].group_mask_known = true;
channel.addresses[1].group_mask = 1U << 2;
channel.addresses[1].dali_device_type_mask = 1U << 8;
channel.addresses[1].dt8_color_type_features = 0x23;
const auto plan = BuildMatterEndpointPlan({channel}, {}, 64);
assert(plan.active.size() == 2);
assert(plan.active[0].target.kind == MatterTargetKind::broadcast);
assert(plan.active[1].target.address == 66);
assert(plan.active[1].type == MatterEndpointType::color);
assert(plan.active[1].color_method == MatterColorMethod::both);
}
{
auto channel = Channel(0);
MatterChannelConfiguration config;
config.channel_index = 0;
config.groups[4].type = MatterEndpointType::color;
const auto plan = BuildMatterEndpointPlan({channel}, {config}, 64);
assert(plan.active.size() == 2);
assert(plan.active[1].target.address == 68);
assert(plan.active[1].type_is_manual);
assert(plan.active[1].color_method == MatterColorMethod::both);
}
{
auto channel = Channel(0);
channel.addresses[7].online = true;
MatterChannelConfiguration config;
config.channel_index = 0;
config.automatic_short_addresses = false;
config.short_address_overrides[9] = MatterAddressOverride::include;
const auto plan = BuildMatterEndpointPlan({channel}, {config}, 64);
assert(plan.active.size() == 2);
assert(plan.active[1].target.address == 9);
assert(plan.active[1].explicitly_included);
}
{
auto channel1 = Channel(1);
auto channel0 = Channel(0);
channel0.addresses[0].online = true;
channel1.addresses[0].online = true;
const auto plan = BuildMatterEndpointPlan({channel1, channel0}, {}, 64);
assert(plan.active[0].target.channel_index == 0);
assert(plan.active[1].target.channel_index == 1);
assert(plan.active[2].target.channel_index == 0);
assert(plan.active[3].target.channel_index == 1);
}
{
auto channel = Channel(0);
for (auto& address : channel.addresses) address.online = true;
const auto plan = BuildMatterEndpointPlan({channel}, {}, 32);
assert(plan.active.size() == 32);
assert(plan.dropped.size() == 33);
assert(plan.active.back().target.address == 30);
assert(plan.dropped.front().allocation.target.address == 31);
assert(plan.dropped.front().reason == "capacity");
}
}