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