Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2023 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 : // TODO(#32628): Remove the CHIPCore.h header when the esp32 build is correctly fixed
22 : #include <lib/core/CHIPCore.h>
23 :
24 : #include <app/ReadHandler.h>
25 : #include <app/icd/server/ICDStateObserver.h>
26 : #include <lib/core/CHIPError.h>
27 : #include <system/SystemClock.h>
28 :
29 : namespace chip {
30 : namespace app {
31 : namespace reporting {
32 :
33 : // Forward declaration of TestReportScheduler to allow it to be friend with ReportScheduler
34 : class TestReportScheduler;
35 :
36 : class TimerContext
37 : {
38 : public:
39 0 : virtual ~TimerContext() {}
40 : virtual void TimerFired() = 0;
41 : };
42 :
43 : /**
44 : * @class ReportScheduler
45 : *
46 : * @brief This class is responsible for scheduling Engine runs based on the reporting intervals of the ReadHandlers.
47 : *
48 : *
49 : * This class holds a pool of ReadHandlerNodes that are used to keep track of the minimum and maximum timestamps for a report to be
50 : * emitted based on the reporting intervals of the ReadHandlers associated with the node.
51 : *
52 : * The ReportScheduler also holds a TimerDelegate pointer that is used to start and cancel timers for the ReadHandlers depending
53 : * on the reporting logic of the Scheduler.
54 : *
55 : * It inherits the ReadHandler::Observer class to be notified of reportability changes in the ReadHandlers.
56 : * It inherits the ICDStateObserver class to allow the implementation to generate reports based on the changes in ICD devices state,
57 : * such as going from idle to active mode and vice-versa.
58 : *
59 : * @note The logic for how and when to schedule reports is implemented in the subclasses of ReportScheduler, such as
60 : * ReportSchedulerImpl and SyncronizedReportSchedulerImpl.
61 : */
62 : class ReportScheduler : public ReadHandler::Observer, public ICDStateObserver
63 : {
64 : public:
65 : using Timestamp = System::Clock::Timestamp;
66 :
67 : /// @brief This class acts as an interface between the report scheduler and the system timer to reduce dependencies on the
68 : /// system layer.
69 : class TimerDelegate
70 : {
71 : public:
72 0 : virtual ~TimerDelegate() {}
73 : /// @brief Start a timer for a given context. The report scheduler must always cancel an existing timer for a context (using
74 : /// CancelTimer) before starting a new one for that context.
75 : /// @param context context to pass to the timer callback.
76 : /// @param aTimeout time in milliseconds before the timer expires
77 : virtual CHIP_ERROR StartTimer(TimerContext * context, System::Clock::Timeout aTimeout) = 0;
78 : /// @brief Cancel a timer for a given context
79 : /// @param context used to identify the timer to cancel
80 : virtual void CancelTimer(TimerContext * context) = 0;
81 : virtual bool IsTimerActive(TimerContext * context) = 0;
82 : virtual Timestamp GetCurrentMonotonicTimestamp() = 0;
83 : };
84 :
85 : /**
86 : * @class ReadHandlerNode
87 : *
88 : * @brief This class is responsible for determining when a ReadHandler is reportable depending on the monotonic timestamp of
89 : * the system and the intervals of the ReadHandler. It inherits the TimerContext class to allow it to be used as a context for
90 : * a TimerDelegate so that the TimerDelegate can call the TimerFired method when the timer expires.
91 : *
92 : * Three conditions that can prevent the ReadHandler from being reportable:
93 : * 1: The ReadHandler is not in the CanStartReporting state:
94 : * This condition can be resolved by setting the CanStartReporting flag on the ReadHandler
95 : *
96 : * 2: The minimal interval since the last report has not elapsed
97 : * This condition can be resolved after enough time has passed since the last report or by setting the EngineRunScheduled
98 : * flag
99 : *
100 : * 3: The maximal interval since the last report has not elapsed and the ReadHandler is not dirty:
101 : * This condition can be resolved after enough time has passed since the last report to reach the max interval, by the
102 : * ReadHandler becoming dirty or by setting the CanBeSynced flag and having another ReadHandler needing to report.
103 : *
104 : * Once the 3 conditions are met, the ReadHandler is considered reportable.
105 : *
106 : * Flags:
107 : *
108 : * CanBeSynced: Mechanism to allow the ReadHandler to emit a report if another readHandler is ReportableNow.
109 : * This flag is currently only used by the SynchronizedReportScheduler to allow firing reports of ReadHandlers at the same
110 : * time.
111 : *
112 : * EngineRunScheduled: Mechanism to ensure that the reporting engine will see the ReadHandler as reportable if a timer fires.
113 : * This flag is used to confirm that the next report timer has fired for a ReadHandler, thus allowing reporting when timers
114 : * fire earlier than the minimal timestamp due to mechanisms such as NTP clock adjustments.
115 : *
116 : */
117 : class ReadHandlerNode : public TimerContext
118 : {
119 : public:
120 : enum class ReadHandlerNodeFlags : uint8_t
121 : {
122 : // Flag to indicate if the engine run is already scheduled so the scheduler can ignore
123 : // it when calculating the next run time
124 : EngineRunScheduled = (1 << 0),
125 : // Flag to allow the read handler to be synced with other handlers that have an earlier max timestamp
126 : CanBeSynced = (1 << 1),
127 : };
128 :
129 237 : ReadHandlerNode(ReadHandler * aReadHandler, ReportScheduler * aScheduler, const Timestamp & now) : mScheduler(aScheduler)
130 : {
131 237 : VerifyOrDie(aReadHandler != nullptr);
132 237 : VerifyOrDie(aScheduler != nullptr);
133 :
134 237 : mReadHandler = aReadHandler;
135 237 : SetIntervalTimeStamps(aReadHandler, now);
136 237 : }
137 18926 : ReadHandler * GetReadHandler() const { return mReadHandler; }
138 :
139 : /// @brief Check if the Node is reportable now, meaning its readhandler was made reportable by attribute dirtying and
140 : /// handler state, and minimal time interval since the last report has elapsed, or the maximal time interval since the last
141 : /// report has elapsed.
142 : /// @note If a handler has been flagged as scheduled for an engine run, it will be reported regardless of the timestamps.
143 : /// This is done to guarantee that the reporting engine will see the handler as reportable if a timer fires, even if it
144 : /// fires early.
145 : /// @param now current time to use for the check, the user must ensure to provide a valid time for this to be reliable
146 507 : bool IsReportableNow(const Timestamp & now) const
147 : {
148 1431 : return (mReadHandler->CanStartReporting() &&
149 1285 : ((now >= mMinTimestamp && (mReadHandler->IsDirty() || now >= mMaxTimestamp || CanBeSynced())) ||
150 869 : IsEngineRunScheduled()));
151 : }
152 :
153 65 : bool CanStartReporting() const { return mReadHandler->CanStartReporting(); }
154 80 : bool IsChunkedReport() const { return mReadHandler->IsChunkedReport(); }
155 362 : bool IsEngineRunScheduled() const { return mFlags.Has(ReadHandlerNodeFlags::EngineRunScheduled); }
156 307 : void SetEngineRunScheduled(bool aEngineRunScheduled)
157 : {
158 307 : mFlags.Set(ReadHandlerNodeFlags::EngineRunScheduled, aEngineRunScheduled);
159 307 : }
160 361 : bool CanBeSynced() const { return mFlags.Has(ReadHandlerNodeFlags::CanBeSynced); }
161 178 : void SetCanBeSynced(bool aCanBeSynced) { mFlags.Set(ReadHandlerNodeFlags::CanBeSynced, aCanBeSynced); }
162 :
163 : /// @brief Set the interval timestamps for the node based on the read handler reporting intervals
164 : /// @param aReadHandler read handler to get the intervals from
165 : /// @param now current time to calculate the mMin and mMax timestamps, the user must ensure to provide a valid time for this
166 : /// to be reliable
167 258 : void SetIntervalTimeStamps(ReadHandler * aReadHandler, const Timestamp & now)
168 : {
169 : uint16_t minInterval, maxInterval;
170 258 : aReadHandler->GetReportingIntervals(minInterval, maxInterval);
171 258 : mMinTimestamp = now + System::Clock::Seconds16(minInterval);
172 258 : mMaxTimestamp = now + System::Clock::Seconds16(maxInterval);
173 258 : }
174 :
175 129 : void TimerFired() override
176 : {
177 129 : SetEngineRunScheduled(true);
178 129 : mScheduler->ReportTimerCallback();
179 129 : }
180 :
181 342 : System::Clock::Timestamp GetMinTimestamp() const { return mMinTimestamp; }
182 573 : System::Clock::Timestamp GetMaxTimestamp() const { return mMaxTimestamp; }
183 :
184 : private:
185 : ReadHandler * mReadHandler;
186 : ReportScheduler * mScheduler;
187 : Timestamp mMinTimestamp;
188 : Timestamp mMaxTimestamp;
189 :
190 : BitFlags<ReadHandlerNodeFlags> mFlags;
191 : };
192 :
193 54 : ReportScheduler(TimerDelegate * aTimerDelegate) : mTimerDelegate(aTimerDelegate) {}
194 :
195 0 : virtual ~ReportScheduler() = default;
196 :
197 : virtual void ReportTimerCallback() = 0;
198 :
199 : /// @brief Check whether a ReadHandler is reportable right now, taking into account its minimum and maximum intervals.
200 : /// @param aReadHandler read handler to check
201 222 : bool IsReportableNow(ReadHandler * aReadHandler)
202 : {
203 : // Update the now timestamp to ensure external calls to IsReportableNow are always comparing to the current time
204 222 : Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp();
205 222 : ReadHandlerNode * node = FindReadHandlerNode(aReadHandler);
206 222 : return (nullptr != node) ? node->IsReportableNow(now) : false;
207 : }
208 :
209 : /// @brief Check if a ReadHandler is reportable without considering the timing
210 348 : bool IsReadHandlerReportable(ReadHandler * aReadHandler) const
211 : {
212 348 : return (nullptr != aReadHandler) ? aReadHandler->ShouldStartReporting() : false;
213 : }
214 : /// @brief Sets the ForceDirty flag of a ReadHandler
215 : void HandlerForceDirtyState(ReadHandler * aReadHandler) { aReadHandler->ForceDirtyState(); }
216 :
217 : /// @brief Get the number of ReadHandlers registered in the scheduler's node pool
218 : size_t GetNumReadHandlers() const { return mNodesPool.Allocated(); }
219 :
220 : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
221 : Timestamp GetMinTimestampForHandler(const ReadHandler * aReadHandler)
222 : {
223 : ReadHandlerNode * node = FindReadHandlerNode(aReadHandler);
224 : return node->GetMinTimestamp();
225 : }
226 : Timestamp GetMaxTimestampForHandler(const ReadHandler * aReadHandler)
227 : {
228 : ReadHandlerNode * node = FindReadHandlerNode(aReadHandler);
229 : return node->GetMaxTimestamp();
230 : }
231 : ReadHandlerNode * GetReadHandlerNode(const ReadHandler * aReadHandler) { return FindReadHandlerNode(aReadHandler); }
232 : #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
233 :
234 : protected:
235 : friend class chip::app::reporting::TestReportScheduler;
236 :
237 : /// @brief Find the ReadHandlerNode for a given ReadHandler pointer
238 : /// @param [in] aReadHandler ReadHandler pointer to look for in the ReadHandler nodes list
239 : /// @return Node Address if the node was found, nullptr otherwise
240 3105 : ReadHandlerNode * FindReadHandlerNode(const ReadHandler * aReadHandler)
241 : {
242 3105 : ReadHandlerNode * foundNode = nullptr;
243 3105 : mNodesPool.ForEachActiveObject([&foundNode, aReadHandler](ReadHandlerNode * node) {
244 18410 : if (node->GetReadHandler() == aReadHandler)
245 : {
246 1134 : foundNode = node;
247 1134 : return Loop::Break;
248 : }
249 :
250 17276 : return Loop::Continue;
251 : });
252 3105 : return foundNode;
253 : }
254 :
255 : ObjectPool<ReadHandlerNode, CHIP_IM_MAX_NUM_READS + CHIP_IM_MAX_NUM_SUBSCRIPTIONS> mNodesPool;
256 : TimerDelegate * mTimerDelegate;
257 : };
258 : }; // namespace reporting
259 : }; // namespace app
260 : }; // namespace chip
|