/* * * 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 #include #include #include #include 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 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 mNodesPool; TimerDelegate * mTimerDelegate; }; }; // namespace reporting }; // namespace app }; // namespace chip