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 52 : void SynchronizedReportSchedulerImpl::OnReadHandlerDestroyed(ReadHandler * aReadHandler) 30 : { 31 : // Verify list is populated 32 52 : 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 to prevent holding the reports 113 261 : if (node->GetMinTimestamp() > latest && this->IsReadHandlerReportable(node->GetReadHandler()) && 114 261 : node->GetMinTimestamp() <= this->mNextMaxTimestamp) 115 : { 116 : // We do not want the new min to be set above the max for any handler 117 7 : latest = node->GetMinTimestamp(); 118 : } 119 : 120 252 : return Loop::Continue; 121 : }); 122 : 123 154 : mNextMinTimestamp = latest; 124 : 125 154 : return CHIP_NO_ERROR; 126 : } 127 : 128 154 : CHIP_ERROR SynchronizedReportSchedulerImpl::CalculateNextReportTimeout(Timeout & timeout, ReadHandlerNode * aNode, 129 : const Timestamp & now) 130 : { 131 154 : ReturnErrorOnFailure(FindNextMaxInterval(now)); 132 154 : ReturnErrorOnFailure(FindNextMinInterval(now)); 133 154 : bool reportableNow = false; 134 154 : bool reportableAtMin = false; 135 : 136 : // Find out if any handler is reportable now or at the next min interval 137 154 : mNodesPool.ForEachActiveObject([&reportableNow, &reportableAtMin, this, now](ReadHandlerNode * node) { 138 : // If a node is already scheduled, we don't need to check if it is reportable now, unless a chunked report is in progress 139 : // in which case we need to keep scheduling engine runs until the report is complete 140 250 : if (!node->IsEngineRunScheduled() || node->IsChunkedReport()) 141 : { 142 176 : if (node->IsReportableNow(now)) 143 : { 144 29 : reportableNow = true; 145 29 : return Loop::Break; 146 : } 147 : 148 147 : if (this->IsReadHandlerReportable(node->GetReadHandler()) && node->GetMinTimestamp() <= this->mNextMaxTimestamp) 149 : { 150 7 : reportableAtMin = true; 151 : } 152 : } 153 : 154 221 : return Loop::Continue; 155 : }); 156 : 157 154 : if (reportableNow) 158 : { 159 29 : timeout = Milliseconds32(0); 160 : } 161 125 : else if (reportableAtMin) 162 : { 163 7 : timeout = mNextMinTimestamp - now; 164 : } 165 : else 166 : { 167 : // Schedule report at next max otherwise 168 118 : timeout = mNextMaxTimestamp - now; 169 : } 170 : 171 154 : return CHIP_NO_ERROR; 172 : } 173 : 174 49 : void SynchronizedReportSchedulerImpl::TimerFired() 175 : { 176 49 : Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp(); 177 49 : bool firedEarly = true; 178 : 179 : // If there are no handlers registered, no need to do anything. 180 49 : VerifyOrReturn(mNodesPool.Allocated()); 181 : 182 49 : mNodesPool.ForEachActiveObject([now, &firedEarly](ReadHandlerNode * node) { 183 71 : if (node->GetMinTimestamp() <= now) 184 : { 185 : // Mark the handler as CanBeSynced if the min interval has elapsed so it will emit a report on the next engine run 186 65 : node->SetCanBeSynced(true); 187 : } 188 : 189 71 : if (node->IsReportableNow(now)) 190 : { 191 : // We set firedEarly false here because we assume we fired the timer early if no handler is reportable at the 192 : // moment, which becomes false if we find a handler that is reportable 193 65 : firedEarly = false; 194 65 : node->SetEngineRunScheduled(true); 195 65 : ChipLogProgress(DataManagement, "Handler: %p with min: 0x" ChipLogFormatX64 " and max: 0x" ChipLogFormatX64 "", (node), 196 : ChipLogValueX64(node->GetMinTimestamp().count()), ChipLogValueX64(node->GetMaxTimestamp().count())); 197 : } 198 : 199 71 : return Loop::Continue; 200 : }); 201 : 202 49 : if (firedEarly) 203 : { 204 : // If we fired the timer early, we need to recalculate the next report timeout and reschedule the report 205 1 : Timeout timeout = Milliseconds32(0); 206 1 : ReturnOnFailure(CalculateNextReportTimeout(timeout, nullptr, now)); 207 1 : ScheduleReport(timeout, nullptr, now); 208 : } 209 : else 210 : { 211 : // If we did not fire the timer early, we can schedule an engine run 212 48 : InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun(); 213 : } 214 : } 215 : 216 : } // namespace reporting 217 : } // namespace app 218 : } // namespace chip