Skip to content

Commit 80de34e

Browse files
tcarmelveilleuxrestyled-commits
authored andcommitted
Add Q quality to OperationalState CountdownTime (project-chip#34422)
* Fix post-review comments on Q quality utils from project-chip#34266 - PR project-chip#34266 had post review comments See: project-chip#34266 (review) - Fix 0->0 case - Introduce `Timestamp` more places where it makes sense - Simplify some code lines Fixes project-chip#34334 Testing done: - Added unit test for 0->0 - Tests still pass * Restyled by clang-format * Address review comments * Restyled by clang-format * Add Q quality to OperationalState CountdownTIme - Update operational state cluster server to report countdown time at edges. - Add a way for cluster delegate to request an update of time. Issue project-chip#34421 Testing done: - Existing tests still pass - Will add cert test when the text is finalized (week of July 22) * Restyled by clang-format * Address review comments * Address review comments * Update src/app/clusters/operational-state-server/operational-state-server.cpp * Fix several Opstate test cases * Restyled by autopep8 * Fix minor grammar typo * Restyled by autopep8 * Fix TC-OPSTATE-2.2 --------- Co-authored-by: Restyled.io <commits@restyled.io>
1 parent 226f964 commit 80de34e

File tree

3 files changed

+104
-20
lines changed

3 files changed

+104
-20
lines changed

src/app/clusters/operational-state-server/operational-state-server.cpp

+63-7
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <app/InteractionModelEngine.h>
2727
#include <app/reporting/reporting.h>
2828
#include <app/util/attribute-storage.h>
29+
#include <lib/support/logging/CHIPLogging.h>
2930

3031
using namespace chip;
3132
using namespace chip::app;
@@ -40,6 +41,9 @@ Instance::Instance(Delegate * aDelegate, EndpointId aEndpointId, ClusterId aClus
4041
mDelegate(aDelegate), mEndpointId(aEndpointId), mClusterId(aClusterId)
4142
{
4243
mDelegate->SetInstance(this);
44+
mCountdownTime.policy()
45+
.Set(QuieterReportingPolicyEnum::kMarkDirtyOnIncrement)
46+
.Set(QuieterReportingPolicyEnum::kMarkDirtyOnChangeToFromZero);
4347
}
4448

4549
Instance::Instance(Delegate * aDelegate, EndpointId aEndpointId) : Instance(aDelegate, aEndpointId, OperationalState::Id) {}
@@ -81,6 +85,7 @@ CHIP_ERROR Instance::SetCurrentPhase(const DataModel::Nullable<uint8_t> & aPhase
8185
if (mCurrentPhase != oldPhase)
8286
{
8387
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::CurrentPhase::Id);
88+
UpdateCountdownTimeFromClusterLogic();
8489
}
8590
return CHIP_NO_ERROR;
8691
}
@@ -93,9 +98,11 @@ CHIP_ERROR Instance::SetOperationalState(uint8_t aOpState)
9398
return CHIP_ERROR_INVALID_ARGUMENT;
9499
}
95100

101+
bool countdownTimeUpdateNeeded = false;
96102
if (mOperationalError.errorStateID != to_underlying(ErrorStateEnum::kNoError))
97103
{
98104
mOperationalError.Set(to_underlying(ErrorStateEnum::kNoError));
105+
countdownTimeUpdateNeeded = true;
99106
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::OperationalError::Id);
100107
}
101108

@@ -104,6 +111,12 @@ CHIP_ERROR Instance::SetOperationalState(uint8_t aOpState)
104111
if (mOperationalState != oldState)
105112
{
106113
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::OperationalState::Id);
114+
countdownTimeUpdateNeeded = true;
115+
}
116+
117+
if (countdownTimeUpdateNeeded)
118+
{
119+
UpdateCountdownTimeFromClusterLogic();
107120
}
108121
return CHIP_NO_ERROR;
109122
}
@@ -140,6 +153,8 @@ void Instance::OnOperationalErrorDetected(const Structs::ErrorStateStruct::Type
140153
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::OperationalError::Id);
141154
}
142155

156+
UpdateCountdownTimeFromClusterLogic();
157+
143158
// Generate an ErrorDetected event
144159
GenericErrorEvent event(mClusterId, aError);
145160
EventNumber eventNumber;
@@ -153,7 +168,7 @@ void Instance::OnOperationalErrorDetected(const Structs::ErrorStateStruct::Type
153168

154169
void Instance::OnOperationCompletionDetected(uint8_t aCompletionErrorCode,
155170
const Optional<DataModel::Nullable<uint32_t>> & aTotalOperationalTime,
156-
const Optional<DataModel::Nullable<uint32_t>> & aPausedTime) const
171+
const Optional<DataModel::Nullable<uint32_t>> & aPausedTime)
157172
{
158173
ChipLogDetail(Zcl, "OperationalStateServer: OnOperationCompletionDetected");
159174

@@ -166,6 +181,8 @@ void Instance::OnOperationCompletionDetected(uint8_t aCompletionErrorCode,
166181
ChipLogError(Zcl, "OperationalStateServer: Failed to record OperationCompletion event: %" CHIP_ERROR_FORMAT,
167182
error.Format());
168183
}
184+
185+
UpdateCountdownTimeFromClusterLogic();
169186
}
170187

171188
void Instance::ReportOperationalStateListChange()
@@ -176,6 +193,43 @@ void Instance::ReportOperationalStateListChange()
176193
void Instance::ReportPhaseListChange()
177194
{
178195
MatterReportingAttributeChangeCallback(ConcreteAttributePath(mEndpointId, mClusterId, Attributes::PhaseList::Id));
196+
UpdateCountdownTimeFromClusterLogic();
197+
}
198+
199+
void Instance::UpdateCountdownTime(bool fromDelegate)
200+
{
201+
app::DataModel::Nullable<uint32_t> newCountdownTime = mDelegate->GetCountdownTime();
202+
auto now = System::SystemClock().GetMonotonicTimestamp();
203+
204+
bool markDirty = false;
205+
206+
if (fromDelegate)
207+
{
208+
// Updates from delegate are reduce-reported to every 10s max (choice of this implementation), in addition
209+
// to default change-from-null, change-from-zero and increment policy.
210+
auto predicate = [](const decltype(mCountdownTime)::SufficientChangePredicateCandidate & candidate) -> bool {
211+
if (candidate.lastDirtyValue.IsNull() || candidate.newValue.IsNull())
212+
{
213+
return false;
214+
}
215+
216+
uint32_t lastDirtyValue = candidate.lastDirtyValue.Value();
217+
uint32_t newValue = candidate.newValue.Value();
218+
uint32_t kNumSecondsDeltaToReport = 10;
219+
return (newValue < lastDirtyValue) && ((lastDirtyValue - newValue) > kNumSecondsDeltaToReport);
220+
};
221+
markDirty = (mCountdownTime.SetValue(newCountdownTime, now, predicate) == AttributeDirtyState::kMustReport);
222+
}
223+
else
224+
{
225+
auto predicate = [](const decltype(mCountdownTime)::SufficientChangePredicateCandidate &) -> bool { return true; };
226+
markDirty = (mCountdownTime.SetValue(newCountdownTime, now, predicate) == AttributeDirtyState::kMustReport);
227+
}
228+
229+
if (markDirty)
230+
{
231+
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::CountdownTime::Id);
232+
}
179233
}
180234

181235
bool Instance::IsSupportedPhase(uint8_t aPhase)
@@ -267,6 +321,7 @@ void Instance::InvokeCommand(HandlerContext & handlerContext)
267321
ChipLogDetail(Zcl, "OperationalState: Entering handling derived cluster commands");
268322

269323
InvokeDerivedClusterCommand(handlerContext);
324+
break;
270325
}
271326
}
272327

@@ -291,18 +346,18 @@ CHIP_ERROR Instance::Read(const ConcreteReadAttributePath & aPath, AttributeValu
291346
}
292347
return err;
293348
});
349+
break;
294350
}
295-
break;
296351

297352
case OperationalState::Attributes::OperationalState::Id: {
298353
ReturnErrorOnFailure(aEncoder.Encode(GetCurrentOperationalState()));
354+
break;
299355
}
300-
break;
301356

302357
case OperationalState::Attributes::OperationalError::Id: {
303358
ReturnErrorOnFailure(aEncoder.Encode(mOperationalError));
359+
break;
304360
}
305-
break;
306361

307362
case OperationalState::Attributes::PhaseList::Id: {
308363

@@ -329,18 +384,19 @@ CHIP_ERROR Instance::Read(const ConcreteReadAttributePath & aPath, AttributeValu
329384
ReturnErrorOnFailure(encoder.Encode(phase2));
330385
}
331386
});
387+
break;
332388
}
333-
break;
334389

335390
case OperationalState::Attributes::CurrentPhase::Id: {
336391
ReturnErrorOnFailure(aEncoder.Encode(GetCurrentPhase()));
392+
break;
337393
}
338-
break;
339394

340395
case OperationalState::Attributes::CountdownTime::Id: {
396+
// Read through to get value closest to reality.
341397
ReturnErrorOnFailure(aEncoder.Encode(mDelegate->GetCountdownTime()));
398+
break;
342399
}
343-
break;
344400
}
345401
return CHIP_NO_ERROR;
346402
}

src/app/clusters/operational-state-server/operational-state-server.h

+27-5
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
#include <app-common/zap-generated/cluster-objects.h>
2323
#include <app/AttributeAccessInterface.h>
2424
#include <app/CommandHandlerInterface.h>
25-
#include <app/util/af.h>
25+
#include <app/cluster-building-blocks/QuieterReporting.h>
26+
#include <app/data-model/Nullable.h>
2627

2728
namespace chip {
2829
namespace app {
@@ -114,6 +115,12 @@ class Instance : public CommandHandlerInterface, public AttributeAccessInterface
114115
*/
115116
void GetCurrentOperationalError(GenericOperationalError & error) const;
116117

118+
/**
119+
* @brief Whenever application delegate wants to possibly report a new updated time,
120+
* call this method. The `GetCountdownTime()` method will be called on the delegate.
121+
*/
122+
void UpdateCountdownTimeFromDelegate() { UpdateCountdownTime(/* fromDelegate = */ true); }
123+
117124
// Event triggers
118125
/**
119126
* @brief Called when the Node detects a OperationalError has been raised.
@@ -130,7 +137,7 @@ class Instance : public CommandHandlerInterface, public AttributeAccessInterface
130137
*/
131138
void OnOperationCompletionDetected(uint8_t aCompletionErrorCode,
132139
const Optional<DataModel::Nullable<uint32_t>> & aTotalOperationalTime = NullOptional,
133-
const Optional<DataModel::Nullable<uint32_t>> & aPausedTime = NullOptional) const;
140+
const Optional<DataModel::Nullable<uint32_t>> & aPausedTime = NullOptional);
134141

135142
// List change reporting
136143
/**
@@ -193,6 +200,19 @@ class Instance : public CommandHandlerInterface, public AttributeAccessInterface
193200
*/
194201
virtual void InvokeDerivedClusterCommand(HandlerContext & handlerContext) { return; };
195202

203+
/**
204+
* Causes reporting/udpating of CountdownTime attribute from driver if sufficient changes have
205+
* occurred (based on Q quality definition for operational state). Calls the Delegate::GetCountdownTime() method.
206+
*
207+
* @param fromDelegate true if the change notice was triggered by the delegate, false if internal to cluster logic.
208+
*/
209+
void UpdateCountdownTime(bool fromDelegate);
210+
211+
/**
212+
* @brief Whenever the cluster logic thinks time should be updated, call this.
213+
*/
214+
void UpdateCountdownTimeFromClusterLogic() { UpdateCountdownTime(/* fromDelegate=*/false); }
215+
196216
private:
197217
Delegate * mDelegate;
198218

@@ -203,6 +223,7 @@ class Instance : public CommandHandlerInterface, public AttributeAccessInterface
203223
app::DataModel::Nullable<uint8_t> mCurrentPhase;
204224
uint8_t mOperationalState = 0; // assume 0 for now.
205225
GenericOperationalError mOperationalError = to_underlying(ErrorStateEnum::kNoError);
226+
app::QuieterReportingAttribute<uint32_t> mCountdownTime{ DataModel::NullNullable };
206227

207228
/**
208229
* This method is inherited from CommandHandlerInterface.
@@ -263,9 +284,10 @@ class Delegate
263284
virtual ~Delegate() = default;
264285

265286
/**
266-
* Get the countdown time.
267-
* NOTE: Changes to this attribute should not be reported.
268-
* From the spec: Changes to this value SHALL NOT be reported in a subscription.
287+
* Get the countdown time. This will get called on many edges such as
288+
* commands to change operational state, or when the delegate deals with
289+
* changes. Make sure it becomes null whenever it is appropriate.
290+
*
269291
* @return The current countdown time.
270292
*/
271293
virtual app::DataModel::Nullable<uint32_t> GetCountdownTime() = 0;

src/python_testing/TC_OpstateCommon.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ async def TEST_TC_OPSTATE_BASE_2_1(self, endpoint=1):
354354
if phase_list is not NullValue:
355355
phase_list_len = len(phase_list)
356356
asserts.assert_less_equal(phase_list_len, 32,
357-
f"PhaseList length({phase_list_len}) must be less than 32!")
357+
f"PhaseList length({phase_list_len}) must be at most 32 entries!")
358358

359359
# STEP 3: TH reads from the DUT the CurrentPhase attribute
360360
self.step(3)
@@ -364,8 +364,9 @@ async def TEST_TC_OPSTATE_BASE_2_1(self, endpoint=1):
364364
if (phase_list == NullValue) or (not phase_list):
365365
asserts.assert_true(current_phase == NullValue, f"CurrentPhase({current_phase}) should be null")
366366
else:
367-
asserts.assert_true(0 <= current_phase and current_phase < phase_list_len,
368-
f"CurrentPhase({current_phase}) must be between 0 and {(phase_list_len - 1)}")
367+
asserts.assert_greater_equal(current_phase, 0, f"CurrentPhase({current_phase}) must be >= 0")
368+
asserts.assert_less(current_phase, phase_list_len,
369+
f"CurrentPhase({current_phase}) must be less than {phase_list_len}")
369370

370371
# STEP 4: TH reads from the DUT the CountdownTime attribute
371372
self.step(4)
@@ -652,7 +653,7 @@ async def TEST_TC_OPSTATE_BASE_2_2(self, endpoint=1):
652653
if phase_list is not NullValue:
653654
phase_list_len = len(phase_list)
654655
asserts.assert_less_equal(phase_list_len, 32,
655-
f"PhaseList length({phase_list_len}) must be less than 32!")
656+
f"PhaseList length({phase_list_len}) must be at most 32 entries!")
656657

657658
# STEP 9: TH reads from the DUT the CurrentPhase attribute
658659
self.step(9)
@@ -662,10 +663,10 @@ async def TEST_TC_OPSTATE_BASE_2_2(self, endpoint=1):
662663
if (phase_list == NullValue) or (not phase_list):
663664
asserts.assert_equal(current_phase, NullValue, f"CurrentPhase({current_phase}) should be null")
664665
else:
665-
asserts.assert_less_equal(0, current_phase,
666-
f"CurrentPhase({current_phase}) must be greater or equal than 0")
667-
asserts.assert_less(current_phase < phase_list_len,
668-
f"CurrentPhase({current_phase}) must be less then {(phase_list_len - 1)}")
666+
asserts.assert_greater_equal(current_phase, 0,
667+
f"CurrentPhase({current_phase}) must be greater or equal to 0")
668+
asserts.assert_less(current_phase, phase_list_len,
669+
f"CurrentPhase({current_phase}) must be less than {(phase_list_len)}")
669670

670671
# STEP 10: TH waits for {PIXIT.WAITTIME.COUNTDOWN}
671672
self.step(10)
@@ -679,6 +680,8 @@ async def TEST_TC_OPSTATE_BASE_2_2(self, endpoint=1):
679680
attribute=attributes.CountdownTime)
680681

681682
if (countdown_time is not NullValue) and (initial_countdown_time is not NullValue):
683+
logging.info(f" -> Initial countdown time: {initial_countdown_time}")
684+
logging.info(f" -> New countdown time: {countdown_time}")
682685
asserts.assert_less_equal(countdown_time, (initial_countdown_time - wait_time),
683686
f"The countdown time shall have decreased at least {wait_time:.1f} since start command")
684687

@@ -821,6 +824,7 @@ async def TEST_TC_OPSTATE_BASE_2_3(self, endpoint=1):
821824
initial_countdown_time = await self.read_expect_success(endpoint=endpoint,
822825
attribute=attributes.CountdownTime)
823826
if initial_countdown_time is not NullValue:
827+
logging.info(f" -> Initial ountdown time: {initial_countdown_time}")
824828
asserts.assert_true(0 <= initial_countdown_time <= 259200,
825829
f"CountdownTime({initial_countdown_time}) must be between 0 and 259200")
826830

@@ -835,6 +839,8 @@ async def TEST_TC_OPSTATE_BASE_2_3(self, endpoint=1):
835839
attribute=attributes.CountdownTime)
836840

837841
if (countdown_time is not NullValue) and (initial_countdown_time is not NullValue):
842+
logging.info(f" -> Initial countdown time: {initial_countdown_time}")
843+
logging.info(f" -> New countdown time: {countdown_time}")
838844
asserts.assert_equal(countdown_time, initial_countdown_time,
839845
"The countdown time shall be equal since pause command")
840846

0 commit comments

Comments
 (0)