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 7ffee39eb5101f..9d6a33dee07ccd 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 @@ -4092,6 +4092,64 @@ cluster ElectricalEnergyMeasurement = 145 { readonly attribute int16u clusterRevision = 65533; } +/** This cluster is used to allow clients to control the operation of a hot water heating appliance so that it can be used with energy management. */ +provisional cluster WaterHeaterManagement = 148 { + revision 1; + + enum BoostStateEnum : enum8 { + kInactive = 0; + kActive = 1; + } + + bitmap Feature : bitmap32 { + kEnergyManagement = 0x1; + kTankPercent = 0x2; + } + + bitmap WaterHeaterDemandBitmap : bitmap8 { + kImmersionElement1 = 0x1; + kImmersionElement2 = 0x2; + kHeatPump = 0x4; + kBoiler = 0x8; + kOther = 0x10; + } + + bitmap WaterHeaterTypeBitmap : bitmap8 { + kImmersionElement1 = 0x1; + kImmersionElement2 = 0x2; + kHeatPump = 0x4; + kBoiler = 0x8; + kOther = 0x10; + } + + readonly attribute WaterHeaterTypeBitmap heaterTypes = 0; + readonly attribute WaterHeaterDemandBitmap heatDemand = 1; + readonly attribute optional int16u tankVolume = 2; + readonly attribute optional energy_mwh estimatedHeatRequired = 3; + readonly attribute optional percent tankPercentage = 4; + readonly attribute BoostStateEnum boostState = 5; + 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; + + request struct BoostRequest { + elapsed_s duration = 0; + optional boolean oneShot = 1; + optional boolean emergencyBoost = 2; + optional temperature temporarySetpoint = 3; + optional percent targetPercentage = 4; + optional percent targetReheat = 5; + } + + /** Allows a client to request that the water heater is put into a Boost state. */ + command access(invoke: manage) Boost(BoostRequest): DefaultSuccess = 0; + /** Allows a client to cancel an ongoing Boost operation. */ + command access(invoke: manage) CancelBoost(): DefaultSuccess = 1; +} + /** This cluster allows a client to manage the power draw of a device. An example of such a client could be an Energy Management System (EMS) which controls an Energy Smart Appliance (ESA). */ provisional cluster DeviceEnergyManagement = 152 { revision 4; @@ -8434,6 +8492,24 @@ endpoint 1 { ram attribute clusterRevision default = 1; } + server cluster WaterHeaterManagement { + callback attribute heaterTypes; + callback attribute heatDemand; + callback attribute tankVolume; + callback attribute estimatedHeatRequired; + callback attribute tankPercentage; + callback attribute boostState; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + callback attribute featureMap; + callback attribute clusterRevision; + + handle command Boost; + handle command CancelBoost; + } + server cluster DeviceEnergyManagement { emits event PowerAdjustStart; emits event PowerAdjustEnd; 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 9e0452b0c423fc..2e8944e8b184b7 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 @@ -13413,6 +13413,227 @@ } ] }, + { + "name": "Water Heater Management", + "code": 148, + "mfgCode": null, + "define": "WATER_HEATER_MANAGEMENT_CLUSTER", + "side": "server", + "enabled": 1, + "apiMaturity": "provisional", + "commands": [ + { + "name": "Boost", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "CancelBoost", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "HeaterTypes", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "WaterHeaterTypeBitmap", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "HeatDemand", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "WaterHeaterDemandBitmap", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TankVolume", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EstimatedHeatRequired", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "energy_mwh", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TankPercentage", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "percent", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BoostState", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "BoostStateEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "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": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, { "name": "Device Energy Management", "code": 152, diff --git a/examples/all-clusters-app/all-clusters-common/include/WhmDelegate.h b/examples/all-clusters-app/all-clusters-common/include/WhmDelegate.h new file mode 100644 index 00000000000000..ecabfe22792f25 --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/include/WhmDelegate.h @@ -0,0 +1,270 @@ +/* + * + * 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. + */ + +#pragma once + +#include +#include + +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace WaterHeaterManagement { + +class WhmManufacturer; + +enum HeatingOp +{ + TurnHeatingOn, + TurnHeatingOff, + LeaveHeatingUnchanged +}; + +// This is an application level delegate to handle operational state commands according to the specific business logic. +class WaterHeaterManagementDelegate : public WaterHeaterManagement::Delegate +{ +public: + WaterHeaterManagementDelegate(EndpointId clustersEndpoint); + + virtual ~WaterHeaterManagementDelegate() = default; + + void SetWaterHeaterManagementInstance(WaterHeaterManagement::Instance & instance); + + void SetWhmManufacturer(WhmManufacturer & whmManufacturer); + + /********************************************************************************* + * + * Methods implementing the WaterHeaterManagement::Delegate interface + * + *********************************************************************************/ + + /** + * @brief Delegate should implement a handler to start boosting the water temperature as required. + * Upon receipt, the Water Heater SHALL transition into the BOOST state, which SHALL cause the + * water in the tank (or the TargetPercentage of the water, if included) to be heated towards + * the set point (or the TemporarySetpoint, if included), which in turn may cause a call for heat, + * even if the mode is OFF, or is TIMED and it is during one of the Off periods. + * + * @param duration Indicates the time period in seconds for which the BOOST state is activated before it + * automatically reverts to the previous mode (e.g. OFF, MANUAL or TIMED). + * @param oneShot Indicates whether the BOOST state should be automatically canceled once the hot water has + * first reached the set point temperature (or the TemporarySetpoint temperature, if specified) + * for the TargetPercentage (if specified). + * @param emergencyBoost Indicates that the consumer wants the water to be heated as quickly as practicable. + * This MAY cause multiple heat sources to be activated (e.g. a heat pump and direct + * electric heating element). + * @param temporarySetpoint Indicates the target temperature to which to heat the hot water for this Boost command. + * It SHALL be used instead of the normal set point temperature whilst the BOOST state is active. + * @param targetPercentage If the tank supports the TankPercent feature, this field indicates the amount of water + * that SHALL be heated by this Boost command before the heater is switched off. + * @param targetReheat If the tank supports the TankPercent feature, and the heating by this Boost command has ceased + * because the TargetPercentage of the water in the tank has been heated to the set point (or + * TemporarySetpoint if included), this field indicates the percentage to which the hot water in + * the tank SHALL be allowed to fall before again beginning to reheat it. + * + * @return Success if the boost command is accepted; otherwise the command SHALL be rejected with appropriate error. + */ + Protocols::InteractionModel::Status HandleBoost(uint32_t duration, Optional oneShot, Optional emergencyBoost, + Optional temporarySetpoint, Optional targetPercentage, + Optional targetReheat) override; + + /** + * @brief Delegate should implement a handler to cancel a boost command. + * Upon receipt, the Water Heater SHALL transition back from the BOOST state to the previous mode + * (e.g. OFF, MANUAL or TIMED). + * + * @return It should report SUCCESS if successful and FAILURE otherwise. + */ + Protocols::InteractionModel::Status HandleCancelBoost() override; + + // ------------------------------------------------------------------ + // Get attribute methods + BitMask GetHeaterTypes() override; + BitMask GetHeatDemand() override; + uint16_t GetTankVolume() override; + int64_t GetEstimatedHeatRequired() override; + Percent GetTankPercentage() override; + BoostStateEnum GetBoostState() override; + + // ------------------------------------------------------------------ + // Set attribute methods + void SetHeaterTypes(BitMask heaterTypes); + void SetHeatDemand(BitMask heatDemand); + void SetTankVolume(uint16_t tankVolume); + void SetEstimatedHeatRequired(int64_t estimatedHeatRequired); + void SetTankPercentage(Percent tankPercentage); + void SetBoostState(BoostStateEnum boostState); + + /********************************************************************************* + * + * WaterHeaterManagementDelegate specific methods + * + *********************************************************************************/ + + /** + * @brief Set the Water Header Mode and act accordingly. + * + * @param mode The Water Heater Mode (e.g. OFF, MANUAL or TIMED). + */ + Protocols::InteractionModel::Status SetWaterHeaterMode(uint8_t mode); + + /** + * @brief Set the water temperature of the tank + * + * @param waterTemperature The water temperature in 100th's Celsius + */ + void SetWaterTemperature(uint16_t waterTemperature); + + /** + * @brief Set the target water temperature of the tank + * + * @param targetWaterTemperature The water temperature in 100th's Celsius + */ + void SetTargetWaterTemperature(uint16_t targetWaterTemperature); + + /** + * @brief Determine whether the heating sources need to be turned on or off + */ + Protocols::InteractionModel::Status CheckIfHeatNeedsToBeTurnedOnOrOff(); + + /** + * @brief Static timer callback for when Boost timer expires. + */ + static void BoostTimerExpiry(System::Layer * systemLayer, void * delegate); + + /** + * @brief Object timer callback for when Boost timer expires. + */ + void HandleBoostTimerExpiry(); + + /** + * Determines whether the tank water temperature has reached the target temperature. + * + * @return Returns True is tank water temperature has reached the target temperature, False otherwise. + */ + bool HasWaterTemperatureReachedTarget() const; + + /** + * Simulates water being drawn from the water tank. + * + * @param percentageReplaced The % of water being replaced with water with a temperature of replacedWaterTemperature. + * @param replacedWaterTemperature The temperature of the percentageReplaced water. + */ + void DrawOffHotWater(uint8_t percentageReplaced, uint16_t replacedWaterTemperature); + +private: + /** + * @brief Determine whether heating needs to be turned on or off or left as is. + * + * @param heatingOp[out] Set as determined whether heating needs to be turned on/off or left unchanged. + * + * @return Success if the heating operation could be determined otherwise returns with appropriate error. + */ + Protocols::InteractionModel::Status DetermineIfChangingHeatingState(HeatingOp & heatingOp); + +private: + /********************************************************************************* + * + * WaterHeaterManagementDelegate specific attributes + * + *********************************************************************************/ + + // Need the following so can determine which features are supported + WaterHeaterManagement::Instance * mpWhmInstance; + + // Pointer to the manufacturer specific object which understand the hardware + WhmManufacturer * mpWhmManufacturer; + + // Target water temperature in 100ths of a C + uint16_t mTargetWaterTemperature; + + // Actual water temperature in 100ths of a C + uint16_t mWaterTemperature; + + // The % of water at temperature mReplacedWaterTemperature + uint16_t mReplacedWaterTemperature; + + // Boost command parameters + + // This field SHALL indicate whether the BOOST state should be automatically canceled once the hot water has first reached the + // set point temperature (or the TemporarySetpoint temperature, if specified) for the TargetPercentage (if specified). + Optional mBoostOneShot; + + // This field indicates that the consumer wants the water to be heated as quickly as practicable. This MAY cause multiple heat + // sources to be activated (e.g. a heat pump and direct electric heating element). + Optional mBoostEmergencyBoost; + + // This field indicates the target temperature to which to heat the hot water for this Boost command. It SHALL be used instead + // of the normal set point temperature whilst the BOOST state is active. + Optional mBoostTemporarySetpoint; + + // If the tank supports the TankPercent feature, this field indicates the amount of water that SHALL be heated by this Boost + // command before the heater is switched off. This field is optional, however it SHALL be included if the TargetReheat field is + // included. + Optional mBoostTargetPercentage; + + // If the tank supports the TankPercent feature, and the heating by this Boost command has ceased because the TargetPercentage + // of the water in the tank has been heated to the set point (or TemporarySetpoint if included), this field indicates the + // percentage to which the hot water in the tank SHALL be allowed to fall before again beginning to reheat it. For example if + // the TargetPercentage was 80%, and the TargetReheat was 40%, then after initial heating to 80% hot water, the tank may have + // hot water drawn off until only 40% hot water remains. At this point the heater will begin to heat back up to 80% of hot + // water. If this field and the OneShot field were both omitted, heating would begin again after any water draw which reduced + // the TankPercentage below 80%. + Optional mBoostTargetReheat; + + // Track whether the water temperature has reached the water temperature specified in the boost command. Used in conjunction + // with the boost command boostTargetReheat parameter + bool mBoostTargetTemperatureReached; + + /********************************************************************************* + * + * Member variables implementing the WaterHeaterManagement::Delegate interface + * + *********************************************************************************/ + + // This attribute SHALL indicate the methods to call for heat that the controller supports. If a bit is set then the controller + // supports the corresponding method. + BitMask mHeaterTypes; + + // This attribute SHALL indicate if the controller is asking for heat. If a bit is set then the corresponding call for heat is + // active. + BitMask mHeatDemand; + + // This attribute SHALL indicate the volume of water that the hot water tank can hold (in units of Litres). This allows an + // energy management system to estimate the required heating energy needed to reach the target temperature. + uint16_t mTankVolume; + + // This attribute SHALL indicate the estimated heat energy needed to raise the water temperature to the target setpoint. This + // can be computed by taking the specific heat capacity of water (4182 J/kg °C) and by knowing the current temperature of the + // water, the tank volume and target temperature. + int64_t mEstimatedHeatRequired; + + // This attribute SHALL indicate an approximate level of hot water stored in the tank, which may help consumers understand the + // amount of hot water remaining in the tank. + Percent mTankPercentage; + + // This attribute SHALL indicate if the BOOST state, as triggered by a Boost command, is currently active. + BoostStateEnum mBoostState; +}; + +} // namespace WaterHeaterManagement +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/all-clusters-app/all-clusters-common/include/WhmInstance.h b/examples/all-clusters-app/all-clusters-common/include/WhmInstance.h new file mode 100644 index 00000000000000..a776bd92b99534 --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/include/WhmInstance.h @@ -0,0 +1,58 @@ +/* + * + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace WaterHeaterManagement { +using namespace chip::app::Clusters::WaterHeaterManagement; + +class WaterHeaterManagementInstance : public Instance +{ +public: + WaterHeaterManagementInstance(EndpointId aEndpointId, WaterHeaterManagementDelegate & aDelegate, Feature aFeature) : + WaterHeaterManagement::Instance(aEndpointId, aDelegate, aFeature) + { + mDelegate = &aDelegate; + } + + // Delete copy constructor and assignment operator. + WaterHeaterManagementInstance(const WaterHeaterManagementInstance &) = delete; + WaterHeaterManagementInstance(const WaterHeaterManagementInstance &&) = delete; + WaterHeaterManagementInstance & operator=(const WaterHeaterManagementInstance &) = delete; + + CHIP_ERROR Init(); + void Shutdown(); + + WaterHeaterManagementDelegate * GetDelegate() { return mDelegate; }; + +private: + WaterHeaterManagementDelegate * mDelegate; +}; + +} // namespace WaterHeaterManagement +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/all-clusters-app/all-clusters-common/include/WhmMain.h b/examples/all-clusters-app/all-clusters-common/include/WhmMain.h new file mode 100644 index 00000000000000..21dc9d86519307 --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/include/WhmMain.h @@ -0,0 +1,32 @@ +/* + * + * 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. + */ + +#pragma once + +namespace chip { +namespace app { +namespace Clusters { +namespace WaterHeaterManagement { + +void WhmApplicationInit(); +void WhmApplicationShutdown(); + +} // namespace WaterHeaterManagement +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/all-clusters-app/all-clusters-common/include/WhmManufacturer.h b/examples/all-clusters-app/all-clusters-common/include/WhmManufacturer.h new file mode 100644 index 00000000000000..d51e6a62f2021c --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/include/WhmManufacturer.h @@ -0,0 +1,139 @@ +/* + * + * 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. + */ + +#pragma once + +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace WaterHeaterManagement { + +/** + * The WhmManufacturer example class + * + * Helps with handling the test triggers. + */ + +class WhmManufacturer +{ +public: + WhmManufacturer(WaterHeaterManagementInstance * whmInstance) { mWhmInstance = whmInstance; } + + WaterHeaterManagementInstance * GetWhmInstance() { return mWhmInstance; } + + WaterHeaterManagementDelegate * GetWhmDelegate() + { + if (mWhmInstance) + { + return mWhmInstance->GetDelegate(); + } + + return nullptr; + } + + /** + * @brief Called at start up to apply hardware settings + */ + CHIP_ERROR Init(); + + /** + * @brief Called at shutdown + */ + CHIP_ERROR Shutdown(); + + /** + * @brief Called to determine which heating sources to use, + */ + BitMask DetermineHeatingSources(); + + /** + * @brief Turn the heating of the water tank on. + * + * @param emergencyBoost Indicates that the consumer wants the water to be heated as quickly as practicable. This MAY cause + * multiple heat sources to be activated (e.g. a heat pump and direct electric heating element). + * @return An error if a problem occurs turning the heating on + */ + Protocols::InteractionModel::Status TurnHeatingOn(bool emergencyBoost); + + /** + * @brief Turn the heating of the water tank off. + * + * @return An error if a problem occurs turning the heating off + */ + Protocols::InteractionModel::Status TurnHeatingOff(); + + /** + * @brief Called to handle a boost command. + * + * @param duration Indicates the time period in seconds for which the BOOST state is activated before it automatically reverts + * to the previous mode (e.g. OFF, MANUAL or TIMED). + * @param oneShot Indicates whether the BOOST state should be automatically canceled once the hot water has first reached the + * set point temperature (or the TemporarySetpoint temperature, if specified) for the TargetPercentage (if + * specified). + * @param emergencyBoost Indicates that the consumer wants the water to be heated as quickly as practicable. This MAY cause + * multiple heat sources to be activated (e.g. a heat pump and direct electric heating element). + * @param temporarySetpoint Indicates the target temperature to which to heat the hot water for this Boost command. It SHALL be + * used instead of the normal set point temperature whilst the BOOST state is active. + * @param targetPercentage If the tank supports the TankPercent feature, this field indicates the amount of water that SHALL be + * heated by this Boost command before the heater is switched off. + * @param targetReheat If the tank supports the TankPercent feature, and the heating by this Boost command has ceased because + * the TargetPercentage of the water in the tank has been heated to the set point (or TemporarySetpoint if + * included), this field indicates the percentage to which the hot water in the tank SHALL be allowed to fall before again + * beginning to reheat it. + * + * @return Success if the boost command is successful; otherwise return the appropriate error. + */ + Protocols::InteractionModel::Status BoostCommandStarted(uint32_t duration, Optional oneShot, + Optional emergencyBoost, Optional temporarySetpoint, + Optional targetPercentage, + Optional targetReheat); + + /** + * @brief Called when the Boost command has been cancelled. + * + * @return It should report SUCCESS if successful and FAILURE otherwise. + */ + Protocols::InteractionModel::Status BoostCommandCancelled(); + + /** + * @brief Called when a boost command has completed. + */ + void BoostCommandFinished(); + +private: + WaterHeaterManagementInstance * mWhmInstance; +}; + +/** @brief Helper function to return the singleton WhmManufacturer instance + * + * This is needed by the WhmManufacturer class to support TestEventTriggers + * which are called outside of any class context. This allows the WhmManufacturer + * class to return the relevant Delegate instance in which to invoke the test + * events on. + * + * This function is typically found in main.cpp or wherever the singleton is created. + */ +WhmManufacturer * GetWhmManufacturer(); + +} // namespace WaterHeaterManagement +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/all-clusters-app/all-clusters-common/include/water-heater-mode.h b/examples/all-clusters-app/all-clusters-common/include/water-heater-mode.h old mode 100644 new mode 100755 diff --git a/examples/all-clusters-app/all-clusters-common/src/WhmDelegateImpl.cpp b/examples/all-clusters-app/all-clusters-common/src/WhmDelegateImpl.cpp new file mode 100644 index 00000000000000..fd19a3c59d740b --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/src/WhmDelegateImpl.cpp @@ -0,0 +1,461 @@ +/* + * + * 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 + +#include +#include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::WaterHeaterManagement; + +using Protocols::InteractionModel::Status; + +WaterHeaterManagementDelegate::WaterHeaterManagementDelegate(EndpointId clustersEndpoint) : + mpWhmInstance(nullptr), mpWhmManufacturer(nullptr), mWaterTemperature(0), mReplacedWaterTemperature(0), + mBoostTargetTemperatureReached(false), mTankVolume(0), mEstimatedHeatRequired(0), mTankPercentage(0), + mBoostState(BoostStateEnum::kInactive) +{} + +void WaterHeaterManagementDelegate::SetWaterHeaterManagementInstance(WaterHeaterManagement::Instance & instance) +{ + mpWhmInstance = &instance; +} + +void WaterHeaterManagementDelegate::SetWhmManufacturer(WhmManufacturer & whmManufacturer) +{ + mpWhmManufacturer = &whmManufacturer; +} + +/********************************************************************************* + * + * Methods implementing the WaterHeaterManagement::Delegate interace + * + *********************************************************************************/ + +BitMask WaterHeaterManagementDelegate::GetHeaterTypes() +{ + return mHeaterTypes; +} + +BitMask WaterHeaterManagementDelegate::GetHeatDemand() +{ + return mHeatDemand; +} + +uint16_t WaterHeaterManagementDelegate::GetTankVolume() +{ + return mTankVolume; +} + +int64_t WaterHeaterManagementDelegate::GetEstimatedHeatRequired() +{ + return mEstimatedHeatRequired; +} + +Percent WaterHeaterManagementDelegate::GetTankPercentage() +{ + return mTankPercentage; +} + +BoostStateEnum WaterHeaterManagementDelegate::GetBoostState() +{ + return mBoostState; +} + +void WaterHeaterManagementDelegate::SetHeaterTypes(BitMask heaterTypes) +{ + if (mHeaterTypes != heaterTypes) + { + mHeaterTypes = heaterTypes; + + MatterReportingAttributeChangeCallback(mEndpointId, WaterHeaterManagement::Id, Attributes::HeaterTypes::Id); + } +} + +void WaterHeaterManagementDelegate::SetHeatDemand(BitMask heatDemand) +{ + if (mHeatDemand != heatDemand) + { + mHeatDemand = heatDemand; + + MatterReportingAttributeChangeCallback(mEndpointId, WaterHeaterManagement::Id, Attributes::HeatDemand::Id); + } +} + +void WaterHeaterManagementDelegate::SetTankVolume(uint16_t tankVolume) +{ + if (mTankVolume != tankVolume) + { + mTankVolume = tankVolume; + + MatterReportingAttributeChangeCallback(mEndpointId, WaterHeaterManagement::Id, Attributes::TankVolume::Id); + } +} + +void WaterHeaterManagementDelegate::SetEstimatedHeatRequired(int64_t estimatedHeatRequired) +{ + if (mEstimatedHeatRequired != estimatedHeatRequired) + { + mEstimatedHeatRequired = estimatedHeatRequired; + + MatterReportingAttributeChangeCallback(mEndpointId, WaterHeaterManagement::Id, Attributes::EstimatedHeatRequired::Id); + } +} + +void WaterHeaterManagementDelegate::SetTankPercentage(Percent tankPercentage) +{ + if (mpWhmInstance != nullptr && mpWhmInstance->HasFeature(Feature::kTankPercent)) + { + if (mTankPercentage != tankPercentage) + { + mTankPercentage = tankPercentage; + + CheckIfHeatNeedsToBeTurnedOnOrOff(); + + MatterReportingAttributeChangeCallback(mEndpointId, WaterHeaterManagement::Id, Attributes::TankPercentage::Id); + } + } +} + +void WaterHeaterManagementDelegate::SetBoostState(BoostStateEnum boostState) +{ + if (mBoostState != boostState) + { + mBoostState = boostState; + + MatterReportingAttributeChangeCallback(mEndpointId, WaterHeaterManagement::Id, Attributes::BoostState::Id); + } +} + +/** + * @brief Handles the boost command + * + * Upon receipt, the Water Heater SHALL transition into the BOOST state, which SHALL cause the water in the tank (or + * the TargetPercentage of the water, if included) to be heated towards the set point (or the TemporarySetpoint, if + * included), which in turn may cause a call for heat, even if the mode is OFF, or is TIMED and it is during one of + * the Off periods. + */ +Status WaterHeaterManagementDelegate::HandleBoost(uint32_t durationS, Optional oneShot, Optional emergencyBoost, + Optional temporarySetpoint, Optional targetPercentage, + Optional targetReheat) +{ + Status status = Status::Success; + + ChipLogProgress(AppServer, "HandleBoost"); + + // Keep track of the boost command parameters + mBoostOneShot = oneShot; + mBoostEmergencyBoost = emergencyBoost; + mBoostTemporarySetpoint = temporarySetpoint; + mBoostTargetPercentage = targetPercentage; + mBoostTargetReheat = targetReheat; + + mBoostTargetTemperatureReached = false; + + // If a timer is running, cancel it so we can start a new boost command with the new duration + if (mBoostState == BoostStateEnum::kActive) + { + DeviceLayer::SystemLayer().CancelTimer(BoostTimerExpiry, this); + } + + CHIP_ERROR err = DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(durationS), BoostTimerExpiry, this); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "HandleBoost: Unable to start a Boost timer: %" CHIP_ERROR_FORMAT, err.Format()); + + // Not a lot we can do -> just set the boost state to inactive + SetBoostState(BoostStateEnum::kInactive); + + return Status::Failure; + } + + // Now running a boost command + SetBoostState(BoostStateEnum::kActive); + + if (mpWhmManufacturer != nullptr) + { + status = mpWhmManufacturer->BoostCommandStarted(durationS, oneShot, emergencyBoost, temporarySetpoint, targetPercentage, + targetReheat); + } + else + { + status = Status::InvalidInState; + ChipLogError(AppServer, "HandleBoost: mpWhmManufacturer == nullptr"); + } + + if (status == Status::Success) + { + // See if the heat needs to be turned on or off as a result of this boost command + status = CheckIfHeatNeedsToBeTurnedOnOrOff(); + } + + return status; +} + +void WaterHeaterManagementDelegate::BoostTimerExpiry(System::Layer * systemLayer, void * delegate) +{ + WaterHeaterManagementDelegate * dg = static_cast(delegate); + + dg->HandleBoostTimerExpiry(); +} + +/** + * @brief Timer for handling the completion of a boost command + */ +void WaterHeaterManagementDelegate::HandleBoostTimerExpiry() +{ + ChipLogError(AppServer, "HandleBoostTimerExpiry"); + + // The PowerAdjustment is no longer in progress + SetBoostState(BoostStateEnum::kInactive); + + if (mpWhmManufacturer != nullptr) + { + mpWhmManufacturer->BoostCommandFinished(); + } + else + { + ChipLogError(AppServer, "HandleBoostTimerExpiry: mpWhmManufacturer == nullptr"); + } + + CheckIfHeatNeedsToBeTurnedOnOrOff(); +} + +/** + * @brief Cancels a boost command + * + * Upon receipt, the Water Heater SHALL transition back from the BOOST state to the previous mode (e.g. OFF, MANUAL or TIMED). + */ +Status WaterHeaterManagementDelegate::HandleCancelBoost() +{ + ChipLogProgress(AppServer, "HandleCancelBoost"); + + if (mBoostState == BoostStateEnum::kActive) + { + SetBoostState(BoostStateEnum::kInactive); + mBoostEmergencyBoost.ClearValue(); + + DeviceLayer::SystemLayer().CancelTimer(BoostTimerExpiry, this); + + VerifyOrReturnValue(mpWhmManufacturer != nullptr, Status::InvalidInState); + + Status status = mpWhmManufacturer->BoostCommandCancelled(); + VerifyOrReturnValue(status == Status::Success, status); + + status = CheckIfHeatNeedsToBeTurnedOnOrOff(); + VerifyOrReturnValue(status == Status::Success, status); + } + + return Status::Success; +} + +/********************************************************************************* + * + * WaterHeaterManagementDelegate specific methods + * + *********************************************************************************/ + +void WaterHeaterManagementDelegate::SetWaterTemperature(uint16_t waterTemperature) +{ + mWaterTemperature = waterTemperature; + + if (mpWhmInstance != nullptr && mpWhmInstance->HasFeature(Feature::kTankPercent)) + { + mTankPercentage = 100; + } + + // See if the heat needs to be turned on or off + CheckIfHeatNeedsToBeTurnedOnOrOff(); +} + +void WaterHeaterManagementDelegate::SetTargetWaterTemperature(uint16_t targetWaterTemperature) +{ + mTargetWaterTemperature = targetWaterTemperature; + + // See if the heat needs to be turned on or off + CheckIfHeatNeedsToBeTurnedOnOrOff(); +} + +void WaterHeaterManagementDelegate::DrawOffHotWater(uint8_t percentageReplaced, uint16_t replacedWaterTemperature) +{ + // Only supported in the kTankPercent is supported. + // Replaces percentageReplaced% of the water in the tank with water of a temperature replacedWaterTemperature + if (mpWhmInstance != nullptr && mpWhmInstance->HasFeature(Feature::kTankPercent)) + { + // See if all of the water has now been replaced with replacedWaterTemperature + if (mTankPercentage >= percentageReplaced) + { + mTankPercentage -= percentageReplaced; + } + else + { + mTankPercentage = 0; + } + + mReplacedWaterTemperature = replacedWaterTemperature; + + CheckIfHeatNeedsToBeTurnedOnOrOff(); + } +} + +bool WaterHeaterManagementDelegate::HasWaterTemperatureReachedTarget() const +{ + // Determine the target temperature. If a boost command is in progress and has a mBoostTemporarySetpoint value use that as the + // target temperature. + // Note, in practise the actual heating is likely to be controlled by the thermostat's occupiedHeatingSetpoint most of the + // time, and the TemporarySetpoint (if not null) would be overiding the thermostat's occupiedHeatingSetpoint. + // However, this code doesn't rely upon the thermostat cluster. + uint16_t targetTemperature = (mBoostState == BoostStateEnum::kActive && mBoostTemporarySetpoint.HasValue()) + ? static_cast(mBoostTemporarySetpoint.Value()) + : mTargetWaterTemperature; + + VerifyOrReturnValue(mWaterTemperature >= targetTemperature, false); + + if (mBoostState == BoostStateEnum::kActive) + { + if (mBoostTargetTemperatureReached && mBoostTargetReheat.HasValue()) + { + // If the tank supports the TankPercent feature, and the heating by this Boost command has ceased because the + // TargetPercentage of the water in the tank has been heated to the set point (or TemporarySetpoint if included), + // mBoostTargetReheat indicates the percentage to which the hot water in the tank SHALL be allowed to fall before + // again beginning to reheat it. + // + // For example if the TargetPercentage was 80%, and the TargetReheat was 40%, then after initial heating to 80% hot + // water, the tank may have hot water drawn off until only 40% hot water remains. At this point the heater will begin to + // heat back up to 80% of hot water. If this field and the OneShot field were both omitted, heating would begin again + // after any water draw which reduced the TankPercentage below 80%. + + // If this field is included then the TargetPercentage field SHALL also be included, and the OneShot excluded. + VerifyOrReturnValue(mTankPercentage >= mBoostTargetReheat.Value(), false); + } + else if (mBoostTargetPercentage.HasValue()) + { + // If tank percentage is supported AND the targetPercentage.HasValue() then use target percentage to heat up. + VerifyOrReturnValue(mTankPercentage >= mBoostTargetPercentage.Value(), false); + } + } + + // Must have reached the right temperature + return true; +} + +Status WaterHeaterManagementDelegate::CheckIfHeatNeedsToBeTurnedOnOrOff() +{ + VerifyOrReturnError(mpWhmManufacturer != nullptr, Status::InvalidInState); + + HeatingOp heatingOp = HeatingOp::LeaveHeatingUnchanged; + + Status status = DetermineIfChangingHeatingState(heatingOp); + + VerifyOrReturnError(status == Status::Success, status); + + if (heatingOp == HeatingOp::TurnHeatingOn) + { + status = mpWhmManufacturer->TurnHeatingOn(mBoostEmergencyBoost.HasValue() ? mBoostEmergencyBoost.Value() : false); + } + else if (heatingOp == HeatingOp::TurnHeatingOff) + { + // If running a boost command with the oneShot parameter and turning heat off, then must have + // reached the boost command target temperature -> that's the boost command complete. + if (mBoostState == BoostStateEnum::kActive && mBoostOneShot.HasValue() && mBoostOneShot.Value()) + { + SetBoostState(BoostStateEnum::kInactive); + + DeviceLayer::SystemLayer().CancelTimer(BoostTimerExpiry, this); + + mBoostEmergencyBoost.ClearValue(); + + status = mpWhmManufacturer->BoostCommandCancelled(); + } + + // Turn the heating off + status = mpWhmManufacturer->TurnHeatingOff(); + } + + return status; +} + +Status WaterHeaterManagementDelegate::DetermineIfChangingHeatingState(HeatingOp & heatingOp) +{ + heatingOp = LeaveHeatingUnchanged; + + if (!HasWaterTemperatureReachedTarget()) + { + VerifyOrReturnError(WaterHeaterMode::Instance() != nullptr, Status::InvalidInState); + + uint8_t mode = WaterHeaterMode::Instance()->GetCurrentMode(); + + // The water in the tank is not at the target temperature. See if heating is currently off + if (mHeatDemand.Raw() == 0) + { + // Need to track whether the water temperature has reached the target temperature for the boost + // command when a oneShot option has been applied. + if (mBoostState == BoostStateEnum::kActive) + { + mBoostTargetTemperatureReached = false; + } + + // If a boost command is in progress or in manual mode, find a heating source and "turn it on". + if (mBoostState == BoostStateEnum::kActive || mode == WaterHeaterMode::kModeManual) + { + heatingOp = HeatingOp::TurnHeatingOn; + } + } + else if (mBoostState == BoostStateEnum::kInactive && mode == WaterHeaterMode::kModeOff) + { + // The water temperature is not at the target temperature but there is no boost command in progress and the mode is Off + // so need to ensure the heating is turned off. + ChipLogError(AppServer, "DetermineIfChangingHeatingState turning heating off due to no boost cmd and kModeOff"); + + heatingOp = HeatingOp::TurnHeatingOff; + } + } + else if (mHeatDemand.Raw() != 0) + { + // The water in the tank has reached the target temperature - need to turn the heating off + heatingOp = HeatingOp::TurnHeatingOff; + + // If a boost command is in progress, record that the target temperature has been reached. + mBoostTargetTemperatureReached = (mBoostState == BoostStateEnum::kActive); + } + + return Status::Success; +} + +Status WaterHeaterManagementDelegate::SetWaterHeaterMode(uint8_t modeValue) +{ + VerifyOrReturnError(WaterHeaterMode::Instance() != nullptr, Status::InvalidInState); + + if (!WaterHeaterMode::Instance()->IsSupportedMode(modeValue)) + { + ChipLogError(AppServer, "SetWaterHeaterMode bad mode"); + return Status::ConstraintError; + } + + Status status = WaterHeaterMode::Instance()->UpdateCurrentMode(modeValue); + if (status != Status::Success) + { + ChipLogError(AppServer, "SetWaterHeaterMode updateMode failed 0x%02x", to_underlying(status)); + return status; + } + + return CheckIfHeatNeedsToBeTurnedOnOrOff(); +} diff --git a/examples/all-clusters-app/all-clusters-common/src/WhmInstance.cpp b/examples/all-clusters-app/all-clusters-common/src/WhmInstance.cpp new file mode 100644 index 00000000000000..9d4ad58fefd976 --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/src/WhmInstance.cpp @@ -0,0 +1,34 @@ +/* + * + * 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 + +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::WaterHeaterManagement; + +CHIP_ERROR WaterHeaterManagementInstance::Init() +{ + ChipLogDetail(AppServer, "WaterHeaterManagementInstance::Init()"); + return Instance::Init(); +} + +void WaterHeaterManagementInstance::Shutdown() +{ + Instance::Shutdown(); +} diff --git a/examples/all-clusters-app/all-clusters-common/src/WhmMain.cpp b/examples/all-clusters-app/all-clusters-common/src/WhmMain.cpp new file mode 100644 index 00000000000000..ea59fef2d095f5 --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/src/WhmMain.cpp @@ -0,0 +1,194 @@ +/* + * + * 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 +#include + +#include +#include +#include +#include + +static constexpr int WHM_ENDPOINT = 1; + +using namespace chip; +using namespace chip::app; +using namespace chip::app::DataModel; +using namespace chip::app::Clusters; + +namespace chip { +namespace app { +namespace Clusters { +namespace WaterHeaterManagement { + +static std::unique_ptr gWhmDelegate; +static std::unique_ptr gWhmInstance; + +static std::unique_ptr gWhmManufacturer; + +WhmManufacturer * GetWhmManufacturer() +{ + return gWhmManufacturer.get(); +} + +/* + * @brief Creates a Delegate and Instance for Water Heater Management cluster + * + * The Instance is a container around the Delegate, so + * create the Delegate first, then wrap it in the Instance + * Then call the Instance->Init() to register the attribute and command handlers + */ +CHIP_ERROR WhmInit() +{ + CHIP_ERROR err; + + if (gWhmDelegate || gWhmInstance) + { + ChipLogError(AppServer, "WaterHeaterManager Instance or Delegate already exist."); + return CHIP_ERROR_INCORRECT_STATE; + } + + gWhmDelegate = std::make_unique(WHM_ENDPOINT); + if (!gWhmDelegate) + { + ChipLogError(AppServer, "Failed to allocate memory for WaterHeaterManagementDelegate"); + return CHIP_ERROR_NO_MEMORY; + } + + /* Manufacturer may optionally not support all features, commands & attributes */ + gWhmInstance = std::make_unique( + EndpointId(WHM_ENDPOINT), *gWhmDelegate, BitMask(Feature::kEnergyManagement, Feature::kTankPercent)); + if (!gWhmInstance) + { + ChipLogError(AppServer, "Failed to allocate memory for WaterHeaterManagementInstance"); + gWhmDelegate.reset(); + return CHIP_ERROR_NO_MEMORY; + } + + /* Register Attribute & Command handlers */ + err = gWhmInstance->Init(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "gWhmInstance->Init failed %s", chip::ErrorStr(err)); + gWhmInstance.reset(); + gWhmDelegate.reset(); + return err; + } + + gWhmDelegate->SetWaterHeaterManagementInstance(*gWhmInstance); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR WhmShutdown() +{ + /* Do this in the order Instance first, then delegate + * Ensure we call the Instance->Shutdown to free attribute & command handlers first + */ + if (gWhmInstance) + { + /* Deregister attribute & command handlers */ + gWhmInstance->Shutdown(); + gWhmInstance.reset(); + } + + if (gWhmDelegate) + { + gWhmDelegate.reset(); + } + + return CHIP_NO_ERROR; +} + +/* + * @brief Creates a WhmManufacturer class to hold the Whm cluster + * + * The Instance is a container around the Delegate, so + * create the Delegate first, then wrap it in the Instance + * Then call the Instance->Init() to register the attribute and command handlers + */ +CHIP_ERROR WhmManufacturerInit() +{ + CHIP_ERROR err; + + if (gWhmManufacturer) + { + ChipLogError(AppServer, "WhmManufacturer already exist."); + return CHIP_ERROR_INCORRECT_STATE; + } + + /* Now create WhmManufacturer */ + gWhmManufacturer = std::make_unique(gWhmInstance.get()); + if (!gWhmManufacturer) + { + ChipLogError(AppServer, "Failed to allocate memory for WhmManufacturer"); + return CHIP_ERROR_NO_MEMORY; + } + + /* Call Manufacturer specific init */ + err = gWhmManufacturer->Init(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "Init failed on gWhmManufacturer"); + gWhmManufacturer.reset(); + return err; + } + + // Let the WhmDelegate know about the WhmManufacturer object. + gWhmDelegate->SetWhmManufacturer(*gWhmManufacturer); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR WhmManufacturerShutdown() +{ + if (gWhmManufacturer) + { + /* Shutdown the WhmManufacturer */ + gWhmManufacturer->Shutdown(); + gWhmManufacturer.reset(); + } + + return CHIP_NO_ERROR; +} + +void WhmApplicationInit() +{ + if (WhmInit() != CHIP_NO_ERROR) + { + return; + } + + /* Do this last so that the instances for other clusters can be wrapped inside */ + if (WhmManufacturerInit() != CHIP_NO_ERROR) + { + WhmShutdown(); + return; + } +} + +void WhmApplicationShutdown() +{ + /* Shutdown in reverse order that they were created */ + WhmManufacturerShutdown(); +} + +} // namespace WaterHeaterManagement +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/all-clusters-app/all-clusters-common/src/WhmManufacturer.cpp b/examples/all-clusters-app/all-clusters-common/src/WhmManufacturer.cpp new file mode 100644 index 00000000000000..dd8452a5c816ca --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/src/WhmManufacturer.cpp @@ -0,0 +1,286 @@ +/* + * + * 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 +#include +#include + +#include +#include +#include + +using namespace chip; +using namespace chip::app::Clusters::WaterHeaterManagement; + +using Protocols::InteractionModel::Status; + +namespace chip { +namespace app { +namespace Clusters { +namespace WaterHeaterManagement { + +CHIP_ERROR WhmManufacturer::Init() +{ + WaterHeaterManagementDelegate * dg = GetWhmManufacturer()->GetWhmDelegate(); + if (dg == nullptr) + { + ChipLogError(AppServer, "WhmDelegate is not initialized"); + return CHIP_ERROR_UNINITIALIZED; + } + + dg->SetHeaterTypes(BitMask(WaterHeaterTypeBitmap::kImmersionElement1)); + dg->SetHeatDemand(BitMask(WaterHeaterDemandBitmap::kImmersionElement1)); + dg->SetEstimatedHeatRequired(10000); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR WhmManufacturer::Shutdown() +{ + return CHIP_NO_ERROR; +} + +BitMask WhmManufacturer::DetermineHeatingSources() +{ + WaterHeaterManagementDelegate * dg = GetWhmManufacturer()->GetWhmDelegate(); + if (dg == nullptr) + { + ChipLogError(AppServer, "WhmDelegate is not initialized"); + return BitMask(0); + } + + // A list of valid heaterTypes + uint8_t waterHeaterTypeValues[] = { + static_cast(WaterHeaterTypeBitmap::kImmersionElement1), + static_cast(WaterHeaterTypeBitmap::kImmersionElement2), + static_cast(WaterHeaterTypeBitmap::kHeatPump), + static_cast(WaterHeaterTypeBitmap::kBoiler), + static_cast(WaterHeaterTypeBitmap::kOther), + }; + + // The corresponding list of valid headerDemands + uint8_t waterHeaterDemandValues[] = { + static_cast(WaterHeaterDemandBitmap::kImmersionElement1), + static_cast(WaterHeaterDemandBitmap::kImmersionElement2), + static_cast(WaterHeaterDemandBitmap::kHeatPump), + static_cast(WaterHeaterDemandBitmap::kBoiler), + static_cast(WaterHeaterDemandBitmap::kOther), + }; + + // Iterate across the valid waterHeaterTypes seeing which heating sources are available based on heaterTypes. + // Set the corresponding bit in the heaterDemand bitmap. + BitMask heaterTypes = dg->GetHeaterTypes(); + + uint8_t heaterDemandMask = 0; + for (uint16_t idx = 0; idx < static_cast(sizeof(waterHeaterTypeValues) / sizeof(waterHeaterTypeValues[0])); idx++) + { + // Is this heating source being used? + if (heaterTypes.Raw() & waterHeaterTypeValues[idx]) + { + heaterDemandMask |= waterHeaterDemandValues[idx]; + } + } + + return BitMask(heaterDemandMask); +} + +Status WhmManufacturer::TurnHeatingOn(bool emergencyBoost) +{ + Status status = Status::Success; + + ChipLogProgress(AppServer, "WhmManufacturer::TurnHeatingOn"); + + WaterHeaterManagementDelegate * dg = GetWhmDelegate(); + + if (emergencyBoost) + { + // emergencyBoost that the consumer wants the water to be heated as quickly as practicable. + // Thus, cause multiple heat sources to be activated + dg->SetHeatDemand(BitMask(WaterHeaterDemandBitmap::kImmersionElement1, + WaterHeaterDemandBitmap::kImmersionElement2)); + } + else + { + dg->SetHeatDemand(BitMask(WaterHeaterDemandBitmap::kImmersionElement1)); + } + + return status; +} + +Status WhmManufacturer::TurnHeatingOff() +{ + Status status = Status::Success; + + ChipLogProgress(AppServer, "WhmManufacturer::TurnHeatingOff"); + + WaterHeaterManagementDelegate * dg = GetWhmDelegate(); + + dg->SetHeatDemand(BitMask(0)); + + return status; +} + +Status WhmManufacturer::BoostCommandStarted(uint32_t duration, Optional oneShot, Optional emergencyBoost, + Optional temporarySetpoint, Optional targetPercentage, + Optional targetReheat) +{ + return Status::Success; +} + +Status WhmManufacturer::BoostCommandCancelled() +{ + return Status::Success; +} + +void WhmManufacturer::BoostCommandFinished() {} + +WaterHeaterManagementDelegate * GetWhmDelegate() +{ + WhmManufacturer * mn = GetWhmManufacturer(); + VerifyOrDieWithMsg(mn != nullptr, AppServer, "WhmManufacturer is null"); + + WaterHeaterManagementDelegate * wg = mn->GetWhmDelegate(); + VerifyOrDieWithMsg(wg != nullptr, AppServer, "WhmDelegate is null"); + + return wg; +} + +void SetTestEventTrigger_BasicInstallationTestEvent() +{ + WaterHeaterManagementDelegate * dg = GetWhmDelegate(); + + // Simulate installation in a 100L tank full of water at 20C, with a target temperature of 60C, in OFF mode + dg->SetTankVolume(100); + dg->SetTargetWaterTemperature(6000); + dg->SetHeaterTypes(BitMask(WaterHeaterTypeBitmap::kImmersionElement1)); + dg->DrawOffHotWater(100, 2000); +} + +void SetTestEventTrigger_BasicInstallationTestEventClear() {} + +void SetTestEventTrigger_WaterTemperature20CTestEvent() +{ + WaterHeaterManagementDelegate * dg = GetWhmDelegate(); + + // Simulate 100% of the water in the tank being at 20C + dg->SetWaterTemperature(2000); +} + +void SetTestEventTrigger_WaterTemperature61CTestEvent() +{ + WaterHeaterManagementDelegate * dg = GetWhmDelegate(); + + // Simulate 100% of the water in the tank being at 61C + dg->SetWaterTemperature(6100); +} + +void SetTestEventTrigger_WaterTemperature66CTestEvent() +{ + WaterHeaterManagementDelegate * dg = GetWhmDelegate(); + + // Simulate 100% of the water in the tank being at 66C + dg->SetWaterTemperature(6600); +} + +void SetTestEventTrigger_ManualModeTestEvent() +{ + WaterHeaterManagementDelegate * dg = GetWhmDelegate(); + + // Simulate the Water Heater Mode being set to MANUAL + Status status = dg->SetWaterHeaterMode(WaterHeaterMode::kModeManual); + if (status != Status::Success) + { + ChipLogError(Zcl, "SetTestEventTrigger_OffModeTestEvent setting mode -> KModeManual failed 0x%02x", to_underlying(status)); + } +} + +void SetTestEventTrigger_OffModeTestEvent() +{ + WaterHeaterManagementDelegate * dg = GetWhmDelegate(); + + // Simulate the Water Heater Mode being set to OFF + Status status = dg->SetWaterHeaterMode(WaterHeaterMode::kModeOff); + if (status != Status::Success) + { + ChipLogError(Zcl, "SetTestEventTrigger_OffModeTestEvent setting mode -> KModeOff failed 0x%02x", to_underlying(status)); + } +} + +void SetTestEventTrigger_DrawOffHotWaterTestEvent() +{ + WaterHeaterManagementDelegate * dg = GetWhmDelegate(); + + // Simulate drawing off 25% of the tank volume of hot water, replaced with water at 20C + dg->DrawOffHotWater(25, 2000); +} + +} // namespace WaterHeaterManagement +} // namespace Clusters +} // namespace app +} // namespace chip + +using namespace chip::app::Clusters::WaterHeaterManagement; + +bool HandleWaterHeaterManagementTestEventTrigger(uint64_t eventTrigger) +{ + WaterHeaterManagementTrigger trigger = static_cast(eventTrigger); + + switch (trigger) + { + case WaterHeaterManagementTrigger::kBasicInstallationTestEvent: + ChipLogProgress(Support, + "[Whm::kBasicInstallationTestEvent] => Simulate installation in a 100L tank full of water at 20C, with a " + "target temperature of 60C, in OFF mode"); + SetTestEventTrigger_BasicInstallationTestEvent(); + break; + case WaterHeaterManagementTrigger::kBasicInstallationTestEventClear: + ChipLogProgress(Support, "[Whm::kBasicInstallationTestEventClear] => End simulation of installation"); + SetTestEventTrigger_BasicInstallationTestEventClear(); + break; + case WaterHeaterManagementTrigger::kWaterTemperature20CTestEvent: + ChipLogProgress(Support, "[Whm::kWaterTemperature20CTestEvent] => Simulate 100%% of the water in the tank being at 20C"); + SetTestEventTrigger_WaterTemperature20CTestEvent(); + break; + case WaterHeaterManagementTrigger::kWaterTemperature61CTestEvent: + ChipLogProgress(Support, "[Whm::kWaterTemperature61CTestEvent] => Simulate 100%% of the water in the tank being at 61C"); + SetTestEventTrigger_WaterTemperature61CTestEvent(); + break; + case WaterHeaterManagementTrigger::kWaterTemperature66CTestEvent: + ChipLogProgress(Support, "[Whm::kWaterTemperature66CTestEvent] => Simulate 100%% of the water in the tank being at 66C"); + SetTestEventTrigger_WaterTemperature66CTestEvent(); + break; + case WaterHeaterManagementTrigger::kManualModeTestEvent: + ChipLogProgress(Support, "[Whm::kManualModeTestEvent] => Simulate the Water Heater Mode being set to MANUAL"); + SetTestEventTrigger_ManualModeTestEvent(); + break; + case WaterHeaterManagementTrigger::kOffModeTestEvent: + ChipLogProgress(Support, "[Whm::kOffModeTestEvent] => Simulate the Water Heater Mode being set to OFF"); + SetTestEventTrigger_OffModeTestEvent(); + break; + case WaterHeaterManagementTrigger::kDrawOffHotWaterTestEvent: + ChipLogProgress(Support, + "[Whm::kDrawOffHotWaterTestEvent] => Simulate drawing off 25%% of the tank volume of hot water, replaced " + "with water at 20C"); + SetTestEventTrigger_DrawOffHotWaterTestEvent(); + break; + default: + return false; + } + + return true; +} diff --git a/examples/all-clusters-app/all-clusters-common/src/water-heater-mode.cpp b/examples/all-clusters-app/all-clusters-common/src/water-heater-mode.cpp old mode 100644 new mode 100755 diff --git a/examples/all-clusters-app/linux/BUILD.gn b/examples/all-clusters-app/linux/BUILD.gn index 3564153a482cca..0bf2e1445028b6 100644 --- a/examples/all-clusters-app/linux/BUILD.gn +++ b/examples/all-clusters-app/linux/BUILD.gn @@ -29,6 +29,10 @@ if (chip_enable_pw_rpc) { source_set("chip-all-clusters-common") { sources = [ + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/WhmDelegateImpl.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/WhmInstance.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/WhmMain.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/WhmManufacturer.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/air-quality-instance.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/boolcfg-stub.cpp", diff --git a/examples/all-clusters-app/linux/args.gni b/examples/all-clusters-app/linux/args.gni index d414ad5dedaf5d..92d01ea3358b30 100644 --- a/examples/all-clusters-app/linux/args.gni +++ b/examples/all-clusters-app/linux/args.gni @@ -29,3 +29,4 @@ matter_log_json_payload_decode_full = true matter_log_json_payload_hex = true chip_enable_smoke_co_trigger = true chip_enable_boolean_state_configuration_trigger = true +chip_enable_water_heater_management_trigger = true diff --git a/examples/all-clusters-app/linux/main-common.cpp b/examples/all-clusters-app/linux/main-common.cpp index ddbcaee06b889c..bc547d5416377c 100644 --- a/examples/all-clusters-app/linux/main-common.cpp +++ b/examples/all-clusters-app/linux/main-common.cpp @@ -63,6 +63,8 @@ #include +#include + using namespace chip; using namespace chip::app; using namespace chip::DeviceLayer; @@ -246,6 +248,8 @@ void ApplicationInit() Clusters::ValveConfigurationAndControl::SetDefaultDelegate(chip::EndpointId(1), &sValveDelegate); Clusters::TimeSynchronization::SetDefaultDelegate(&sTimeSyncDelegate); + Clusters::WaterHeaterManagement::WhmApplicationInit(); + SetTagList(/* endpoint= */ 0, Span(gEp0TagList)); SetTagList(/* endpoint= */ 1, Span(gEp1TagList)); SetTagList(/* endpoint= */ 2, Span(gEp2TagList)); @@ -275,6 +279,9 @@ void ApplicationShutdown() Clusters::EnergyEvseMode::Shutdown(); Clusters::WaterHeaterMode::Shutdown(); + Clusters::WaterHeaterManagement::WhmApplicationShutdown(); + Clusters::WaterHeaterMode::Shutdown(); + if (sChipNamedPipeCommands.Stop() != CHIP_NO_ERROR) { ChipLogError(NotSpecified, "Failed to stop CHIP NamedPipeCommands"); diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index a63175b51200eb..410b1a189245d2 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -54,6 +54,10 @@ source_set("energy-reporting-test-event-trigger") { sources = [ "${chip_root}/src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerHandler.h" ] } +source_set("water-heater-management-test-event-trigger") { + sources = [ "${chip_root}/src/app/clusters/water-heater-management-server/WaterHeaterManagementTestEventTriggerHandler.h" ] +} + source_set("device-energy-management-test-event-trigger") { sources = [ "${chip_root}/src/app/clusters/device-energy-management-server/DeviceEnergyManagementTestEventTriggerHandler.h" ] } @@ -85,6 +89,7 @@ source_set("app-main") { ":energy-evse-test-event-trigger", ":energy-reporting-test-event-trigger", ":smco-test-event-trigger", + ":water-heater-management-test-event-trigger", "${chip_root}/src/controller:controller", "${chip_root}/src/controller:gen_check_chip_controller_headers", "${chip_root}/src/lib", diff --git a/src/app/clusters/water-heater-management-server/water-heater-management-server.cpp b/src/app/clusters/water-heater-management-server/water-heater-management-server.cpp index 447a6646766452..6af249370e8fb8 100644 --- a/src/app/clusters/water-heater-management-server/water-heater-management-server.cpp +++ b/src/app/clusters/water-heater-management-server/water-heater-management-server.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -39,7 +40,7 @@ constexpr uint16_t kClusterRevision = 1; CHIP_ERROR Instance::Init() { - ReturnErrorOnFailure(InteractionModelEngine::GetInstance()->RegisterCommandHandler(this)); + ReturnErrorOnFailure(CommandHandlerInterfaceRegistry::RegisterCommandHandler(this)); VerifyOrReturnError(registerAttributeAccessOverride(this), CHIP_ERROR_INCORRECT_STATE); return CHIP_NO_ERROR; @@ -47,7 +48,7 @@ CHIP_ERROR Instance::Init() void Instance::Shutdown() { - InteractionModelEngine::GetInstance()->UnregisterCommandHandler(this); + CommandHandlerInterfaceRegistry::UnregisterCommandHandler(this); unregisterAttributeAccessOverride(this); } diff --git a/src/app/common/templates/config-data.yaml b/src/app/common/templates/config-data.yaml index a3ae2dc621fd03..43f063a6098288 100644 --- a/src/app/common/templates/config-data.yaml +++ b/src/app/common/templates/config-data.yaml @@ -43,6 +43,7 @@ CommandHandlerInterfaceOnlyClusters: - Electrical Energy Measurement - Wi-Fi Network Management - Thread Network Directory + - Water Heater Management - Water Heater Mode # We need a more configurable way of deciding which clusters have which init functions.... diff --git a/zzz_generated/app-common/app-common/zap-generated/callback.h b/zzz_generated/app-common/app-common/zap-generated/callback.h index f1aa9705d7c8c3..609cf23e6202d5 100644 --- a/zzz_generated/app-common/app-common/zap-generated/callback.h +++ b/zzz_generated/app-common/app-common/zap-generated/callback.h @@ -6059,18 +6059,6 @@ bool emberAfValveConfigurationAndControlClusterOpenCallback( bool emberAfValveConfigurationAndControlClusterCloseCallback( chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, const chip::app::Clusters::ValveConfigurationAndControl::Commands::Close::DecodableType & commandData); -/** - * @brief Water Heater Management Cluster Boost Command callback (from client) - */ -bool emberAfWaterHeaterManagementClusterBoostCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::WaterHeaterManagement::Commands::Boost::DecodableType & commandData); -/** - * @brief Water Heater Management Cluster CancelBoost Command callback (from client) - */ -bool emberAfWaterHeaterManagementClusterCancelBoostCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::WaterHeaterManagement::Commands::CancelBoost::DecodableType & commandData); /** * @brief Demand Response Load Control Cluster RegisterLoadControlProgramRequest Command callback (from client) */