[ReadHandler] Report Scheduler class (#27553)
* Added a new class that will handle the scheduling of reports.
* Restyled by clang-format
* Removed un-necessary define in TestReportScheduler and applied refactor of SetReportingIntervals to SetMaxReportingIntervals to platform code
* Added TimerDelegate and wrapper functions around calls to Timer. Remove unnecessary checks for nullptr
* Added VerifyOrReturn after NL_TEST_ASSERTS for nullptr
* Completed TimerDelegate class and modified ReadHandlerNodes so they carry their own callback
* Modified TimerDelegate to allow to pass different objects as context
* ifdefing out ScheduleRun() to debug failing CI
* Added issue # to TODOs, refactored Min/Max Intervals to Min/Max Timestamp
* Clarified some comments regarding timing
* Restyled by whitespace
* Restyled by clang-format
* Added interface to GetMonotonicTimestamp in the timer delegate
* Apply suggestions from code review
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Completed renaming to eliminate compiling error, moved TestReporScehduler in reporting namespace, addressed some low hanging fruits
* Removed useless objects from tests as well as useless typecasting, and unnecessary check
* Fixed comment about private methods used in ReportScheduler as a friend class
* Changed to SetMinReportInterval to SetMinReportingIntervalForTests, removed the IsChunkedReport from comment about friend class, added a mock timestamp and timer to test to better control time in simulation for specific timing test cases
* Apply suggestions from code review
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Restyled by clang-format
* Removed all calls to ReadHandler States to prevent Engine calls from the Test as it seems to impact the CI
---------
Co-authored-by: Restyled.io <commits@restyled.io>
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
diff --git a/examples/platform/nrfconnect/util/ICDUtil.cpp b/examples/platform/nrfconnect/util/ICDUtil.cpp
index fd2130c..b3dc9c8 100644
--- a/examples/platform/nrfconnect/util/ICDUtil.cpp
+++ b/examples/platform/nrfconnect/util/ICDUtil.cpp
@@ -36,5 +36,5 @@
agreedMaxInterval = kSubscriptionMaxIntervalPublisherLimit;
}
- return aReadHandler.SetReportingIntervals(agreedMaxInterval);
+ return aReadHandler.SetMaxReportingInterval(agreedMaxInterval);
}
diff --git a/examples/platform/silabs/ICDSubscriptionCallback.cpp b/examples/platform/silabs/ICDSubscriptionCallback.cpp
index 735246f..eba2896 100644
--- a/examples/platform/silabs/ICDSubscriptionCallback.cpp
+++ b/examples/platform/silabs/ICDSubscriptionCallback.cpp
@@ -61,5 +61,5 @@
decidedMaxInterval = maximumMaxInterval;
}
- return aReadHandler.SetReportingIntervals(decidedMaxInterval);
+ return aReadHandler.SetMaxReportingInterval(decidedMaxInterval);
}
diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn
index 8211d22..7f2c4d8 100644
--- a/src/app/BUILD.gn
+++ b/src/app/BUILD.gn
@@ -193,6 +193,9 @@
"WriteHandler.cpp",
"reporting/Engine.cpp",
"reporting/Engine.h",
+ "reporting/ReportScheduler.h",
+ "reporting/ReportSchedulerImpl.cpp",
+ "reporting/ReportSchedulerImpl.h",
"reporting/reporting.h",
]
diff --git a/src/app/ReadHandler.cpp b/src/app/ReadHandler.cpp
index 4a92fcf..a5209ae 100644
--- a/src/app/ReadHandler.cpp
+++ b/src/app/ReadHandler.cpp
@@ -39,7 +39,7 @@
using Status = Protocols::InteractionModel::Status;
ReadHandler::ReadHandler(ManagementCallback & apCallback, Messaging::ExchangeContext * apExchangeContext,
- InteractionType aInteractionType) :
+ InteractionType aInteractionType, Observer * observer) :
mExchangeCtx(*this),
mManagementCallback(apCallback)
#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
@@ -63,15 +63,37 @@
SetStateFlag(ReadHandlerFlags::PrimingReports);
mSessionHandle.Grab(mExchangeCtx->GetSessionHandle());
+
+// TODO (#27672): Uncomment when the ReportScheduler is implemented
+#if 0
+ if (nullptr != observer)
+ {
+ if (CHIP_NO_ERROR == SetObserver(observer))
+ {
+ mObserver->OnReadHandlerCreated(this);
+ }
+ }
+#endif
}
#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
-ReadHandler::ReadHandler(ManagementCallback & apCallback) :
+ReadHandler::ReadHandler(ManagementCallback & apCallback, Observer * observer) :
mExchangeCtx(*this), mManagementCallback(apCallback), mOnConnectedCallback(HandleDeviceConnected, this),
mOnConnectionFailureCallback(HandleDeviceConnectionFailure, this)
{
mInteractionType = InteractionType::Subscribe;
mFlags.ClearAll();
+
+// TODO (#27672): Uncomment when the ReportScheduler is implemented
+#if 0
+ if (nullptr != observer)
+ {
+ if (CHIP_NO_ERROR == SetObserver(observer))
+ {
+ mObserver->OnReadHandlerCreated(this);
+ }
+ }
+#endif
}
void ReadHandler::ResumeSubscription(CASESessionManager & caseSessionManager,
@@ -115,6 +137,13 @@
ReadHandler::~ReadHandler()
{
+ // TODO (#27672): Enable when the ReportScheduler is implemented and move in Close() after testing
+#if 0
+ if (nullptr != mObserver)
+ {
+ mObserver->OnReadHandlerDestroyed(this);
+ }
+#endif
auto * appCallback = mManagementCallback.GetAppCallback();
if (mFlags.Has(ReadHandlerFlags::ActiveSubscription) && appCallback)
{
@@ -319,6 +348,15 @@
if (IsType(InteractionType::Subscribe) && !IsPriming())
{
+// TODO (#27672): Enable when the ReportScheduler is implemented and remove call to UpdateReportTimer, will be handled by
+// the report Scheduler
+#if 0
+ if (nullptr != mObserver)
+ {
+ mObserver->OnSubscriptionAction(this);
+ }
+#endif
+
// Ignore the error from UpdateReportTimer. If we've
// successfully sent the message, we need to return success from
// this method.
@@ -593,6 +631,13 @@
//
if (aTargetState == HandlerState::GeneratingReports && IsReportableNow())
{
+// TODO (#27672): Enable when the ReportScheduler is implemented and remove the call to ScheduleRun()
+#if 0
+ if(nullptr != mObserver)
+ {
+ mObserver->OnBecameReportable(this);
+ }
+#endif
InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun();
}
}
@@ -634,6 +679,14 @@
ReturnErrorOnFailure(writer.Finalize(&packet));
VerifyOrReturnLogError(mExchangeCtx, CHIP_ERROR_INCORRECT_STATE);
+ // TODO (#27672): Uncomment when the ReportScheduler is implemented and remove call to UpdateReportTimer, handled by
+ // the report Scheduler
+#if 0
+ if (nullptr != mObserver)
+ {
+ mObserver->OnSubscriptionAction(this);
+ }
+#endif
ReturnErrorOnFailure(UpdateReportTimer());
ClearStateFlag(ReadHandlerFlags::PrimingReports);
@@ -753,6 +806,7 @@
}
}
+// TODO (#27672): Remove when ReportScheduler is enabled as timing will now be handled by the ReportScheduler
void ReadHandler::MinIntervalExpiredCallback(System::Layer * apSystemLayer, void * apAppState)
{
VerifyOrReturn(apAppState != nullptr);
@@ -764,6 +818,7 @@
readHandler);
}
+// TODO (#27672): Remove when ReportScheduler is enabled as timing will now be handled by the ReportScheduler
void ReadHandler::MaxIntervalExpiredCallback(System::Layer * apSystemLayer, void * apAppState)
{
VerifyOrReturn(apAppState != nullptr);
@@ -773,6 +828,7 @@
readHandler->mMaxInterval - readHandler->mMinIntervalFloorSeconds);
}
+// TODO (#27672): Remove when ReportScheduler is enabled as timing will now be handled by the ReportScheduler
CHIP_ERROR ReadHandler::UpdateReportTimer()
{
InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer(
@@ -812,7 +868,7 @@
// Here we just reset the iterator to the beginning of the current cluster, if the dirty path affects it.
// This will ensure the reports are consistent within a single cluster generated from a single path in the request.
- // TODO (#16699): Currently we can only gurentee the reports generated from a single path in the request are consistent. The
+ // TODO (#16699): Currently we can only guarantee the reports generated from a single path in the request are consistent. The
// data might be inconsistent if the user send a request with two paths from the same cluster. We need to clearify the behavior
// or make it consistent.
if (mAttributePathExpandIterator.Get(path) &&
@@ -831,6 +887,13 @@
if (IsReportableNow())
{
+ // TODO (#27672): Enable when the ReportScheduler is implemented and remove the call to ScheduleRun()
+#if 0
+ if(nullptr != mObserver)
+ {
+ mObserver->OnBecameReportable(this);
+ }
+#endif
InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun();
}
}
@@ -853,9 +916,17 @@
{
bool oldReportable = IsReportableNow();
mFlags.Set(aFlag, aValue);
+
// If we became reportable, schedule a reporting run.
if (!oldReportable && IsReportableNow())
{
+// TODO (#27672): Enable when the ReportScheduler is implemented and remove the call to ScheduleRun()
+#if 0
+ if(nullptr != mObserver)
+ {
+ mObserver->OnBecameReportable(this);
+ }
+#endif
InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun();
}
}
diff --git a/src/app/ReadHandler.h b/src/app/ReadHandler.h
index f460188..77c88c1 100644
--- a/src/app/ReadHandler.h
+++ b/src/app/ReadHandler.h
@@ -64,6 +64,8 @@
namespace reporting {
class Engine;
class TestReportingEngine;
+class ReportScheduler;
+class TestReportScheduler;
} // namespace reporting
class InteractionModelEngine;
@@ -152,6 +154,38 @@
virtual ApplicationCallback * GetAppCallback() = 0;
};
+ // TODO (#27675) : Merge existing callback and observer into one class and have an observer pool in the Readhandler to notify
+ // every
+ /*
+ * Observer class for ReadHandler, meant to allow multiple objects to observe the ReadHandler. Currently only one observer is
+ * supported but all above callbacks should be merged into observer type and an observer pool should be added to allow multiple
+ * objects to observe ReadHandler
+ */
+ class Observer
+ {
+ public:
+ virtual ~Observer() = default;
+
+ /// @brief Callback invoked to notify a ReadHandler was created and can be registered
+ /// @param[in] apReadHandler ReadHandler getting added
+ virtual void OnReadHandlerCreated(ReadHandler * apReadHandler) = 0;
+
+ /// @brief Callback invoked when a ReadHandler went from a non reportable state to a reportable state so a report can be
+ /// sent immediately if the minimal interval allows it. Otherwise the report should be rescheduled to the earliest time
+ /// allowed.
+ /// @param[in] apReadHandler ReadHandler that became dirty
+ virtual void OnBecameReportable(ReadHandler * apReadHandler) = 0;
+
+ /// @brief Callback invoked when the read handler needs to make sure to send a message to the subscriber within the next
+ /// maxInterval time period.
+ /// @param[in] apReadHandler ReadHandler that has generated a report
+ virtual void OnSubscriptionAction(ReadHandler * apReadHandler) = 0;
+
+ /// @brief Callback invoked when a ReadHandler is getting removed so it can be unregistered
+ /// @param[in] apReadHandler ReadHandler getting destroyed
+ virtual void OnReadHandlerDestroyed(ReadHandler * apReadHandler) = 0;
+ };
+
/*
* Destructor - as part of destruction, it will abort the exchange context
* if a valid one still exists.
@@ -167,7 +201,8 @@
* The callback passed in has to outlive this handler object.
*
*/
- ReadHandler(ManagementCallback & apCallback, Messaging::ExchangeContext * apExchangeContext, InteractionType aInteractionType);
+ ReadHandler(ManagementCallback & apCallback, Messaging::ExchangeContext * apExchangeContext, InteractionType aInteractionType,
+ Observer * observer = nullptr);
#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
/**
@@ -177,7 +212,7 @@
* The callback passed in has to outlive this handler object.
*
*/
- ReadHandler(ManagementCallback & apCallback);
+ ReadHandler(ManagementCallback & apCallback, Observer * observer = nullptr);
#endif
const ObjectList<AttributePathParams> * GetAttributePathList() const { return mpAttributePathList; }
@@ -190,13 +225,22 @@
aMaxInterval = mMaxInterval;
}
+ CHIP_ERROR SetMinReportingIntervalForTests(uint16_t aMinInterval)
+ {
+ VerifyOrReturnError(IsIdle(), CHIP_ERROR_INCORRECT_STATE);
+ VerifyOrReturnError(aMinInterval <= mMaxInterval, CHIP_ERROR_INVALID_ARGUMENT);
+ // Ensures the new min interval is higher than the subscriber established one.
+ mMinIntervalFloorSeconds = std::max(mMinIntervalFloorSeconds, aMinInterval);
+ return CHIP_NO_ERROR;
+ }
+
/*
- * Set the reporting intervals for the subscription. This SHALL only be called
+ * Set the maximum reporting interval for the subscription. This SHALL only be called
* from the OnSubscriptionRequested callback above. The restriction is as below
* MinIntervalFloor ≤ MaxInterval ≤ MAX(SUBSCRIPTION_MAX_INTERVAL_PUBLISHER_LIMIT, MaxIntervalCeiling)
* Where SUBSCRIPTION_MAX_INTERVAL_PUBLISHER_LIMIT is set to 60m in the spec.
*/
- CHIP_ERROR SetReportingIntervals(uint16_t aMaxInterval)
+ CHIP_ERROR SetMaxReportingInterval(uint16_t aMaxInterval)
{
VerifyOrReturnError(IsIdle(), CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mMinIntervalFloorSeconds <= aMaxInterval, CHIP_ERROR_INVALID_ARGUMENT);
@@ -206,6 +250,18 @@
return CHIP_NO_ERROR;
}
+ /// @brief Add an observer to the read handler, currently only one observer is supported but all other callbacks should be
+ /// merged with a general observer type to allow multiple object to observe readhandlers
+ /// @param aObserver observer to be added
+ /// @return CHIP_ERROR_INVALID_ARGUMENT if passing in nullptr
+ CHIP_ERROR SetObserver(Observer * aObserver)
+ {
+ VerifyOrReturnError(nullptr != aObserver, CHIP_ERROR_INVALID_ARGUMENT);
+ // TODO (#27675) : After merging the callbacks and observer, change so the method adds a new observer to an observer pool
+ mObserver = aObserver;
+ return CHIP_NO_ERROR;
+ }
+
private:
PriorityLevel GetCurrentPriority() const { return mCurrentPriority; }
EventNumber & GetEventMin() { return mEventMin; }
@@ -214,13 +270,13 @@
{
// WaitingUntilMinInterval is used to prevent subscription data delivery while we are
// waiting for the min reporting interval to elapse.
- WaitingUntilMinInterval = (1 << 0),
+ WaitingUntilMinInterval = (1 << 0), // TODO (#27672): Remove once ReportScheduler is implemented or change to test flag
// WaitingUntilMaxInterval is used to prevent subscription empty report delivery while we
// are waiting for the max reporting interval to elaps. When WaitingUntilMaxInterval
// becomes false, we are allowed to send an empty report to keep the
// subscription alive on the client.
- WaitingUntilMaxInterval = (1 << 1),
+ WaitingUntilMaxInterval = (1 << 1), // TODO (#27672): Remove once ReportScheduler is implemented
// The flag indicating we are in the middle of a series of chunked report messages, this flag will be cleared during
// sending last chunked message.
@@ -291,6 +347,8 @@
bool IsIdle() const { return mState == HandlerState::Idle; }
+ // TODO (#27672): Change back to IsReportable once ReportScheduler is implemented so this can assess reportability without
+ // considering timing. The ReporScheduler will handle timing.
/// @brief Returns whether the ReadHandler is in a state where it can immediately send a report. This function
/// is used to determine whether a report generation should be scheduled for the handler.
bool IsReportableNow() const
@@ -370,6 +428,7 @@
friend class TestReadInteraction;
friend class chip::app::reporting::TestReportingEngine;
+ friend class chip::app::reporting::TestReportScheduler;
//
// The engine needs to be able to Abort/Close a ReadHandler instance upon completion of work for a given read/subscribe
@@ -379,6 +438,10 @@
friend class chip::app::reporting::Engine;
friend class chip::app::InteractionModelEngine;
+ // The report scheduler needs to be able to access StateFlag private functions IsGeneratingReports() and IsDirty() to
+ // know when to schedule a run so it is declared as a friend class.
+ friend class chip::app::reporting::ReportScheduler;
+
enum class HandlerState : uint8_t
{
Idle, ///< The handler has been initialized and is ready
@@ -404,10 +467,13 @@
/// @brief This function is called when the min interval timer has expired, it restarts the timer on a timeout equal to the
/// difference between the max interval and the min interval.
- static void MinIntervalExpiredCallback(System::Layer * apSystemLayer, void * apAppState);
- static void MaxIntervalExpiredCallback(System::Layer * apSystemLayer, void * apAppState);
+ static void MinIntervalExpiredCallback(System::Layer * apSystemLayer, void * apAppState); // TODO (#27672): Remove once
+ // ReportScheduler is implemented.
+ static void MaxIntervalExpiredCallback(System::Layer * apSystemLayer, void * apAppState); // TODO (#27672): Remove once
+ // ReportScheduler is implemented.
/// @brief This function is called when a report is sent and it restarts the min interval timer.
- CHIP_ERROR UpdateReportTimer();
+ CHIP_ERROR UpdateReportTimer(); // TODO (#27672) : Remove once ReportScheduler is implemented.
+
CHIP_ERROR SendSubscribeResponse();
CHIP_ERROR ProcessSubscribeRequest(System::PacketBufferHandle && aPayload);
CHIP_ERROR ProcessReadRequest(System::PacketBufferHandle && aPayload);
@@ -520,6 +586,9 @@
BitFlags<ReadHandlerFlags> mFlags;
InteractionType mInteractionType = InteractionType::Read;
+ // TODO (#27675): Merge all observers into one and that one will dispatch the callbacks to the right place.
+ Observer * mObserver = nullptr;
+
#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
// Callbacks to handle server-initiated session success/failure
chip::Callback::Callback<OnDeviceConnected> mOnConnectedCallback;
diff --git a/src/app/reporting/Engine.cpp b/src/app/reporting/Engine.cpp
index a985321..f0fea66 100644
--- a/src/app/reporting/Engine.cpp
+++ b/src/app/reporting/Engine.cpp
@@ -636,6 +636,7 @@
ReadHandler * readHandler = imEngine->ActiveHandlerAt(mCurReadHandlerIdx % (uint32_t) imEngine->mReadHandlers.Allocated());
VerifyOrDie(readHandler != nullptr);
+ // TODO (#27672): Replace with check with Report Scheduler if the read handler is reportable
if (readHandler->IsReportableNow())
{
mRunningReadHandler = readHandler;
diff --git a/src/app/reporting/ReportScheduler.h b/src/app/reporting/ReportScheduler.h
new file mode 100644
index 0000000..80d391c
--- /dev/null
+++ b/src/app/reporting/ReportScheduler.h
@@ -0,0 +1,150 @@
+/*
+ *
+ * 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 <lib/support/IntrusiveList.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 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(void * 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(void * context) = 0;
+ virtual bool IsTimerActive(void * context) = 0;
+ virtual Timestamp GetCurrentMonotonicTimestamp() = 0;
+ };
+
+ class ReadHandlerNode : public IntrusiveListNodeBase<>
+ {
+ public:
+ using TimerCompleteCallback = void (*)();
+
+ ReadHandlerNode(ReadHandler * aReadHandler, TimerDelegate * aTimerDelegate, TimerCompleteCallback aCallback) :
+ mTimerDelegate(aTimerDelegate), mCallback(aCallback)
+ {
+ VerifyOrDie(aReadHandler != nullptr);
+ VerifyOrDie(aTimerDelegate != nullptr);
+ VerifyOrDie(aCallback != 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
+ {
+ // TODO: Add flags to allow for test to simulate waiting for the min interval or max intrval to elapse when integrating
+ // the scheduler in the ReadHandler
+ Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp();
+ return (mReadHandler->IsGeneratingReports() &&
+ ((now >= mMinTimestamp && mReadHandler->IsDirty()) || now >= mMaxTimestamp));
+ }
+
+ 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);
+ }
+
+ void RunCallback() { mCallback(); }
+
+ Timestamp GetMinTimestamp() const { return mMinTimestamp; }
+ Timestamp GetMaxTimestamp() const { return mMaxTimestamp; }
+
+ private:
+ TimerDelegate * mTimerDelegate;
+ TimerCompleteCallback mCallback;
+ ReadHandler * mReadHandler;
+ Timestamp mMinTimestamp;
+ Timestamp mMaxTimestamp;
+ };
+
+ ReportScheduler(TimerDelegate * aTimerDelegate) : mTimerDelegate(aTimerDelegate) {}
+ /**
+ * Interface to act on changes in the ReadHandler reportability
+ */
+ virtual ~ReportScheduler() = default;
+
+ /// @brief Check if a ReadHandler is scheduled for reporting
+ virtual bool IsReportScheduled(ReadHandler * aReadHandler) = 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->IsGeneratingReports() && aReadHandler->IsDirty();
+ }
+
+ /// @brief Get the number of ReadHandlers registered in the scheduler's node pool
+ size_t GetNumReadHandlers() const { return mNodesPool.Allocated(); }
+
+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)
+ {
+ for (auto & iter : mReadHandlerList)
+ {
+ if (iter.GetReadHandler() == aReadHandler)
+ {
+ return &iter;
+ }
+ }
+ return nullptr;
+ }
+
+ IntrusiveList<ReadHandlerNode> mReadHandlerList;
+ ObjectPool<ReadHandlerNode, CHIP_IM_MAX_NUM_READS + CHIP_IM_MAX_NUM_SUBSCRIPTIONS> mNodesPool;
+ TimerDelegate * mTimerDelegate;
+};
+}; // namespace reporting
+}; // namespace app
+}; // namespace chip
diff --git a/src/app/reporting/ReportSchedulerImpl.cpp b/src/app/reporting/ReportSchedulerImpl.cpp
new file mode 100644
index 0000000..4b45ab9
--- /dev/null
+++ b/src/app/reporting/ReportSchedulerImpl.cpp
@@ -0,0 +1,188 @@
+/*
+ *
+ * Copyright (c) 2023 Project CHIP Authors
+ *
+ * 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.
+ */
+
+#include <app/InteractionModelEngine.h>
+#include <app/reporting/ReportSchedulerImpl.h>
+
+namespace chip {
+namespace app {
+namespace reporting {
+
+using Seconds16 = System::Clock::Seconds16;
+using Milliseconds32 = System::Clock::Milliseconds32;
+using Timeout = System::Clock::Timeout;
+using Timestamp = System::Clock::Timestamp;
+using ReadHandlerNode = ReportScheduler::ReadHandlerNode;
+
+/// @brief Callback called when the report timer expires to schedule an engine run regardless of the state of the ReadHandlers, as
+/// the engine already verifies that read handlers are reportable before sending a report
+static void ReportTimerCallback()
+{
+ InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun();
+}
+
+ReportSchedulerImpl::ReportSchedulerImpl(TimerDelegate * aTimerDelegate) : ReportScheduler(aTimerDelegate)
+{
+ VerifyOrDie(nullptr != mTimerDelegate);
+}
+
+/// @brief When a ReadHandler is added, register it, which will schedule an engine run
+void ReportSchedulerImpl::OnReadHandlerCreated(ReadHandler * aReadHandler)
+{
+ RegisterReadHandler(aReadHandler);
+}
+
+/// @brief When a ReadHandler becomes reportable, schedule, verifies if the min interval of a handleris elapsed. If not,
+/// reschedule the report to happen when the min interval is elapsed. If it is, schedule an engine run.
+void ReportSchedulerImpl::OnBecameReportable(ReadHandler * aReadHandler)
+{
+ ReadHandlerNode * node = FindReadHandlerNode(aReadHandler);
+ VerifyOrReturn(nullptr != node);
+
+ Milliseconds32 newTimeout;
+ if (node->IsReportableNow())
+ {
+ // If the handler is reportable now, just schedule a report immediately
+ newTimeout = Milliseconds32(0);
+ }
+ else
+ {
+ // If the handler is not reportable now, schedule a report for the min interval
+ newTimeout = node->GetMinTimestamp() - mTimerDelegate->GetCurrentMonotonicTimestamp();
+ }
+
+ ScheduleReport(newTimeout, node);
+}
+
+void ReportSchedulerImpl::OnSubscriptionAction(ReadHandler * apReadHandler)
+{
+ ReadHandlerNode * node = FindReadHandlerNode(apReadHandler);
+ VerifyOrReturn(nullptr != node);
+ // Schedule callback for max interval by computing the difference between the max timestamp and the current timestamp
+ node->SetIntervalTimeStamps(apReadHandler);
+ Milliseconds32 newTimeout = node->GetMaxTimestamp() - mTimerDelegate->GetCurrentMonotonicTimestamp();
+ ScheduleReport(newTimeout, node);
+}
+
+/// @brief When a ReadHandler is removed, unregister it, which will cancel any scheduled report
+void ReportSchedulerImpl::OnReadHandlerDestroyed(ReadHandler * aReadHandler)
+{
+ UnregisterReadHandler(aReadHandler);
+}
+
+CHIP_ERROR ReportSchedulerImpl::RegisterReadHandler(ReadHandler * aReadHandler)
+{
+ ReadHandlerNode * newNode = FindReadHandlerNode(aReadHandler);
+ // Handler must not be registered yet; it's just being constructed.
+ VerifyOrDie(nullptr == newNode);
+ // The NodePool is the same size as the ReadHandler pool from the IM Engine, so we don't need a check for size here since if a
+ // ReadHandler was created, space should be available.
+ newNode = mNodesPool.CreateObject(aReadHandler, mTimerDelegate, ReportTimerCallback);
+ mReadHandlerList.PushBack(newNode);
+
+ ChipLogProgress(DataManagement,
+ "Registered a ReadHandler that will schedule a report between system Timestamp: %" PRIu64
+ " and system Timestamp %" PRIu64 ".",
+ newNode->GetMinTimestamp().count(), newNode->GetMaxTimestamp().count());
+
+ Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp();
+ Milliseconds32 newTimeout;
+ // If the handler is reportable, schedule a report for the min interval, otherwise schedule a report for the max interval
+ if (newNode->IsReportableNow())
+ {
+ // If the handler is reportable now, just schedule a report immediately
+ newTimeout = Milliseconds32(0);
+ }
+ else if (IsReadHandlerReportable(aReadHandler) && (newNode->GetMinTimestamp() > now))
+ {
+ // If the handler is reportable now, but the min interval is not elapsed, schedule a report for the moment the min interval
+ // has elapsed
+ newTimeout = newNode->GetMinTimestamp() - now;
+ }
+ else
+ {
+ // If the handler is not reportable now, schedule a report for the max interval
+ newTimeout = newNode->GetMaxTimestamp() - now;
+ }
+
+ ReturnErrorOnFailure(ScheduleReport(newTimeout, newNode));
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ReportSchedulerImpl::ScheduleReport(Timeout timeout, ReadHandlerNode * node)
+{
+ // Cancel Report if it is currently scheduled
+ CancelSchedulerTimer(node);
+ StartSchedulerTimer(node, timeout);
+
+ return CHIP_NO_ERROR;
+}
+
+void ReportSchedulerImpl::CancelReport(ReadHandler * aReadHandler)
+{
+ ReadHandlerNode * node = FindReadHandlerNode(aReadHandler);
+ VerifyOrReturn(nullptr != node);
+ CancelSchedulerTimer(node);
+}
+
+void ReportSchedulerImpl::UnregisterReadHandler(ReadHandler * aReadHandler)
+{
+ CancelReport(aReadHandler);
+
+ ReadHandlerNode * removeNode = FindReadHandlerNode(aReadHandler);
+ // Nothing to remove if the handler is not found in the list
+ VerifyOrReturn(nullptr != removeNode);
+
+ mReadHandlerList.Remove(removeNode);
+ mNodesPool.ReleaseObject(removeNode);
+}
+
+void ReportSchedulerImpl::UnregisterAllHandlers()
+{
+ while (!mReadHandlerList.Empty())
+ {
+ ReadHandler * firstReadHandler = mReadHandlerList.begin()->GetReadHandler();
+ UnregisterReadHandler(firstReadHandler);
+ }
+}
+
+bool ReportSchedulerImpl::IsReportScheduled(ReadHandler * aReadHandler)
+{
+ ReadHandlerNode * node = FindReadHandlerNode(aReadHandler);
+ VerifyOrReturnValue(nullptr != node, false);
+ return CheckSchedulerTimerActive(node);
+}
+
+CHIP_ERROR ReportSchedulerImpl::StartSchedulerTimer(ReadHandlerNode * node, System::Clock::Timeout aTimeout)
+{
+ // Schedule Report
+ return mTimerDelegate->StartTimer(node, aTimeout);
+}
+
+void ReportSchedulerImpl::CancelSchedulerTimer(ReadHandlerNode * node)
+{
+ mTimerDelegate->CancelTimer(node);
+}
+
+bool ReportSchedulerImpl::CheckSchedulerTimerActive(ReadHandlerNode * node)
+{
+ return mTimerDelegate->IsTimerActive(node);
+}
+
+} // namespace reporting
+} // namespace app
+} // namespace chip
diff --git a/src/app/reporting/ReportSchedulerImpl.h b/src/app/reporting/ReportSchedulerImpl.h
new file mode 100644
index 0000000..849f9b7
--- /dev/null
+++ b/src/app/reporting/ReportSchedulerImpl.h
@@ -0,0 +1,63 @@
+/*
+ *
+ * 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/reporting/ReportScheduler.h>
+
+namespace chip {
+namespace app {
+namespace reporting {
+
+class ReportSchedulerImpl : public ReportScheduler
+{
+public:
+ ReportSchedulerImpl(TimerDelegate * aTimerDelegate);
+ ~ReportSchedulerImpl() override { UnregisterAllHandlers(); }
+
+ // ReadHandlerObserver
+ void OnReadHandlerCreated(ReadHandler * aReadHandler) override;
+ void OnBecameReportable(ReadHandler * aReadHandler) override;
+ void OnSubscriptionAction(ReadHandler * aReadHandler) override;
+ void OnReadHandlerDestroyed(ReadHandler * aReadHandler) override;
+
+protected:
+ virtual CHIP_ERROR RegisterReadHandler(ReadHandler * aReadHandler);
+ virtual CHIP_ERROR ScheduleReport(System::Clock::Timeout timeout, ReadHandlerNode * node);
+ virtual void CancelReport(ReadHandler * aReadHandler);
+ virtual void UnregisterReadHandler(ReadHandler * aReadHandler);
+ virtual void UnregisterAllHandlers();
+
+private:
+ friend class chip::app::reporting::TestReportScheduler;
+
+ bool IsReportScheduled(ReadHandler * aReadHandler) override;
+
+ /// @brief Start a timer for a given ReadHandlerNode, ensures that if a timer is already running for this node, it is cancelled
+ /// @param node Node of the ReadHandler list to start a timer for
+ /// @param aTimeout Delay before the timer expires
+ virtual CHIP_ERROR StartSchedulerTimer(ReadHandlerNode * node, System::Clock::Timeout aTimeout);
+ /// @brief Cancel the timer for a given ReadHandlerNode
+ virtual void CancelSchedulerTimer(ReadHandlerNode * node);
+ /// @brief Check if the timer for a given ReadHandlerNode is active
+ virtual bool CheckSchedulerTimerActive(ReadHandlerNode * node);
+};
+
+} // namespace reporting
+} // namespace app
+} // namespace chip
diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn
index c716f44..365596d 100644
--- a/src/app/tests/BUILD.gn
+++ b/src/app/tests/BUILD.gn
@@ -144,6 +144,7 @@
"TestOperationalStateDelegate.cpp",
"TestPendingNotificationMap.cpp",
"TestReadInteraction.cpp",
+ "TestReportScheduler.cpp",
"TestReportingEngine.cpp",
"TestSceneTable.cpp",
"TestStatusIB.cpp",
diff --git a/src/app/tests/TestReportScheduler.cpp b/src/app/tests/TestReportScheduler.cpp
new file mode 100644
index 0000000..a244220
--- /dev/null
+++ b/src/app/tests/TestReportScheduler.cpp
@@ -0,0 +1,423 @@
+/*
+ *
+ * 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.
+ */
+
+#include <app/InteractionModelEngine.h>
+#include <app/reporting/ReportSchedulerImpl.h>
+#include <app/tests/AppTestContext.h>
+#include <lib/support/UnitTestContext.h>
+#include <lib/support/UnitTestRegistration.h>
+#include <lib/support/logging/CHIPLogging.h>
+#include <nlunit-test.h>
+
+namespace {
+
+class TestContext : public chip::Test::AppContext
+{
+public:
+ static int Initialize(void * context)
+ {
+ if (AppContext::Initialize(context) != SUCCESS)
+ return FAILURE;
+
+ auto * ctx = static_cast<TestContext *>(context);
+
+ if (ctx->mEventCounter.Init(0) != CHIP_NO_ERROR)
+ {
+ return FAILURE;
+ }
+
+ return SUCCESS;
+ }
+
+ static int Finalize(void * context)
+ {
+ chip::app::EventManagement::DestroyEventManagement();
+
+ if (AppContext::Finalize(context) != SUCCESS)
+ return FAILURE;
+
+ return SUCCESS;
+ }
+
+private:
+ chip::MonotonicallyIncreasingCounter<chip::EventNumber> mEventCounter;
+};
+
+class NullReadHandlerCallback : public chip::app::ReadHandler::ManagementCallback
+{
+public:
+ void OnDone(chip::app::ReadHandler & apReadHandlerObj) override {}
+ chip::app::ReadHandler::ApplicationCallback * GetAppCallback() override { return nullptr; }
+};
+
+} // namespace
+
+namespace chip {
+namespace app {
+namespace reporting {
+
+using InteractionModelEngine = InteractionModelEngine;
+using ReportScheduler = reporting::ReportScheduler;
+using ReportSchedulerImpl = reporting::ReportSchedulerImpl;
+using ReadHandlerNode = reporting::ReportScheduler::ReadHandlerNode;
+using Milliseconds64 = System::Clock::Milliseconds64;
+
+static const size_t kNumMaxReadHandlers = 16;
+
+class TestTimerDelegate : public ReportScheduler::TimerDelegate
+{
+public:
+ struct NodeTimeoutPair
+ {
+ ReadHandlerNode * node;
+ System::Clock::Timeout timeout;
+ };
+
+ NodeTimeoutPair mPairArray[kNumMaxReadHandlers];
+ size_t mPairArraySize = 0;
+ System::Clock::Timestamp mMockSystemTimestamp = System::Clock::Milliseconds64(0);
+
+ NodeTimeoutPair * FindPair(ReadHandlerNode * node, size_t & position)
+ {
+ for (size_t i = 0; i < mPairArraySize; i++)
+ {
+ if (mPairArray[i].node == node)
+ {
+ position = i;
+ return &mPairArray[i];
+ }
+ }
+ return nullptr;
+ }
+
+ CHIP_ERROR insertPair(ReadHandlerNode * node, System::Clock::Timeout timeout)
+ {
+ VerifyOrReturnError(mPairArraySize < kNumMaxReadHandlers, CHIP_ERROR_NO_MEMORY);
+ mPairArray[mPairArraySize].node = node;
+ mPairArray[mPairArraySize].timeout = timeout;
+ mPairArraySize++;
+
+ return CHIP_NO_ERROR;
+ }
+
+ void removePair(ReadHandlerNode * node)
+ {
+ size_t position;
+ NodeTimeoutPair * pair = FindPair(node, position);
+ VerifyOrReturn(pair != nullptr);
+
+ size_t nextPos = static_cast<size_t>(position + 1);
+ size_t moveNum = static_cast<size_t>(mPairArraySize - nextPos);
+
+ // Compress array after removal, if the removed position is not the last
+ if (moveNum)
+ {
+ memmove(&mPairArray[position], &mPairArray[nextPos], sizeof(NodeTimeoutPair) * moveNum);
+ }
+
+ mPairArraySize--;
+ }
+
+ static void TimerCallbackInterface(System::Layer * aLayer, void * aAppState)
+ {
+ // Normaly we would call the callback here, thus scheduling an engine run, but we don't need it for this test as we simulate
+ // all the callbacks related to report emissions. The actual callback should look like this:
+ //
+ // ReadHandlerNode * node = static_cast<ReadHandlerNode *>(aAppState);
+ // node->RunCallback();
+ ChipLogProgress(DataManagement, "Simluating engine run for Handler: %p", aAppState);
+ }
+ virtual CHIP_ERROR StartTimer(void * context, System::Clock::Timeout aTimeout) override
+ {
+ return insertPair(static_cast<ReadHandlerNode *>(context), aTimeout + mMockSystemTimestamp);
+ }
+ virtual void CancelTimer(void * context) override { removePair(static_cast<ReadHandlerNode *>(context)); }
+ virtual bool IsTimerActive(void * context) override
+ {
+ size_t position;
+ NodeTimeoutPair * pair = FindPair(static_cast<ReadHandlerNode *>(context), position);
+ VerifyOrReturnValue(pair != nullptr, false);
+
+ return pair->timeout > mMockSystemTimestamp;
+ }
+
+ virtual System::Clock::Timestamp GetCurrentMonotonicTimestamp() override { return mMockSystemTimestamp; }
+
+ void SetMockSystemTimestamp(System::Clock::Timestamp aMockTimestamp) { mMockSystemTimestamp = aMockTimestamp; }
+
+ // Increment the mock timestamp one milisecond at a time for a total of aTime miliseconds. Checks if
+ void IncrementMockTimestamp(System::Clock::Milliseconds64 aTime)
+ {
+ mMockSystemTimestamp = mMockSystemTimestamp + aTime;
+ for (size_t i = 0; i < mPairArraySize; i++)
+ {
+ if (mPairArray[i].timeout <= mMockSystemTimestamp)
+ {
+ TimerCallbackInterface(nullptr, mPairArray[i].node);
+ }
+ }
+ }
+};
+
+TestTimerDelegate sTestTimerDelegate;
+ReportSchedulerImpl sScheduler(&sTestTimerDelegate);
+
+class TestReportScheduler
+{
+public:
+ static void TestReadHandlerList(nlTestSuite * aSuite, void * aContext)
+ {
+ TestContext & ctx = *static_cast<TestContext *>(aContext);
+ NullReadHandlerCallback nullCallback;
+ // exchange context
+ Messaging::ExchangeContext * exchangeCtx = ctx.NewExchangeToAlice(nullptr, false);
+
+ // Read handler pool
+ ObjectPool<ReadHandler, kNumMaxReadHandlers> readHandlerPool;
+
+ // Initialize mock timestamp
+ sTestTimerDelegate.SetMockSystemTimestamp(Milliseconds64(0));
+
+ for (size_t i = 0; i < kNumMaxReadHandlers; i++)
+ {
+ ReadHandler * readHandler =
+ readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe);
+ NL_TEST_ASSERT(aSuite, nullptr != readHandler);
+ VerifyOrReturn(nullptr != readHandler);
+ NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sScheduler.RegisterReadHandler(readHandler));
+ NL_TEST_ASSERT(aSuite, nullptr != sScheduler.FindReadHandlerNode(readHandler));
+ }
+
+ NL_TEST_ASSERT(aSuite, readHandlerPool.Allocated() == kNumMaxReadHandlers);
+ NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == kNumMaxReadHandlers);
+ NL_TEST_ASSERT(aSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 1);
+
+ // Test unregister first ReadHandler
+ ReadHandler * firstReadHandler = sScheduler.mReadHandlerList.begin()->GetReadHandler();
+ sScheduler.UnregisterReadHandler(firstReadHandler);
+ NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == kNumMaxReadHandlers - 1);
+ NL_TEST_ASSERT(aSuite, nullptr == sScheduler.FindReadHandlerNode(firstReadHandler));
+
+ // Test unregister middle ReadHandler
+ auto iter = sScheduler.mReadHandlerList.begin();
+ for (size_t i = 0; i < static_cast<size_t>(kNumMaxReadHandlers / 2); i++)
+ {
+ iter++;
+ }
+ ReadHandler * middleReadHandler = iter->GetReadHandler();
+ sScheduler.UnregisterReadHandler(middleReadHandler);
+ NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == kNumMaxReadHandlers - 2);
+ NL_TEST_ASSERT(aSuite, nullptr == sScheduler.FindReadHandlerNode(middleReadHandler));
+
+ // Test unregister last ReadHandler
+ iter = sScheduler.mReadHandlerList.end();
+ iter--;
+ ReadHandler * lastReadHandler = iter->GetReadHandler();
+ sScheduler.UnregisterReadHandler(lastReadHandler);
+ NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == kNumMaxReadHandlers - 3);
+ NL_TEST_ASSERT(aSuite, nullptr == sScheduler.FindReadHandlerNode(lastReadHandler));
+
+ sScheduler.UnregisterAllHandlers();
+ // Confirm all ReadHandlers are unregistered from the scheduler
+ NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == 0);
+ readHandlerPool.ForEachActiveObject([&](ReadHandler * handler) {
+ NL_TEST_ASSERT(aSuite, nullptr == sScheduler.FindReadHandlerNode(handler));
+ return Loop::Continue;
+ });
+
+ readHandlerPool.ReleaseAll();
+ exchangeCtx->Close();
+ NL_TEST_ASSERT(aSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+ }
+
+ static void TestReportTiming(nlTestSuite * aSuite, void * aContext)
+ {
+ TestContext & ctx = *static_cast<TestContext *>(aContext);
+ NullReadHandlerCallback nullCallback;
+ // exchange context
+ Messaging::ExchangeContext * exchangeCtx = ctx.NewExchangeToAlice(nullptr, false);
+
+ // Read handler pool
+ ObjectPool<ReadHandler, kNumMaxReadHandlers> readHandlerPool;
+
+ // Initialize mock timestamp
+ sTestTimerDelegate.SetMockSystemTimestamp(Milliseconds64(0));
+
+ // Dirty read handler, will be triggered at min interval
+ ReadHandler * readHandler1 =
+ readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe);
+ NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler1->SetMaxReportingInterval(2));
+ NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler1->SetMinReportingIntervalForTests(1));
+ // Do those manually to avoid scheduling an engine run
+ readHandler1->mFlags.Set(ReadHandler::ReadHandlerFlags::ForceDirty, true);
+ readHandler1->mState = ReadHandler::HandlerState::GeneratingReports;
+
+ // Clean read handler, will be triggered at max interval
+ ReadHandler * readHandler2 =
+ readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe);
+ NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler2->SetMaxReportingInterval(3));
+ NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler2->SetMinReportingIntervalForTests(0));
+ // Do those manually to avoid scheduling an engine run
+ readHandler2->mState = ReadHandler::HandlerState::GeneratingReports;
+
+ // Clean read handler, will be triggered at max interval, but will be cancelled before
+ ReadHandler * readHandler3 =
+ readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe);
+ NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler3->SetMaxReportingInterval(3));
+ NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler3->SetMinReportingIntervalForTests(0));
+ // Do those manually to avoid scheduling an engine run
+ readHandler3->mState = ReadHandler::HandlerState::GeneratingReports;
+
+ NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sScheduler.RegisterReadHandler(readHandler1));
+ NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sScheduler.RegisterReadHandler(readHandler2));
+ NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sScheduler.RegisterReadHandler(readHandler3));
+
+ // Confirms that none of the ReadHandlers are currently reportable
+ NL_TEST_ASSERT(aSuite, !sScheduler.IsReportableNow(readHandler1));
+ NL_TEST_ASSERT(aSuite, !sScheduler.IsReportableNow(readHandler2));
+ NL_TEST_ASSERT(aSuite, !sScheduler.IsReportableNow(readHandler3));
+
+ // Simulate system clock increment
+ sTestTimerDelegate.IncrementMockTimestamp(Milliseconds64(1100));
+
+ // Checks that the first ReadHandler is reportable after 1 second since it is dirty and min interval has expired
+ NL_TEST_ASSERT(aSuite, sScheduler.IsReportableNow(readHandler1));
+ NL_TEST_ASSERT(aSuite, !sScheduler.IsReportableNow(readHandler2));
+ NL_TEST_ASSERT(aSuite, !sScheduler.IsReportableNow(readHandler3));
+
+ NL_TEST_ASSERT(aSuite, sScheduler.IsReportScheduled(readHandler3));
+ sScheduler.CancelReport(readHandler3);
+ NL_TEST_ASSERT(aSuite, !sScheduler.IsReportScheduled(readHandler3));
+
+ // Simulate system clock increment
+ sTestTimerDelegate.IncrementMockTimestamp(Milliseconds64(2000));
+
+ // Checks that all ReadHandlers are reportable
+ NL_TEST_ASSERT(aSuite, sScheduler.IsReportableNow(readHandler1));
+ NL_TEST_ASSERT(aSuite, sScheduler.IsReportableNow(readHandler2));
+ // Even if its timer got cancelled, readHandler3 should still be considered reportable as the max interval has expired
+ // and it is in generating report state
+ NL_TEST_ASSERT(aSuite, sScheduler.IsReportableNow(readHandler3));
+
+ sScheduler.UnregisterAllHandlers();
+ readHandlerPool.ReleaseAll();
+ exchangeCtx->Close();
+ NL_TEST_ASSERT(aSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+ }
+
+ static void TestObserverCallbacks(nlTestSuite * aSuite, void * aContext)
+ {
+ TestContext & ctx = *static_cast<TestContext *>(aContext);
+ NullReadHandlerCallback nullCallback;
+ // exchange context
+ Messaging::ExchangeContext * exchangeCtx = ctx.NewExchangeToAlice(nullptr, false);
+
+ // Read handler pool
+ ObjectPool<ReadHandler, kNumMaxReadHandlers> readHandlerPool;
+
+ // Initialize mock timestamp
+ sTestTimerDelegate.SetMockSystemTimestamp(Milliseconds64(0));
+
+ ReadHandler * readHandler =
+ readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe);
+ NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler->SetMaxReportingInterval(2));
+ NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler->SetMinReportingIntervalForTests(1));
+ // Do those manually to avoid scheduling an engine run
+ readHandler->mState = ReadHandler::HandlerState::GeneratingReports;
+ readHandler->SetObserver(&sScheduler);
+
+ // Test OnReadHandlerCreated
+ readHandler->mObserver->OnReadHandlerCreated(readHandler);
+ // Should have registered the read handler in the scheduler and scheduled a report
+ NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == 1);
+ NL_TEST_ASSERT(aSuite, sScheduler.IsReportScheduled(readHandler));
+ ReadHandlerNode * node = sScheduler.FindReadHandlerNode(readHandler);
+ NL_TEST_ASSERT(aSuite, nullptr != node);
+ VerifyOrReturn(nullptr != node);
+ NL_TEST_ASSERT(aSuite, node->GetReadHandler() == readHandler);
+
+ // Test OnBecameReportable
+ readHandler->mFlags.Set(ReadHandler::ReadHandlerFlags::ForceDirty, true);
+ readHandler->mObserver->OnBecameReportable(readHandler);
+ // Should have changed the scheduled timeout to the handler's min interval, to check, we wait for the min interval to
+ // expire
+ // Simulate system clock increment
+ sTestTimerDelegate.IncrementMockTimestamp(Milliseconds64(1100));
+
+ // Check that no report is scheduled since the min interval has expired, the timer should now be stopped
+ NL_TEST_ASSERT(aSuite, !sScheduler.IsReportScheduled(readHandler));
+
+ // Test OnSubscriptionAction
+ readHandler->mFlags.Set(ReadHandler::ReadHandlerFlags::ForceDirty, false);
+ readHandler->mObserver->OnSubscriptionAction(readHandler);
+ // Should have changed the scheduled timeout to the handlers max interval, to check, we wait for the min interval to
+ // confirm it is not expired yet so the report should still be scheduled
+
+ NL_TEST_ASSERT(aSuite, sScheduler.IsReportScheduled(readHandler));
+ // Simulate system clock increment
+ sTestTimerDelegate.IncrementMockTimestamp(Milliseconds64(1100));
+
+ // Check that the report is still scheduled as the max interval has not expired yet and the dirty flag was cleared
+ NL_TEST_ASSERT(aSuite, sScheduler.IsReportScheduled(readHandler));
+ // Simulate system clock increment
+ sTestTimerDelegate.IncrementMockTimestamp(Milliseconds64(2100));
+
+ // Check that no report is scheduled since the max interval should have expired, the timer should now be stopped
+ NL_TEST_ASSERT(aSuite, !sScheduler.IsReportScheduled(readHandler));
+
+ // Test OnReadHandlerDestroyed
+ readHandler->mObserver->OnReadHandlerDestroyed(readHandler);
+ // Should have unregistered the read handler in the scheduler and cancelled the report
+ NL_TEST_ASSERT(aSuite, !sScheduler.IsReportScheduled(readHandler));
+ NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == 0);
+ NL_TEST_ASSERT(aSuite, nullptr == sScheduler.FindReadHandlerNode(readHandler));
+
+ sScheduler.UnregisterReadHandler(readHandler);
+ readHandlerPool.ReleaseAll();
+ exchangeCtx->Close();
+ NL_TEST_ASSERT(aSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+ }
+};
+
+} // namespace reporting
+} // namespace app
+} // namespace chip
+
+namespace {
+
+/**
+ * Test Suite. It lists all the test functions.
+ */
+
+static nlTest sTests[] = {
+ NL_TEST_DEF("TestReadHandlerList", chip::app::reporting::TestReportScheduler::TestReadHandlerList),
+ NL_TEST_DEF("TestReportTiming", chip::app::reporting::TestReportScheduler::TestReportTiming),
+ NL_TEST_DEF("TestObserverCallbacks", chip::app::reporting::TestReportScheduler::TestObserverCallbacks),
+ NL_TEST_SENTINEL(),
+};
+
+nlTestSuite sSuite = { "TestReportScheduler", &sTests[0], TestContext::Initialize, TestContext::Finalize };
+
+} // namespace
+
+int TestReportScheduler()
+{
+ return chip::ExecuteTestsWithContext<TestContext>(&sSuite);
+}
+
+CHIP_REGISTER_TEST_SUITE(TestReportScheduler);
diff --git a/src/controller/tests/data_model/TestRead.cpp b/src/controller/tests/data_model/TestRead.cpp
index 4bf9475..5c59c6c 100644
--- a/src/controller/tests/data_model/TestRead.cpp
+++ b/src/controller/tests/data_model/TestRead.cpp
@@ -302,7 +302,7 @@
if (mAlterSubscriptionIntervals)
{
- ReturnErrorOnFailure(aReadHandler.SetReportingIntervals(mMaxInterval));
+ ReturnErrorOnFailure(aReadHandler.SetMaxReportingInterval(mMaxInterval));
}
return CHIP_NO_ERROR;
}
diff --git a/src/platform/telink/ICDUtil.cpp b/src/platform/telink/ICDUtil.cpp
index fd2130c..b3dc9c8 100644
--- a/src/platform/telink/ICDUtil.cpp
+++ b/src/platform/telink/ICDUtil.cpp
@@ -36,5 +36,5 @@
agreedMaxInterval = kSubscriptionMaxIntervalPublisherLimit;
}
- return aReadHandler.SetReportingIntervals(agreedMaxInterval);
+ return aReadHandler.SetMaxReportingInterval(agreedMaxInterval);
}