blob: de4f145c4b220122f4cdcec6060108c8b54dcca5 [file]
/*
* Copyright (c) 2026 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/clusters/ambient-context-sensing-server/AmbientContextSensingCluster.h>
#include <app/clusters/ambient-context-sensing-server/ambient-context-sensing-namespace.h>
#include <app/persistence/AttributePersistence.h>
#include <pw_unit_test/framework.h>
#include <app/server-cluster/AttributeListBuilder.h>
#include <app/server-cluster/testing/AttributeTesting.h>
#include <app/server-cluster/testing/ClusterTester.h>
#include <app/server-cluster/testing/TestEventGenerator.h>
#include <app/server-cluster/testing/TestServerClusterContext.h>
#include <app/server-cluster/testing/ValidateGlobalAttributes.h>
#include <clusters/AmbientContextSensing/Attributes.h>
#include <clusters/AmbientContextSensing/Metadata.h>
#include <lib/support/TimerDelegateMock.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::AmbientContextSensing;
using namespace chip::Testing;
using chip::app::DataModel::DecodableList;
using TagDecodable = Globals::Structs::SemanticTagStruct::DecodableType;
using AmbientContextSensingTypeDecodable = AmbientContextSensing::Structs::AmbientContextTypeStruct::DecodableType;
using PredictedActivityDecodable = AmbientContextSensing::Structs::PredictedActivityStruct::DecodableType;
namespace {
constexpr EndpointId kTestEndpointId = 1;
constexpr BitFlags<Feature> kFeaturesAllForACSTest{ Feature::kHumanActivity, Feature::kObjectCounting,
Feature::kObjectIdentification, Feature::kSoundIdentification,
Feature::kPredictedActivity };
static SemanticTagType g_kACTSupportedArray[] = {
{
.namespaceID = kNamespaceIdentifiedHumanActivity,
.tag = static_cast<uint8_t>(TagIdentifiedHumanActivity::kFall),
},
{
.namespaceID = kNamespaceIdentifiedHumanActivity,
.tag = static_cast<uint8_t>(TagIdentifiedHumanActivity::kSleeping),
},
{
.namespaceID = kNamespaceIdentifiedObject,
.tag = static_cast<uint8_t>(TagIdentifiedObject::kAdult),
},
{
.namespaceID = kNamespaceIdentifiedObject,
.tag = static_cast<uint8_t>(TagIdentifiedObject::kChild),
},
{
.namespaceID = kNamespaceIdentifiedSound,
.tag = static_cast<uint8_t>(TagIdentifiedSound::kObjectFall),
},
{
.namespaceID = kNamespaceIdentifiedSound,
.tag = static_cast<uint8_t>(TagIdentifiedSound::kSnoring),
},
};
static SemanticTagType g_kSemanticTagDetectArray[] = {
{
.namespaceID = kNamespaceIdentifiedHumanActivity,
.tag = static_cast<uint8_t>(TagIdentifiedHumanActivity::kFall),
},
{
.namespaceID = kNamespaceIdentifiedObject,
.tag = static_cast<uint8_t>(TagIdentifiedObject::kAdult),
},
{
.namespaceID = kNamespaceIdentifiedObject,
.tag = static_cast<uint8_t>(TagIdentifiedObject::kChild),
},
{
.namespaceID = kNamespaceIdentifiedSound,
.tag = static_cast<uint8_t>(TagIdentifiedSound::kObjectFall),
},
};
static AmbientContextSensingType g_kACTDetectArray[] = {
{
.ambientContextSensed = DataModel::List<const SemanticTagType>(&g_kSemanticTagDetectArray[0], 1),
},
{
.ambientContextSensed = DataModel::List<const SemanticTagType>(&g_kSemanticTagDetectArray[1], 1),
},
{
.ambientContextSensed = DataModel::List<const SemanticTagType>(&g_kSemanticTagDetectArray[2], 1),
},
{
.ambientContextSensed = DataModel::List<const SemanticTagType>(&g_kSemanticTagDetectArray[3], 1),
},
};
class TestACSDelegate : public AmbientContextSensingDelegate
{
public:
TestACSDelegate();
~TestACSDelegate() = default;
SemanticTagType * GetAmbientContextTypeSupportedBuf(size_t size) override;
CHIP_ERROR SetPredictedActivity(const Span<PredictedActivityType> & predictedActivityList) override;
PredictActivity * GetPredictedActivityBuf() override { return mPredictActivityBuf; };
AmbientContextSensed * AllocDetection() override;
CHIP_ERROR DelDetection(AmbientContextSensed * pitem) override;
uint64_t GetEpochNow() override;
TestACSDelegate(const TestACSDelegate &) = delete;
TestACSDelegate & operator=(const TestACSDelegate &) = delete;
TestACSDelegate(TestACSDelegate &&) = delete;
TestACSDelegate & operator=(TestACSDelegate &&) = delete;
static constexpr uint8_t kMaxACTypeSupportedForTest = 10;
static constexpr uint8_t kMaxPredictedActivityForTest = 3;
private:
SemanticTagType mAmbientContextTypeSupportedBuf[kMaxACTypeSupportedForTest];
PredictActivity mPredictActivityBuf[kMaxPredictedActivityForTest];
std::unique_ptr<AmbientContextSensed> mAmbientContextTypeList[kMaxSimultaneousDetectionLimit];
};
TestACSDelegate::TestACSDelegate() : mAmbientContextTypeSupportedBuf{}, mPredictActivityBuf{} {}
SemanticTagType * TestACSDelegate::GetAmbientContextTypeSupportedBuf(size_t size)
{
VerifyOrReturnError(size <= kMaxACTypeSupportedForTest, nullptr);
return mAmbientContextTypeSupportedBuf;
}
CHIP_ERROR TestACSDelegate::SetPredictedActivity(const Span<PredictedActivityType> & predictedActivityList)
{
constexpr size_t kPredictActivityBufSize = sizeof(mPredictActivityBuf) / sizeof(mPredictActivityBuf[0]);
VerifyOrReturnError(predictedActivityList.size() <= kPredictActivityBufSize, CHIP_ERROR_INVALID_ARGUMENT);
// Copy the input predicted activity to local array
for (size_t i = 0; i < predictedActivityList.size(); i++)
{
const auto & src = predictedActivityList[i];
auto & dst = mPredictActivityBuf[i];
dst.mInfo = src;
if (!src.ambientContextType.HasValue())
{
dst.mInfo.ambientContextType.ClearValue();
continue;
}
// Copy tags
const auto & acTypeList = src.ambientContextType.Value();
const auto tagCount = acTypeList.size();
VerifyOrReturnError(tagCount <= kMaxPredictedACType, CHIP_ERROR_INVALID_ARGUMENT);
dst.mOwnedTags = std::make_unique<SemanticTagType[]>(tagCount);
for (size_t t = 0; t < tagCount; t++)
{
dst.mOwnedTags[t] = acTypeList[t];
}
dst.mInfo.ambientContextType.SetValue(
DataModel::List<const SemanticTagType>(dst.mOwnedTags.get(), static_cast<uint16_t>(tagCount)));
}
return CHIP_NO_ERROR;
}
AmbientContextSensed * TestACSDelegate::AllocDetection()
{
for (uint8_t id = 0; id < kMaxSimultaneousDetectionLimit; id++)
{
if (mAmbientContextTypeList[id] == nullptr)
{
auto ptr = std::make_unique<AmbientContextSensed>();
ptr->id = id;
auto raw = ptr.get();
mAmbientContextTypeList[id] = std::move(ptr);
return raw;
}
}
return nullptr;
}
CHIP_ERROR TestACSDelegate::DelDetection(AmbientContextSensed * pitem)
{
VerifyOrReturnError(pitem != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
const uint8_t id = pitem->id;
VerifyOrReturnError(id < kMaxSimultaneousDetectionLimit, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError((mAmbientContextTypeList[id] != nullptr) && (mAmbientContextTypeList[id].get() == pitem),
CHIP_ERROR_INVALID_ARGUMENT);
mAmbientContextTypeList[id] = nullptr;
return CHIP_NO_ERROR;
}
uint64_t TestACSDelegate::GetEpochNow()
{
using namespace chip::System::Clock;
Milliseconds64 timestamp_ms(0);
CHIP_ERROR err = System::SystemClock().GetClock_RealTimeMS(timestamp_ms);
return (err == CHIP_NO_ERROR) ? (timestamp_ms.count()) : (0);
}
struct TestAmbientContextSensingCluster : public ::testing::Test
{
static void SetUpTestSuite() { ASSERT_EQ(Platform::MemoryInit(), CHIP_NO_ERROR); }
static void TearDownTestSuite() { Platform::MemoryShutdown(); }
TimerDelegateMock mMockTimerDelegate;
};
TEST_F(TestAmbientContextSensingCluster, TestAttributes)
{
chip::Testing::TestServerClusterContext context;
static TestACSDelegate testACSDelegate;
AmbientContextSensingCluster cluster{ kTestEndpointId,
AmbientContextSensingCluster::Config{ mMockTimerDelegate }.WithFeatures(
BitMask<AmbientContextSensing::Feature>(kFeaturesAllForACSTest)) };
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
cluster.SetDelegate(&testACSDelegate);
chip::Testing::ClusterTester tester(cluster);
// Read the revision
uint16_t clusterRevision;
EXPECT_EQ(tester.ReadAttribute(Attributes::ClusterRevision::Id, clusterRevision), CHIP_NO_ERROR);
EXPECT_EQ(clusterRevision, AmbientContextSensing::kRevision);
// Read the feature map
uint32_t featureVal;
EXPECT_EQ(tester.ReadAttribute(Attributes::FeatureMap::Id, featureVal), CHIP_NO_ERROR);
BitMask<AmbientContextSensing::Feature> featureMap = featureVal;
EXPECT_TRUE(featureMap.Has(Feature::kHumanActivity));
EXPECT_TRUE(featureMap.Has(Feature::kObjectCounting));
EXPECT_TRUE(featureMap.Has(Feature::kObjectIdentification));
EXPECT_TRUE(featureMap.Has(Feature::kSoundIdentification));
EXPECT_TRUE(featureMap.Has(Feature::kPredictedActivity));
// Read the attributes
bool eventDetected;
EXPECT_EQ(tester.ReadAttribute(Attributes::HumanActivityDetected::Id, eventDetected), CHIP_NO_ERROR);
EXPECT_FALSE(eventDetected);
EXPECT_EQ(tester.ReadAttribute(Attributes::ObjectIdentified::Id, eventDetected), CHIP_NO_ERROR);
EXPECT_FALSE(eventDetected);
EXPECT_EQ(tester.ReadAttribute(Attributes::AudioContextDetected::Id, eventDetected), CHIP_NO_ERROR);
EXPECT_FALSE(eventDetected);
ASSERT_TRUE(Testing::IsAttributesListEqualTo(cluster,
{
Attributes::HumanActivityDetected::kMetadataEntry,
Attributes::ObjectIdentified::kMetadataEntry,
Attributes::AudioContextDetected::kMetadataEntry,
Attributes::AmbientContextType::kMetadataEntry,
Attributes::AmbientContextTypeSupported::kMetadataEntry,
Attributes::ObjectCountThresholdReached::kMetadataEntry,
Attributes::ObjectCountConfig::kMetadataEntry,
Attributes::SimultaneousDetectionLimit::kMetadataEntry,
Attributes::HoldTime::kMetadataEntry,
Attributes::HoldTimeLimits::kMetadataEntry,
Attributes::PredictedActivity::kMetadataEntry,
}));
{ // Read AmbientContextTypeSupported attribute, whose type is: list[SemanticTagStruct]
DecodableList<TagDecodable> out;
EXPECT_EQ(tester.ReadAttribute(Attributes::AmbientContextTypeSupported::Id, out), CHIP_NO_ERROR);
size_t size = 0;
EXPECT_EQ(out.ComputeSize(&size), CHIP_NO_ERROR);
EXPECT_EQ(size, 0u);
}
bool objectCountReached;
EXPECT_EQ(tester.ReadAttribute(Attributes::ObjectCountThresholdReached::Id, objectCountReached), CHIP_NO_ERROR);
EXPECT_FALSE(objectCountReached);
uint16_t objectCount;
EXPECT_EQ(tester.ReadAttribute(Attributes::ObjectCount::Id, objectCount),
Protocols::InteractionModel::Status::UnsupportedAttribute);
uint8_t sDetectLimit;
EXPECT_EQ(tester.ReadAttribute(Attributes::SimultaneousDetectionLimit::Id, sDetectLimit), CHIP_NO_ERROR);
EXPECT_EQ(sDetectLimit, kDefaultSimultaneousDetectionLimit);
uint16_t holdTime;
EXPECT_EQ(tester.ReadAttribute(Attributes::HoldTime::Id, holdTime), CHIP_NO_ERROR);
EXPECT_EQ(holdTime, kDefaultHoldTimeDefault);
AmbientContextSensing::Structs::HoldTimeLimitsStruct::Type holdTimeLimit;
EXPECT_EQ(tester.ReadAttribute(Attributes::HoldTimeLimits::Id, holdTimeLimit), CHIP_NO_ERROR);
EXPECT_EQ(holdTimeLimit.holdTimeMin, kDefaultHoldTimeMin);
EXPECT_EQ(holdTimeLimit.holdTimeMax, kDefaultHoldTimeMax);
EXPECT_EQ(holdTimeLimit.holdTimeDefault, kDefaultHoldTimeDefault);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
TEST_F(TestAmbientContextSensingCluster, TestAmbientContextTypeSupported)
{
chip::Testing::TestServerClusterContext context;
constexpr BitFlags<Feature> kFeatures(Feature::kHumanActivity, Feature::kObjectCounting, Feature::kObjectIdentification,
Feature::kPredictedActivity);
SemanticTagType kACTSupported_all[] = {
{
.namespaceID = kNamespaceIdentifiedHumanActivity,
.tag = static_cast<uint8_t>(TagIdentifiedHumanActivity::kFall),
},
{
.namespaceID = kNamespaceIdentifiedHumanActivity,
.tag = static_cast<uint8_t>(TagIdentifiedHumanActivity::kSleeping),
},
{
.namespaceID = kNamespaceIdentifiedObject,
.tag = static_cast<uint8_t>(TagIdentifiedObject::kAdult),
},
{
.namespaceID = kNamespaceIdentifiedObject,
.tag = static_cast<uint8_t>(TagIdentifiedObject::kChild),
},
// Duplicated one
{
.namespaceID = kNamespaceIdentifiedObject,
.tag = static_cast<uint8_t>(TagIdentifiedObject::kAdult),
},
// Not enabled in feature-map
{
.namespaceID = kNamespaceIdentifiedSound,
.tag = static_cast<uint8_t>(TagIdentifiedSound::kObjectFall),
},
{
.namespaceID = kNamespaceIdentifiedSound,
.tag = static_cast<uint8_t>(TagIdentifiedSound::kSnoring),
},
};
SemanticTagType kACTSupported_dup[] = {
kACTSupported_all[0],
kACTSupported_all[1],
kACTSupported_all[2],
kACTSupported_all[3],
// the duplicate one
kACTSupported_all[3],
};
SemanticTagType kACTSupported_unsupport[] = {
kACTSupported_all[0],
kACTSupported_all[1],
kACTSupported_all[2],
kACTSupported_all[3],
// the unsupported ones
kACTSupported_all[4],
kACTSupported_all[5],
};
SemanticTagType kACTSupported[] = {
kACTSupported_all[0],
kACTSupported_all[1],
kACTSupported_all[2],
kACTSupported_all[3],
};
static TestACSDelegate testACSDelegate;
AmbientContextSensingCluster cluster{ kTestEndpointId,
AmbientContextSensingCluster::Config{ mMockTimerDelegate }.WithFeatures(
BitMask<AmbientContextSensing::Feature>(kFeatures)) };
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
cluster.SetDelegate(&testACSDelegate);
Span<SemanticTagType> ACTypeList_support = Span<SemanticTagType>(kACTSupported_dup, MATTER_ARRAY_SIZE(kACTSupported_dup));
EXPECT_EQ(cluster.SetAmbientContextTypeSupported(ACTypeList_support), CHIP_ERROR_INVALID_ARGUMENT);
chip::Testing::ClusterTester tester(cluster);
// Read the AmbientContextTypeSupported.
DecodableList<TagDecodable> out;
EXPECT_EQ(tester.ReadAttribute(Attributes::AmbientContextTypeSupported::Id, out), CHIP_NO_ERROR);
size_t size = 0;
EXPECT_EQ(out.ComputeSize(&size), CHIP_NO_ERROR);
// Exp: No supported type is saved, since there are errors in kACTSupported_dup
EXPECT_EQ(size, 0u);
// Push the unsupported one
Span<SemanticTagType> ACTypeList_unsupport =
Span<SemanticTagType>(kACTSupported_unsupport, MATTER_ARRAY_SIZE(kACTSupported_unsupport));
EXPECT_EQ(cluster.SetAmbientContextTypeSupported(ACTypeList_unsupport), CHIP_ERROR_INVALID_ARGUMENT);
EXPECT_EQ(tester.ReadAttribute(Attributes::AmbientContextTypeSupported::Id, out), CHIP_NO_ERROR);
EXPECT_EQ(out.ComputeSize(&size), CHIP_NO_ERROR);
EXPECT_EQ(size, 0u);
// Push the correct one
Span<SemanticTagType> ACTypeList = Span<SemanticTagType>(kACTSupported, MATTER_ARRAY_SIZE(kACTSupported));
EXPECT_EQ(cluster.SetAmbientContextTypeSupported(ACTypeList), CHIP_NO_ERROR);
EXPECT_EQ(tester.ReadAttribute(Attributes::AmbientContextTypeSupported::Id, out), CHIP_NO_ERROR);
EXPECT_EQ(out.ComputeSize(&size), CHIP_NO_ERROR);
EXPECT_EQ(size, 4u);
// Set the unsupported event. Exp: Discarded
{
AmbientContextSensingType detectEvent;
std::vector<SemanticTagType> tags;
tags.push_back(SemanticTagType{
.namespaceID = kNamespaceIdentifiedSound,
.tag = static_cast<uint8_t>(TagIdentifiedSound::kObjectFall),
});
detectEvent.ambientContextSensed = chip::app::DataModel::List<const SemanticTagType>(tags.data(), tags.size());
EXPECT_EQ(cluster.AddDetection(detectEvent), CHIP_ERROR_INCORRECT_STATE);
}
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
TEST_F(TestAmbientContextSensingCluster, TestHoldTimeAttribute)
{
chip::Testing::TestServerClusterContext context;
AmbientContextSensing::Structs::HoldTimeLimitsStruct::Type holdTimeLimitsConfig = { .holdTimeMin = 10,
.holdTimeMax = 200,
.holdTimeDefault = 100 };
static TestACSDelegate testACSDelegate;
AmbientContextSensingCluster cluster{ kTestEndpointId,
AmbientContextSensingCluster::Config{ mMockTimerDelegate }
.WithHoldTime(100, holdTimeLimitsConfig)
.WithFeatures(BitMask<AmbientContextSensing::Feature>(kFeaturesAllForACSTest)) };
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
cluster.SetDelegate(&testACSDelegate);
chip::Testing::ClusterTester tester(cluster);
// Verify that we can set a valid hold time via direct method call
EXPECT_EQ(cluster.SetHoldTime(150), Protocols::InteractionModel::Status::Success);
uint16_t holdTime;
EXPECT_EQ(tester.ReadAttribute(Attributes::HoldTime::Id, holdTime), CHIP_NO_ERROR);
EXPECT_EQ(holdTime, 150);
EXPECT_EQ(cluster.GetHoldTime(), 150);
// Verify that setting the same value returns CHIP_NO_ERROR
EXPECT_EQ(cluster.SetHoldTime(150), DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp);
// Verify that we cannot set a hold time less than the minimum
EXPECT_EQ(cluster.SetHoldTime(5), Protocols::InteractionModel::Status::ConstraintError);
// Verify that we cannot set a hold time greater than the maximum
EXPECT_EQ(cluster.SetHoldTime(250), Protocols::InteractionModel::Status::ConstraintError);
// Verify that we can write a valid hold time via WriteAttribute
EXPECT_EQ(tester.WriteAttribute(Attributes::HoldTime::Id, static_cast<uint16_t>(180)), CHIP_NO_ERROR);
EXPECT_EQ(tester.ReadAttribute(Attributes::HoldTime::Id, holdTime), CHIP_NO_ERROR);
EXPECT_EQ(holdTime, 180);
EXPECT_EQ(cluster.GetHoldTime(), 180);
// Verify that we cannot write a hold time less than the minimum via WriteAttribute
EXPECT_EQ(tester.WriteAttribute(Attributes::HoldTime::Id, static_cast<uint16_t>(5)),
Protocols::InteractionModel::Status::ConstraintError);
// Verify that we cannot write a hold time greater than the maximum via WriteAttribute
EXPECT_EQ(tester.WriteAttribute(Attributes::HoldTime::Id, static_cast<uint16_t>(250)),
Protocols::InteractionModel::Status::ConstraintError);
// Give the new HoldTimeLimitation whose range is different. The HoldTime should be reset
holdTimeLimitsConfig.holdTimeMin = 201;
holdTimeLimitsConfig.holdTimeMax = 300;
holdTimeLimitsConfig.holdTimeDefault = 250;
cluster.SetHoldTimeLimits(holdTimeLimitsConfig);
EXPECT_EQ(cluster.GetHoldTime(), holdTimeLimitsConfig.holdTimeDefault);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
TEST_F(TestAmbientContextSensingCluster, TestHoldTimePersistence)
{
chip::Testing::TestServerClusterContext context;
constexpr uint16_t kDefaultHoldTime = 100;
constexpr uint16_t kNewHoldTime = 150;
AmbientContextSensing::Structs::HoldTimeLimitsStruct::Type holdTimeLimitsConfig = { .holdTimeMin = 10,
.holdTimeMax = 200,
.holdTimeDefault = kDefaultHoldTime };
// 1. Create a cluster. On startup, it should store the default hold time.
{
static TestACSDelegate testACSDelegate;
AmbientContextSensingCluster cluster{ kTestEndpointId,
AmbientContextSensingCluster::Config{ mMockTimerDelegate }
.WithHoldTime(kDefaultHoldTime, holdTimeLimitsConfig)
.WithFeatures(BitMask<AmbientContextSensing::Feature>(kFeaturesAllForACSTest)) };
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
cluster.SetDelegate(&testACSDelegate);
chip::Testing::ClusterTester tester(cluster);
uint16_t holdTime = 0;
EXPECT_EQ(tester.ReadAttribute(Attributes::HoldTime::Id, holdTime), CHIP_NO_ERROR);
EXPECT_EQ(holdTime, kDefaultHoldTime);
EXPECT_EQ(cluster.GetHoldTime(), kDefaultHoldTime);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// 2. Write a new value to the attribute. This should update the value in persistence.
{
static TestACSDelegate testACSDelegate;
AmbientContextSensingCluster cluster{ kTestEndpointId,
AmbientContextSensingCluster::Config{ mMockTimerDelegate }
.WithHoldTime(kDefaultHoldTime, holdTimeLimitsConfig)
.WithFeatures(BitMask<AmbientContextSensing::Feature>(kFeaturesAllForACSTest)) };
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR); // Startup will load the default value again
cluster.SetDelegate(&testACSDelegate);
chip::Testing::ClusterTester tester(cluster);
EXPECT_EQ(tester.WriteAttribute(Attributes::HoldTime::Id, kNewHoldTime), CHIP_NO_ERROR);
EXPECT_EQ(cluster.GetHoldTime(), kNewHoldTime);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// 3. Create a new cluster instance. It should load the new value from persistence on startup.
{
static TestACSDelegate testACSDelegate;
AmbientContextSensingCluster cluster{ kTestEndpointId,
AmbientContextSensingCluster::Config{ mMockTimerDelegate }
.WithHoldTime(kDefaultHoldTime, holdTimeLimitsConfig)
.WithFeatures(BitMask<AmbientContextSensing::Feature>(kFeaturesAllForACSTest)) };
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
cluster.SetDelegate(&testACSDelegate);
chip::Testing::ClusterTester tester(cluster);
uint16_t holdTime = 0;
EXPECT_EQ(tester.ReadAttribute(Attributes::HoldTime::Id, holdTime), CHIP_NO_ERROR);
EXPECT_EQ(holdTime, kNewHoldTime);
EXPECT_EQ(cluster.GetHoldTime(), kNewHoldTime);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
}
TEST_F(TestAmbientContextSensingCluster, TestAmbientContextDetect)
{
chip::Testing::TestServerClusterContext context;
AmbientContextSensing::Structs::HoldTimeLimitsStruct::Type holdTimeLimitsConfig = { .holdTimeMin = 10,
.holdTimeMax = 200,
.holdTimeDefault = 100 };
static TestACSDelegate testACSDelegate;
AmbientContextSensingCluster cluster{ kTestEndpointId,
AmbientContextSensingCluster::Config{ mMockTimerDelegate }
.WithFeatures(BitMask<AmbientContextSensing::Feature>(kFeaturesAllForACSTest))
.WithHoldTime(kDefaultHoldTimeDefault, holdTimeLimitsConfig) };
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
cluster.SetDelegate(&testACSDelegate);
Span<SemanticTagType> ACTypeList_support = Span<SemanticTagType>(g_kACTSupportedArray, MATTER_ARRAY_SIZE(g_kACTSupportedArray));
EXPECT_EQ(cluster.SetAmbientContextTypeSupported(ACTypeList_support), CHIP_NO_ERROR);
chip::Testing::ClusterTester tester(cluster);
// Read the AmbientContextTypeSupported.
{
DecodableList<TagDecodable> out;
EXPECT_EQ(tester.ReadAttribute(Attributes::AmbientContextTypeSupported::Id, out), CHIP_NO_ERROR);
size_t size = 0;
EXPECT_EQ(out.ComputeSize(&size), CHIP_NO_ERROR);
EXPECT_EQ(size, 6u);
}
// Read the detection
bool isDetected;
EXPECT_EQ(tester.ReadAttribute(Attributes::HumanActivityDetected::Id, isDetected), CHIP_NO_ERROR);
EXPECT_FALSE(isDetected);
EXPECT_EQ(tester.ReadAttribute(Attributes::ObjectIdentified::Id, isDetected), CHIP_NO_ERROR);
EXPECT_FALSE(isDetected);
EXPECT_EQ(tester.ReadAttribute(Attributes::AudioContextDetected::Id, isDetected), CHIP_NO_ERROR);
EXPECT_FALSE(isDetected);
// Add a detection event
{
AmbientContextSensingType detectEvent;
std::vector<SemanticTagType> tags;
tags.push_back(SemanticTagType{
.namespaceID = kNamespaceIdentifiedHumanActivity,
.tag = static_cast<uint8_t>(TagIdentifiedHumanActivity::kFall),
});
detectEvent.ambientContextSensed = chip::app::DataModel::List<const SemanticTagType>(tags.data(), tags.size());
EXPECT_EQ(cluster.AddDetection(detectEvent), CHIP_NO_ERROR);
}
// Check if it's detected. Expect: Detected, & timer running
EXPECT_EQ(tester.ReadAttribute(Attributes::HumanActivityDetected::Id, isDetected), CHIP_NO_ERROR);
EXPECT_TRUE(isDetected);
EXPECT_TRUE(mMockTimerDelegate.IsTimerActive(&cluster));
// Fetch the detection info
{
DecodableList<AmbientContextSensingTypeDecodable> out;
EXPECT_EQ(tester.ReadAttribute(Attributes::AmbientContextType::Id, out), CHIP_NO_ERROR);
size_t size = 0;
EXPECT_EQ(out.ComputeSize(&size), CHIP_NO_ERROR);
EXPECT_EQ(size, 1u);
auto it = out.begin();
while (it.Next())
{
const auto & ACType = it.GetValue().ambientContextSensed;
size_t tagCount;
EXPECT_EQ(ACType.ComputeSize(&tagCount), CHIP_NO_ERROR);
EXPECT_EQ(tagCount, 1u);
auto it_e = ACType.begin();
while (it_e.Next())
{
const auto & tag = it_e.GetValue();
EXPECT_TRUE((tag.namespaceID == kNamespaceIdentifiedHumanActivity) &&
(tag.tag == static_cast<uint8_t>(TagIdentifiedHumanActivity::kFall)));
}
}
}
// Advance clock by more than the hold time
mMockTimerDelegate.AdvanceClock(System::Clock::Seconds16(kDefaultHoldTimeDefault + 1));
// Check again. Expect: Not-detected
EXPECT_EQ(tester.ReadAttribute(Attributes::HumanActivityDetected::Id, isDetected), CHIP_NO_ERROR);
EXPECT_FALSE(isDetected);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
TEST_F(TestAmbientContextSensingCluster, TestSimultaneousDetectLimit)
{
chip::Testing::TestServerClusterContext context;
AmbientContextSensing::Structs::HoldTimeLimitsStruct::Type holdTimeLimitsConfig = { .holdTimeMin = 10,
.holdTimeMax = 200,
.holdTimeDefault = 100 };
static TestACSDelegate testACSDelegate;
AmbientContextSensingCluster cluster{ kTestEndpointId,
AmbientContextSensingCluster::Config{ mMockTimerDelegate }
.WithFeatures(BitMask<AmbientContextSensing::Feature>(kFeaturesAllForACSTest))
.WithHoldTime(kDefaultHoldTimeDefault, holdTimeLimitsConfig) };
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
cluster.SetDelegate(&testACSDelegate);
Span<SemanticTagType> ACTypeList_support = Span<SemanticTagType>(g_kACTSupportedArray, MATTER_ARRAY_SIZE(g_kACTSupportedArray));
EXPECT_EQ(cluster.SetAmbientContextTypeSupported(ACTypeList_support), CHIP_NO_ERROR);
chip::Testing::ClusterTester tester(cluster);
// Read SimultaneousDetectionLimit attribute: Exp: kDefaultSimultaneousDetectionLimit;
bool isDetected;
uint8_t simultaneousDetectLimit;
EXPECT_EQ(tester.ReadAttribute(Attributes::SimultaneousDetectionLimit::Id, simultaneousDetectLimit), CHIP_NO_ERROR);
EXPECT_EQ(simultaneousDetectLimit, kDefaultSimultaneousDetectionLimit);
// Write a bigger SimultaneousDetectionLimit
uint8_t newSDL = simultaneousDetectLimit + 1;
EXPECT_EQ(cluster.SetSimultaneousDetectionLimit(newSDL), CHIP_NO_ERROR);
// Add Detection
EXPECT_EQ(cluster.AddDetection(g_kACTDetectArray[0]), CHIP_NO_ERROR);
mMockTimerDelegate.AdvanceClock(System::Clock::Seconds16(1));
EXPECT_EQ(cluster.AddDetection(g_kACTDetectArray[1]), CHIP_NO_ERROR);
mMockTimerDelegate.AdvanceClock(System::Clock::Seconds16(1));
EXPECT_EQ(cluster.AddDetection(g_kACTDetectArray[2]), CHIP_NO_ERROR);
EXPECT_EQ(tester.ReadAttribute(Attributes::HumanActivityDetected::Id, isDetected), CHIP_NO_ERROR);
EXPECT_TRUE(isDetected);
EXPECT_EQ(tester.ReadAttribute(Attributes::ObjectIdentified::Id, isDetected), CHIP_NO_ERROR);
EXPECT_TRUE(isDetected);
constexpr uint8_t kTestSimultaneousDetectionLimit = 2;
// Reduce the simultaneous detection limitation to the value which is less than the current detection number
EXPECT_EQ(cluster.SetSimultaneousDetectionLimit(kTestSimultaneousDetectionLimit), CHIP_NO_ERROR);
// Re-write it again. Exp: kWriteSuccessNoOp
EXPECT_EQ(cluster.SetSimultaneousDetectionLimit(kTestSimultaneousDetectionLimit),
DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp);
// Read back the simultaneous detection limitation, exp: kTestSimultaneousDetectionLimit
EXPECT_EQ(tester.ReadAttribute(Attributes::SimultaneousDetectionLimit::Id, simultaneousDetectLimit), CHIP_NO_ERROR);
EXPECT_EQ(simultaneousDetectLimit, kTestSimultaneousDetectionLimit);
// Check the AmbientContextType. exp: the 1st one is removed
EXPECT_EQ(tester.ReadAttribute(Attributes::HumanActivityDetected::Id, isDetected), CHIP_NO_ERROR);
EXPECT_FALSE(isDetected);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
TEST_F(TestAmbientContextSensingCluster, TestObjectCount)
{
constexpr uint16_t kTestObjCntThreshold = 10;
constexpr uint16_t kTestObjectCount = 11;
chip::Testing::TestServerClusterContext context;
AmbientContextSensing::Structs::HoldTimeLimitsStruct::Type holdTimeLimitsConfig = {
.holdTimeMin = 10, .holdTimeMax = 200, .holdTimeDefault = kDefaultHoldTimeDefault
};
static TestACSDelegate testACSDelegate;
AmbientContextSensingCluster cluster{ kTestEndpointId,
AmbientContextSensingCluster::Config{ mMockTimerDelegate }
.WithFeatures(BitMask<AmbientContextSensing::Feature>(kFeaturesAllForACSTest))
.WithOptionalAttributes(0xfff)
.WithHoldTime(kDefaultHoldTimeDefault, holdTimeLimitsConfig) };
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
cluster.SetDelegate(&testACSDelegate);
chip::Testing::ClusterTester tester(cluster);
// Set the ObjectCountConfig attribute
ObjectCountConfigType defObjCountCfg = {
.countingObject = { .namespaceID = kNamespaceIdentifiedObject, .tag = static_cast<uint8_t>(TagIdentifiedObject::kAdult) },
.objectCountThreshold = kTestObjCntThreshold,
};
// Expect error, since support-list has not been set.
EXPECT_EQ(cluster.SetObjectCountConfig(defObjCountCfg), Protocols::InteractionModel::Status::ConstraintError);
// Set the supported list
Span<SemanticTagType> ACTypeList = Span<SemanticTagType>(g_kACTSupportedArray, MATTER_ARRAY_SIZE(g_kACTSupportedArray));
EXPECT_EQ(cluster.SetAmbientContextTypeSupported(ACTypeList), CHIP_NO_ERROR);
EXPECT_EQ(tester.WriteAttribute(Attributes::ObjectCountConfig::Id, defObjCountCfg),
Protocols::InteractionModel::Status::Success);
// Read back the ObjectCountConfig attribute and check if it's the new value
ObjectCountConfigType rdObjCountCfg;
EXPECT_EQ(tester.ReadAttribute(Attributes::ObjectCountConfig::Id, rdObjCountCfg), Protocols::InteractionModel::Status::Success);
EXPECT_EQ(rdObjCountCfg.countingObject.namespaceID, defObjCountCfg.countingObject.namespaceID);
EXPECT_EQ(rdObjCountCfg.countingObject.tag, defObjCountCfg.countingObject.tag);
EXPECT_EQ(rdObjCountCfg.objectCountThreshold, defObjCountCfg.objectCountThreshold);
bool objCntReached;
uint16_t objCount;
// Read ObjectCountThresholdReached attribute, exp: false
EXPECT_EQ(tester.ReadAttribute(Attributes::ObjectCountThresholdReached::Id, objCntReached),
Protocols::InteractionModel::Status::Success);
EXPECT_FALSE(objCntReached);
// Set ObjectCount attribute which is smaller than ObjectCountThreshold
EXPECT_EQ(cluster.SetObjectCount(kMinObjectCount), CHIP_NO_ERROR);
// Read ObjectCountThresholdReached attribute, exp: false
EXPECT_EQ(tester.ReadAttribute(Attributes::ObjectCountThresholdReached::Id, objCntReached),
Protocols::InteractionModel::Status::Success);
EXPECT_FALSE(objCntReached);
// Set ObjectCount attribute which is bigger than ObjectCountThreshold
EXPECT_EQ(cluster.SetObjectCount(kTestObjectCount), CHIP_NO_ERROR);
EXPECT_TRUE(mMockTimerDelegate.IsTimerActive(&cluster));
// Read ObjectCountThresholdReached attribute, exp: true
EXPECT_EQ(tester.ReadAttribute(Attributes::ObjectCountThresholdReached::Id, objCntReached),
Protocols::InteractionModel::Status::Success);
EXPECT_TRUE(objCntReached);
// Read ObjectCount attribute, exp: the value to be set
EXPECT_EQ(tester.ReadAttribute(Attributes::ObjectCount::Id, objCount), Protocols::InteractionModel::Status::Success);
EXPECT_EQ(objCount, kTestObjectCount);
// Advance clock by more than the hold time
mMockTimerDelegate.AdvanceClock(System::Clock::Seconds16(kDefaultHoldTimeDefault + 1));
// Read ObjectCountThresholdReached attribute, exp: false
EXPECT_EQ(tester.ReadAttribute(Attributes::ObjectCountThresholdReached::Id, objCntReached),
Protocols::InteractionModel::Status::Success);
EXPECT_FALSE(objCntReached);
// Read ObjectCount attribute, exp: ObjectCount is resetted to 0
EXPECT_EQ(tester.ReadAttribute(Attributes::ObjectCount::Id, objCount), Protocols::InteractionModel::Status::Success);
EXPECT_EQ(objCount, 0);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
TEST_F(TestAmbientContextSensingCluster, TestObjCntThresholdPersistence)
{
constexpr uint16_t kTestObjCntThreshold = 10;
chip::Testing::TestServerClusterContext context;
// 1. Create a cluster. Read the ObjectCountConfig. ObjectCountThreshold == kDefaultCountThreshold
{
AmbientContextSensing::Structs::HoldTimeLimitsStruct::Type holdTimeLimitsConfig = {
.holdTimeMin = 10, .holdTimeMax = 200, .holdTimeDefault = kDefaultHoldTimeDefault
};
static TestACSDelegate testACSDelegate;
AmbientContextSensingCluster cluster{ kTestEndpointId,
AmbientContextSensingCluster::Config{ mMockTimerDelegate }
.WithFeatures(BitMask<AmbientContextSensing::Feature>(kFeaturesAllForACSTest))
.WithHoldTime(kDefaultHoldTimeDefault, holdTimeLimitsConfig) };
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
cluster.SetDelegate(&testACSDelegate);
chip::Testing::ClusterTester tester(cluster);
// Set the supported list
Span<SemanticTagType> ACTypeList = Span<SemanticTagType>(g_kACTSupportedArray, MATTER_ARRAY_SIZE(g_kACTSupportedArray));
EXPECT_EQ(cluster.SetAmbientContextTypeSupported(ACTypeList), CHIP_NO_ERROR);
// Set the ObjectCountConfig attribute
ObjectCountConfigType defObjCountCfg = {
.countingObject = { .namespaceID = kNamespaceIdentifiedObject,
.tag = static_cast<uint8_t>(TagIdentifiedObject::kAdult) },
.objectCountThreshold = kTestObjCntThreshold,
};
// Read back the ObjectCountConfig attribute. ObjectCountConfig.objectCountThreshold should be the default value
ObjectCountConfigType rdObjCountCfg;
EXPECT_EQ(tester.ReadAttribute(Attributes::ObjectCountConfig::Id, rdObjCountCfg),
Protocols::InteractionModel::Status::Success);
EXPECT_EQ(rdObjCountCfg.objectCountThreshold, kDefaultCountThreshold);
// Write the new ObjectCountConfig attribute
EXPECT_EQ(tester.WriteAttribute(Attributes::ObjectCountConfig::Id, defObjCountCfg),
Protocols::InteractionModel::Status::Success);
// Read back the ObjectCountConfig attribute. ObjectCountConfig.objectCountThreshold should be the new value
EXPECT_EQ(tester.ReadAttribute(Attributes::ObjectCountConfig::Id, rdObjCountCfg),
Protocols::InteractionModel::Status::Success);
EXPECT_EQ(rdObjCountCfg.objectCountThreshold, defObjCountCfg.objectCountThreshold);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// 2. Create a new cluster and read the ObjectCountConfig. ObjectCountThreshold should be the new value which is loaded from the
// persistence storage
{
AmbientContextSensing::Structs::HoldTimeLimitsStruct::Type holdTimeLimitsConfig = {
.holdTimeMin = 10, .holdTimeMax = 200, .holdTimeDefault = kDefaultHoldTimeDefault
};
static TestACSDelegate testACSDelegate;
AmbientContextSensingCluster cluster{ kTestEndpointId,
AmbientContextSensingCluster::Config{ mMockTimerDelegate }
.WithFeatures(BitMask<AmbientContextSensing::Feature>(kFeaturesAllForACSTest))
.WithHoldTime(kDefaultHoldTimeDefault, holdTimeLimitsConfig) };
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
cluster.SetDelegate(&testACSDelegate);
chip::Testing::ClusterTester tester(cluster);
// Set the supported list
Span<SemanticTagType> ACTypeList = Span<SemanticTagType>(g_kACTSupportedArray, MATTER_ARRAY_SIZE(g_kACTSupportedArray));
EXPECT_EQ(cluster.SetAmbientContextTypeSupported(ACTypeList), CHIP_NO_ERROR);
ObjectCountConfigType rdObjCountCfg;
EXPECT_EQ(tester.ReadAttribute(Attributes::ObjectCountConfig::Id, rdObjCountCfg),
Protocols::InteractionModel::Status::Success);
EXPECT_EQ(rdObjCountCfg.objectCountThreshold, kTestObjCntThreshold);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
}
TEST_F(TestAmbientContextSensingCluster, TestPredictActivity)
{
chip::Testing::TestServerClusterContext context;
static TestACSDelegate testACSDelegate;
AmbientContextSensingCluster cluster{ kTestEndpointId,
AmbientContextSensingCluster::Config{ mMockTimerDelegate }.WithFeatures(
BitMask<AmbientContextSensing::Feature>(kFeaturesAllForACSTest)) };
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
cluster.SetDelegate(&testACSDelegate);
chip::Testing::ClusterTester tester(cluster);
const uint32_t now =
std::chrono::duration_cast<System::Clock::Seconds16>(mMockTimerDelegate.GetCurrentMonotonicTimestamp()).count();
const DataModel::List<const SemanticTagType> kACTList(g_kSemanticTagDetectArray, std::size(g_kSemanticTagDetectArray));
PredictedActivityType predictActivityData[] = {
{
.startTimestamp = now + 10,
.endTimestamp = now + 20,
.ambientContextType = MakeOptional(kACTList),
.crowdDetected = MakeOptional(false),
.crowdCount = MakeOptional(uint8_t(1)),
.confidence = 100,
},
};
Span<PredictedActivityType> predictActivity =
Span<PredictedActivityType>(predictActivityData, MATTER_ARRAY_SIZE(predictActivityData));
EXPECT_EQ(cluster.SetPredictedActivity(predictActivity), CHIP_NO_ERROR);
// Readback PredictedActivity
DecodableList<PredictedActivityDecodable> out;
EXPECT_EQ(tester.ReadAttribute(Attributes::PredictedActivity::Id, out), CHIP_NO_ERROR);
size_t size = 0;
EXPECT_EQ(out.ComputeSize(&size), CHIP_NO_ERROR);
EXPECT_EQ(size, 1u);
auto it = out.begin();
while (it.Next())
{
const auto & ACTypeList = it.GetValue().ambientContextType.Value();
auto it_e = ACTypeList.begin();
auto i = 0;
while (it_e.Next())
{
const auto & ACType = it_e.GetValue();
EXPECT_TRUE((ACType.namespaceID == g_kSemanticTagDetectArray[i].namespaceID) &&
(ACType.tag == g_kSemanticTagDetectArray[i].tag));
i++;
}
EXPECT_EQ(it.GetValue().startTimestamp, (now + 10));
EXPECT_EQ(it.GetValue().endTimestamp, (now + 20));
EXPECT_EQ(it.GetValue().crowdDetected.Value(), false);
EXPECT_EQ(it.GetValue().crowdCount.Value(), 1);
EXPECT_EQ(it.GetValue().confidence, 100);
}
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
} // namespace