| /* |
| * |
| * Copyright (c) 2023 Project CHIP Authors |
| * All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #pragma once |
| |
| // TODO(#32628): Remove the CHIPCore.h header when the esp32 build is correctly fixed |
| #include <lib/core/CHIPCore.h> |
| |
| #include <app/ReadHandler.h> |
| #include <app/icd/server/ICDStateObserver.h> |
| #include <lib/core/CHIPError.h> |
| #include <system/SystemClock.h> |
| |
| namespace chip { |
| namespace app { |
| namespace reporting { |
| |
| // Forward declaration of TestReportScheduler to allow it to be friend with ReportScheduler |
| class TestReportScheduler; |
| |
| class TimerContext |
| { |
| public: |
| virtual ~TimerContext() {} |
| virtual void TimerFired() = 0; |
| }; |
| |
| /** |
| * @class ReportScheduler |
| * |
| * @brief This class is responsible for scheduling Engine runs based on the reporting intervals of the ReadHandlers. |
| * |
| * |
| * This class holds a pool of ReadHandlerNodes that are used to keep track of the minimum and maximum timestamps for a report to be |
| * emitted based on the reporting intervals of the ReadHandlers associated with the node. |
| * |
| * The ReportScheduler also holds a TimerDelegate pointer that is used to start and cancel timers for the ReadHandlers depending |
| * on the reporting logic of the Scheduler. |
| * |
| * It inherits the ReadHandler::Observer class to be notified of reportability changes in the ReadHandlers. |
| * It inherits the ICDStateObserver class to allow the implementation to generate reports based on the changes in ICD devices state, |
| * such as going from idle to active mode and vice-versa. |
| * |
| * @note The logic for how and when to schedule reports is implemented in the subclasses of ReportScheduler, such as |
| * ReportSchedulerImpl and SyncronizedReportSchedulerImpl. |
| */ |
| class ReportScheduler : public ReadHandler::Observer, public ICDStateObserver |
| { |
| public: |
| using Timestamp = System::Clock::Timestamp; |
| |
| /// @brief This class acts as an interface between the report scheduler and the system timer to reduce dependencies on the |
| /// system layer. |
| class TimerDelegate |
| { |
| public: |
| virtual ~TimerDelegate() {} |
| /// @brief Start a timer for a given context. The report scheduler must always cancel an existing timer for a context (using |
| /// CancelTimer) before starting a new one for that context. |
| /// @param context context to pass to the timer callback. |
| /// @param aTimeout time in milliseconds before the timer expires |
| virtual CHIP_ERROR StartTimer(TimerContext * context, System::Clock::Timeout aTimeout) = 0; |
| /// @brief Cancel a timer for a given context |
| /// @param context used to identify the timer to cancel |
| virtual void CancelTimer(TimerContext * context) = 0; |
| virtual bool IsTimerActive(TimerContext * context) = 0; |
| virtual Timestamp GetCurrentMonotonicTimestamp() = 0; |
| }; |
| |
| /** |
| * @class ReadHandlerNode |
| * |
| * @brief This class is responsible for determining when a ReadHandler is reportable depending on the monotonic timestamp of |
| * the system and the intervals of the ReadHandler. It inherits the TimerContext class to allow it to be used as a context for |
| * a TimerDelegate so that the TimerDelegate can call the TimerFired method when the timer expires. |
| * |
| * Three conditions that can prevent the ReadHandler from being reportable: |
| * 1: The ReadHandler is not in the CanStartReporting state: |
| * This condition can be resolved by setting the CanStartReporting flag on the ReadHandler |
| * |
| * 2: The minimal interval since the last report has not elapsed |
| * This condition can be resolved after enough time has passed since the last report or by setting the EngineRunScheduled |
| * flag |
| * |
| * 3: The maximal interval since the last report has not elapsed and the ReadHandler is not dirty: |
| * This condition can be resolved after enough time has passed since the last report to reach the max interval, by the |
| * ReadHandler becoming dirty or by setting the CanBeSynced flag and having another ReadHandler needing to report. |
| * |
| * Once the 3 conditions are met, the ReadHandler is considered reportable. |
| * |
| * Flags: |
| * |
| * CanBeSynced: Mechanism to allow the ReadHandler to emit a report if another readHandler is ReportableNow. |
| * This flag is currently only used by the SynchronizedReportScheduler to allow firing reports of ReadHandlers at the same |
| * time. |
| * |
| * EngineRunScheduled: Mechanism to ensure that the reporting engine will see the ReadHandler as reportable if a timer fires. |
| * This flag is used to confirm that the next report timer has fired for a ReadHandler, thus allowing reporting when timers |
| * fire earlier than the minimal timestamp due to mechanisms such as NTP clock adjustments. |
| * |
| */ |
| class ReadHandlerNode : public TimerContext |
| { |
| public: |
| enum class ReadHandlerNodeFlags : uint8_t |
| { |
| // Flag to indicate if the engine run is already scheduled so the scheduler can ignore |
| // it when calculating the next run time |
| EngineRunScheduled = (1 << 0), |
| // Flag to allow the read handler to be synced with other handlers that have an earlier max timestamp |
| CanBeSynced = (1 << 1), |
| }; |
| |
| ReadHandlerNode(ReadHandler * aReadHandler, ReportScheduler * aScheduler, const Timestamp & now) : mScheduler(aScheduler) |
| { |
| VerifyOrDie(aReadHandler != nullptr); |
| VerifyOrDie(aScheduler != nullptr); |
| |
| mReadHandler = aReadHandler; |
| SetIntervalTimeStamps(aReadHandler, now); |
| } |
| ReadHandler * GetReadHandler() const { return mReadHandler; } |
| |
| /// @brief Check if the Node is reportable now, meaning its readhandler was made reportable by attribute dirtying and |
| /// handler state, and minimal time interval since the last report has elapsed, or the maximal time interval since the last |
| /// report has elapsed. |
| /// @note If a handler has been flagged as scheduled for an engine run, it will be reported regardless of the timestamps. |
| /// This is done to guarantee that the reporting engine will see the handler as reportable if a timer fires, even if it |
| /// fires early. |
| /// @param now current time to use for the check, the user must ensure to provide a valid time for this to be reliable |
| bool IsReportableNow(const Timestamp & now) const |
| { |
| return (mReadHandler->CanStartReporting() && |
| ((now >= mMinTimestamp && (mReadHandler->IsDirty() || now >= mMaxTimestamp || CanBeSynced())) || |
| IsEngineRunScheduled())); |
| } |
| |
| bool CanStartReporting() const { return mReadHandler->CanStartReporting(); } |
| bool IsChunkedReport() const { return mReadHandler->IsChunkedReport(); } |
| bool IsEngineRunScheduled() const { return mFlags.Has(ReadHandlerNodeFlags::EngineRunScheduled); } |
| void SetEngineRunScheduled(bool aEngineRunScheduled) |
| { |
| mFlags.Set(ReadHandlerNodeFlags::EngineRunScheduled, aEngineRunScheduled); |
| } |
| bool CanBeSynced() const { return mFlags.Has(ReadHandlerNodeFlags::CanBeSynced); } |
| void SetCanBeSynced(bool aCanBeSynced) { mFlags.Set(ReadHandlerNodeFlags::CanBeSynced, aCanBeSynced); } |
| |
| /// @brief Set the interval timestamps for the node based on the read handler reporting intervals |
| /// @param aReadHandler read handler to get the intervals from |
| /// @param now current time to calculate the mMin and mMax timestamps, the user must ensure to provide a valid time for this |
| /// to be reliable |
| void SetIntervalTimeStamps(ReadHandler * aReadHandler, const Timestamp & now) |
| { |
| uint16_t minInterval, maxInterval; |
| aReadHandler->GetReportingIntervals(minInterval, maxInterval); |
| mMinTimestamp = now + System::Clock::Seconds16(minInterval); |
| mMaxTimestamp = now + System::Clock::Seconds16(maxInterval); |
| } |
| |
| void TimerFired() override |
| { |
| SetEngineRunScheduled(true); |
| mScheduler->ReportTimerCallback(); |
| } |
| |
| System::Clock::Timestamp GetMinTimestamp() const { return mMinTimestamp; } |
| System::Clock::Timestamp GetMaxTimestamp() const { return mMaxTimestamp; } |
| |
| private: |
| ReadHandler * mReadHandler; |
| ReportScheduler * mScheduler; |
| Timestamp mMinTimestamp; |
| Timestamp mMaxTimestamp; |
| |
| BitFlags<ReadHandlerNodeFlags> mFlags; |
| }; |
| |
| ReportScheduler(TimerDelegate * aTimerDelegate) : mTimerDelegate(aTimerDelegate) {} |
| |
| virtual ~ReportScheduler() = default; |
| |
| virtual void ReportTimerCallback() = 0; |
| |
| /// @brief Check whether a ReadHandler is reportable right now, taking into account its minimum and maximum intervals. |
| /// @param aReadHandler read handler to check |
| bool IsReportableNow(ReadHandler * aReadHandler) |
| { |
| // Update the now timestamp to ensure external calls to IsReportableNow are always comparing to the current time |
| Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp(); |
| ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); |
| return (nullptr != node) ? node->IsReportableNow(now) : false; |
| } |
| |
| /// @brief Check if a ReadHandler is reportable without considering the timing |
| bool IsReadHandlerReportable(ReadHandler * aReadHandler) const |
| { |
| return (nullptr != aReadHandler) ? aReadHandler->ShouldStartReporting() : false; |
| } |
| /// @brief Sets the ForceDirty flag of a ReadHandler |
| void HandlerForceDirtyState(ReadHandler * aReadHandler) { aReadHandler->ForceDirtyState(); } |
| |
| /// @brief Get the number of ReadHandlers registered in the scheduler's node pool |
| size_t GetNumReadHandlers() const { return mNodesPool.Allocated(); } |
| |
| #if CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| Timestamp GetMinTimestampForHandler(const ReadHandler * aReadHandler) |
| { |
| ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); |
| return node->GetMinTimestamp(); |
| } |
| Timestamp GetMaxTimestampForHandler(const ReadHandler * aReadHandler) |
| { |
| ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); |
| return node->GetMaxTimestamp(); |
| } |
| ReadHandlerNode * GetReadHandlerNode(const ReadHandler * aReadHandler) { return FindReadHandlerNode(aReadHandler); } |
| #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| |
| protected: |
| friend class chip::app::reporting::TestReportScheduler; |
| |
| /// @brief Find the ReadHandlerNode for a given ReadHandler pointer |
| /// @param [in] aReadHandler ReadHandler pointer to look for in the ReadHandler nodes list |
| /// @return Node Address if the node was found, nullptr otherwise |
| ReadHandlerNode * FindReadHandlerNode(const ReadHandler * aReadHandler) |
| { |
| ReadHandlerNode * foundNode = nullptr; |
| mNodesPool.ForEachActiveObject([&foundNode, aReadHandler](ReadHandlerNode * node) { |
| if (node->GetReadHandler() == aReadHandler) |
| { |
| foundNode = node; |
| return Loop::Break; |
| } |
| |
| return Loop::Continue; |
| }); |
| return foundNode; |
| } |
| |
| ObjectPool<ReadHandlerNode, CHIP_IM_MAX_NUM_READS + CHIP_IM_MAX_NUM_SUBSCRIPTIONS> mNodesPool; |
| TimerDelegate * mTimerDelegate; |
| }; |
| }; // namespace reporting |
| }; // namespace app |
| }; // namespace chip |