Feature/energypreferences (#30244)
* Adding the Energy Preferences XML
* Added just the xml and regenerated. No implementations yet as
requested.
* regen
* put the cluster back into all-clusters after merge.
* Apply suggestions from code review
Added suggestions for ARRAY vs array
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* regenerate
* Finished up impl for energy-preferences
* Added some error checking for features, and constraint checking.
Also turned on the features in the all-clusters-app
* Restyled by whitespace
* Restyled by clang-format
* Fixed copy paste bug
* Removed some unecessary changes and a comment.
* initialize the delegate pointer
* Put the cluster back in the all-clusters app after the merge
* Fixed minor issues from review
* Restyled by whitespace
* Addressing some review concerns
* Some more comments resolved
* Better documentation, a set of braces I missed last time around, and
added an extra header include.
* fix build error after merge
* Restyled by whitespace
* Restyled by clang-format
* Update src/app/clusters/energy-preference-server/energy-preference-server.cpp
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Implement proposed API changes with clearer delineation of storage
* Restyled by clang-format
* Typo
* Regen zap, update code to reflect the updated return from Feature::Get
* ZAP regen, to fix merge errors
* Apply suggestions from code review
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Address suggestion re: optionality conversion
* Fix one more omission
* another small fix
* Fix up copyright headers
* Restyled by clang-format
* Reinitialize storage objects on every iteration
* fix typo
* Restyled by clang-format
* Apply suggestions from code review
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
---------
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
Co-authored-by: Restyled.io <commits@restyled.io>
Co-authored-by: Robert Szewczyk <szewczyk@google.com>
diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter
index 62dc0d7..5a7b510 100644
--- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter
+++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter
@@ -4455,6 +4455,40 @@
timed command ClearTargets(): DefaultSuccess = 7;
}
+/** This cluster provides an interface to specify preferences for how devices should consume energy. */
+provisional cluster EnergyPreference = 155 {
+ revision 1;
+
+ enum EnergyPriorityEnum : enum8 {
+ kComfort = 0;
+ kSpeed = 1;
+ kEfficiency = 2;
+ kWaterConsumption = 3;
+ }
+
+ bitmap Feature : bitmap32 {
+ kEnergyBalance = 0x1;
+ kLowPowerModeSensitivity = 0x2;
+ }
+
+ struct BalanceStruct {
+ percent step = 0;
+ optional char_string<64> label = 1;
+ }
+
+ readonly attribute optional BalanceStruct energyBalances[] = 0;
+ attribute optional int8u currentEnergyBalance = 1;
+ readonly attribute optional EnergyPriorityEnum energyPriorities[] = 2;
+ readonly attribute optional BalanceStruct lowPowerModeSensitivities[] = 3;
+ attribute optional int8u currentLowPowerModeSensitivity = 4;
+ readonly attribute command_id generatedCommandList[] = 65528;
+ readonly attribute command_id acceptedCommandList[] = 65529;
+ readonly attribute event_id eventList[] = 65530;
+ readonly attribute attrib_id attributeList[] = 65531;
+ readonly attribute bitmap32 featureMap = 65532;
+ readonly attribute int16u clusterRevision = 65533;
+}
+
/** The Power Topology Cluster provides a mechanism for expressing how power is flowing between endpoints. */
provisional cluster PowerTopology = 156 {
revision 1;
@@ -8361,6 +8395,20 @@
handle command ClearTargets;
}
+ server cluster EnergyPreference {
+ callback attribute energyBalances;
+ ram attribute currentEnergyBalance;
+ callback attribute energyPriorities;
+ callback attribute lowPowerModeSensitivities;
+ ram attribute currentLowPowerModeSensitivity;
+ callback attribute generatedCommandList;
+ callback attribute acceptedCommandList;
+ callback attribute eventList;
+ callback attribute attributeList;
+ ram attribute featureMap default = 3;
+ ram attribute clusterRevision default = 1;
+ }
+
server cluster PowerTopology {
callback attribute availableEndpoints;
callback attribute activeEndpoints;
diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap
index 0a2a3fa..21bcfee 100644
--- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap
+++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap
@@ -14693,6 +14693,192 @@
]
},
{
+ "name": "Energy Preference",
+ "code": 155,
+ "mfgCode": null,
+ "define": "ENERGY_PREFERENCE_CLUSTER",
+ "side": "server",
+ "enabled": 1,
+ "attributes": [
+ {
+ "name": "EnergyBalances",
+ "code": 0,
+ "mfgCode": null,
+ "side": "server",
+ "type": "array",
+ "included": 1,
+ "storageOption": "External",
+ "singleton": 0,
+ "bounded": 0,
+ "defaultValue": null,
+ "reportable": 1,
+ "minInterval": 1,
+ "maxInterval": 65534,
+ "reportableChange": 0
+ },
+ {
+ "name": "CurrentEnergyBalance",
+ "code": 1,
+ "mfgCode": null,
+ "side": "server",
+ "type": "int8u",
+ "included": 1,
+ "storageOption": "RAM",
+ "singleton": 0,
+ "bounded": 0,
+ "defaultValue": "",
+ "reportable": 1,
+ "minInterval": 1,
+ "maxInterval": 65534,
+ "reportableChange": 0
+ },
+ {
+ "name": "EnergyPriorities",
+ "code": 2,
+ "mfgCode": null,
+ "side": "server",
+ "type": "array",
+ "included": 1,
+ "storageOption": "External",
+ "singleton": 0,
+ "bounded": 0,
+ "defaultValue": null,
+ "reportable": 1,
+ "minInterval": 1,
+ "maxInterval": 65534,
+ "reportableChange": 0
+ },
+ {
+ "name": "LowPowerModeSensitivities",
+ "code": 3,
+ "mfgCode": null,
+ "side": "server",
+ "type": "array",
+ "included": 1,
+ "storageOption": "External",
+ "singleton": 0,
+ "bounded": 0,
+ "defaultValue": null,
+ "reportable": 1,
+ "minInterval": 1,
+ "maxInterval": 65534,
+ "reportableChange": 0
+ },
+ {
+ "name": "CurrentLowPowerModeSensitivity",
+ "code": 4,
+ "mfgCode": null,
+ "side": "server",
+ "type": "int8u",
+ "included": 1,
+ "storageOption": "RAM",
+ "singleton": 0,
+ "bounded": 0,
+ "defaultValue": "",
+ "reportable": 1,
+ "minInterval": 1,
+ "maxInterval": 65534,
+ "reportableChange": 0
+ },
+ {
+ "name": "GeneratedCommandList",
+ "code": 65528,
+ "mfgCode": null,
+ "side": "server",
+ "type": "array",
+ "included": 1,
+ "storageOption": "External",
+ "singleton": 0,
+ "bounded": 0,
+ "defaultValue": null,
+ "reportable": 1,
+ "minInterval": 1,
+ "maxInterval": 65534,
+ "reportableChange": 0
+ },
+ {
+ "name": "AcceptedCommandList",
+ "code": 65529,
+ "mfgCode": null,
+ "side": "server",
+ "type": "array",
+ "included": 1,
+ "storageOption": "External",
+ "singleton": 0,
+ "bounded": 0,
+ "defaultValue": null,
+ "reportable": 1,
+ "minInterval": 1,
+ "maxInterval": 65534,
+ "reportableChange": 0
+ },
+ {
+ "name": "EventList",
+ "code": 65530,
+ "mfgCode": null,
+ "side": "server",
+ "type": "array",
+ "included": 1,
+ "storageOption": "External",
+ "singleton": 0,
+ "bounded": 0,
+ "defaultValue": null,
+ "reportable": 1,
+ "minInterval": 1,
+ "maxInterval": 65534,
+ "reportableChange": 0
+ },
+ {
+ "name": "AttributeList",
+ "code": 65531,
+ "mfgCode": null,
+ "side": "server",
+ "type": "array",
+ "included": 1,
+ "storageOption": "External",
+ "singleton": 0,
+ "bounded": 0,
+ "defaultValue": null,
+ "reportable": 1,
+ "minInterval": 1,
+ "maxInterval": 65534,
+ "reportableChange": 0
+ },
+ {
+ "name": "FeatureMap",
+ "code": 65532,
+ "mfgCode": null,
+ "side": "server",
+ "type": "bitmap32",
+ "included": 1,
+ "storageOption": "RAM",
+ "singleton": 0,
+ "bounded": 0,
+ "defaultValue": "3",
+ "reportable": 1,
+ "minInterval": 1,
+ "maxInterval": 65534,
+ "reportableChange": 0
+ },
+ {
+ "name": "ClusterRevision",
+ "code": 65533,
+ "mfgCode": null,
+ "side": "server",
+ "type": "int16u",
+ "included": 1,
+ "storageOption": "RAM",
+ "singleton": 0,
+ "bounded": 0,
+ "defaultValue": "1",
+ "reportable": 1,
+ "minInterval": 0,
+ "maxInterval": 65344,
+ "reportableChange": 0
+ }
+ ]
+ },
+ {
"name": "Window Covering",
"code": 258,
"mfgCode": null,
diff --git a/examples/all-clusters-app/all-clusters-common/src/energy-preference-delegate.cpp b/examples/all-clusters-app/all-clusters-common/src/energy-preference-delegate.cpp
new file mode 100644
index 0000000..cd9b47d
--- /dev/null
+++ b/examples/all-clusters-app/all-clusters-common/src/energy-preference-delegate.cpp
@@ -0,0 +1,137 @@
+/*
+ *
+ * Copyright (c) 2024 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/clusters/energy-preference-server/energy-preference-server.h>
+
+using namespace chip;
+using namespace chip::app::Clusters::EnergyPreference;
+using namespace chip::app::Clusters::EnergyPreference::Structs;
+
+static BalanceStruct::Type gsEnergyBalances[] = {
+ { .step = 0, .label = Optional<chip::CharSpan>("Efficient"_span) },
+ { .step = 50, .label = Optional<chip::CharSpan>() },
+ { .step = 100, .label = Optional<chip::CharSpan>("Comfort"_span) },
+};
+
+static BalanceStruct::Type gsPowerBalances[] = {
+ { .step = 0, .label = Optional<chip::CharSpan>("1 Minute"_span) },
+ { .step = 12, .label = Optional<chip::CharSpan>("5 Minutes"_span) },
+ { .step = 24, .label = Optional<chip::CharSpan>("10 Minutes"_span) },
+ { .step = 36, .label = Optional<chip::CharSpan>("15 Minutes"_span) },
+ { .step = 48, .label = Optional<chip::CharSpan>("20 Minutes"_span) },
+ { .step = 60, .label = Optional<chip::CharSpan>("25 Minutes"_span) },
+ { .step = 70, .label = Optional<chip::CharSpan>("30 Minutes"_span) },
+ { .step = 80, .label = Optional<chip::CharSpan>("60 Minutes"_span) },
+ { .step = 90, .label = Optional<chip::CharSpan>("120 Minutes"_span) },
+ { .step = 100, .label = Optional<chip::CharSpan>("Never"_span) },
+};
+
+// assumes it'll be the only delegate for it's lifetime.
+struct EPrefDelegate : public Delegate
+{
+ EPrefDelegate();
+ virtual ~EPrefDelegate();
+
+ CHIP_ERROR GetEnergyBalanceAtIndex(chip::EndpointId aEndpoint, size_t aIndex, chip::Percent & aOutStep,
+ chip::Optional<chip::MutableCharSpan> & aOutLabel) override;
+ CHIP_ERROR GetEnergyPriorityAtIndex(chip::EndpointId aEndpoint, size_t aIndex, EnergyPriorityEnum & priority) override;
+ CHIP_ERROR GetLowPowerModeSensitivityAtIndex(chip::EndpointId aEndpoint, size_t aIndex, chip::Percent & aOutStep,
+ chip::Optional<chip::MutableCharSpan> & aOutLabel) override;
+
+ size_t GetNumEnergyBalances(chip::EndpointId aEndpoint) override;
+ size_t GetNumLowPowerModeSensitivities(chip::EndpointId aEndpoint) override;
+};
+
+EPrefDelegate::EPrefDelegate() : Delegate()
+{
+ VerifyOrDie(GetDelegate() == nullptr);
+ SetDelegate(this);
+}
+
+EPrefDelegate::~EPrefDelegate()
+{
+ VerifyOrDie(GetDelegate() == this);
+ SetDelegate(nullptr);
+}
+
+size_t EPrefDelegate::GetNumEnergyBalances(chip::EndpointId aEndpoint)
+{
+ return (ArraySize(gsEnergyBalances));
+}
+
+size_t EPrefDelegate::GetNumLowPowerModeSensitivities(chip::EndpointId aEndpoint)
+{
+ return (ArraySize(gsEnergyBalances));
+}
+
+CHIP_ERROR
+EPrefDelegate::GetEnergyBalanceAtIndex(chip::EndpointId aEndpoint, size_t aIndex, chip::Percent & aOutStep,
+ chip::Optional<chip::MutableCharSpan> & aOutLabel)
+{
+ if (aIndex < GetNumEnergyBalances(aEndpoint))
+ {
+ aOutStep = gsEnergyBalances[aIndex].step;
+ if (gsEnergyBalances[aIndex].label.HasValue())
+ {
+ chip::CopyCharSpanToMutableCharSpan(gsEnergyBalances[aIndex].label.Value(), aOutLabel.Value());
+ }
+ else
+ {
+ aOutLabel.ClearValue();
+ }
+ return CHIP_NO_ERROR;
+ }
+ return CHIP_ERROR_NOT_FOUND;
+}
+
+CHIP_ERROR
+EPrefDelegate::GetEnergyPriorityAtIndex(chip::EndpointId aEndpoint, size_t aIndex, EnergyPriorityEnum & priority)
+{
+ static EnergyPriorityEnum priorities[] = { EnergyPriorityEnum::kEfficiency, EnergyPriorityEnum::kComfort };
+
+ if (aIndex < ArraySize(priorities))
+ {
+ priority = priorities[aIndex];
+ return CHIP_NO_ERROR;
+ }
+
+ return CHIP_ERROR_NOT_FOUND;
+}
+
+CHIP_ERROR
+EPrefDelegate::GetLowPowerModeSensitivityAtIndex(chip::EndpointId aEndpoint, size_t aIndex, chip::Percent & aOutStep,
+ chip::Optional<chip::MutableCharSpan> & aOutLabel)
+{
+ if (aIndex < GetNumLowPowerModeSensitivities(aEndpoint))
+ {
+ aOutStep = gsPowerBalances[aIndex].step;
+ if (gsPowerBalances[aIndex].label.HasValue())
+ {
+ chip::CopyCharSpanToMutableCharSpan(gsPowerBalances[aIndex].label.Value(), aOutLabel.Value());
+ }
+ else
+ {
+ aOutLabel.ClearValue();
+ }
+ return CHIP_NO_ERROR;
+ }
+
+ return CHIP_ERROR_NOT_FOUND;
+}
+
+static EPrefDelegate gsDelegate;
diff --git a/examples/all-clusters-app/ameba/chip_main.cmake b/examples/all-clusters-app/ameba/chip_main.cmake
index 0d8b99c..c457206 100755
--- a/examples/all-clusters-app/ameba/chip_main.cmake
+++ b/examples/all-clusters-app/ameba/chip_main.cmake
@@ -158,6 +158,7 @@
${chip_dir}/examples/all-clusters-app/all-clusters-common/src/concentration-measurement-instances.cpp
${chip_dir}/examples/all-clusters-app/all-clusters-common/src/device-energy-management-stub.cpp
${chip_dir}/examples/all-clusters-app/all-clusters-common/src/energy-evse-stub.cpp
+ ${chip_dir}/examples/all-clusters-app/all-clusters-common/src/energy-preference-delegate.cpp
${chip_dir}/examples/all-clusters-app/all-clusters-common/src/fan-stub.cpp
${chip_dir}/examples/all-clusters-app/all-clusters-common/src/oven-modes.cpp
${chip_dir}/examples/all-clusters-app/all-clusters-common/src/laundry-washer-controls-delegate-impl.cpp
diff --git a/examples/all-clusters-app/esp32/main/CMakeLists.txt b/examples/all-clusters-app/esp32/main/CMakeLists.txt
index 238f0e8..63851b1 100644
--- a/examples/all-clusters-app/esp32/main/CMakeLists.txt
+++ b/examples/all-clusters-app/esp32/main/CMakeLists.txt
@@ -96,8 +96,11 @@
"${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/time-synchronization-server"
"${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/valve-configuration-and-control-server"
"${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/dishwasher-alarm-server"
- "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/laundry-washer-controls-server"
+ "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/laundry-washer-controls-server"
+ "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/laundry-washer-controls-server"
"${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/laundry-dryer-controls-server"
+ "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/examples/all-clusters-app/all-clusters-common/src"
+ "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/energy-preference-server"
"${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/electrical-energy-measurement-server"
"${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/electrical-power-measurement-server"
)
diff --git a/examples/all-clusters-app/linux/BUILD.gn b/examples/all-clusters-app/linux/BUILD.gn
index 998f98a..3d52ef7 100644
--- a/examples/all-clusters-app/linux/BUILD.gn
+++ b/examples/all-clusters-app/linux/BUILD.gn
@@ -40,6 +40,7 @@
"${chip_root}/examples/all-clusters-app/all-clusters-common/src/electrical-energy-measurement-stub.cpp",
"${chip_root}/examples/all-clusters-app/all-clusters-common/src/electrical-power-measurement-stub.cpp",
"${chip_root}/examples/all-clusters-app/all-clusters-common/src/energy-evse-stub.cpp",
+ "${chip_root}/examples/all-clusters-app/all-clusters-common/src/energy-preference-delegate.cpp",
"${chip_root}/examples/all-clusters-app/all-clusters-common/src/fan-stub.cpp",
"${chip_root}/examples/all-clusters-app/all-clusters-common/src/laundry-dryer-controls-delegate-impl.cpp",
"${chip_root}/examples/all-clusters-app/all-clusters-common/src/laundry-washer-controls-delegate-impl.cpp",
diff --git a/src/app/clusters/energy-preference-server/energy-preference-server.cpp b/src/app/clusters/energy-preference-server/energy-preference-server.cpp
new file mode 100644
index 0000000..7cb377a
--- /dev/null
+++ b/src/app/clusters/energy-preference-server/energy-preference-server.cpp
@@ -0,0 +1,235 @@
+/**
+ *
+ * Copyright (c) 2024 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 "energy-preference-server.h"
+
+#include <app/util/attribute-storage.h> // Needed for registerAttributeAccessOverride
+
+#include <app-common/zap-generated/attributes/Accessors.h>
+#include <app-common/zap-generated/callback.h>
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app-common/zap-generated/ids/Attributes.h>
+#include <app/AttributeAccessInterface.h>
+#include <app/ConcreteAttributePath.h>
+#include <lib/core/CHIPEncoding.h>
+
+using namespace chip;
+using namespace chip::app;
+using namespace chip::app::Clusters;
+using namespace chip::app::Clusters::EnergyPreference;
+using namespace chip::app::Clusters::EnergyPreference::Structs;
+using namespace chip::app::Clusters::EnergyPreference::Attributes;
+
+using Status = Protocols::InteractionModel::Status;
+
+namespace {
+
+class EnergyPrefAttrAccess : public AttributeAccessInterface
+{
+public:
+ EnergyPrefAttrAccess() : AttributeAccessInterface(Optional<EndpointId>::Missing(), EnergyPreference::Id) {}
+
+ CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
+};
+
+EnergyPrefAttrAccess gEnergyPrefAttrAccess;
+Delegate * gsDelegate = nullptr;
+
+CHIP_ERROR EnergyPrefAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
+{
+ VerifyOrDie(aPath.mClusterId == EnergyPreference::Id);
+ EndpointId endpoint = aPath.mEndpointId;
+ uint32_t ourFeatureMap = 0;
+ const bool featureMapIsGood = FeatureMap::Get(aPath.mEndpointId, &ourFeatureMap) == Status::Success;
+ const bool balanceSupported = featureMapIsGood && ((ourFeatureMap & to_underlying(Feature::kEnergyBalance)) != 0);
+ const bool lowPowerSupported = featureMapIsGood && ((ourFeatureMap & to_underlying(Feature::kLowPowerModeSensitivity)) != 0);
+
+ switch (aPath.mAttributeId)
+ {
+ case EnergyBalances::Id:
+ if (!balanceSupported)
+ {
+ return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute);
+ }
+
+ if (gsDelegate != nullptr)
+ {
+ return aEncoder.EncodeList([endpoint](const auto & encoder) -> CHIP_ERROR {
+ size_t index = 0;
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ do
+ {
+ Percent step;
+ char buffer[64];
+ Optional<MutableCharSpan> label{ MutableCharSpan(buffer) };
+ if ((err = gsDelegate->GetEnergyBalanceAtIndex(endpoint, index, step, label)) == CHIP_NO_ERROR)
+ {
+ BalanceStruct::Type balance = { step,
+ label.HasValue() ? Optional<CharSpan>(label.Value())
+ : Optional<CharSpan>() };
+ ReturnErrorOnFailure(encoder.Encode(balance));
+ index++;
+ }
+ } while (err == CHIP_NO_ERROR);
+
+ if (err == CHIP_ERROR_NOT_FOUND)
+ {
+ return CHIP_NO_ERROR;
+ }
+ return err;
+ });
+ }
+ return CHIP_ERROR_INCORRECT_STATE;
+ case EnergyPriorities::Id:
+ if (balanceSupported == false)
+ {
+ return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute);
+ }
+
+ if (gsDelegate != nullptr)
+ {
+ return aEncoder.EncodeList([endpoint](const auto & encoder) -> CHIP_ERROR {
+ EnergyPriorityEnum priority;
+ size_t index = 0;
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ while ((err = gsDelegate->GetEnergyPriorityAtIndex(endpoint, index, priority)) == CHIP_NO_ERROR)
+ {
+ ReturnErrorOnFailure(encoder.Encode(priority));
+ index++;
+ }
+ if (err == CHIP_ERROR_NOT_FOUND)
+ {
+ return CHIP_NO_ERROR;
+ }
+ return err;
+ });
+ }
+ return CHIP_ERROR_INCORRECT_STATE;
+ case LowPowerModeSensitivities::Id:
+ if (lowPowerSupported == false)
+ {
+ return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute);
+ }
+
+ if (gsDelegate != nullptr)
+ {
+ return aEncoder.EncodeList([endpoint](const auto & encoder) -> CHIP_ERROR {
+ size_t index = 0;
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ do
+ {
+ Percent step;
+ char buffer[64];
+ Optional<MutableCharSpan> label{ MutableCharSpan(buffer) };
+ if ((err = gsDelegate->GetLowPowerModeSensitivityAtIndex(endpoint, index, step, label)) == CHIP_NO_ERROR)
+ {
+ BalanceStruct::Type balance = { step,
+ label.HasValue() ? Optional<CharSpan>(label.Value())
+ : Optional<CharSpan>() };
+ ReturnErrorOnFailure(encoder.Encode(balance));
+ index++;
+ }
+ } while (err == CHIP_NO_ERROR);
+ if (err == CHIP_ERROR_NOT_FOUND)
+ {
+ return CHIP_NO_ERROR;
+ }
+ return err;
+ });
+ }
+ return CHIP_ERROR_INCORRECT_STATE;
+ default: // return CHIP_NO_ERROR and just read from the attribute store in default
+ break;
+ }
+
+ return CHIP_NO_ERROR;
+}
+
+} // anonymous namespace
+
+namespace chip::app::Clusters::EnergyPreference {
+
+void SetDelegate(Delegate * aDelegate)
+{
+ gsDelegate = aDelegate;
+}
+
+Delegate * GetDelegate()
+{
+ return gsDelegate;
+}
+
+} // namespace chip::app::Clusters::EnergyPreference
+
+Status MatterEnergyPreferenceClusterServerPreAttributeChangedCallback(const ConcreteAttributePath & attributePath,
+ EmberAfAttributeType attributeType, uint16_t size,
+ uint8_t * value)
+{
+ EndpointId endpoint = attributePath.mEndpointId;
+ Delegate * delegate = GetDelegate();
+ uint32_t ourFeatureMap;
+ const bool featureMapIsGood = FeatureMap::Get(attributePath.mEndpointId, &ourFeatureMap) == Status::Success;
+ const bool balanceSupported = featureMapIsGood && ((ourFeatureMap & to_underlying(Feature::kEnergyBalance)) != 0);
+ const bool lowPowerSupported = featureMapIsGood && ((ourFeatureMap & to_underlying(Feature::kLowPowerModeSensitivity)) != 0);
+
+ if (delegate == nullptr)
+ {
+ return Status::UnsupportedWrite;
+ }
+
+ switch (attributePath.mAttributeId)
+ {
+ case CurrentEnergyBalance::Id: {
+ if (balanceSupported == false)
+ {
+ return Status::UnsupportedAttribute;
+ }
+
+ uint8_t index = Encoding::Get8(value);
+ size_t arraySize = delegate->GetNumEnergyBalances(endpoint);
+ if (index >= arraySize)
+ {
+ return Status::ConstraintError;
+ }
+
+ return Status::Success;
+ }
+
+ case CurrentLowPowerModeSensitivity::Id: {
+ if (lowPowerSupported == false)
+ {
+ return Status::UnsupportedAttribute;
+ }
+
+ uint8_t index = Encoding::Get8(value);
+ size_t arraySize = delegate->GetNumLowPowerModeSensitivities(endpoint);
+ if (index >= arraySize)
+ {
+ return Status::ConstraintError;
+ }
+
+ return Status::Success;
+ }
+ default:
+ return Status::Success;
+ }
+}
+
+void MatterEnergyPreferencePluginServerInitCallback()
+{
+ registerAttributeAccessOverride(&gEnergyPrefAttrAccess);
+}
diff --git a/src/app/clusters/energy-preference-server/energy-preference-server.h b/src/app/clusters/energy-preference-server/energy-preference-server.h
new file mode 100644
index 0000000..7710578
--- /dev/null
+++ b/src/app/clusters/energy-preference-server/energy-preference-server.h
@@ -0,0 +1,107 @@
+/**
+ *
+ * Copyright (c) 2024 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.
+ */
+
+#pragma once
+
+#include <stddef.h>
+
+#include <app-common/zap-generated/cluster-enums.h>
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app/util/basic-types.h>
+#include <lib/core/CHIPError.h>
+
+namespace chip::app::Clusters::EnergyPreference {
+
+struct Delegate
+{
+ // Note: This delegate does not handle the "Current Active" indexes attributes storage.
+ // eg: Current Energy Balance and Current Low Power Mode Sensitivity. These can be handled using
+ // ember built in storage, or via the external callbacks as desired by the implementer.
+
+ virtual ~Delegate() {}
+
+ /**
+ * Get an Energy Balance.
+ *
+ * The delegate method is called by the cluster to fill out the
+ * values for the list in EnergyBalances attribute. Storage for
+ * both aOutStep and aOutLabel is provided by the caller.
+ *
+ * @param aEndpoint The endpoint to query.
+ * @param aIndex The index of the balance, with 0 representing the first one.
+ * @param aOutStep The Step value from BalanceStruct
+ *
+ * @param aOutLabel The Label value from BalanceStruct. Storage is
+ * provided by the caller, and is large enough to accomodate the
+ * longest label (64 chars), on successful return the size of the span must be
+ * adjusted to reflect the length of the value, or ClearValue() called on the Optional to indicate there is no label.
+ *
+ * @return CHIP_ERROR_NOT_FOUND if the index is out of range.
+ */
+ virtual CHIP_ERROR GetEnergyBalanceAtIndex(chip::EndpointId aEndpoint, size_t aIndex, chip::Percent & aOutStep,
+ chip::Optional<chip::MutableCharSpan> & aOutLabel) = 0;
+
+ /**
+ * Get an Energy Priority.
+ * @param aEndpoint The endpoint to query.
+ * @param aIndex The index of the priority, with 0 representing the first one.
+ * @param aOutPriority The EnergyPriorityEnum to copy the data into.
+ * @return CHIP_ERROR_NOT_FOUND if the index is out of range.
+ */
+ virtual CHIP_ERROR GetEnergyPriorityAtIndex(chip::EndpointId aEndpoint, size_t aIndex,
+ chip::app::Clusters::EnergyPreference::EnergyPriorityEnum & aOutPriority) = 0;
+
+ /**
+ * Get a Power Sensitity Balance Struct data at the specified index.
+ *
+ * The delegate method is called by the cluster to fill out the
+ * values for the list in LowPowerSensitivities attribute. Storage for
+ * both aOutStep and aOutLabel is provided by the caller.
+ *
+ * @param aEndpoint The endpoint to query.
+ * @param aIndex The index of the priority, with 0 representing the first one.
+ * @param aOutStep The Step value from BalanceStruct
+ *
+ * @param aOutLabel The Label value from BalanceStruct. Storage is
+ * provided by the caller, and is large enough to accomodate the
+ * longest label (64 chars), on successful return the size of the span must be
+ * adjusted to reflect the length of the value, or ClearValue() called on the Optional to indicate there is no label.
+ *
+ * @return CHIP_ERROR_NOT_FOUND if the index is out of range.
+ */
+ virtual CHIP_ERROR GetLowPowerModeSensitivityAtIndex(chip::EndpointId aEndpoint, size_t aIndex, chip::Percent & aOutStep,
+ chip::Optional<chip::MutableCharSpan> & aOutLabel) = 0;
+
+ /**
+ * Get the number of energy balances this endpoint has.
+ * @param aEndpoint The endpoint to query.
+ * @return the number of balance structs in the list.
+ */
+ virtual size_t GetNumEnergyBalances(chip::EndpointId aEndpoint) = 0;
+
+ /**
+ * Get the number of low power mode sensitivities this endpoint has.
+ * @param aEndpoint The endpoint to query.
+ * @return the number of balance structs in the list.
+ */
+ virtual size_t GetNumLowPowerModeSensitivities(chip::EndpointId aEndpoint) = 0;
+};
+
+void SetDelegate(Delegate * aDelegate);
+Delegate * GetDelegate();
+
+} // namespace chip::app::Clusters::EnergyPreference
diff --git a/src/app/common/templates/config-data.yaml b/src/app/common/templates/config-data.yaml
index 2067718..875cdbc 100644
--- a/src/app/common/templates/config-data.yaml
+++ b/src/app/common/templates/config-data.yaml
@@ -84,5 +84,6 @@
- Mode Select
- Fan Control
- Thermostat
+ - Energy Preference
- Laundry Washer Controls
- Laundry Dryer Controls
diff --git a/src/app/zap-templates/zcl/data-model/chip/energy-preference-cluster.xml b/src/app/zap-templates/zcl/data-model/chip/energy-preference-cluster.xml
index ceff984..32893bd 100644
--- a/src/app/zap-templates/zcl/data-model/chip/energy-preference-cluster.xml
+++ b/src/app/zap-templates/zcl/data-model/chip/energy-preference-cluster.xml
@@ -22,6 +22,7 @@
<server init="false" tick="false">true</server>
<description>This cluster provides an interface to specify preferences for how devices should consume energy.</description>
<!--Attributes-->
+ <globalAttribute side="either" code="0xFFFD" value="1"/>
<!--Conformance feature BALA - for now optional-->
<attribute code="0x0000" side="server" type="array" entryType="BalanceStruct" define="ENERGY_PREFERENCE_ENERGY_BALANCES" isNullable="false" min="2" max="10" writable="false" optional="true">EnergyBalances</attribute>
diff --git a/src/app/zap_cluster_list.json b/src/app/zap_cluster_list.json
index 56adf0a..2edfb27 100644
--- a/src/app/zap_cluster_list.json
+++ b/src/app/zap_cluster_list.json
@@ -192,7 +192,7 @@
"ETHERNET_NETWORK_DIAGNOSTICS_CLUSTER": [
"ethernet-network-diagnostics-server"
],
- "ENERGY_PREFERENCE_CLUSTER": [""],
+ "ENERGY_PREFERENCE_CLUSTER": ["energy-preference-server"],
"FAN_CONTROL_CLUSTER": ["fan-control-server"],
"FAULT_INJECTION_CLUSTER": ["fault-injection-server"],
"FIXED_LABEL_CLUSTER": ["fixed-label-server"],
diff --git a/src/controller/data_model/controller-clusters.matter b/src/controller/data_model/controller-clusters.matter
index 8986463..235366f 100644
--- a/src/controller/data_model/controller-clusters.matter
+++ b/src/controller/data_model/controller-clusters.matter
@@ -4978,7 +4978,7 @@
/** This cluster provides an interface to specify preferences for how devices should consume energy. */
provisional cluster EnergyPreference = 155 {
- revision 1; // NOTE: Default/not specifically set
+ revision 1;
enum EnergyPriorityEnum : enum8 {
kComfort = 0;