blob: 82c33e201a4cdb91a292bee2322c0ce883da39ae [file]
/**
* Copyright (c) 2026 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 <pw_unit_test/framework.h>
#include <app/clusters/energy-preference-server/EnergyPreferenceCluster.h>
#include <app/server-cluster/testing/AttributeTesting.h>
#include <app/server-cluster/testing/ClusterTester.h>
#include <app/server-cluster/testing/TestServerClusterContext.h>
#include <app/server-cluster/testing/ValidateGlobalAttributes.h>
#include <clusters/EnergyPreference/Metadata.h>
namespace {
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::EnergyPreference;
using namespace chip::app::Clusters::EnergyPreference::Attributes;
using namespace chip::app::Clusters::EnergyPreference::Structs;
using namespace chip::Testing;
struct TestEnergyPreferenceDelegate : public Delegate
{
inline static BalanceStruct::Type energyBalances[] = {
{ .step = 0, .label = Optional<chip::CharSpan>("Efficient"_span) },
{ .step = 50, .label = Optional<chip::CharSpan>() },
{ .step = 100, .label = Optional<chip::CharSpan>("Comfort"_span) },
};
inline static BalanceStruct::Type powerBalances[] = {
{ .step = 0, .label = Optional<chip::CharSpan>("1 Minute"_span) },
{ .step = 24, .label = Optional<chip::CharSpan>("10 Minutes"_span) },
{ .step = 48, .label = Optional<chip::CharSpan>("40 Minutes"_span) },
{ .step = 70, .label = Optional<chip::CharSpan>("120 Minutes"_span) },
{ .step = 100, .label = Optional<chip::CharSpan>("Never"_span) },
};
CHIP_ERROR GetEnergyBalanceAtIndex(chip::EndpointId aEndpoint, size_t aIndex, chip::Percent & aOutStep,
chip::Optional<chip::MutableCharSpan> & aOutLabel) override
{
if (aIndex < GetNumEnergyBalances(aEndpoint))
{
aOutStep = energyBalances[aIndex].step;
if (energyBalances[aIndex].label.HasValue())
{
TEMPORARY_RETURN_IGNORED chip::CopyCharSpanToMutableCharSpan(energyBalances[aIndex].label.Value(),
aOutLabel.Value());
}
else
{
aOutLabel.ClearValue();
}
return CHIP_NO_ERROR;
}
return CHIP_ERROR_NOT_FOUND;
}
CHIP_ERROR GetEnergyPriorityAtIndex(chip::EndpointId aEndpoint, size_t aIndex, EnergyPriorityEnum & priority) override
{
static EnergyPriorityEnum priorities[] = { EnergyPriorityEnum::kEfficiency, EnergyPriorityEnum::kComfort };
if (aIndex < MATTER_ARRAY_SIZE(priorities))
{
priority = priorities[aIndex];
return CHIP_NO_ERROR;
}
return CHIP_ERROR_NOT_FOUND;
}
CHIP_ERROR GetLowPowerModeSensitivityAtIndex(chip::EndpointId aEndpoint, size_t aIndex, chip::Percent & aOutStep,
chip::Optional<chip::MutableCharSpan> & aOutLabel) override
{
if (aIndex < GetNumLowPowerModeSensitivities(aEndpoint))
{
aOutStep = powerBalances[aIndex].step;
if (powerBalances[aIndex].label.HasValue())
{
TEMPORARY_RETURN_IGNORED chip::CopyCharSpanToMutableCharSpan(powerBalances[aIndex].label.Value(),
aOutLabel.Value());
}
else
{
aOutLabel.ClearValue();
}
return CHIP_NO_ERROR;
}
return CHIP_ERROR_NOT_FOUND;
}
size_t GetNumEnergyBalances(chip::EndpointId aEndpoint) override { return (MATTER_ARRAY_SIZE(energyBalances)); }
size_t GetNumLowPowerModeSensitivities(chip::EndpointId aEndpoint) override { return (MATTER_ARRAY_SIZE(powerBalances)); }
};
struct TestEnergyPreferenceCluster : public ::testing::Test
{
inline static TestEnergyPreferenceDelegate testDelegate;
static void SetUpTestSuite()
{
ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR);
EnergyPreferenceCluster::SetDelegate(&testDelegate);
}
static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); }
TestServerClusterContext testContext;
BitFlags<Feature> bothFeatures{ Feature::kEnergyBalance, Feature::kLowPowerModeSensitivity };
EndpointId kTestEndpointId = 1;
};
} // namespace
TEST_F(TestEnergyPreferenceCluster, TestAttributes)
{
{
EnergyPreferenceCluster cluster(kTestEndpointId, bothFeatures);
ASSERT_EQ(cluster.Startup(testContext.Get()), CHIP_NO_ERROR);
EXPECT_TRUE(IsAttributesListEqualTo(cluster,
{ EnergyBalances::kMetadataEntry, CurrentEnergyBalance::kMetadataEntry,
EnergyPriorities::kMetadataEntry, LowPowerModeSensitivities::kMetadataEntry,
CurrentLowPowerModeSensitivity::kMetadataEntry }));
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
{
EnergyPreferenceCluster cluster(kTestEndpointId, BitFlags<Feature>(Feature::kEnergyBalance));
ASSERT_EQ(cluster.Startup(testContext.Get()), CHIP_NO_ERROR);
EXPECT_TRUE(IsAttributesListEqualTo(
cluster, { EnergyBalances::kMetadataEntry, CurrentEnergyBalance::kMetadataEntry, EnergyPriorities::kMetadataEntry }));
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
{
EnergyPreferenceCluster cluster(kTestEndpointId, BitFlags<Feature>(Feature::kLowPowerModeSensitivity));
ASSERT_EQ(cluster.Startup(testContext.Get()), CHIP_NO_ERROR);
EXPECT_TRUE(IsAttributesListEqualTo(
cluster, { LowPowerModeSensitivities::kMetadataEntry, CurrentLowPowerModeSensitivity::kMetadataEntry }));
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
}
TEST_F(TestEnergyPreferenceCluster, TestReadAttribute)
{
EnergyPreferenceCluster cluster(kTestEndpointId, bothFeatures);
ASSERT_EQ(cluster.Startup(testContext.Get()), CHIP_NO_ERROR);
ClusterTester tester(cluster);
ClusterRevision::TypeInfo::DecodableType clusterRevision{};
EXPECT_EQ(tester.ReadAttribute(ClusterRevision::Id, clusterRevision), CHIP_NO_ERROR);
FeatureMap::TypeInfo::DecodableType featureMap{};
EXPECT_EQ(tester.ReadAttribute(FeatureMap::Id, featureMap), CHIP_NO_ERROR);
EnergyBalances::TypeInfo::DecodableType energyBalances{};
EXPECT_EQ(tester.ReadAttribute(EnergyBalances::Id, energyBalances), CHIP_NO_ERROR);
CurrentEnergyBalance::TypeInfo::DecodableType currentEnergyBalance{};
EXPECT_EQ(tester.ReadAttribute(CurrentEnergyBalance::Id, currentEnergyBalance), CHIP_NO_ERROR);
EnergyPriorities::TypeInfo::DecodableType energyPriorities{};
EXPECT_EQ(tester.ReadAttribute(EnergyPriorities::Id, energyPriorities), CHIP_NO_ERROR);
LowPowerModeSensitivities::TypeInfo::DecodableType lowPowerModeSensitivities{};
EXPECT_EQ(tester.ReadAttribute(LowPowerModeSensitivities::Id, lowPowerModeSensitivities), CHIP_NO_ERROR);
CurrentLowPowerModeSensitivity::TypeInfo::DecodableType currentLowPowerModeSensitivity{};
EXPECT_EQ(tester.ReadAttribute(CurrentLowPowerModeSensitivity::Id, currentLowPowerModeSensitivity), CHIP_NO_ERROR);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
TEST_F(TestEnergyPreferenceCluster, TestListAttributesRead)
{
EnergyPreferenceCluster cluster(kTestEndpointId, bothFeatures);
ASSERT_EQ(cluster.Startup(testContext.Get()), CHIP_NO_ERROR);
ClusterTester tester(cluster);
// energyBalances
{
EnergyBalances::TypeInfo::DecodableType energyBalances{};
ASSERT_EQ(tester.ReadAttribute(EnergyBalances::Id, energyBalances), CHIP_NO_ERROR);
auto it = energyBalances.begin();
ASSERT_TRUE(it.Next());
auto energyBalance = it.GetValue();
EXPECT_EQ(energyBalance.step, 0);
EXPECT_TRUE(energyBalance.label.HasValue());
EXPECT_TRUE(energyBalance.label.Value().data_equal("Efficient"_span));
ASSERT_TRUE(it.Next());
energyBalance = it.GetValue();
EXPECT_EQ(energyBalance.step, 50);
EXPECT_FALSE(energyBalance.label.HasValue());
ASSERT_TRUE(it.Next());
energyBalance = it.GetValue();
EXPECT_EQ(energyBalance.step, 100);
EXPECT_TRUE(energyBalance.label.HasValue());
EXPECT_TRUE(energyBalance.label.Value().data_equal("Comfort"_span));
EXPECT_FALSE(it.Next());
}
// energyPriorities
{
EnergyPriorities::TypeInfo::DecodableType energyPriorities{};
ASSERT_EQ(tester.ReadAttribute(EnergyPriorities::Id, energyPriorities), CHIP_NO_ERROR);
auto it = energyPriorities.begin();
ASSERT_TRUE(it.Next());
auto energyPriority = it.GetValue();
EXPECT_EQ(energyPriority, EnergyPriorityEnum::kEfficiency);
ASSERT_TRUE(it.Next());
energyPriority = it.GetValue();
EXPECT_EQ(energyPriority, EnergyPriorityEnum::kComfort);
EXPECT_FALSE(it.Next());
}
// lowPowerModeSensitivities
{
LowPowerModeSensitivities::TypeInfo::DecodableType lowPowerModeSensitivities{};
ASSERT_EQ(tester.ReadAttribute(LowPowerModeSensitivities::Id, lowPowerModeSensitivities), CHIP_NO_ERROR);
auto it = lowPowerModeSensitivities.begin();
ASSERT_TRUE(it.Next());
auto lowPowerModeSensitivity = it.GetValue();
EXPECT_EQ(lowPowerModeSensitivity.step, 0);
EXPECT_TRUE(lowPowerModeSensitivity.label.HasValue());
EXPECT_TRUE(lowPowerModeSensitivity.label.Value().data_equal("1 Minute"_span));
ASSERT_TRUE(it.Next());
lowPowerModeSensitivity = it.GetValue();
EXPECT_EQ(lowPowerModeSensitivity.step, 24);
EXPECT_TRUE(lowPowerModeSensitivity.label.HasValue());
EXPECT_TRUE(lowPowerModeSensitivity.label.Value().data_equal("10 Minutes"_span));
ASSERT_TRUE(it.Next());
lowPowerModeSensitivity = it.GetValue();
EXPECT_EQ(lowPowerModeSensitivity.step, 48);
EXPECT_TRUE(lowPowerModeSensitivity.label.HasValue());
EXPECT_TRUE(lowPowerModeSensitivity.label.Value().data_equal("40 Minutes"_span));
ASSERT_TRUE(it.Next());
lowPowerModeSensitivity = it.GetValue();
EXPECT_EQ(lowPowerModeSensitivity.step, 70);
EXPECT_TRUE(lowPowerModeSensitivity.label.HasValue());
EXPECT_TRUE(lowPowerModeSensitivity.label.Value().data_equal("120 Minutes"_span));
ASSERT_TRUE(it.Next());
lowPowerModeSensitivity = it.GetValue();
EXPECT_EQ(lowPowerModeSensitivity.step, 100);
EXPECT_TRUE(lowPowerModeSensitivity.label.HasValue());
EXPECT_TRUE(lowPowerModeSensitivity.label.Value().data_equal("Never"_span));
EXPECT_FALSE(it.Next());
}
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
TEST_F(TestEnergyPreferenceCluster, TestReadWriteAttributeAndBounds)
{
EnergyPreferenceCluster cluster(kTestEndpointId, bothFeatures);
ASSERT_EQ(cluster.Startup(testContext.Get()), CHIP_NO_ERROR);
ClusterTester tester(cluster);
// currentEnergyBalance
// In bounds
uint8_t i = 0;
for (; i < static_cast<uint8_t>(testDelegate.GetNumEnergyBalances(kTestEndpointId)); ++i)
{
CurrentEnergyBalance::TypeInfo::Type currentEnergyBalanceWrite = i;
ASSERT_EQ(tester.WriteAttribute(CurrentEnergyBalance::Id, currentEnergyBalanceWrite), CHIP_NO_ERROR);
CurrentEnergyBalance::TypeInfo::DecodableType currentEnergyBalanceRead{};
ASSERT_EQ(tester.ReadAttribute(CurrentEnergyBalance::Id, currentEnergyBalanceRead), CHIP_NO_ERROR);
EXPECT_EQ(currentEnergyBalanceRead, currentEnergyBalanceWrite);
}
// Out of bounds
CurrentEnergyBalance::TypeInfo::Type currentEnergyBalanceWrite = i;
EXPECT_EQ(tester.WriteAttribute(CurrentEnergyBalance::Id, currentEnergyBalanceWrite),
Protocols::InteractionModel::Status::ConstraintError);
// lowPowerModeSensitivity
// In bounds
i = 0;
for (; i < static_cast<uint8_t>(testDelegate.GetNumLowPowerModeSensitivities(kTestEndpointId)); ++i)
{
CurrentLowPowerModeSensitivity::TypeInfo::Type currentLowPowerModeSensitivityWrite = i;
ASSERT_EQ(tester.WriteAttribute(CurrentLowPowerModeSensitivity::Id, currentLowPowerModeSensitivityWrite), CHIP_NO_ERROR);
CurrentLowPowerModeSensitivity::TypeInfo::DecodableType currentLowPowerModeSensitivityRead{};
ASSERT_EQ(tester.ReadAttribute(CurrentLowPowerModeSensitivity::Id, currentLowPowerModeSensitivityRead), CHIP_NO_ERROR);
EXPECT_EQ(currentLowPowerModeSensitivityRead, currentLowPowerModeSensitivityWrite);
}
// Out of bounds
CurrentLowPowerModeSensitivity::TypeInfo::Type currentLowPowerModeSensitivityWrite =
static_cast<CurrentLowPowerModeSensitivity::TypeInfo::Type>(testDelegate.GetNumLowPowerModeSensitivities(kTestEndpointId));
EXPECT_EQ(tester.WriteAttribute(CurrentLowPowerModeSensitivity::Id, currentLowPowerModeSensitivityWrite),
Protocols::InteractionModel::Status::ConstraintError);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
TEST_F(TestEnergyPreferenceCluster, TestSettersAndGetters)
{
EnergyPreferenceCluster cluster(kTestEndpointId, bothFeatures);
ASSERT_EQ(cluster.Startup(testContext.Get()), CHIP_NO_ERROR);
// currentEnergyBalance
for (CurrentEnergyBalance::TypeInfo::Type i = 0;
i < static_cast<CurrentEnergyBalance::TypeInfo::Type>(testDelegate.GetNumEnergyBalances(kTestEndpointId)); ++i)
{
EXPECT_EQ(cluster.SetCurrentEnergyBalance(i), CHIP_NO_ERROR);
}
CurrentEnergyBalance::TypeInfo::DecodableType currentEnergyBalance = cluster.GetCurrentEnergyBalance();
EXPECT_EQ(currentEnergyBalance,
static_cast<CurrentEnergyBalance::TypeInfo::Type>(testDelegate.GetNumEnergyBalances(kTestEndpointId) - 1));
EXPECT_EQ(cluster.SetCurrentEnergyBalance(
static_cast<CurrentEnergyBalance::TypeInfo::Type>(testDelegate.GetNumEnergyBalances(kTestEndpointId))),
CHIP_IM_GLOBAL_STATUS(ConstraintError));
// lowPowerModeSensitivity
for (CurrentLowPowerModeSensitivity::TypeInfo::Type i = 0; i <
static_cast<CurrentLowPowerModeSensitivity::TypeInfo::Type>(testDelegate.GetNumLowPowerModeSensitivities(kTestEndpointId));
++i)
{
EXPECT_EQ(cluster.SetCurrentLowPowerModeSensitivity(i), CHIP_NO_ERROR);
}
CurrentLowPowerModeSensitivity::TypeInfo::DecodableType currentLowPowerModeSensitivity =
cluster.GetCurrentLowPowerModeSensitivity();
EXPECT_EQ(currentLowPowerModeSensitivity,
static_cast<CurrentLowPowerModeSensitivity::TypeInfo::Type>(
testDelegate.GetNumLowPowerModeSensitivities(kTestEndpointId) - 1));
EXPECT_EQ(cluster.SetCurrentLowPowerModeSensitivity(static_cast<CurrentLowPowerModeSensitivity::TypeInfo::Type>(
testDelegate.GetNumLowPowerModeSensitivities(kTestEndpointId))),
CHIP_IM_GLOBAL_STATUS(ConstraintError));
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
TEST_F(TestEnergyPreferenceCluster, TestPersistence)
{
CurrentEnergyBalance::TypeInfo::Type currentEnergyBalanceWrite = 1;
CurrentLowPowerModeSensitivity::TypeInfo::Type currentLowPowerModeSensitivityWrite = 2;
// Set the values
{
EnergyPreferenceCluster cluster(kTestEndpointId, bothFeatures);
ASSERT_EQ(cluster.Startup(testContext.Get()), CHIP_NO_ERROR);
ASSERT_EQ(cluster.SetCurrentEnergyBalance(currentEnergyBalanceWrite), CHIP_NO_ERROR);
ASSERT_EQ(cluster.SetCurrentLowPowerModeSensitivity(currentLowPowerModeSensitivityWrite), CHIP_NO_ERROR);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// Read the values back after shutdown and startup
{
EnergyPreferenceCluster cluster(kTestEndpointId, bothFeatures);
ASSERT_EQ(cluster.Startup(testContext.Get()), CHIP_NO_ERROR);
CurrentEnergyBalance::TypeInfo::DecodableType currentEnergyBalanceRead = cluster.GetCurrentEnergyBalance();
EXPECT_EQ(currentEnergyBalanceRead, currentEnergyBalanceWrite);
CurrentLowPowerModeSensitivity::TypeInfo::DecodableType currentLowPowerModeSensitivityRead =
cluster.GetCurrentLowPowerModeSensitivity();
EXPECT_EQ(currentLowPowerModeSensitivityRead, currentLowPowerModeSensitivityWrite);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
}