Skip to content

Commit 84f9ed4

Browse files
Introduce a building block usable for all Q attributes
- Q quality requires marking attributes as dirty for the purposes of reporting only when certain conditions arise. - This PR introduces a building block attribute value wrapper compatible with any nullable or non-nullable numerical attribute, which allows applying the necessary policies, and all complex policies that currently exist in the Matter spec (e.g. any CountdownTime, CurrentLevel, etc). Testing done: - Added unit tests. Integration in existing clusters will follow in a different PR.
1 parent fbb44ea commit 84f9ed4

File tree

5 files changed

+555
-0
lines changed

5 files changed

+555
-0
lines changed

src/BUILD.gn

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ if (chip_build_tests) {
5151
deps = []
5252
tests = [
5353
"${chip_root}/src/app/data-model/tests",
54+
"${chip_root}/src/app/cluster-building-blocks/tests",
5455
"${chip_root}/src/app/data-model-interface/tests",
5556
"${chip_root}/src/access/tests",
5657
"${chip_root}/src/crypto/tests",
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copyright (c) 2024 Project CHIP Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
import("//build_overrides/chip.gni")
15+
16+
source_set("cluster-building-blocks") {
17+
sources = [ "QuieterReporting.h" ]
18+
19+
public_deps = [
20+
"${chip_root}/src/app/data-model:nullable",
21+
"${chip_root}/src/system",
22+
"${chip_root}src//lib/support:support",
23+
]
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
*
3+
* Copyright (c) 2024 Project CHIP Authors
4+
* All rights reserved.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#pragma once
20+
21+
#include <functional>
22+
#include <stdbool.h>
23+
24+
#include <lib/support/BitFlags.h>
25+
#include <app/data-model/Nullable.h>
26+
#include <system/SystemClock.h>
27+
28+
namespace chip {
29+
namespace app {
30+
31+
// - If it has changed due to a change in the CurrentPhase or OperationalState attributes, or
32+
// - When it changes from 0 to any other value and vice versa, or
33+
// - When it changes from null to any other value and vice versa, or
34+
// - When it increases, or
35+
// - When there is any increase or decrease in the estimated time remaining that was due to progressing insight of the server's control logic, or
36+
// - When it changes at a rate significantly different from one unit per second.
37+
38+
enum class QuieterReportingPolicyEnum
39+
{
40+
kMarkDirtyOnChangeToFromZero = (1u << 0),
41+
kMarkDirtyOnDecrement = (1u << 1),
42+
kMarkDirtyOnIncrement = (1u << 2),
43+
};
44+
45+
using QuieterReportingPolicyFlags = ::chip::BitFlags<QuieterReportingPolicyEnum>;
46+
47+
namespace detail {
48+
49+
using Timestamp = chip::System::Clock::Milliseconds64;
50+
template <typename T>
51+
using Nullable = chip::app::DataModel::Nullable<T>;
52+
53+
/**
54+
* This class helps track reporting state of an attribute to properly keep track of whether
55+
* it needs to be marked as dirty or not for purposes of reporting using Q quality.
56+
*
57+
* The class can be configured via `SetPolicy` to have some/all of the common reasons
58+
* for reporting (e.g. increment only, decrement only, change to/from zero).
59+
*
60+
* Changes of null to non-null or non-null to null are always considered dirty.
61+
*
62+
* It is possible to force mark the attribute as dirty (see `ForceMarkAsDirty`) such as
63+
* for conditions like "When there is any increase or decrease in the estimated time
64+
* remaining that was due to progressing insight of the server's control logic".
65+
*
66+
* Usage is simple:
67+
*
68+
* - Call `SetValue()` with the new value and current monotonic system timestamp
69+
* - There is an overload with a `SufficientChangePredicate` which will apply externally
70+
* provided checks between old/new value and time last marked dirty to allow for complex
71+
* rules like marking dirty less than once per second, etc.
72+
* - *Maybe* call `ForceMarkAsDirty()` in some choice situations (e.g. know for sure it's
73+
* an important update, like at the edge of an operational state change).
74+
* - Call `GetThenResetDirtyState()`. If it returns true, mark the attribute dirty, with the
75+
* method most suitable at the call site (e.g. `MatterReportingAttributeChangeCallback` call
76+
* or similar methods).
77+
*
78+
* @tparam T - the type of underlying numerical value that will be held by the class.
79+
*/
80+
template <typename T>
81+
class QuieterReportingAttribute
82+
{
83+
public:
84+
explicit QuieterReportingAttribute(const chip::app::DataModel::Nullable<T>& initialValue) : mValue(initialValue), mLastDirtyValue(initialValue) {}
85+
86+
using SufficientChangePredicate = std::function<bool(Timestamp /* previousDirtyTime */, Timestamp /* now */, const Nullable<T> & /* previousDirtyValue */, const Nullable<T> & /* newValue */)>;
87+
88+
/**
89+
* @brief Factory to generate a functor for "attribute was last reported" at least `minimumDurationMillis` ago.
90+
*
91+
* @param minimumDurationMillis - number of millis needed since last marked as dirty before we mark dirty again.
92+
* @return a functor usable for the `changedPredicate` arg of `SetValue()`
93+
*/
94+
static SufficientChangePredicate GetPredicateForSufficientTimeSinceLastDirty(chip::System::Clock::Milliseconds64 minimumDurationMillis)
95+
{
96+
return [minimumDurationMillis](Timestamp previousDirtyTime, Timestamp now, const Nullable<T> &oldDirtyValue, const Nullable<T> &newValue) -> bool {
97+
return (oldDirtyValue != newValue) && ((now - previousDirtyTime) >= minimumDurationMillis);
98+
};
99+
}
100+
101+
chip::app::DataModel::Nullable<T> value() const { return mValue; }
102+
QuieterReportingPolicyFlags policy() const { return mPolicyFlags; }
103+
104+
void SetPolicy(QuieterReportingPolicyFlags policyFlags) { mPolicyFlags = policyFlags; }
105+
106+
/**
107+
* When this returns true, attribute should be marked for reporting. Auto-resets to false after call.
108+
*/
109+
bool GetThenResetDirtyState()
110+
{
111+
bool wasDirty = mIsDirty;
112+
mIsDirty = false;
113+
return wasDirty;
114+
}
115+
116+
/**
117+
* Force marking this attribute as dirty, with the `now` timestamp given as the reference point.
118+
*
119+
* WARNING: Only call `ForceMarkAsDirty` after a `SetValue` call.
120+
*/
121+
void ForceMarkAsDirty(Timestamp now)
122+
{
123+
mIsDirty = true;
124+
mLastDirtyTimestampMillis = now;
125+
mLastDirtyValue = mValue;
126+
}
127+
128+
/**
129+
* Set the updated value of the attribute, computing whether it needs to be reported according to `changedPredicate` and policies.
130+
*
131+
* - Any change of nullability between `newValue` and the old value will be considered dirty.
132+
* - The policies from `QuieterReportingPolicyEnum` and set via `SetPolicy()` are self-explanatory by name.
133+
* - The changedPredicate will be called with last dirty <timestamp, value> and new <timestamp value> and may override
134+
* the dirty state altogether when it returns true. Use sparingly and default to a functor returning false.
135+
*
136+
* Internal recording will be done about last dirty value and last dirty timestamp based on the policies having applied.
137+
*
138+
* @param newValue - new value to set for the attribute
139+
* @param now - system monotonic timestamp at the time of the call
140+
* @param changedPredicate - functor to possibly override dirty state
141+
*/
142+
void SetValue(const chip::app::DataModel::Nullable<T>& newValue, Timestamp now, SufficientChangePredicate changedPredicate)
143+
{
144+
bool isChangeOfNull = newValue.IsNull() ^ mValue.IsNull();
145+
bool areBothValuesNonNull = !newValue.IsNull() && !mValue.IsNull();
146+
147+
bool changeToFromZero = areBothValuesNonNull && (*newValue == 0 || *mValue == 0);
148+
bool isIncrement = areBothValuesNonNull && (*newValue > *mValue);
149+
bool isDecrement = areBothValuesNonNull && (*newValue < *mValue);
150+
151+
bool wasDirty = mIsDirty;
152+
153+
mIsDirty = mIsDirty || isChangeOfNull;
154+
mIsDirty = mIsDirty || (mPolicyFlags.Has(QuieterReportingPolicyEnum::kMarkDirtyOnChangeToFromZero) && changeToFromZero);
155+
mIsDirty = mIsDirty || (mPolicyFlags.Has(QuieterReportingPolicyEnum::kMarkDirtyOnDecrement) && isDecrement);
156+
mIsDirty = mIsDirty || (mPolicyFlags.Has(QuieterReportingPolicyEnum::kMarkDirtyOnIncrement) && isIncrement);
157+
mIsDirty = mIsDirty || changedPredicate(mLastDirtyTimestampMillis, now, mLastDirtyValue, newValue);
158+
159+
mValue = newValue;
160+
161+
if (!wasDirty && mIsDirty)
162+
{
163+
mLastDirtyValue = newValue;
164+
mLastDirtyTimestampMillis = now;
165+
}
166+
}
167+
168+
/**
169+
* Same as the other `SetValue`, but assumes a changedPredicate that never overrides to dirty.
170+
*
171+
* This is the easy/common case.
172+
*
173+
* @param newValue - new value to set for the attribute
174+
* @param now - system monotonic timestamp at the time of the call
175+
*/
176+
void SetValue(const chip::app::DataModel::Nullable<T>& newValue, Timestamp now)
177+
{
178+
SetValue(newValue, now, [](Timestamp, Timestamp, const Nullable<T> &, const Nullable<T> &) -> bool { return false; });
179+
}
180+
181+
protected:
182+
chip::app::DataModel::Nullable<T> mValue;
183+
chip::app::DataModel::Nullable<T> mLastDirtyValue;
184+
bool mIsDirty = false;
185+
QuieterReportingPolicyFlags mPolicyFlags{0};
186+
chip::System::Clock::Milliseconds64 mLastDirtyTimestampMillis{};
187+
};
188+
189+
} // namespace detail
190+
191+
using detail::QuieterReportingAttribute;
192+
193+
} // namespace app
194+
} // namespace chip
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright (c) 2024 Project CHIP Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import("//build_overrides/chip.gni")
16+
import("${chip_root}/build/chip/chip_test_suite.gni")
17+
18+
chip_test_suite("tests") {
19+
output_name = "libAppClusterBuildingBlockTests"
20+
21+
test_sources = [ "TestQuieterReporting.cpp" ]
22+
23+
public_deps = [
24+
"${chip_root}/src/app/cluster-building-blocks",
25+
"${chip_root}/src/app/data-model:nullable",
26+
"${chip_root}/src/lib/core:error",
27+
"${chip_root}/src/lib/support/tests:pw-test-macros",
28+
"${chip_root}/src/system",
29+
]
30+
}

0 commit comments

Comments
 (0)