| /* |
| * |
| * 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 |
| |
| #include <app/ReadHandler.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; |
| |
| using Timestamp = System::Clock::Timestamp; |
| |
| class TimerContext |
| { |
| public: |
| virtual ~TimerContext() {} |
| virtual void TimerFired() = 0; |
| }; |
| |
| class ReportScheduler : public ReadHandler::Observer |
| { |
| public: |
| /// @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 miliseconds 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 : public TimerContext |
| { |
| public: |
| #ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| /// Test flags to allow TestReadInteraction to simulate expiration of the minimal and maximal intervals without |
| /// waiting |
| enum class TestFlags : uint8_t{ |
| MinIntervalElapsed = (1 << 0), |
| MaxIntervalElapsed = (1 << 1), |
| }; |
| void SetTestFlags(TestFlags aFlag, bool aValue) { mFlags.Set(aFlag, aValue); } |
| bool GetTestFlags(TestFlags aFlag) const { return mFlags.Has(aFlag); } |
| #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| |
| ReadHandlerNode(ReadHandler * aReadHandler, TimerDelegate * aTimerDelegate, ReportScheduler * aScheduler) : |
| mTimerDelegate(aTimerDelegate), mScheduler(aScheduler) |
| { |
| VerifyOrDie(aReadHandler != nullptr); |
| VerifyOrDie(aTimerDelegate != nullptr); |
| VerifyOrDie(aScheduler != nullptr); |
| |
| mReadHandler = aReadHandler; |
| SetIntervalTimeStamps(aReadHandler); |
| } |
| 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 last report has elapsed, or the maximal time interval since last |
| /// report has elapsed |
| bool IsReportableNow() const |
| { |
| Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp(); |
| |
| #ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| return (mReadHandler->IsGeneratingReports() && (now >= mMinTimestamp || mFlags.Has(TestFlags::MinIntervalElapsed)) && |
| (mReadHandler->IsDirty() || (now >= mMaxTimestamp || mFlags.Has(TestFlags::MaxIntervalElapsed)) || |
| now >= mSyncTimestamp)); |
| #else |
| return (mReadHandler->IsGeneratingReports() && |
| (now >= mMinTimestamp && (mReadHandler->IsDirty() || now >= mMaxTimestamp || now >= mSyncTimestamp))); |
| #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| } |
| |
| bool IsEngineRunScheduled() const { return mEngineRunScheduled; } |
| void SetEngineRunScheduled(bool aEngineRunScheduled) |
| { |
| mEngineRunScheduled = aEngineRunScheduled; |
| #ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| // If the engine becomes unscheduled, this means a run just took place so we reset the test flags |
| if (!mEngineRunScheduled) |
| { |
| mFlags.Set(TestFlags::MinIntervalElapsed, false); |
| mFlags.Set(TestFlags::MaxIntervalElapsed, false); |
| } |
| #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| } |
| |
| void SetIntervalTimeStamps(ReadHandler * aReadHandler) |
| { |
| uint16_t minInterval, maxInterval; |
| aReadHandler->GetReportingIntervals(minInterval, maxInterval); |
| Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp(); |
| mMinTimestamp = now + System::Clock::Seconds16(minInterval); |
| mMaxTimestamp = now + System::Clock::Seconds16(maxInterval); |
| mSyncTimestamp = mMaxTimestamp; |
| } |
| |
| void TimerFired() override |
| { |
| mScheduler->ReportTimerCallback(); |
| SetEngineRunScheduled(true); |
| } |
| |
| void SetSyncTimestamp(System::Clock::Timestamp aSyncTimestamp) |
| { |
| // Prevents the sync timestamp being set to a value lower than the min timestamp to prevent it to appear as reportable |
| // on the next timeout calculation and cause the scheduler to run the engine too early |
| VerifyOrReturn(aSyncTimestamp >= mMinTimestamp); |
| mSyncTimestamp = aSyncTimestamp; |
| } |
| |
| System::Clock::Timestamp GetMinTimestamp() const { return mMinTimestamp; } |
| System::Clock::Timestamp GetMaxTimestamp() const { return mMaxTimestamp; } |
| System::Clock::Timestamp GetSyncTimestamp() const { return mSyncTimestamp; } |
| |
| private: |
| #ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| BitFlags<TestFlags> mFlags; |
| #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| TimerDelegate * mTimerDelegate; |
| ReadHandler * mReadHandler; |
| ReportScheduler * mScheduler; |
| Timestamp mMinTimestamp; |
| Timestamp mMaxTimestamp; |
| Timestamp mSyncTimestamp; // Timestamp at which the read handler will be allowed to emit a report so it can be synced with |
| // other handlers that have an earlier max timestamp |
| bool mEngineRunScheduled = false; // Flag to indicate if the engine run is already scheduled so the scheduler can ignore |
| // it when calculating the next run time |
| }; |
| |
| ReportScheduler(TimerDelegate * aTimerDelegate) : mTimerDelegate(aTimerDelegate) {} |
| /** |
| * Interface to act on changes in the ReadHandler reportability |
| */ |
| 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) { return FindReadHandlerNode(aReadHandler)->IsReportableNow(); } |
| /// @brief Check if a ReadHandler is reportable without considering the timing |
| bool IsReadHandlerReportable(ReadHandler * aReadHandler) const { return aReadHandler->IsReportable(); } |
| |
| /// @brief Get the number of ReadHandlers registered in the scheduler's node pool |
| size_t GetNumReadHandlers() const { return mNodesPool.Allocated(); } |
| |
| #ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| void RunNodeCallbackForHandler(const ReadHandler * aReadHandler) |
| { |
| ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); |
| node->TimerFired(); |
| } |
| void SetFlagsForHandler(const ReadHandler * aReadHandler, ReadHandlerNode::TestFlags aFlag, bool aValue) |
| { |
| ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); |
| node->SetTestFlags(aFlag, aValue); |
| } |
| |
| bool CheckFlagsForHandler(const ReadHandler * aReadHandler, ReadHandlerNode::TestFlags aFlag) |
| { |
| ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); |
| return node->GetTestFlags(aFlag); |
| } |
| Timestamp GetMinTimestampForHandler(const ReadHandler * aReadHandler) |
| { |
| ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); |
| return node->GetMinTimestamp(); |
| } |
| Timestamp GetMaxTimestampForHandler(const ReadHandler * aReadHandler) |
| { |
| ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); |
| return node->GetMaxTimestamp(); |
| } |
| #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 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 |