[Scenes] Added the on-off cluster handler for scene EFS (#27041)
* Added wrapper and shim to use GetClusterCountFromEndpoint in tests with expected results from TestSceneTable
* Restyled by clang-format
* Added the on-off cluster handler for scene EFS
* Added a way to track OnOff state during scene transition time with an <Endpoint, State> array, addressed comments related to code style and nits
* Added shutdown and finish methods in scenetable and scenes-server to ensure memory is released
* Removed SceneTable->Finish in scenes-server shutdown as it is now called in SceneTable's destructor
* Apply suggestions from code review
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Applied suggestion WIP on fix for intrusive list empty assert
* Apply suggestions from code review
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
---------
Co-authored-by: Restyled.io <commits@restyled.io>
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
diff --git a/src/app/clusters/on-off-server/on-off-server.cpp b/src/app/clusters/on-off-server/on-off-server.cpp
index 78be247..bfda035 100644
--- a/src/app/clusters/on-off-server/on-off-server.cpp
+++ b/src/app/clusters/on-off-server/on-off-server.cpp
@@ -41,15 +41,253 @@
using namespace chip::app::Clusters::OnOff;
using chip::Protocols::InteractionModel::Status;
+#ifdef EMBER_AF_PLUGIN_LEVEL_CONTROL
+static bool LevelControlWithOnOffFeaturePresent(EndpointId endpoint)
+{
+ if (!emberAfContainsServer(endpoint, LevelControl::Id))
+ {
+ return false;
+ }
+
+ return LevelControlHasFeature(endpoint, LevelControl::Feature::kOnOff);
+}
+#endif // EMBER_AF_PLUGIN_LEVEL_CONTROL
+
+static constexpr size_t kOnOffMaxEnpointCount =
+ EMBER_AF_ON_OFF_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT;
+
+#ifdef EMBER_AF_PLUGIN_SCENES
+static EmberEventControl sceneHandlerEventControls[kOnOffMaxEnpointCount];
+static void sceneOnOffCallback(EndpointId endpoint);
+
+class DefaultOnOffSceneHandler : public scenes::DefaultSceneHandlerImpl
+{
+public:
+ /// @brief Struct to keep track of the desired state of the OnOff attribute between ApplyScene and
+ /// transition time expiration
+ struct EndpointStatePair
+ {
+ EndpointStatePair(EndpointId endpoint = kInvalidEndpointId, bool status = false) : mEndpoint(endpoint), mState(status) {}
+ EndpointId mEndpoint;
+ bool mState;
+ };
+
+ /// @brief Struct holding an array of EndpointStatePair. Handles insertion, get and removal by EndpointID.
+ /// TODO: Implement generic object to handle this boilerplate array manipulation
+ struct StatePairBuffer
+ {
+ bool IsEmpty() const { return (mPairCount == 0); }
+
+ CHIP_ERROR FindPair(const EndpointId endpoint, uint16_t & found_index) const
+ {
+ VerifyOrReturnError(!IsEmpty(), CHIP_ERROR_NOT_FOUND);
+ for (found_index = 0; found_index < mPairCount; found_index++)
+ {
+ if (endpoint == mStatePairBuffer[found_index].mEndpoint)
+ {
+ return CHIP_NO_ERROR;
+ }
+ }
+
+ return CHIP_ERROR_NOT_FOUND;
+ }
+
+ CHIP_ERROR InsertPair(const EndpointStatePair & status)
+ {
+ uint16_t idx;
+ CHIP_ERROR err = FindPair(status.mEndpoint, idx);
+
+ if (CHIP_NO_ERROR == err)
+ {
+ mStatePairBuffer[idx] = status;
+ }
+ else if (mPairCount < MAX_ENDPOINT_COUNT)
+ {
+ // if not found, insert at the end
+ mStatePairBuffer[mPairCount] = status;
+ mPairCount++;
+ }
+ else
+ {
+ return CHIP_ERROR_NO_MEMORY;
+ }
+
+ return CHIP_NO_ERROR;
+ }
+
+ CHIP_ERROR GetPair(const EndpointId endpoint, EndpointStatePair & status) const
+ {
+ uint16_t idx;
+ ReturnErrorOnFailure(FindPair(endpoint, idx));
+
+ status = mStatePairBuffer[idx];
+ return CHIP_NO_ERROR;
+ }
+
+ /// @brief Removes Pair and decrements Pair count if the endpoint existed in the array
+ /// @param endpoint : endpoint id of the pair
+ CHIP_ERROR RemovePair(const EndpointId endpoint)
+ {
+ uint16_t position;
+ VerifyOrReturnValue(CHIP_NO_ERROR == FindPair(endpoint, position), CHIP_NO_ERROR);
+
+ uint16_t nextPos = static_cast<uint16_t>(position + 1);
+ uint16_t moveNum = static_cast<uint16_t>(mPairCount - nextPos);
+
+ // Compress array after removal, if the removed position is not the last
+ if (moveNum)
+ {
+ memmove(&mStatePairBuffer[position], &mStatePairBuffer[nextPos], sizeof(EndpointStatePair) * moveNum);
+ }
+
+ mPairCount--;
+ // Clear last occupied position
+ mStatePairBuffer[mPairCount].mEndpoint = kInvalidEndpointId;
+
+ return CHIP_NO_ERROR;
+ }
+
+ uint16_t mPairCount;
+ EndpointStatePair mStatePairBuffer[kOnOffMaxEnpointCount];
+ };
+
+ StatePairBuffer mSceneEndpointStatePairs;
+ // As per spec, 1 attribute is scenable in the on off cluster
+ static constexpr uint8_t scenableAttributeCount = 1;
+
+ DefaultOnOffSceneHandler() = default;
+ ~DefaultOnOffSceneHandler() override {}
+
+ // Default function for OnOff cluster, only puts the OnOff cluster ID in the span if supported on the given endpoint
+ virtual void GetSupportedClusters(EndpointId endpoint, Span<ClusterId> & clusterBuffer) override
+ {
+ ClusterId * buffer = clusterBuffer.data();
+ if (emberAfContainsServer(endpoint, OnOff::Id) && clusterBuffer.size() >= 1)
+ {
+ buffer[0] = OnOff::Id;
+ clusterBuffer.reduce_size(1);
+ }
+ else
+ {
+ clusterBuffer.reduce_size(0);
+ }
+ }
+
+ // Default function for OnOff cluster, only checks if OnOff is enabled on the endpoint
+ bool SupportsCluster(EndpointId endpoint, ClusterId cluster) override
+ {
+ return (cluster == OnOff::Id) && (emberAfContainsServer(endpoint, OnOff::Id));
+ }
+
+ /// @brief Serialize the Cluster's EFS value
+ /// @param endpoint target endpoint
+ /// @param cluster target cluster
+ /// @param serializedBytes data to serialize into EFS
+ /// @return CHIP_NO_ERROR if successfully serialized the data, CHIP_ERROR_INVALID_ARGUMENT otherwise
+ CHIP_ERROR SerializeSave(EndpointId endpoint, ClusterId cluster, MutableByteSpan & serializedBytes) override
+ {
+ using AttributeValuePair = Scenes::Structs::AttributeValuePair::Type;
+
+ bool currentValue;
+ // read current on/off value
+ EmberAfStatus status = Attributes::OnOff::Get(endpoint, ¤tValue);
+ if (status != EMBER_ZCL_STATUS_SUCCESS)
+ {
+ ChipLogError(Zcl, "ERR: reading on/off %x", status);
+ return CHIP_ERROR_READ_FAILED;
+ }
+
+ AttributeValuePair pairs[scenableAttributeCount];
+
+ pairs[0].attributeID.SetValue(Attributes::OnOff::Id);
+ pairs[0].attributeValue = currentValue;
+
+ app::DataModel::List<AttributeValuePair> attributeValueList(pairs);
+
+ return EncodeAttributeValueList(attributeValueList, serializedBytes);
+ }
+
+ /// @brief Default EFS interaction when applying scene to the OnOff Cluster
+ /// @param endpoint target endpoint
+ /// @param cluster target cluster
+ /// @param serializedBytes Data from nvm
+ /// @param timeMs transition time in ms
+ /// @return CHIP_NO_ERROR if value as expected, CHIP_ERROR_INVALID_ARGUMENT otherwise
+ CHIP_ERROR ApplyScene(EndpointId endpoint, ClusterId cluster, const ByteSpan & serializedBytes,
+ scenes::TransitionTimeMs timeMs) override
+ {
+ app::DataModel::DecodableList<Scenes::Structs::AttributeValuePair::DecodableType> attributeValueList;
+
+ VerifyOrReturnError(cluster == OnOff::Id, CHIP_ERROR_INVALID_ARGUMENT);
+
+ ReturnErrorOnFailure(DecodeAttributeValueList(serializedBytes, attributeValueList));
+
+ size_t attributeCount = 0;
+ ReturnErrorOnFailure(attributeValueList.ComputeSize(&attributeCount));
+ VerifyOrReturnError(attributeCount <= scenableAttributeCount, CHIP_ERROR_BUFFER_TOO_SMALL);
+
+ auto pair_iterator = attributeValueList.begin();
+ while (pair_iterator.Next())
+ {
+ auto & decodePair = pair_iterator.GetValue();
+ if (decodePair.attributeID.HasValue())
+ {
+ // If attribute ID was encoded, verify it is the proper ID for the OnOff attribute
+ VerifyOrReturnError(decodePair.attributeID.Value() == Attributes::OnOff::Id, CHIP_ERROR_INVALID_ARGUMENT);
+ }
+ ReturnErrorOnFailure(
+ mSceneEndpointStatePairs.InsertPair(EndpointStatePair(endpoint, static_cast<bool>(decodePair.attributeValue))));
+ }
+ // Verify that the EFS was completely read
+ CHIP_ERROR err = pair_iterator.GetStatus();
+ if (CHIP_NO_ERROR != err)
+ {
+ mSceneEndpointStatePairs.RemovePair(endpoint);
+ return err;
+ }
+
+ OnOffServer::Instance().scheduleTimerCallbackMs(sceneEventControl(endpoint), timeMs);
+
+ return CHIP_NO_ERROR;
+ }
+
+private:
+ /**
+ * @brief Configures EventControl callback when setting On Off through scenes callback
+ *
+ * @param[in] endpoint endpoint to start timer for
+ * @return EmberEventControl* configured event control
+ */
+ EmberEventControl * sceneEventControl(EndpointId endpoint)
+ {
+ EmberEventControl * controller =
+ OnOffServer::Instance().getEventControl(endpoint, Span<EmberEventControl>(sceneHandlerEventControls));
+ VerifyOrReturnValue(controller != nullptr, nullptr);
+
+ controller->endpoint = endpoint;
+ controller->callback = &sceneOnOffCallback;
+
+ return controller;
+ }
+};
+static DefaultOnOffSceneHandler sOnOffSceneHandler;
+
+static void sceneOnOffCallback(EndpointId endpoint)
+{
+ DefaultOnOffSceneHandler::EndpointStatePair savedState;
+ ReturnOnFailure(sOnOffSceneHandler.mSceneEndpointStatePairs.GetPair(endpoint, savedState));
+ chip::CommandId command = (savedState.mState) ? Commands::On::Id : Commands::Off::Id;
+ OnOffServer::Instance().setOnOffValue(endpoint, command, false);
+ ReturnOnFailure(sOnOffSceneHandler.mSceneEndpointStatePairs.RemovePair(endpoint));
+}
+#endif // EMBER_AF_PLUGIN_SCENES
+
/**********************************************************
* Attributes Definition
*********************************************************/
static OnOffEffect * firstEffect = nullptr;
OnOffServer OnOffServer::instance;
-
-static constexpr size_t kOnOffMaxEnpointCount =
- EMBER_AF_ON_OFF_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT;
static EmberEventControl gEventControls[kOnOffMaxEnpointCount];
/**********************************************************
@@ -85,7 +323,7 @@
void OnOffServer::cancelEndpointTimerCallback(EndpointId endpoint)
{
- auto control = OnOffServer::getEventControl(endpoint);
+ auto control = OnOffServer::getEventControl(endpoint, Span<EmberEventControl>(gEventControls));
if (control)
{
cancelEndpointTimerCallback(control);
@@ -107,6 +345,16 @@
return instance;
}
+chip::scenes::SceneHandler * OnOffServer::GetSceneHandler()
+{
+
+#ifdef EMBER_AF_PLUGIN_SCENES
+ return &sOnOffSceneHandler;
+#else
+ return nullptr;
+#endif // EMBER_AF_PLUGIN_SCENES
+}
+
bool OnOffServer::HasFeature(chip::EndpointId endpoint, Feature feature)
{
bool success;
@@ -130,18 +378,6 @@
return status;
}
-#ifdef EMBER_AF_PLUGIN_LEVEL_CONTROL
-static bool LevelControlWithOnOffFeaturePresent(EndpointId endpoint)
-{
- if (!emberAfContainsServer(endpoint, LevelControl::Id))
- {
- return false;
- }
-
- return LevelControlHasFeature(endpoint, LevelControl::Feature::kOnOff);
-}
-#endif // EMBER_AF_PLUGIN_LEVEL_CONTROL
-
/** @brief On/off Cluster Set Value
*
* This function is called when the on/off value needs to be set, either through
@@ -194,7 +430,7 @@
Attributes::OffWaitTime::Set(endpoint, 0);
// Stop timer on the endpoint
- EmberEventControl * event = getEventControl(endpoint);
+ EmberEventControl * event = getEventControl(endpoint, Span<EmberEventControl>(gEventControls));
if (event != nullptr)
{
cancelEndpointTimerCallback(event);
@@ -307,6 +543,11 @@
status = setOnOffValue(endpoint, onOffValueForStartUp, true);
}
+#ifdef EMBER_AF_PLUGIN_SCENES
+ // Registers Scene handlers for the On/Off cluster on the server
+ // app::Clusters::Scenes::ScenesServer::Instance().RegisterSceneHandler(OnOffServer::Instance().GetSceneHandler());
+#endif
+
#ifdef EMBER_AF_PLUGIN_MODE_SELECT
// If OnMode is not a null value, then change the current mode to it.
if (onOffValueForStartUp && emberAfContainsServer(endpoint, ModeSelect::Id) &&
@@ -322,6 +563,7 @@
#endif
}
#endif // IGNORE_ON_OFF_CLUSTER_START_UP_ON_OFF
+
emberAfPluginOnOffClusterServerPostInitCallback(endpoint);
}
@@ -629,7 +871,7 @@
ChipLogProgress(Zcl, "Timer Callback - wait Off Time cycle finished");
// Stop timer on the endpoint
- cancelEndpointTimerCallback(getEventControl(endpoint));
+ cancelEndpointTimerCallback(getEventControl(endpoint, Span<EmberEventControl>(gEventControls)));
}
}
}
@@ -645,28 +887,31 @@
/**
* @brief event control object for an endpoint
*
- * @param[in] endpoint
+ * @param[in] endpoint target endpoint
+ * @param[in] eventControlArray Array where to find the event control
+ * @param[in] eventControlArraySize Size of the event control array
* @return EmberEventControl* configured event control
*/
-EmberEventControl * OnOffServer::getEventControl(EndpointId endpoint)
+EmberEventControl * OnOffServer::getEventControl(EndpointId endpoint, const Span<EmberEventControl> & eventControlArray)
{
uint16_t index = emberAfGetClusterServerEndpointIndex(endpoint, OnOff::Id, EMBER_AF_ON_OFF_CLUSTER_SERVER_ENDPOINT_COUNT);
- if (index >= ArraySize(gEventControls))
+ if (index >= eventControlArray.size())
{
return nullptr;
}
- return &gEventControls[index];
+
+ return &eventControlArray[index];
}
/**
- * @brief Configures EnventControl callback when using XY colors
+ * @brief Configures EventControl callback when using XY colors
*
* @param[in] endpoint endpoint to start timer for
* @return EmberEventControl* configured event control
*/
EmberEventControl * OnOffServer::configureEventControl(EndpointId endpoint)
{
- EmberEventControl * controller = getEventControl(endpoint);
+ EmberEventControl * controller = getEventControl(endpoint, Span<EmberEventControl>(gEventControls));
VerifyOrReturnError(controller != nullptr, nullptr);
controller->endpoint = endpoint;
diff --git a/src/app/clusters/on-off-server/on-off-server.h b/src/app/clusters/on-off-server/on-off-server.h
index 4b9da3d..c91c061 100644
--- a/src/app/clusters/on-off-server/on-off-server.h
+++ b/src/app/clusters/on-off-server/on-off-server.h
@@ -20,6 +20,7 @@
#include <app-common/zap-generated/cluster-objects.h>
#include <app/CommandHandler.h>
#include <app/ConcreteCommandPath.h>
+#include <app/clusters/scenes-server/SceneTable.h>
#include <app/util/af-types.h>
#include <app/util/basic-types.h>
#include <platform/CHIPDeviceConfig.h>
@@ -49,6 +50,8 @@
static OnOffServer & Instance();
+ chip::scenes::SceneHandler * GetSceneHandler();
+
bool offCommand(chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath);
bool onCommand(chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath);
bool toggleCommand(chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath);
@@ -76,7 +79,7 @@
#ifndef IGNORE_ON_OFF_CLUSTER_START_UP_ON_OFF
bool areStartUpOnOffServerAttributesNonVolatile(chip::EndpointId endpoint);
#endif
- EmberEventControl * getEventControl(chip::EndpointId endpoint);
+ EmberEventControl * getEventControl(chip::EndpointId endpoint, const chip::Span<EmberEventControl> & eventControlArray);
EmberEventControl * configureEventControl(chip::EndpointId endpoint);
uint32_t calculateNextWaitTimeMS(void);
@@ -92,6 +95,8 @@
static OnOffServer instance;
chip::System::Clock::Timestamp nextDesiredOnWithTimedOffTimestamp;
+
+ friend class DefaultOnOffSceneHandler;
};
struct OnOffEffect
diff --git a/src/app/clusters/scenes-server/SceneTable.h b/src/app/clusters/scenes-server/SceneTable.h
index 0453161..b76ade0 100644
--- a/src/app/clusters/scenes-server/SceneTable.h
+++ b/src/app/clusters/scenes-server/SceneTable.h
@@ -250,7 +250,7 @@
SceneTable(){};
- virtual ~SceneTable() = default;
+ virtual ~SceneTable(){};
// Not copyable
SceneTable(const SceneTable &) = delete;
diff --git a/src/app/clusters/scenes-server/SceneTableImpl.h b/src/app/clusters/scenes-server/SceneTableImpl.h
index b162f81..c7b7e18 100644
--- a/src/app/clusters/scenes-server/SceneTableImpl.h
+++ b/src/app/clusters/scenes-server/SceneTableImpl.h
@@ -40,8 +40,7 @@
class DefaultSceneHandlerImpl : public scenes::SceneHandler
{
public:
- static constexpr uint8_t kMaxValueSize = 4;
- static constexpr uint8_t kMaxAvPair = 15;
+ static constexpr uint8_t kMaxAvPair = CHIP_CONFIG_SCENES_MAX_AV_PAIRS_EFS;
DefaultSceneHandlerImpl() = default;
~DefaultSceneHandlerImpl() override{};
@@ -98,8 +97,7 @@
{
public:
DefaultSceneTableImpl() {}
-
- ~DefaultSceneTableImpl() override {}
+ ~DefaultSceneTableImpl() { Finish(); };
CHIP_ERROR Init(PersistentStorageDelegate * storage) override;
void Finish() override;
diff --git a/src/app/clusters/scenes-server/scenes-server.cpp b/src/app/clusters/scenes-server/scenes-server.cpp
index 3655b0d..e039856 100644
--- a/src/app/clusters/scenes-server/scenes-server.cpp
+++ b/src/app/clusters/scenes-server/scenes-server.cpp
@@ -143,8 +143,6 @@
{
chip::app::InteractionModelEngine::GetInstance()->UnregisterCommandHandler(this);
- SceneTable * sceneTable = scenes::GetSceneTableImpl();
- sceneTable->Finish();
mGroupProvider = nullptr;
mIsInitialized = false;
}
@@ -547,6 +545,32 @@
}
}
+bool ScenesServer::IsHandlerRegistered(scenes::SceneHandler * handler)
+{
+ SceneTable * sceneTable = scenes::GetSceneTableImpl();
+ return sceneTable->mHandlerList.Contains(handler);
+}
+
+void ScenesServer::RegisterSceneHandler(scenes::SceneHandler * handler)
+{
+ SceneTable * sceneTable = scenes::GetSceneTableImpl();
+
+ if (!IsHandlerRegistered(handler))
+ {
+ sceneTable->RegisterHandler(handler);
+ }
+}
+
+void ScenesServer::UnregisterSceneHandler(scenes::SceneHandler * handler)
+{
+ SceneTable * sceneTable = scenes::GetSceneTableImpl();
+
+ if (IsHandlerRegistered(handler))
+ {
+ sceneTable->UnregisterHandler(handler);
+ }
+}
+
void ScenesServer::HandleAddScene(HandlerContext & ctx, const Commands::AddScene::DecodableType & req)
{
AddSceneParse<Commands::AddScene::DecodableType, Commands::AddSceneResponse::Type>(ctx, req, mGroupProvider);
diff --git a/src/app/clusters/scenes-server/scenes-server.h b/src/app/clusters/scenes-server/scenes-server.h
index ed14cc8..47eb05d 100644
--- a/src/app/clusters/scenes-server/scenes-server.h
+++ b/src/app/clusters/scenes-server/scenes-server.h
@@ -51,9 +51,13 @@
void StoreCurrentScene(FabricIndex aFabricIx, EndpointId aEndpointId, GroupId aGroupId, SceneId aSceneId);
void RecallScene(FabricIndex aFabricIx, EndpointId aEndpointId, GroupId aGroupId, SceneId aSceneId);
+ bool IsHandlerRegistered(scenes::SceneHandler * handler);
+ void RegisterSceneHandler(scenes::SceneHandler * handler);
+ void UnregisterSceneHandler(scenes::SceneHandler * handler);
+
private:
ScenesServer() : CommandHandlerInterface(Optional<EndpointId>(), Id), AttributeAccessInterface(Optional<EndpointId>(), Id) {}
- ~ScenesServer() {}
+ ~ScenesServer() { Shutdown(); }
bool mIsInitialized = false;
diff --git a/src/app/tests/TestSceneTable.cpp b/src/app/tests/TestSceneTable.cpp
index b45bc3e..f9c65ad 100644
--- a/src/app/tests/TestSceneTable.cpp
+++ b/src/app/tests/TestSceneTable.cpp
@@ -159,7 +159,7 @@
clusterBuffer.reduce_size(2);
}
}
- else if (endpoint == kTestEndpoint1)
+ else if (endpoint == kTestEndpoint2)
{
if (clusterBuffer.size() >= 2)
{
@@ -168,7 +168,7 @@
clusterBuffer.reduce_size(2);
}
}
- else if (endpoint == kTestEndpoint1)
+ else if (endpoint == kTestEndpoint3)
{
if (clusterBuffer.size() >= 3)
{
@@ -178,6 +178,10 @@
clusterBuffer.reduce_size(3);
}
}
+ else
+ {
+ clusterBuffer.reduce_size(0);
+ }
}
// Default function only checks if endpoint and clusters are valid
@@ -432,6 +436,18 @@
{
sceneTable->RegisterHandler(&tmpHandler[i]);
}
+
+ // Emptying Handler array
+ sceneTable->UnregisterAllHandlers();
+
+ // Verify the handler num has been updated properly
+ NL_TEST_ASSERT(aSuite, sceneTable->HandlerListEmpty());
+
+ for (uint8_t i = 0; i < scenes::kMaxClustersPerScene; i++)
+ {
+ sceneTable->RegisterHandler(&tmpHandler[i]);
+ }
+
// Hanlder order in table : [H0, H1, H2]
NL_TEST_ASSERT(aSuite, !sceneTable->HandlerListEmpty());
diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h
index 24a88cb..c6c6726 100644
--- a/src/lib/core/CHIPConfig.h
+++ b/src/lib/core/CHIPConfig.h
@@ -1388,6 +1388,13 @@
#endif
/**
+ * @brief The maximum number of attribute value pairs in an extension field set.
+ */
+#ifndef CHIP_CONFIG_SCENES_MAX_AV_PAIRS_EFS
+#define CHIP_CONFIG_SCENES_MAX_AV_PAIRS_EFS 15
+#endif
+
+/**
* @brief The maximum number of clusters per scene, defaults to 3 for a typical usecase (onOff + level control + color control
* cluster). Needs to be changed in case a greater number of clusters is chosen.
*/