Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2023 Project CHIP Authors
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 :
18 : #include <app/InteractionModelEngine.h>
19 : #include <app/reporting/SynchronizedReportSchedulerImpl.h>
20 : #include <lib/support/logging/CHIPLogging.h>
21 :
22 : namespace chip {
23 : namespace app {
24 : namespace reporting {
25 :
26 : using namespace System::Clock;
27 : using ReadHandlerNode = ReportScheduler::ReadHandlerNode;
28 :
29 53 : void SynchronizedReportSchedulerImpl::OnReadHandlerDestroyed(ReadHandler * aReadHandler)
30 : {
31 : // Verify list is populated
32 53 : VerifyOrReturn(mNodesPool.Allocated());
33 :
34 26 : ReadHandlerNode * removeNode = FindReadHandlerNode(aReadHandler);
35 : // Nothing to remove if the handler is not found in the list
36 26 : VerifyOrReturn(nullptr != removeNode);
37 :
38 26 : mNodesPool.ReleaseObject(removeNode);
39 :
40 26 : if (!mNodesPool.Allocated())
41 : {
42 : // Only cancel the timer if there are no more handlers registered
43 21 : CancelReport();
44 : }
45 : }
46 :
47 0 : void SynchronizedReportSchedulerImpl::OnTransitionToIdle()
48 : {
49 0 : Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp();
50 0 : uint32_t targetIdleInterval = static_cast<uint32_t>(ICD_SLEEP_TIME_JITTER_MS);
51 0 : VerifyOrReturn(now >= mNextReportTimestamp);
52 0 : if (((mNextReportTimestamp - now) < Seconds16(targetIdleInterval)) && (now > mNextMinTimestamp))
53 : {
54 : // If the next report is due in less than the idle mode duration and we are past the min interval, we can just send it now
55 0 : CancelReport();
56 0 : TimerFired();
57 : }
58 : }
59 :
60 154 : CHIP_ERROR SynchronizedReportSchedulerImpl::ScheduleReport(Timeout timeout, ReadHandlerNode * node, const Timestamp & now)
61 : {
62 : // Cancel Report if it is currently scheduled
63 154 : mTimerDelegate->CancelTimer(this);
64 154 : if (timeout == Milliseconds32(0))
65 : {
66 29 : TimerFired();
67 29 : return CHIP_NO_ERROR;
68 : }
69 125 : ReturnErrorOnFailure(mTimerDelegate->StartTimer(this, timeout));
70 125 : mNextReportTimestamp = now + timeout;
71 :
72 125 : return CHIP_NO_ERROR;
73 : }
74 :
75 21 : void SynchronizedReportSchedulerImpl::CancelReport()
76 : {
77 : // We don't need to take action on the handler, since the timer is common here
78 21 : mTimerDelegate->CancelTimer(this);
79 21 : }
80 :
81 : /// @brief Checks if the timer is active for the ReportScheduler
82 6 : bool SynchronizedReportSchedulerImpl::IsReportScheduled(ReadHandler * ReadHandler)
83 : {
84 6 : return mTimerDelegate->IsTimerActive(this);
85 : }
86 :
87 154 : CHIP_ERROR SynchronizedReportSchedulerImpl::FindNextMaxInterval(const Timestamp & now)
88 : {
89 154 : VerifyOrReturnError(mNodesPool.Allocated(), CHIP_ERROR_INVALID_LIST_LENGTH);
90 154 : System::Clock::Timestamp earliest = now + Seconds16::max();
91 :
92 154 : mNodesPool.ForEachActiveObject([&earliest, now](ReadHandlerNode * node) {
93 252 : if (node->GetMaxTimestamp() < earliest && node->GetMaxTimestamp() > now)
94 : {
95 150 : earliest = node->GetMaxTimestamp();
96 : }
97 :
98 252 : return Loop::Continue;
99 : });
100 :
101 154 : mNextMaxTimestamp = earliest;
102 :
103 154 : return CHIP_NO_ERROR;
104 : }
105 :
106 154 : CHIP_ERROR SynchronizedReportSchedulerImpl::FindNextMinInterval(const Timestamp & now)
107 : {
108 154 : VerifyOrReturnError(mNodesPool.Allocated(), CHIP_ERROR_INVALID_LIST_LENGTH);
109 154 : System::Clock::Timestamp latest = now;
110 :
111 154 : mNodesPool.ForEachActiveObject([&latest, this](ReadHandlerNode * node) {
112 : // We only consider the min interval if the handler is reportable. This is done to have only reportable handlers
113 : // contribute to setting the next min interval and avoid delaying a report for a handler that would not generate
114 : // a one on its min interval anyway.
115 261 : if (node->GetMinTimestamp() > latest && this->IsReadHandlerReportable(node->GetReadHandler()) &&
116 261 : node->GetMinTimestamp() <= this->mNextMaxTimestamp)
117 : {
118 : // We do not want the new min to be set above the max for any handler
119 7 : latest = node->GetMinTimestamp();
120 : }
121 :
122 252 : return Loop::Continue;
123 : });
124 :
125 154 : mNextMinTimestamp = latest;
126 :
127 154 : return CHIP_NO_ERROR;
128 : }
129 :
130 154 : CHIP_ERROR SynchronizedReportSchedulerImpl::CalculateNextReportTimeout(Timeout & timeout, ReadHandlerNode * aNode,
131 : const Timestamp & now)
132 : {
133 154 : ReturnErrorOnFailure(FindNextMaxInterval(now));
134 154 : ReturnErrorOnFailure(FindNextMinInterval(now));
135 154 : bool reportableNow = false;
136 154 : bool reportableAtMin = false;
137 :
138 : // Find out if any handler is reportable now or at the next min interval
139 154 : mNodesPool.ForEachActiveObject([&reportableNow, &reportableAtMin, this, now](ReadHandlerNode * node) {
140 : // If a node is already scheduled, we don't need to check if it is reportable now unless a chunked report is in progress.
141 : // In this case, the node will be Reportable, as it is impossible to have node->IsChunkedReport() == true without being
142 : // reportable, therefore we need to keep scheduling engine runs until the report is complete
143 250 : if (!node->IsEngineRunScheduled() || node->IsChunkedReport())
144 : {
145 176 : if (node->IsReportableNow(now))
146 : {
147 29 : reportableNow = true;
148 29 : return Loop::Break;
149 : }
150 :
151 147 : if (this->IsReadHandlerReportable(node->GetReadHandler()) && node->GetMinTimestamp() <= this->mNextMaxTimestamp)
152 : {
153 7 : reportableAtMin = true;
154 : }
155 : }
156 :
157 221 : return Loop::Continue;
158 : });
159 :
160 154 : if (reportableNow)
161 : {
162 29 : timeout = Milliseconds32(0);
163 : }
164 125 : else if (reportableAtMin)
165 : {
166 7 : timeout = mNextMinTimestamp - now;
167 : }
168 : else
169 : {
170 : // Schedule report at next max otherwise
171 118 : timeout = mNextMaxTimestamp - now;
172 : }
173 :
174 154 : return CHIP_NO_ERROR;
175 : }
176 :
177 49 : void SynchronizedReportSchedulerImpl::TimerFired()
178 : {
179 49 : Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp();
180 49 : bool firedEarly = true;
181 :
182 : // If there are no handlers registered, no need to do anything.
183 49 : VerifyOrReturn(mNodesPool.Allocated());
184 :
185 49 : mNodesPool.ForEachActiveObject([now, &firedEarly](ReadHandlerNode * node) {
186 71 : if (node->GetMinTimestamp() <= now && node->CanStartReporting())
187 : {
188 : // Since this handler can now report whenever it wants to, mark it as allowed to report if any other handler is
189 : // reporting using the CanBeSynced flag.
190 65 : node->SetCanBeSynced(true);
191 : }
192 :
193 71 : if (node->IsReportableNow(now))
194 : {
195 : // We set firedEarly false here because we assume we fired the timer early if no handler is reportable at the
196 : // moment, which becomes false if we find a handler that is reportable
197 65 : firedEarly = false;
198 65 : node->SetEngineRunScheduled(true);
199 65 : ChipLogProgress(DataManagement, "Handler: %p with min: 0x" ChipLogFormatX64 " and max: 0x" ChipLogFormatX64 "", (node),
200 : ChipLogValueX64(node->GetMinTimestamp().count()), ChipLogValueX64(node->GetMaxTimestamp().count()));
201 : }
202 :
203 71 : return Loop::Continue;
204 : });
205 :
206 49 : if (firedEarly)
207 : {
208 : // If we fired the timer early, we need to recalculate the next report timeout and reschedule the report so it can run when
209 : // at least one read handler is reportable. Here we can't set the SetEngineRunScheduled flag to true, because this flag
210 : // allows handlers to generate reports before their min (assuming their min has elapsed from the timer's perspective but not
211 : // from the monotonic timer), and we don't know which handler was the one that should be reportable.
212 1 : Timeout timeout = Milliseconds32(0);
213 1 : ReturnOnFailure(CalculateNextReportTimeout(timeout, nullptr, now));
214 1 : ScheduleReport(timeout, nullptr, now);
215 : }
216 : else
217 : {
218 : // If we have a reportable handler, we can schedule an engine run
219 48 : InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun();
220 : }
221 : }
222 :
223 : } // namespace reporting
224 : } // namespace app
225 : } // namespace chip
|