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