Skip to content

Commit 1049176

Browse files
lpbeliveau-silabsrestyled-commitsbzbarsky-apple
authored andcommitted
[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>
1 parent e0c842e commit 1049176

File tree

8 files changed

+334
-35
lines changed

8 files changed

+334
-35
lines changed

src/app/clusters/on-off-server/on-off-server.cpp

+269-24
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,253 @@ using namespace chip::app::Clusters;
4141
using namespace chip::app::Clusters::OnOff;
4242
using chip::Protocols::InteractionModel::Status;
4343

44+
#ifdef EMBER_AF_PLUGIN_LEVEL_CONTROL
45+
static bool LevelControlWithOnOffFeaturePresent(EndpointId endpoint)
46+
{
47+
if (!emberAfContainsServer(endpoint, LevelControl::Id))
48+
{
49+
return false;
50+
}
51+
52+
return LevelControlHasFeature(endpoint, LevelControl::Feature::kOnOff);
53+
}
54+
#endif // EMBER_AF_PLUGIN_LEVEL_CONTROL
55+
56+
static constexpr size_t kOnOffMaxEnpointCount =
57+
EMBER_AF_ON_OFF_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT;
58+
59+
#ifdef EMBER_AF_PLUGIN_SCENES
60+
static EmberEventControl sceneHandlerEventControls[kOnOffMaxEnpointCount];
61+
static void sceneOnOffCallback(EndpointId endpoint);
62+
63+
class DefaultOnOffSceneHandler : public scenes::DefaultSceneHandlerImpl
64+
{
65+
public:
66+
/// @brief Struct to keep track of the desired state of the OnOff attribute between ApplyScene and
67+
/// transition time expiration
68+
struct EndpointStatePair
69+
{
70+
EndpointStatePair(EndpointId endpoint = kInvalidEndpointId, bool status = false) : mEndpoint(endpoint), mState(status) {}
71+
EndpointId mEndpoint;
72+
bool mState;
73+
};
74+
75+
/// @brief Struct holding an array of EndpointStatePair. Handles insertion, get and removal by EndpointID.
76+
/// TODO: Implement generic object to handle this boilerplate array manipulation
77+
struct StatePairBuffer
78+
{
79+
bool IsEmpty() const { return (mPairCount == 0); }
80+
81+
CHIP_ERROR FindPair(const EndpointId endpoint, uint16_t & found_index) const
82+
{
83+
VerifyOrReturnError(!IsEmpty(), CHIP_ERROR_NOT_FOUND);
84+
for (found_index = 0; found_index < mPairCount; found_index++)
85+
{
86+
if (endpoint == mStatePairBuffer[found_index].mEndpoint)
87+
{
88+
return CHIP_NO_ERROR;
89+
}
90+
}
91+
92+
return CHIP_ERROR_NOT_FOUND;
93+
}
94+
95+
CHIP_ERROR InsertPair(const EndpointStatePair & status)
96+
{
97+
uint16_t idx;
98+
CHIP_ERROR err = FindPair(status.mEndpoint, idx);
99+
100+
if (CHIP_NO_ERROR == err)
101+
{
102+
mStatePairBuffer[idx] = status;
103+
}
104+
else if (mPairCount < MAX_ENDPOINT_COUNT)
105+
{
106+
// if not found, insert at the end
107+
mStatePairBuffer[mPairCount] = status;
108+
mPairCount++;
109+
}
110+
else
111+
{
112+
return CHIP_ERROR_NO_MEMORY;
113+
}
114+
115+
return CHIP_NO_ERROR;
116+
}
117+
118+
CHIP_ERROR GetPair(const EndpointId endpoint, EndpointStatePair & status) const
119+
{
120+
uint16_t idx;
121+
ReturnErrorOnFailure(FindPair(endpoint, idx));
122+
123+
status = mStatePairBuffer[idx];
124+
return CHIP_NO_ERROR;
125+
}
126+
127+
/// @brief Removes Pair and decrements Pair count if the endpoint existed in the array
128+
/// @param endpoint : endpoint id of the pair
129+
CHIP_ERROR RemovePair(const EndpointId endpoint)
130+
{
131+
uint16_t position;
132+
VerifyOrReturnValue(CHIP_NO_ERROR == FindPair(endpoint, position), CHIP_NO_ERROR);
133+
134+
uint16_t nextPos = static_cast<uint16_t>(position + 1);
135+
uint16_t moveNum = static_cast<uint16_t>(mPairCount - nextPos);
136+
137+
// Compress array after removal, if the removed position is not the last
138+
if (moveNum)
139+
{
140+
memmove(&mStatePairBuffer[position], &mStatePairBuffer[nextPos], sizeof(EndpointStatePair) * moveNum);
141+
}
142+
143+
mPairCount--;
144+
// Clear last occupied position
145+
mStatePairBuffer[mPairCount].mEndpoint = kInvalidEndpointId;
146+
147+
return CHIP_NO_ERROR;
148+
}
149+
150+
uint16_t mPairCount;
151+
EndpointStatePair mStatePairBuffer[kOnOffMaxEnpointCount];
152+
};
153+
154+
StatePairBuffer mSceneEndpointStatePairs;
155+
// As per spec, 1 attribute is scenable in the on off cluster
156+
static constexpr uint8_t scenableAttributeCount = 1;
157+
158+
DefaultOnOffSceneHandler() = default;
159+
~DefaultOnOffSceneHandler() override {}
160+
161+
// Default function for OnOff cluster, only puts the OnOff cluster ID in the span if supported on the given endpoint
162+
virtual void GetSupportedClusters(EndpointId endpoint, Span<ClusterId> & clusterBuffer) override
163+
{
164+
ClusterId * buffer = clusterBuffer.data();
165+
if (emberAfContainsServer(endpoint, OnOff::Id) && clusterBuffer.size() >= 1)
166+
{
167+
buffer[0] = OnOff::Id;
168+
clusterBuffer.reduce_size(1);
169+
}
170+
else
171+
{
172+
clusterBuffer.reduce_size(0);
173+
}
174+
}
175+
176+
// Default function for OnOff cluster, only checks if OnOff is enabled on the endpoint
177+
bool SupportsCluster(EndpointId endpoint, ClusterId cluster) override
178+
{
179+
return (cluster == OnOff::Id) && (emberAfContainsServer(endpoint, OnOff::Id));
180+
}
181+
182+
/// @brief Serialize the Cluster's EFS value
183+
/// @param endpoint target endpoint
184+
/// @param cluster target cluster
185+
/// @param serializedBytes data to serialize into EFS
186+
/// @return CHIP_NO_ERROR if successfully serialized the data, CHIP_ERROR_INVALID_ARGUMENT otherwise
187+
CHIP_ERROR SerializeSave(EndpointId endpoint, ClusterId cluster, MutableByteSpan & serializedBytes) override
188+
{
189+
using AttributeValuePair = Scenes::Structs::AttributeValuePair::Type;
190+
191+
bool currentValue;
192+
// read current on/off value
193+
EmberAfStatus status = Attributes::OnOff::Get(endpoint, &currentValue);
194+
if (status != EMBER_ZCL_STATUS_SUCCESS)
195+
{
196+
ChipLogError(Zcl, "ERR: reading on/off %x", status);
197+
return CHIP_ERROR_READ_FAILED;
198+
}
199+
200+
AttributeValuePair pairs[scenableAttributeCount];
201+
202+
pairs[0].attributeID.SetValue(Attributes::OnOff::Id);
203+
pairs[0].attributeValue = currentValue;
204+
205+
app::DataModel::List<AttributeValuePair> attributeValueList(pairs);
206+
207+
return EncodeAttributeValueList(attributeValueList, serializedBytes);
208+
}
209+
210+
/// @brief Default EFS interaction when applying scene to the OnOff Cluster
211+
/// @param endpoint target endpoint
212+
/// @param cluster target cluster
213+
/// @param serializedBytes Data from nvm
214+
/// @param timeMs transition time in ms
215+
/// @return CHIP_NO_ERROR if value as expected, CHIP_ERROR_INVALID_ARGUMENT otherwise
216+
CHIP_ERROR ApplyScene(EndpointId endpoint, ClusterId cluster, const ByteSpan & serializedBytes,
217+
scenes::TransitionTimeMs timeMs) override
218+
{
219+
app::DataModel::DecodableList<Scenes::Structs::AttributeValuePair::DecodableType> attributeValueList;
220+
221+
VerifyOrReturnError(cluster == OnOff::Id, CHIP_ERROR_INVALID_ARGUMENT);
222+
223+
ReturnErrorOnFailure(DecodeAttributeValueList(serializedBytes, attributeValueList));
224+
225+
size_t attributeCount = 0;
226+
ReturnErrorOnFailure(attributeValueList.ComputeSize(&attributeCount));
227+
VerifyOrReturnError(attributeCount <= scenableAttributeCount, CHIP_ERROR_BUFFER_TOO_SMALL);
228+
229+
auto pair_iterator = attributeValueList.begin();
230+
while (pair_iterator.Next())
231+
{
232+
auto & decodePair = pair_iterator.GetValue();
233+
if (decodePair.attributeID.HasValue())
234+
{
235+
// If attribute ID was encoded, verify it is the proper ID for the OnOff attribute
236+
VerifyOrReturnError(decodePair.attributeID.Value() == Attributes::OnOff::Id, CHIP_ERROR_INVALID_ARGUMENT);
237+
}
238+
ReturnErrorOnFailure(
239+
mSceneEndpointStatePairs.InsertPair(EndpointStatePair(endpoint, static_cast<bool>(decodePair.attributeValue))));
240+
}
241+
// Verify that the EFS was completely read
242+
CHIP_ERROR err = pair_iterator.GetStatus();
243+
if (CHIP_NO_ERROR != err)
244+
{
245+
mSceneEndpointStatePairs.RemovePair(endpoint);
246+
return err;
247+
}
248+
249+
OnOffServer::Instance().scheduleTimerCallbackMs(sceneEventControl(endpoint), timeMs);
250+
251+
return CHIP_NO_ERROR;
252+
}
253+
254+
private:
255+
/**
256+
* @brief Configures EventControl callback when setting On Off through scenes callback
257+
*
258+
* @param[in] endpoint endpoint to start timer for
259+
* @return EmberEventControl* configured event control
260+
*/
261+
EmberEventControl * sceneEventControl(EndpointId endpoint)
262+
{
263+
EmberEventControl * controller =
264+
OnOffServer::Instance().getEventControl(endpoint, Span<EmberEventControl>(sceneHandlerEventControls));
265+
VerifyOrReturnValue(controller != nullptr, nullptr);
266+
267+
controller->endpoint = endpoint;
268+
controller->callback = &sceneOnOffCallback;
269+
270+
return controller;
271+
}
272+
};
273+
static DefaultOnOffSceneHandler sOnOffSceneHandler;
274+
275+
static void sceneOnOffCallback(EndpointId endpoint)
276+
{
277+
DefaultOnOffSceneHandler::EndpointStatePair savedState;
278+
ReturnOnFailure(sOnOffSceneHandler.mSceneEndpointStatePairs.GetPair(endpoint, savedState));
279+
chip::CommandId command = (savedState.mState) ? Commands::On::Id : Commands::Off::Id;
280+
OnOffServer::Instance().setOnOffValue(endpoint, command, false);
281+
ReturnOnFailure(sOnOffSceneHandler.mSceneEndpointStatePairs.RemovePair(endpoint));
282+
}
283+
#endif // EMBER_AF_PLUGIN_SCENES
284+
44285
/**********************************************************
45286
* Attributes Definition
46287
*********************************************************/
47288

48289
static OnOffEffect * firstEffect = nullptr;
49290
OnOffServer OnOffServer::instance;
50-
51-
static constexpr size_t kOnOffMaxEnpointCount =
52-
EMBER_AF_ON_OFF_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT;
53291
static EmberEventControl gEventControls[kOnOffMaxEnpointCount];
54292

55293
/**********************************************************
@@ -85,7 +323,7 @@ void OnOffServer::cancelEndpointTimerCallback(EmberEventControl * control)
85323

86324
void OnOffServer::cancelEndpointTimerCallback(EndpointId endpoint)
87325
{
88-
auto control = OnOffServer::getEventControl(endpoint);
326+
auto control = OnOffServer::getEventControl(endpoint, Span<EmberEventControl>(gEventControls));
89327
if (control)
90328
{
91329
cancelEndpointTimerCallback(control);
@@ -107,6 +345,16 @@ OnOffServer & OnOffServer::Instance()
107345
return instance;
108346
}
109347

348+
chip::scenes::SceneHandler * OnOffServer::GetSceneHandler()
349+
{
350+
351+
#ifdef EMBER_AF_PLUGIN_SCENES
352+
return &sOnOffSceneHandler;
353+
#else
354+
return nullptr;
355+
#endif // EMBER_AF_PLUGIN_SCENES
356+
}
357+
110358
bool OnOffServer::HasFeature(chip::EndpointId endpoint, Feature feature)
111359
{
112360
bool success;
@@ -130,18 +378,6 @@ EmberAfStatus OnOffServer::getOnOffValue(chip::EndpointId endpoint, bool * curre
130378
return status;
131379
}
132380

133-
#ifdef EMBER_AF_PLUGIN_LEVEL_CONTROL
134-
static bool LevelControlWithOnOffFeaturePresent(EndpointId endpoint)
135-
{
136-
if (!emberAfContainsServer(endpoint, LevelControl::Id))
137-
{
138-
return false;
139-
}
140-
141-
return LevelControlHasFeature(endpoint, LevelControl::Feature::kOnOff);
142-
}
143-
#endif // EMBER_AF_PLUGIN_LEVEL_CONTROL
144-
145381
/** @brief On/off Cluster Set Value
146382
*
147383
* This function is called when the on/off value needs to be set, either through
@@ -194,7 +430,7 @@ EmberAfStatus OnOffServer::setOnOffValue(chip::EndpointId endpoint, chip::Comman
194430
Attributes::OffWaitTime::Set(endpoint, 0);
195431

196432
// Stop timer on the endpoint
197-
EmberEventControl * event = getEventControl(endpoint);
433+
EmberEventControl * event = getEventControl(endpoint, Span<EmberEventControl>(gEventControls));
198434
if (event != nullptr)
199435
{
200436
cancelEndpointTimerCallback(event);
@@ -307,6 +543,11 @@ void OnOffServer::initOnOffServer(chip::EndpointId endpoint)
307543
status = setOnOffValue(endpoint, onOffValueForStartUp, true);
308544
}
309545

546+
#ifdef EMBER_AF_PLUGIN_SCENES
547+
// Registers Scene handlers for the On/Off cluster on the server
548+
// app::Clusters::Scenes::ScenesServer::Instance().RegisterSceneHandler(OnOffServer::Instance().GetSceneHandler());
549+
#endif
550+
310551
#ifdef EMBER_AF_PLUGIN_MODE_SELECT
311552
// If OnMode is not a null value, then change the current mode to it.
312553
if (onOffValueForStartUp && emberAfContainsServer(endpoint, ModeSelect::Id) &&
@@ -322,6 +563,7 @@ void OnOffServer::initOnOffServer(chip::EndpointId endpoint)
322563
#endif
323564
}
324565
#endif // IGNORE_ON_OFF_CLUSTER_START_UP_ON_OFF
566+
325567
emberAfPluginOnOffClusterServerPostInitCallback(endpoint);
326568
}
327569

@@ -629,7 +871,7 @@ void OnOffServer::updateOnOffTimeCommand(chip::EndpointId endpoint)
629871
ChipLogProgress(Zcl, "Timer Callback - wait Off Time cycle finished");
630872

631873
// Stop timer on the endpoint
632-
cancelEndpointTimerCallback(getEventControl(endpoint));
874+
cancelEndpointTimerCallback(getEventControl(endpoint, Span<EmberEventControl>(gEventControls)));
633875
}
634876
}
635877
}
@@ -645,28 +887,31 @@ bool OnOffServer::areStartUpOnOffServerAttributesNonVolatile(EndpointId endpoint
645887
/**
646888
* @brief event control object for an endpoint
647889
*
648-
* @param[in] endpoint
890+
* @param[in] endpoint target endpoint
891+
* @param[in] eventControlArray Array where to find the event control
892+
* @param[in] eventControlArraySize Size of the event control array
649893
* @return EmberEventControl* configured event control
650894
*/
651-
EmberEventControl * OnOffServer::getEventControl(EndpointId endpoint)
895+
EmberEventControl * OnOffServer::getEventControl(EndpointId endpoint, const Span<EmberEventControl> & eventControlArray)
652896
{
653897
uint16_t index = emberAfGetClusterServerEndpointIndex(endpoint, OnOff::Id, EMBER_AF_ON_OFF_CLUSTER_SERVER_ENDPOINT_COUNT);
654-
if (index >= ArraySize(gEventControls))
898+
if (index >= eventControlArray.size())
655899
{
656900
return nullptr;
657901
}
658-
return &gEventControls[index];
902+
903+
return &eventControlArray[index];
659904
}
660905

661906
/**
662-
* @brief Configures EnventControl callback when using XY colors
907+
* @brief Configures EventControl callback when using XY colors
663908
*
664909
* @param[in] endpoint endpoint to start timer for
665910
* @return EmberEventControl* configured event control
666911
*/
667912
EmberEventControl * OnOffServer::configureEventControl(EndpointId endpoint)
668913
{
669-
EmberEventControl * controller = getEventControl(endpoint);
914+
EmberEventControl * controller = getEventControl(endpoint, Span<EmberEventControl>(gEventControls));
670915
VerifyOrReturnError(controller != nullptr, nullptr);
671916

672917
controller->endpoint = endpoint;

0 commit comments

Comments
 (0)