Skip to content

Commit 2061286

Browse files
tcarmelveilleuxrestyled-commits
authored andcommitted
Add Q quality to OperationalState CountdownTime (#34422)
* Fix post-review comments on Q quality utils from #34266 - PR #34266 had post review comments See: project-chip/connectedhomeip#34266 (review) - Fix 0->0 case - Introduce `Timestamp` more places where it makes sense - Simplify some code lines Fixes #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 #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 c8b5d7f commit 2061286

File tree

3 files changed

+104
-19
lines changed

3 files changed

+104
-19
lines changed

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

+63-7
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <app/InteractionModelEngine.h>
3030
#include <app/reporting/reporting.h>
3131
#include <app/util/attribute-storage.h>
32+
#include <lib/support/logging/CHIPLogging.h>
3233

3334
using namespace chip;
3435
using namespace chip::app;
@@ -43,6 +44,9 @@ Instance::Instance(Delegate * aDelegate, EndpointId aEndpointId, ClusterId aClus
4344
mDelegate(aDelegate), mEndpointId(aEndpointId), mClusterId(aClusterId)
4445
{
4546
mDelegate->SetInstance(this);
47+
mCountdownTime.policy()
48+
.Set(QuieterReportingPolicyEnum::kMarkDirtyOnIncrement)
49+
.Set(QuieterReportingPolicyEnum::kMarkDirtyOnChangeToFromZero);
4650
}
4751

4852
Instance::Instance(Delegate * aDelegate, EndpointId aEndpointId) : Instance(aDelegate, aEndpointId, OperationalState::Id) {}
@@ -84,6 +88,7 @@ CHIP_ERROR Instance::SetCurrentPhase(const DataModel::Nullable<uint8_t> & aPhase
8488
if (mCurrentPhase != oldPhase)
8589
{
8690
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::CurrentPhase::Id);
91+
UpdateCountdownTimeFromClusterLogic();
8792
}
8893
return CHIP_NO_ERROR;
8994
}
@@ -96,9 +101,11 @@ CHIP_ERROR Instance::SetOperationalState(uint8_t aOpState)
96101
return CHIP_ERROR_INVALID_ARGUMENT;
97102
}
98103

104+
bool countdownTimeUpdateNeeded = false;
99105
if (mOperationalError.errorStateID != to_underlying(ErrorStateEnum::kNoError))
100106
{
101107
mOperationalError.Set(to_underlying(ErrorStateEnum::kNoError));
108+
countdownTimeUpdateNeeded = true;
102109
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::OperationalError::Id);
103110
}
104111

@@ -107,6 +114,12 @@ CHIP_ERROR Instance::SetOperationalState(uint8_t aOpState)
107114
if (mOperationalState != oldState)
108115
{
109116
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::OperationalState::Id);
117+
countdownTimeUpdateNeeded = true;
118+
}
119+
120+
if (countdownTimeUpdateNeeded)
121+
{
122+
UpdateCountdownTimeFromClusterLogic();
110123
}
111124
return CHIP_NO_ERROR;
112125
}
@@ -143,6 +156,8 @@ void Instance::OnOperationalErrorDetected(const Structs::ErrorStateStruct::Type
143156
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::OperationalError::Id);
144157
}
145158

159+
UpdateCountdownTimeFromClusterLogic();
160+
146161
// Generate an ErrorDetected event
147162
GenericErrorEvent event(mClusterId, aError);
148163
EventNumber eventNumber;
@@ -156,7 +171,7 @@ void Instance::OnOperationalErrorDetected(const Structs::ErrorStateStruct::Type
156171

157172
void Instance::OnOperationCompletionDetected(uint8_t aCompletionErrorCode,
158173
const Optional<DataModel::Nullable<uint32_t>> & aTotalOperationalTime,
159-
const Optional<DataModel::Nullable<uint32_t>> & aPausedTime) const
174+
const Optional<DataModel::Nullable<uint32_t>> & aPausedTime)
160175
{
161176
ChipLogDetail(Zcl, "OperationalStateServer: OnOperationCompletionDetected");
162177

@@ -169,6 +184,8 @@ void Instance::OnOperationCompletionDetected(uint8_t aCompletionErrorCode,
169184
ChipLogError(Zcl, "OperationalStateServer: Failed to record OperationCompletion event: %" CHIP_ERROR_FORMAT,
170185
error.Format());
171186
}
187+
188+
UpdateCountdownTimeFromClusterLogic();
172189
}
173190

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

184238
bool Instance::IsSupportedPhase(uint8_t aPhase)
@@ -270,6 +324,7 @@ void Instance::InvokeCommand(HandlerContext & handlerContext)
270324
ChipLogDetail(Zcl, "OperationalState: Entering handling derived cluster commands");
271325

272326
InvokeDerivedClusterCommand(handlerContext);
327+
break;
273328
}
274329
}
275330

@@ -294,18 +349,18 @@ CHIP_ERROR Instance::Read(const ConcreteReadAttributePath & aPath, AttributeValu
294349
}
295350
return err;
296351
});
352+
break;
297353
}
298-
break;
299354

300355
case OperationalState::Attributes::OperationalState::Id: {
301356
ReturnErrorOnFailure(aEncoder.Encode(GetCurrentOperationalState()));
357+
break;
302358
}
303-
break;
304359

305360
case OperationalState::Attributes::OperationalError::Id: {
306361
ReturnErrorOnFailure(aEncoder.Encode(mOperationalError));
362+
break;
307363
}
308-
break;
309364

310365
case OperationalState::Attributes::PhaseList::Id: {
311366

@@ -332,18 +387,19 @@ CHIP_ERROR Instance::Read(const ConcreteReadAttributePath & aPath, AttributeValu
332387
ReturnErrorOnFailure(encoder.Encode(phase2));
333388
}
334389
});
390+
break;
335391
}
336-
break;
337392

338393
case OperationalState::Attributes::CurrentPhase::Id: {
339394
ReturnErrorOnFailure(aEncoder.Encode(GetCurrentPhase()));
395+
break;
340396
}
341-
break;
342397

343398
case OperationalState::Attributes::CountdownTime::Id: {
399+
// Read through to get value closest to reality.
344400
ReturnErrorOnFailure(aEncoder.Encode(mDelegate->GetCountdownTime()));
401+
break;
345402
}
346-
break;
347403
}
348404
return CHIP_NO_ERROR;
349405
}

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

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

2628
namespace chip {
2729
namespace app {
@@ -113,6 +115,12 @@ class Instance : public CommandHandlerInterface, public AttributeAccessInterface
113115
*/
114116
void GetCurrentOperationalError(GenericOperationalError & error) const;
115117

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+
116124
// Event triggers
117125
/**
118126
* @brief Called when the Node detects a OperationalError has been raised.
@@ -129,7 +137,7 @@ class Instance : public CommandHandlerInterface, public AttributeAccessInterface
129137
*/
130138
void OnOperationCompletionDetected(uint8_t aCompletionErrorCode,
131139
const Optional<DataModel::Nullable<uint32_t>> & aTotalOperationalTime = NullOptional,
132-
const Optional<DataModel::Nullable<uint32_t>> & aPausedTime = NullOptional) const;
140+
const Optional<DataModel::Nullable<uint32_t>> & aPausedTime = NullOptional);
133141

134142
// List change reporting
135143
/**
@@ -192,6 +200,19 @@ class Instance : public CommandHandlerInterface, public AttributeAccessInterface
192200
*/
193201
virtual void InvokeDerivedClusterCommand(HandlerContext & handlerContext) { return; };
194202

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+
195216
private:
196217
Delegate * mDelegate;
197218

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

206228
/**
207229
* This method is inherited from CommandHandlerInterface.
@@ -262,9 +284,10 @@ class Delegate
262284
virtual ~Delegate() = default;
263285

264286
/**
265-
* Get the countdown time.
266-
* NOTE: Changes to this attribute should not be reported.
267-
* 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+
*
268291
* @return The current countdown time.
269292
*/
270293
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)