Line data Source code
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 : #include <type_traits>
24 :
25 : #include <app/data-model/Nullable.h>
26 : #include <lib/support/BitFlags.h>
27 : #include <system/SystemClock.h>
28 :
29 : namespace chip {
30 : namespace app {
31 :
32 : enum class QuieterReportingPolicyEnum
33 : {
34 : kMarkDirtyOnChangeToFromZero = (1u << 0),
35 : kMarkDirtyOnDecrement = (1u << 1),
36 : kMarkDirtyOnIncrement = (1u << 2),
37 : };
38 :
39 : enum class AttributeDirtyState
40 : {
41 : kNoReportNeeded = 0,
42 : kMustReport = 1,
43 : };
44 :
45 : using QuieterReportingPolicyFlags = BitFlags<QuieterReportingPolicyEnum>;
46 :
47 : namespace detail {
48 :
49 : using Timestamp = System::Clock::Milliseconds64;
50 : template <typename T>
51 : using Nullable = 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
56 : * "7.7.9 Quieter Reporting Quality" (Q quality)
57 : *
58 : * The class can be configured via `policy()` to have some/all of the common reasons
59 : * for reporting (e.g. increment only, decrement only, change to/from zero).
60 : *
61 : * Changes of null to non-null or non-null to null are always considered dirty.
62 : *
63 : * It is possible to force mark the attribute as dirty (see `ForceDirty()`) such as
64 : * for conditions like "When there is any increase or decrease in the estimated time
65 : * remaining that was due to progressing insight of the server's control logic".
66 : *
67 : * Class maintains a `current value` and a timestamped `dirty` state. The `SetValue()`
68 : * method must be used to update the `current value` and will return AttributeDirtyState::kMustReport
69 : * if the attribute should be marked dirty/
70 : *
71 : * - `SetValue()` has internal rules for null/non-null changes and policy-based rules
72 : * - `SetValue()` with a `SufficientChangePredicate` uses the internal rules in addition to
73 : * the predicate to determine dirty state
74 : *
75 : * See [QuieterReportingPolicyEnum] for policy flags on when a value is considered dirty
76 : * beyond non/non-null changes.
77 : *
78 : * Common quieter reporting usecases that can be supported by this class are:
79 : * - If attribute has changed due to a change in the X or Y attributes
80 : * - Use SufficientChangePredicate version
81 : * - When it changes from 0 to any other value and vice versa
82 : * - Use `kMarkDirtyOnChangeToFromZero` internal policy.
83 : * - When it changes from null to any other value and vice versa
84 : * - Built-in rule.
85 : * - When it increases
86 : * - Use `kMarkDirtyOnIncrement` internal policy.
87 : * - When it decreases
88 : * - Use `kMarkDirtyOnDecrement` internal policy.
89 : * - When there is any increase or decrease in the estimated time remaining that was
90 : * due to progressing insight of the server's control logic
91 : * - Use SufficientChangePredicate version with an always-true predicate.
92 : * - When it changes at a rate significantly different from one unit per second.
93 : * - Use SufficientChangePredicate version.
94 : * Example usage in-situ:
95 : *
96 : * Class has:
97 : * QuieterReportingAttribute<uint8_t> mAttrib;
98 : *
99 : * Code at time of setting new value has:
100 : *
101 : * uint8_t newValue = driver.GetNewValue();
102 : * auto now = SystemClock().GetMonotonicTimestamp();
103 : * if (mAttrib.SetValue(newValue, now) == AttributeDirtyState::kMustReport)
104 : * {
105 : * MatterReportingAttributeChangeCallback(path_for_attribute);
106 : * }
107 : *
108 : * @tparam T - the type of underlying numerical value that will be held by the class.
109 : */
110 : template <typename T, std::enable_if_t<std::is_arithmetic<T>::value, bool> = true>
111 : class QuieterReportingAttribute
112 : {
113 : public:
114 131 : explicit QuieterReportingAttribute(const Nullable<T> & initialValue) : mValue(initialValue), mLastDirtyValue(initialValue) {}
115 : // constructor that works with arrays of QuieterReportingAttribute
116 : explicit QuieterReportingAttribute() : mValue(DataModel::NullNullable), mLastDirtyValue(DataModel::NullNullable) {}
117 :
118 : struct SufficientChangePredicateCandidate
119 : {
120 : // Timestamp of last time attribute was marked dirty.
121 : Timestamp lastDirtyTimestamp;
122 : // New (`now`) timestamp passed in `SetValue()`.
123 : Timestamp nowTimestamp;
124 : // Value last marked as dirty.
125 : const Nullable<T> & lastDirtyValue;
126 : // New value passed in `SetValue()`, to compare against lastDirtyValue for sufficient change if needed.
127 : const Nullable<T> & newValue;
128 : };
129 :
130 : using SufficientChangePredicate = std::function<bool(const SufficientChangePredicateCandidate &)>;
131 :
132 : /**
133 : * @brief Factory to generate a functor for "attribute was last reported" at least `minimumDurationMillis` ago.
134 : *
135 : * @param minimumDurationMillis - number of millis needed since last marked as dirty before we mark dirty again.
136 : * @return a functor usable for the `changedPredicate` arg of `SetValue()`
137 : */
138 : static SufficientChangePredicate
139 37 : GetPredicateForSufficientTimeSinceLastDirty(System::Clock::Milliseconds64 minimumDurationMillis)
140 : {
141 12 : return [minimumDurationMillis](const SufficientChangePredicateCandidate & candidate) -> bool {
142 24 : return (candidate.lastDirtyValue != candidate.newValue) &&
143 24 : ((candidate.nowTimestamp - candidate.lastDirtyTimestamp) >= minimumDurationMillis);
144 37 : };
145 : }
146 :
147 60 : Nullable<T> value() const { return mValue; }
148 81 : QuieterReportingPolicyFlags & policy() { return mPolicyFlags; }
149 : const QuieterReportingPolicyFlags & policy() const { return mPolicyFlags; }
150 :
151 : /**
152 : * Set the updated value of the attribute, computing whether it needs to be reported according to `changedPredicate` and
153 : * policies.
154 : *
155 : * - Any change of nullability between `newValue` and the old value will be considered dirty.
156 : * - The policies from `QuieterReportingPolicyEnum` and set via `SetPolicy()` are self-explanatory by name.
157 : * - The changedPredicate will be called with last dirty <timestamp, value> and new <timestamp value> and may override
158 : * the dirty state altogether when it returns true. Use sparingly and default to a functor returning false.
159 : * The changedPredicate is only called on change.
160 : *
161 : * Internal recording will be done about last dirty value and last dirty timestamp based on the policies having applied.
162 : *
163 : * @param newValue - new value to set for the attribute
164 : * @param now - system monotonic timestamp at the time of the call
165 : * @param changedPredicate - functor to possibly override dirty state
166 : * @return AttributeDirtyState::kMustReport if attribute must be marked dirty right away, or
167 : * AttributeDirtyState::kNoReportNeeded otherwise.
168 : */
169 195 : AttributeDirtyState SetValue(const DataModel::Nullable<T> & newValue, Timestamp now, SufficientChangePredicate changedPredicate)
170 : {
171 195 : bool isChangeOfNull = newValue.IsNull() != mValue.IsNull();
172 195 : bool areBothValuesNonNull = !newValue.IsNull() && !mValue.IsNull();
173 195 : bool areBothValuesDifferent = areBothValuesNonNull && (newValue.Value() != mValue.Value());
174 :
175 195 : bool changeToFromZero = areBothValuesNonNull && areBothValuesDifferent && (newValue.Value() == 0 || mValue.Value() == 0);
176 195 : bool isIncrement = areBothValuesNonNull && (newValue.Value() > mValue.Value());
177 195 : bool isDecrement = areBothValuesNonNull && (newValue.Value() < mValue.Value());
178 :
179 195 : bool isNewlyDirty = isChangeOfNull;
180 195 : isNewlyDirty =
181 195 : isNewlyDirty || (mPolicyFlags.Has(QuieterReportingPolicyEnum::kMarkDirtyOnChangeToFromZero) && changeToFromZero);
182 195 : isNewlyDirty = isNewlyDirty || (mPolicyFlags.Has(QuieterReportingPolicyEnum::kMarkDirtyOnDecrement) && isDecrement);
183 195 : isNewlyDirty = isNewlyDirty || (mPolicyFlags.Has(QuieterReportingPolicyEnum::kMarkDirtyOnIncrement) && isIncrement);
184 :
185 : // Only execute predicate on value change from last marked dirty.
186 195 : if (newValue != mLastDirtyValue)
187 : {
188 167 : SufficientChangePredicateCandidate candidate{
189 : mLastDirtyTimestampMillis, // lastDirtyTimestamp
190 : now, // nowTimestamp
191 167 : mLastDirtyValue, // lastDirtyValue
192 : newValue // newValue
193 : };
194 167 : isNewlyDirty = isNewlyDirty || changedPredicate(candidate);
195 : }
196 :
197 195 : mValue = newValue;
198 :
199 195 : if (isNewlyDirty)
200 : {
201 152 : mLastDirtyValue = newValue;
202 152 : mLastDirtyTimestampMillis = now;
203 : }
204 :
205 195 : return isNewlyDirty ? AttributeDirtyState::kMustReport : AttributeDirtyState::kNoReportNeeded;
206 : }
207 :
208 : /**
209 : * Same as the other `SetValue`, but assumes a changedPredicate that never overrides to dirty.
210 : *
211 : * This is the easy/common case.
212 : *
213 : * @param newValue - new value to set for the attribute
214 : * @param now - system monotonic timestamp at the time of the call
215 : * @return AttributeDirtyState::kMustReport if attribute must be marked dirty right away, or
216 : * AttributeDirtyState::kNoReportNeeded otherwise.
217 : */
218 23 : AttributeDirtyState SetValue(const DataModel::Nullable<T> & newValue, Timestamp now)
219 : {
220 31 : return SetValue(newValue, now, [](const SufficientChangePredicateCandidate &) -> bool { return false; });
221 : }
222 :
223 : protected:
224 : // Current value of the attribute.
225 : DataModel::Nullable<T> mValue;
226 : // Last value that was marked as dirty (to use in comparisons for change, e.g. by SufficientChangePredicate).
227 : DataModel::Nullable<T> mLastDirtyValue;
228 : // Enabled internal change detection policies.
229 : QuieterReportingPolicyFlags mPolicyFlags{ 0 };
230 : // Timestamp associated with the last time the attribute was marked dirty (to use in comparisons for change).
231 : chip::System::Clock::Timestamp mLastDirtyTimestampMillis{};
232 : };
233 :
234 : } // namespace detail
235 :
236 : using detail::QuieterReportingAttribute;
237 :
238 : } // namespace app
239 : } // namespace chip
|