diff --git a/examples/all-clusters-app/linux/BUILD.gn b/examples/all-clusters-app/linux/BUILD.gn index ed228b51b2cb32..baac52014d3c3b 100644 --- a/examples/all-clusters-app/linux/BUILD.gn +++ b/examples/all-clusters-app/linux/BUILD.gn @@ -75,7 +75,7 @@ source_set("chip-all-clusters-common") { "${chip_root}/examples/energy-management-app/energy-management-common/energy-evse/src/EnergyEvseTargetsStore.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/energy-evse/src/energy-evse-mode.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/energy-reporting/src/ElectricalPowerMeasurementDelegate.cpp", - "${chip_root}/examples/thermostat/linux/thermostat-delegate-impl.cpp", + "${chip_root}/examples/thermostat/thermostat-common/src/thermostat-delegate-impl.cpp", "AllClustersCommandDelegate.cpp", "AllClustersCommandDelegate.h", "AppOptions.cpp", @@ -102,7 +102,7 @@ source_set("chip-all-clusters-common") { "${chip_root}/examples/energy-management-app/energy-management-common/device-energy-management/include", "${chip_root}/examples/energy-management-app/energy-management-common/energy-evse/include", "${chip_root}/examples/energy-management-app/energy-management-common/energy-reporting/include", - "${chip_root}/examples/thermostat/linux/include", + "${chip_root}/examples/thermostat/thermostat-common/include", ] if (chip_enable_pw_rpc) { diff --git a/examples/thermostat/linux/BUILD.gn b/examples/thermostat/linux/BUILD.gn index 71c0eccfcfae50..0683b39abb4cc6 100644 --- a/examples/thermostat/linux/BUILD.gn +++ b/examples/thermostat/linux/BUILD.gn @@ -17,11 +17,10 @@ import("//build_overrides/chip.gni") executable("thermostat-app") { sources = [ + "${chip_root}/examples/thermostat/thermostat-common/src/thermostat-delegate-impl.cpp", "include/low-power/LowPowerManager.cpp", "include/low-power/LowPowerManager.h", "main.cpp", - "thermostat-delegate-impl.cpp", - "thermostat-manager.cpp", ] deps = [ @@ -30,7 +29,10 @@ executable("thermostat-app") { "${chip_root}/src/lib", ] - include_dirs = [ "include" ] + include_dirs = [ + "include", + "${chip_root}/examples/thermostat/thermostat-common/include", + ] cflags = [ "-Wconversion" ] diff --git a/examples/thermostat/linux/include/thermostat-manager.h b/examples/thermostat/linux/include/thermostat-manager.h deleted file mode 100644 index 274f66c66917cf..00000000000000 --- a/examples/thermostat/linux/include/thermostat-manager.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * - * 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 - -class ThermostatManager -{ -public: - CHIP_ERROR Init(); - - /// @brief Callback called when any attribute changed on the device - void AttributeChangeHandler(chip::EndpointId endpointId, chip::ClusterId clusterId, chip::AttributeId attributeId, - uint8_t * value, uint16_t size); - - chip::app::Clusters::Thermostat::SystemModeEnum GetSystemMode(); - chip::app::Clusters::Thermostat::ThermostatRunningModeEnum GetRunningMode(); - int16_t GetCurrentTemperature(); - int16_t GetCurrentHeatingSetPoint(); - int16_t GetCurrentCoolingSetPoint(); - uint8_t GetNumberOfPresets(); - CHIP_ERROR SetSystemMode(chip::app::Clusters::Thermostat::SystemModeEnum systemMode); - CHIP_ERROR SetRunningMode(chip::app::Clusters::Thermostat::ThermostatRunningModeEnum runningMode); - CHIP_ERROR SetCurrentTemperature(int16_t temperature); - CHIP_ERROR SetCurrentHeatingSetPoint(int16_t heatingSetpoint); - CHIP_ERROR SetCurrentCoolingSetPoint(int16_t coolingSetpoint); - -private: - friend ThermostatManager & ThermostatMgr(); - - chip::app::Clusters::Thermostat::SystemModeEnum mSystemMode; - chip::app::Clusters::Thermostat::ThermostatRunningModeEnum mRunningMode; - int16_t mLocalTemperature; - int16_t mOccupiedCoolingSetpoint; - int16_t mOccupiedHeatingSetpoint; - uint8_t mOccupiedSetback; - - static ThermostatManager sThermostatMgr; - - /// @brief attribute handler for the thermostat endpoint - void ThermostatEndpointAttributeChangeHandler(chip::ClusterId clusterId, chip::AttributeId attributeId, uint8_t * value, - uint16_t size); - void ThermostatClusterAttributeChangeHandler(chip::AttributeId attributeId, uint8_t * value, uint16_t size); - void LocalTemperatureMeasurementEndpointAttributeChangeHandler(chip::ClusterId clusterId, chip::AttributeId attributeId, - uint8_t * value, uint16_t size); - void LocalTemperatureMeasurementClusterAttributeChangeHandler(chip::AttributeId attributeId, uint8_t * value, uint16_t size); - - /// @brief Main method that evaluates the current thermostat state and updates attributes - void EvalThermostatState(); - void UpdateRunningModeForHeating(); - void UpdateRunningModeForCooling(); -}; - -inline ThermostatManager & ThermostatMgr() -{ - return ThermostatManager::sThermostatMgr; -} diff --git a/examples/thermostat/linux/main.cpp b/examples/thermostat/linux/main.cpp index 2279f02bef3963..b9f82696e8ce79 100644 --- a/examples/thermostat/linux/main.cpp +++ b/examples/thermostat/linux/main.cpp @@ -22,8 +22,6 @@ #include #include -#include "thermostat-manager.h" - using namespace chip; using namespace chip::app; // using namespace chip::app::Clusters; @@ -76,19 +74,7 @@ void ApplicationShutdown() {} int main(int argc, char * argv[]) { - if (ChipLinuxAppInit(argc, argv) != 0) - { - return -1; - } - ChipLogProgress(Zcl, "Starting Thermostat Manager"); - CHIP_ERROR err = ThermostatManager().Init(); - - if (err != CHIP_NO_ERROR) - { - ChipLogError(AppServer, "Failed to initialize thermostat manager: %" CHIP_ERROR_FORMAT, err.Format()); - chip::DeviceLayer::PlatformMgr().Shutdown(); - return -1; - } + VerifyOrDie(ChipLinuxAppInit(argc, argv) == 0); ChipLinuxAppMainLoop(); return 0; } diff --git a/examples/thermostat/linux/thermostat-manager.cpp b/examples/thermostat/linux/thermostat-manager.cpp deleted file mode 100644 index ea1f4375c1649d..00000000000000 --- a/examples/thermostat/linux/thermostat-manager.cpp +++ /dev/null @@ -1,497 +0,0 @@ -/* - * - * 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. - */ - -/********************************************************** - * Includes - *********************************************************/ - -#include -#include - -#include -#include -#include -#include - -/********************************************************** - * Defines and Constants - *********************************************************/ - -using namespace chip; -using namespace chip::app; -using namespace chip::app::DataModel; -using namespace chip::Controller; -using namespace chip::app::Clusters; -using namespace chip::app::Clusters::Thermostat; -using namespace chip::app::Clusters::Thermostat::Structs; -using namespace chip::app::Clusters::Thermostat::Attributes; -using namespace chip::app::Clusters::TemperatureMeasurement; -using namespace chip::app::Clusters::TemperatureMeasurement::Attributes; -using namespace Protocols::InteractionModel; - -using namespace chip::DeviceLayer; - -static constexpr EndpointId kThermostatEndpoint = 1; - -static constexpr uint16_t kMaxIntervalCeilingSeconds = 3600; - -static const char * SystemModeString(SystemModeEnum systemMode); -static const char * RunningModeString(ThermostatRunningModeEnum runningMode); - -/********************************************************** - * Variable declarations - *********************************************************/ - -ThermostatManager ThermostatManager::sThermostatMgr; - -namespace { - -template -static void OnAttributeChangeReported(const ConcreteDataAttributePath & path, const DecodableAttributeType & value); - -template <> -void OnAttributeChangeReported(const ConcreteDataAttributePath & path, - const MeasuredValue::TypeInfo::DecodableType & value) -{ - ClusterId clusterId = path.mClusterId; - if (clusterId != TemperatureMeasurement::Id) - { - ChipLogError(AppServer, - "Attribute change reported for TemperatureMeasurement cluster on incorrect cluster id " ChipLogFormatMEI, - ChipLogValueMEI(clusterId)); - return; - } - - AttributeId attributeId = path.mAttributeId; - if (attributeId != MeasuredValue::Id) - { - ChipLogError(AppServer, - "Attribute change reported for TemperatureMeasurement cluster for incorrect attribute" ChipLogFormatMEI, - ChipLogValueMEI(attributeId)); - return; - } - - if (!value.IsNull()) - { - ChipLogDetail(AppServer, "Attribute change reported for TemperatureMeasurement cluster - MeasuredValue is %d", - value.Value()); - } -} - -static void OnError(const ConcreteDataAttributePath * path, ChipError err) -{ - ChipLogError(AppServer, - "Subscribing to cluster Id " ChipLogFormatMEI " and attribute Id " ChipLogFormatMEI - " failed with error %" CHIP_ERROR_FORMAT, - ChipLogValueMEI(path->mClusterId), ChipLogValueMEI(path->mAttributeId), err.Format()); -} - -static void OnSubscriptionEstablished(const ReadClient & client, unsigned int value) -{ - ChipLogDetail(AppServer, "OnSubscriptionEstablished with subscription Id: %d", value); -} - -template -void SubscribeToAttribute(ClusterId clusterId, AttributeId attributeId, const EmberBindingTableEntry & binding, - OperationalDeviceProxy * peer_device) -{ - VerifyOrReturn(peer_device->GetSecureSession().HasValue(), - ChipLogError(AppServer, "SubscribeToAttribute failed. Secure session is null")); - - SubscribeAttribute( - peer_device->GetExchangeManager(), peer_device->GetSecureSession().Value(), binding.remote, clusterId, attributeId, - &OnAttributeChangeReported, &OnError, 0, kMaxIntervalCeilingSeconds, &OnSubscriptionEstablished, - nullptr, true /* fabricFiltered */, false /* keepExistingSubscription */); -} - -static void ThermostatBoundDeviceChangedHandler(const EmberBindingTableEntry & binding, OperationalDeviceProxy * peer_device, - void * context) -{ - VerifyOrReturn(binding.clusterId.has_value(), ChipLogError(AppServer, "Cluster Id is null")); - ClusterId clusterId = binding.clusterId.value(); - - switch (clusterId) - { - case TemperatureMeasurement::Id: - - // Subscribe to the MeasuredValue attribute - SubscribeToAttribute(clusterId, MeasuredValue::Id, binding, peer_device); - break; - default: - ChipLogError(AppServer, "Unsupported Cluster Id"); - break; - } -} - -void NotifyBoundClusterChangedForAllClusters() -{ - BindingManager::GetInstance().NotifyBoundClusterChanged(kThermostatEndpoint, TemperatureMeasurement::Id, nullptr); -} - -static void OnPlatformChipDeviceEvent(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg) -{ - if (event->Type == DeviceLayer::DeviceEventType::kBindingsChangedViaCluster) - { - NotifyBoundClusterChangedForAllClusters(); - } -} - -void InitBindingManager(intptr_t context) -{ - auto & server = Server::GetInstance(); - CHIP_ERROR error = BindingManager::GetInstance().Init( - { &server.GetFabricTable(), server.GetCASESessionManager(), &server.GetPersistentStorage() }); - - if (error != CHIP_NO_ERROR) - { - ChipLogError(AppServer, "Failed to init binding manager"); - } - - BindingManager::GetInstance().RegisterBoundDeviceChangedHandler(ThermostatBoundDeviceChangedHandler); - NotifyBoundClusterChangedForAllClusters(); -} - -} // anonymous namespace - -CHIP_ERROR ThermostatManager::Init() -{ - // Init binding manager - - DeviceLayer::PlatformMgr().AddEventHandler(OnPlatformChipDeviceEvent, reinterpret_cast(this)); - DeviceLayer::PlatformMgr().ScheduleWork(InitBindingManager); - - mLocalTemperature = GetCurrentTemperature(); - mSystemMode = GetSystemMode(); - mRunningMode = GetRunningMode(); - mOccupiedCoolingSetpoint = GetCurrentCoolingSetPoint(); - mOccupiedHeatingSetpoint = GetCurrentHeatingSetPoint(); - // TODO: Gotta expose this properly on attribute - mOccupiedSetback = 5; // 0.5 C - - ChipLogError(AppServer, - "Initialized a thermostat with \n " - "mSystemMode: %u (%s) \n mRunningMode: %u (%s) \n mLocalTemperature: %d \n mOccupiedHeatingSetpoint: %d \n " - "mOccupiedCoolingSetpoint: %d" - "NumberOfPresets: %d", - to_underlying(mSystemMode), SystemModeString(mSystemMode), to_underlying(mRunningMode), - RunningModeString(mRunningMode), mLocalTemperature, mOccupiedHeatingSetpoint, mOccupiedCoolingSetpoint, - GetNumberOfPresets()); - - // TODO: Should this be called later? - EvalThermostatState(); - - return CHIP_NO_ERROR; -} - -void ThermostatManager::AttributeChangeHandler(EndpointId endpointId, ClusterId clusterId, AttributeId attributeId, uint8_t * value, - uint16_t size) -{ - switch (endpointId) - { - case kThermostatEndpoint: - ThermostatEndpointAttributeChangeHandler(clusterId, attributeId, value, size); - break; - - default: - ChipLogError(AppServer, "Attribute change reported for Thermostat on incorrect endpoint. Ignoring."); - break; - } -} - -void ThermostatManager::ThermostatEndpointAttributeChangeHandler(ClusterId clusterId, AttributeId attributeId, uint8_t * value, - uint16_t size) -{ - switch (clusterId) - { - case Thermostat::Id: - ThermostatClusterAttributeChangeHandler(attributeId, value, size); - break; - - default: - ChipLogError(AppServer, - "Attribute change reported for Thermostat on incorrect cluster for the thermostat endpoint. Ignoring."); - break; - } -} - -void ThermostatManager::ThermostatClusterAttributeChangeHandler(AttributeId attributeId, uint8_t * value, uint16_t size) -{ - switch (attributeId) - { - case LocalTemperature::Id: { - memcpy(&mLocalTemperature, value, size); - ChipLogError(AppServer, "Local temperature changed to %d", mLocalTemperature); - EvalThermostatState(); - } - break; - - case OccupiedCoolingSetpoint::Id: { - memcpy(&mOccupiedCoolingSetpoint, value, size); - ChipLogError(AppServer, "Cooling temperature changed to %d", mOccupiedCoolingSetpoint); - EvalThermostatState(); - } - break; - - case OccupiedHeatingSetpoint::Id: { - memcpy(&mOccupiedHeatingSetpoint, value, size); - ChipLogError(AppServer, "Heating temperature changed to %d", mOccupiedHeatingSetpoint); - EvalThermostatState(); - } - break; - - case SystemMode::Id: { - mSystemMode = static_cast(*value); - ChipLogError(AppServer, "System mode changed to %u (%s)", *value, SystemModeString(mSystemMode)); - EvalThermostatState(); - } - break; - - case ThermostatRunningMode::Id: { - mRunningMode = static_cast(*value); - ChipLogError(AppServer, "Running mode changed to %u (%s)", *value, RunningModeString(mRunningMode)); - } - break; - - default: { - ChipLogError(AppServer, "Unhandled thermostat attribute %u", static_cast(attributeId)); - return; - } - break; - } -} - -SystemModeEnum ThermostatManager::GetSystemMode() -{ - SystemModeEnum systemMode; - SystemMode::Get(kThermostatEndpoint, &systemMode); - return systemMode; -} - -ThermostatRunningModeEnum ThermostatManager::GetRunningMode() -{ - ThermostatRunningModeEnum runningMode; - ThermostatRunningMode::Get(kThermostatEndpoint, &runningMode); - return runningMode; -} - -int16_t ThermostatManager::GetCurrentTemperature() -{ - DataModel::Nullable currentTemperature; - currentTemperature.SetNull(); - LocalTemperature::Get(kThermostatEndpoint, currentTemperature); - return currentTemperature.ValueOr(0); -} - -int16_t ThermostatManager::GetCurrentHeatingSetPoint() -{ - int16_t heatingSetpoint; - OccupiedHeatingSetpoint::Get(kThermostatEndpoint, &heatingSetpoint); - return heatingSetpoint; -} - -int16_t ThermostatManager::GetCurrentCoolingSetPoint() -{ - int16_t coolingSetpoint; - OccupiedCoolingSetpoint::Get(kThermostatEndpoint, &coolingSetpoint); - return coolingSetpoint; -} - -uint8_t ThermostatManager::GetNumberOfPresets() -{ - return ThermostatDelegate::GetInstance().GetNumberOfPresets(); -} - -CHIP_ERROR ThermostatManager::SetSystemMode(SystemModeEnum systemMode) -{ - uint8_t systemModeValue = to_underlying(systemMode); - if (mSystemMode == systemMode) - { - ChipLogDetail(AppServer, "Already in system mode: %u (%s)", systemModeValue, SystemModeString(systemMode)); - return CHIP_NO_ERROR; - } - - ChipLogError(AppServer, "Setting system mode: %u (%s)", systemModeValue, SystemModeString(systemMode)); - return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(SystemMode::Set(kThermostatEndpoint, systemMode)); -} - -CHIP_ERROR ThermostatManager::SetRunningMode(ThermostatRunningModeEnum runningMode) -{ - uint8_t runningModeValue = to_underlying(runningMode); - if (mRunningMode == runningMode) - { - ChipLogDetail(AppServer, "Already in running mode: %u (%s)", runningModeValue, RunningModeString(runningMode)); - return CHIP_NO_ERROR; - } - - ChipLogError(AppServer, "Setting running mode: %u (%s)", runningModeValue, RunningModeString(runningMode)); - return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(ThermostatRunningMode::Set(kThermostatEndpoint, runningMode)); -} - -CHIP_ERROR ThermostatManager::SetCurrentTemperature(int16_t temperature) -{ - return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(LocalTemperature::Set(kThermostatEndpoint, temperature)); -} - -CHIP_ERROR ThermostatManager::SetCurrentHeatingSetPoint(int16_t heatingSetpoint) -{ - return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(OccupiedHeatingSetpoint::Set(kThermostatEndpoint, heatingSetpoint)); -} - -CHIP_ERROR ThermostatManager::SetCurrentCoolingSetPoint(int16_t coolingSetpoint) -{ - return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(OccupiedCoolingSetpoint::Set(kThermostatEndpoint, coolingSetpoint)); -} - -void ThermostatManager::EvalThermostatState() -{ - ChipLogError(AppServer, - "Eval Thermostat Running Mode \n " - "mSystemMode: %u (%s) \n mRunningMode: %u (%s) \n mLocalTemperature: %d \n mOccupiedHeatingSetpoint: %d \n " - "mOccupiedCoolingSetpoint: %d", - to_underlying(mSystemMode), SystemModeString(mSystemMode), to_underlying(mRunningMode), - RunningModeString(mRunningMode), mLocalTemperature, mOccupiedHeatingSetpoint, mOccupiedCoolingSetpoint); - - switch (mSystemMode) - { - case SystemModeEnum::kOff: { - SetRunningMode(ThermostatRunningModeEnum::kOff); - break; - } - case SystemModeEnum::kHeat: { - UpdateRunningModeForHeating(); - break; - } - case SystemModeEnum::kCool: { - UpdateRunningModeForCooling(); - break; - } - case SystemModeEnum::kAuto: { - UpdateRunningModeForHeating(); - UpdateRunningModeForCooling(); - break; - } - default: - break; - } -} - -void ThermostatManager::UpdateRunningModeForHeating() -{ - const int16_t heatingOnThreshold = mOccupiedHeatingSetpoint - static_cast(mOccupiedSetback * 10); - const int16_t heatingOffThreshold = mOccupiedHeatingSetpoint + static_cast(mOccupiedSetback * 10); - - if (mRunningMode == ThermostatRunningModeEnum::kHeat) - { - if (mLocalTemperature >= heatingOffThreshold) - { - ChipLogDetail(AppServer, "Eval Heat - Turning off"); - SetRunningMode(ThermostatRunningModeEnum::kOff); - } - else - { - ChipLogDetail(AppServer, "Eval Heat - Keep Heating"); - } - } - else - { - if (mLocalTemperature <= heatingOnThreshold) - { - ChipLogDetail(AppServer, "Eval Heat - Turn on"); - SetRunningMode(ThermostatRunningModeEnum::kHeat); - } - else - { - ChipLogDetail(AppServer, "Eval Heat - Nothing to do"); - } - } -} - -void ThermostatManager::UpdateRunningModeForCooling() -{ - const int16_t coolingOffThreshold = mOccupiedCoolingSetpoint - static_cast(mOccupiedSetback * 10); - const int16_t coolingOnThreshold = mOccupiedCoolingSetpoint + static_cast(mOccupiedSetback * 10); - - if (mRunningMode == ThermostatRunningModeEnum::kCool) - { - if (mLocalTemperature <= coolingOffThreshold) - { - ChipLogDetail(AppServer, "Eval Cool - Turning off"); - SetRunningMode(ThermostatRunningModeEnum::kOff); - } - else - { - ChipLogDetail(AppServer, "Eval Cool - Keep Cooling"); - } - } - else - { - if (mLocalTemperature >= coolingOnThreshold) - { - ChipLogDetail(AppServer, "Eval Cool - Turn on"); - SetRunningMode(ThermostatRunningModeEnum::kCool); - } - else - { - ChipLogDetail(AppServer, "Eval Cool - Nothing to do"); - } - } -} - -static const char * SystemModeString(SystemModeEnum systemMode) -{ - switch (systemMode) - { - case SystemModeEnum::kOff: - return "Off"; - case SystemModeEnum::kAuto: - return "Auto"; - case SystemModeEnum::kCool: - return "Cool"; - case SystemModeEnum::kHeat: - return "Heat"; - default: - return "Unknown"; - } -} - -static const char * RunningModeString(ThermostatRunningModeEnum runningMode) -{ - switch (runningMode) - { - case ThermostatRunningModeEnum::kOff: - return "Off"; - case ThermostatRunningModeEnum::kCool: - return "Cool"; - case ThermostatRunningModeEnum::kHeat: - return "Heat"; - default: - return "Unknown"; - } -} - -void emberAfThermostatClusterInitCallback(EndpointId endpoint) -{ - ChipLogProgress(Zcl, "Starting Thermostat Manager"); - ThermostatManager().Init(); - - // Register the delegate for the Thermostat - auto & delegate = ThermostatDelegate::GetInstance(); - // Set the default delegate for endpoint kThermostatEndpoint. - VerifyOrDie(endpoint == kThermostatEndpoint); - SetDefaultDelegate(endpoint, &delegate); -} diff --git a/examples/thermostat/thermostat-common/BUILD.gn b/examples/thermostat/thermostat-common/BUILD.gn index 93a0c7540fb93a..1f8f839b4a4f3a 100644 --- a/examples/thermostat/thermostat-common/BUILD.gn +++ b/examples/thermostat/thermostat-common/BUILD.gn @@ -16,6 +16,10 @@ import("//build_overrides/chip.gni") import("${chip_root}/src/app/chip_data_model.gni") +config("config") { + include_dirs = [ "include" ] +} + chip_data_model("thermostat-common") { zap_file = "thermostat.zap" is_server = true diff --git a/examples/thermostat/linux/include/thermostat-delegate-impl.h b/examples/thermostat/thermostat-common/include/thermostat-delegate-impl.h similarity index 92% rename from examples/thermostat/linux/include/thermostat-delegate-impl.h rename to examples/thermostat/thermostat-common/include/thermostat-delegate-impl.h index 6bf9d02cea462f..9edf13f839df44 100644 --- a/examples/thermostat/linux/include/thermostat-delegate-impl.h +++ b/examples/thermostat/thermostat-common/include/thermostat-delegate-impl.h @@ -44,9 +44,7 @@ class ThermostatDelegate : public Delegate public: static inline ThermostatDelegate & GetInstance() { return sInstance; } - std::optional - GetAtomicWriteTimeout(DataModel::DecodableList attributeRequests, - System::Clock::Milliseconds16 timeoutRequest) override; + std::optional GetMaxAtomicWriteTimeout(chip::AttributeId attributeId) override; CHIP_ERROR GetPresetTypeAtIndex(size_t index, Structs::PresetTypeStruct::Type & presetType) override; @@ -64,7 +62,7 @@ class ThermostatDelegate : public Delegate CHIP_ERROR GetPendingPresetAtIndex(size_t index, PresetStructWithOwnedMembers & preset) override; - CHIP_ERROR ApplyPendingPresets() override; + CHIP_ERROR CommitPendingPresets() override; void ClearPendingPresetList() override; diff --git a/examples/thermostat/linux/thermostat-delegate-impl.cpp b/examples/thermostat/thermostat-common/src/thermostat-delegate-impl.cpp similarity index 75% rename from examples/thermostat/linux/thermostat-delegate-impl.cpp rename to examples/thermostat/thermostat-common/src/thermostat-delegate-impl.cpp index b931db20b7c4f7..8c411cd5a9176e 100644 --- a/examples/thermostat/linux/thermostat-delegate-impl.cpp +++ b/examples/thermostat/thermostat-common/src/thermostat-delegate-impl.cpp @@ -17,7 +17,6 @@ */ #include -#include #include #include @@ -36,34 +35,12 @@ ThermostatDelegate::ThermostatDelegate() mNextFreeIndexInPresetsList = 0; mNextFreeIndexInPendingPresetsList = 0; - InitializePresetTypes(); InitializePresets(); memset(mActivePresetHandleData, 0, sizeof(mActivePresetHandleData)); mActivePresetHandleDataSize = 0; } -void ThermostatDelegate::InitializePresetTypes() -{ - PresetScenarioEnum presetScenarioEnumArray[kMaxNumberOfPresetTypes] = { - PresetScenarioEnum::kOccupied, PresetScenarioEnum::kUnoccupied, PresetScenarioEnum::kSleep, - PresetScenarioEnum::kWake, PresetScenarioEnum::kVacation, PresetScenarioEnum::kGoingToSleep - }; - static_assert(ArraySize(presetScenarioEnumArray) <= ArraySize(mPresetTypes)); - - uint8_t index = 0; - for (PresetScenarioEnum presetScenario : presetScenarioEnumArray) - { - mPresetTypes[index].presetScenario = presetScenario; - mPresetTypes[index].numberOfPresets = kMaxNumberOfPresetsOfEachType; - mPresetTypes[index].presetTypeFeatures = - (presetScenario == PresetScenarioEnum::kOccupied || presetScenario == PresetScenarioEnum::kUnoccupied) - ? PresetTypeFeaturesBitmap::kAutomatic - : PresetTypeFeaturesBitmap::kSupportsNames; - index++; - } -} - void ThermostatDelegate::InitializePresets() { // Initialize the presets with 2 built in presets - occupied and unoccupied. @@ -94,9 +71,26 @@ void ThermostatDelegate::InitializePresets() CHIP_ERROR ThermostatDelegate::GetPresetTypeAtIndex(size_t index, PresetTypeStruct::Type & presetType) { - if (index < ArraySize(mPresetTypes)) + static PresetTypeStruct::Type presetTypes[] = { + { .presetScenario = PresetScenarioEnum::kOccupied, + .numberOfPresets = kMaxNumberOfPresetsOfEachType, + .presetTypeFeatures = to_underlying(PresetTypeFeaturesBitmap::kAutomatic) }, + { .presetScenario = PresetScenarioEnum::kUnoccupied, + .numberOfPresets = kMaxNumberOfPresetsOfEachType, + .presetTypeFeatures = to_underlying(PresetTypeFeaturesBitmap::kAutomatic) }, + { .presetScenario = PresetScenarioEnum::kSleep, + .numberOfPresets = kMaxNumberOfPresetsOfEachType, + .presetTypeFeatures = to_underlying(PresetTypeFeaturesBitmap::kSupportsNames) }, + { .presetScenario = PresetScenarioEnum::kWake, + .numberOfPresets = kMaxNumberOfPresetsOfEachType, + .presetTypeFeatures = to_underlying(PresetTypeFeaturesBitmap::kSupportsNames) }, + { .presetScenario = PresetScenarioEnum::kVacation, + .numberOfPresets = kMaxNumberOfPresetsOfEachType, + .presetTypeFeatures = to_underlying(PresetTypeFeaturesBitmap::kSupportsNames) }, + }; + if (index < ArraySize(presetTypes)) { - presetType = mPresetTypes[index]; + presetType = presetTypes[index]; return CHIP_NO_ERROR; } return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED; @@ -158,45 +152,19 @@ CHIP_ERROR ThermostatDelegate::SetActivePresetHandle(const DataModel::Nullable -ThermostatDelegate::GetAtomicWriteTimeout(DataModel::DecodableList attributeRequests, - System::Clock::Milliseconds16 timeoutRequest) +std::optional ThermostatDelegate::GetMaxAtomicWriteTimeout(chip::AttributeId attributeId) { - auto attributeIdsIter = attributeRequests.begin(); - bool requestedPresets = false, requestedSchedules = false; - while (attributeIdsIter.Next()) - { - auto & attributeId = attributeIdsIter.GetValue(); - - switch (attributeId) - { - case Attributes::Presets::Id: - requestedPresets = true; - break; - case Attributes::Schedules::Id: - requestedSchedules = true; - break; - default: - return System::Clock::Milliseconds16(0); - } - } - if (attributeIdsIter.GetStatus() != CHIP_NO_ERROR) - { - return System::Clock::Milliseconds16(0); - } - auto timeout = System::Clock::Milliseconds16(0); - if (requestedPresets) + switch (attributeId) { + case Attributes::Presets::Id: // If the client expects to edit the presets, then we'll give it 3 seconds to do so - timeout += std::chrono::milliseconds(3000); - } - if (requestedSchedules) - { + return std::chrono::milliseconds(3000); + case Attributes::Schedules::Id: // If the client expects to edit the schedules, then we'll give it 9 seconds to do so - timeout += std::chrono::milliseconds(9000); + return std::chrono::milliseconds(9000); + default: + return std::nullopt; } - // If the client requested an even smaller timeout, then use that one - return std::min(timeoutRequest, timeout); } void ThermostatDelegate::InitializePendingPresets() @@ -238,7 +206,7 @@ CHIP_ERROR ThermostatDelegate::GetPendingPresetAtIndex(size_t index, PresetStruc return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED; } -CHIP_ERROR ThermostatDelegate::ApplyPendingPresets() +CHIP_ERROR ThermostatDelegate::CommitPendingPresets() { mNextFreeIndexInPresetsList = 0; for (uint8_t indexInPendingPresets = 0; indexInPendingPresets < mNextFreeIndexInPendingPresetsList; indexInPendingPresets++) diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index 3e4448a3bee467..01d47a47cb7184 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -428,6 +428,8 @@ template("chip_data_model") { ] } else if (cluster == "thermostat-server") { sources += [ + "${_app_root}/clusters/${cluster}/${cluster}-atomic.cpp", + "${_app_root}/clusters/${cluster}/${cluster}-presets.cpp", "${_app_root}/clusters/${cluster}/${cluster}.cpp", "${_app_root}/clusters/${cluster}/${cluster}.h", "${_app_root}/clusters/${cluster}/PresetStructWithOwnedMembers.cpp", diff --git a/src/app/clusters/thermostat-server/thermostat-delegate.h b/src/app/clusters/thermostat-server/thermostat-delegate.h index 0f89f69468099b..ccb690a34fba60 100644 --- a/src/app/clusters/thermostat-server/thermostat-delegate.h +++ b/src/app/clusters/thermostat-server/thermostat-delegate.h @@ -39,15 +39,12 @@ class Delegate virtual ~Delegate() = default; /** - * @brief Get the maximum timeout for atomically writing to a set of attributes + * @brief Get the maximum timeout for atomically writing to an attribute * - * @param[in] attributeRequests The list of attributes to write to. - * @param[out] timeoutRequest The timeout proposed by the client. - * @return The maximum allowed timeout; zero if the request is invalid. + * @param[in] attributeId The attribute to write to. + * @return The maximum allowed timeout; nullopt if the request is invalid. */ - virtual std::optional - GetAtomicWriteTimeout(DataModel::DecodableList attributeRequests, - System::Clock::Milliseconds16 timeoutRequest) = 0; + virtual std::optional GetMaxAtomicWriteTimeout(chip::AttributeId attributeId) = 0; /** * @brief Get the preset type at a given index in the PresetTypes attribute @@ -129,7 +126,7 @@ class Delegate * @return CHIP_ERROR if the updates to the presets attribute failed to commit for some reason. * */ - virtual CHIP_ERROR ApplyPendingPresets() = 0; + virtual CHIP_ERROR CommitPendingPresets() = 0; /** * @brief Clears the pending presets list. diff --git a/src/app/clusters/thermostat-server/thermostat-server-atomic.cpp b/src/app/clusters/thermostat-server/thermostat-server-atomic.cpp new file mode 100644 index 00000000000000..2a6e52e504887e --- /dev/null +++ b/src/app/clusters/thermostat-server/thermostat-server-atomic.cpp @@ -0,0 +1,644 @@ +/** + * + * 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 "thermostat-server.h" + +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::Thermostat; +using namespace chip::app::Clusters::Thermostat::Attributes; +using namespace chip::app::Clusters::Thermostat::Structs; +using namespace chip::app::Clusters::Globals::Structs; +using namespace chip::Protocols::InteractionModel; + +namespace chip { +namespace app { +namespace Clusters { +namespace Thermostat { + +extern ThermostatAttrAccess gThermostatAttrAccess; + +/** + * @brief Callback that is called when the timeout for editing the presets expires. + * + * @param[in] systemLayer The system layer. + * @param[in] callbackContext The context passed to the timer callback. + */ +void TimerExpiredCallback(System::Layer * systemLayer, void * callbackContext) +{ + EndpointId endpoint = static_cast(reinterpret_cast(callbackContext)); + gThermostatAttrAccess.ResetAtomicWrite(endpoint); +} + +/** + * @brief Schedules a timer for the given timeout in milliseconds. + * + * @param[in] endpoint The endpoint to use. + * @param[in] timeoutMilliseconds The timeout in milliseconds. + */ +void ScheduleTimer(EndpointId endpoint, System::Clock::Milliseconds16 timeout) +{ + DeviceLayer::SystemLayer().StartTimer(timeout, TimerExpiredCallback, + reinterpret_cast(static_cast(endpoint))); +} + +/** + * @brief Clears the currently scheduled timer. + * + * @param[in] endpoint The endpoint to use. + */ +void ClearTimer(EndpointId endpoint) +{ + DeviceLayer::SystemLayer().CancelTimer(TimerExpiredCallback, reinterpret_cast(static_cast(endpoint))); +} + +/** + * @brief Get the source scoped node id. + * + * @param[in] commandObj The command handler object. + * + * @return The scoped node id of the source node. If the scoped node id is not retreived, return ScopedNodeId(). + */ +ScopedNodeId GetSourceScopedNodeId(CommandHandler * commandObj) +{ + ScopedNodeId sourceNodeId = ScopedNodeId(); + auto sessionHandle = commandObj->GetExchangeContext()->GetSessionHandle(); + + if (sessionHandle->IsSecureSession()) + { + sourceNodeId = sessionHandle->AsSecureSession()->GetPeer(); + } + else if (sessionHandle->IsGroupSession()) + { + sourceNodeId = sessionHandle->AsIncomingGroupSession()->GetPeer(); + } + return sourceNodeId; +} + +/** + * @brief Counts the number of attribute requests + * + * @param attributeRequests The decodable list of attribute IDs + * @param attributeRequestCount The total number of attribute requests + * @param requestedPresets Whether the Presets attribute was requested + * @param requestedSchedules Whether the Schedules attribute was requested + * @return true if the attribute list was counted + * @return false if there was an error reading the list + */ +bool CountAttributeRequests(const DataModel::DecodableList attributeRequests, size_t & attributeRequestCount, + bool & requestedPresets, bool & requestedSchedules) +{ + attributeRequestCount = 0; + requestedPresets = false; + requestedSchedules = false; + auto attributeIdsIter = attributeRequests.begin(); + while (attributeIdsIter.Next()) + { + auto & attributeId = attributeIdsIter.GetValue(); + switch (attributeId) + { + case Presets::Id: + requestedPresets = true; + break; + case Schedules::Id: + requestedSchedules = true; + break; + default: + break; + } + attributeRequestCount++; + } + return attributeIdsIter.GetStatus() == CHIP_NO_ERROR; +} + +/// @brief Builds the list of attribute statuses to return from an AtomicRequest invocation +/// @param endpoint The associated endpoint for the AtomicRequest invocation +/// @param attributeRequests The list of requested attributes +/// @param attributeStatusCount The number of attribute statuses in attributeStatuses +/// @param attributeStatuses The status of each requested attribute, plus additional attributes if needed +/// @return Status::Success if the request is valid, an error status if it is not +Status BuildAttributeStatuses(const EndpointId endpoint, const DataModel::DecodableList attributeRequests, + Platform::ScopedMemoryBufferWithSize & attributeStatuses) +{ + + bool requestedPresets = false, requestedSchedules = false; + size_t attributeStatusCount = 0; + if (!CountAttributeRequests(attributeRequests, attributeStatusCount, requestedPresets, requestedSchedules)) + { + // We errored reading the list + return Status::InvalidCommand; + } + if (attributeStatusCount == 0) + { + // List can't be empty + return Status::InvalidCommand; + } + attributeStatuses.Alloc(attributeStatusCount); + for (size_t i = 0; i < attributeStatusCount; ++i) + { + attributeStatuses[i].attributeID = kInvalidAttributeId; + attributeStatuses[i].statusCode = 0; + } + auto attributeIdsIter = attributeRequests.begin(); + size_t index = 0; + while (attributeIdsIter.Next()) + { + auto & attributeId = attributeIdsIter.GetValue(); + + for (size_t i = 0; i < index; ++i) + { + auto & attributeStatus = attributeStatuses[i]; + if (attributeStatus.attributeID == attributeId) + { + // Double-requesting an attribute is invalid + return Status::InvalidCommand; + } + } + attributeStatuses[index].attributeID = attributeId; + attributeStatuses[index].statusCode = to_underlying(Status::Success); + index++; + } + if (attributeIdsIter.GetStatus() != CHIP_NO_ERROR) + { + return Status::InvalidCommand; + } + for (size_t i = 0; i < index; ++i) + { + auto & attributeStatus = attributeStatuses[i]; + const EmberAfAttributeMetadata * metadata = + emberAfLocateAttributeMetadata(endpoint, Thermostat::Id, attributeStatus.attributeID); + + if (metadata == nullptr) + { + // This is not a valid attribute on the Thermostat cluster on the supplied endpoint + return Status::InvalidCommand; + } + } + return Status::Success; +} + +bool ThermostatAttrAccess::InAtomicWrite(EndpointId endpoint, Optional attributeId) +{ + + uint16_t ep = + emberAfGetClusterServerEndpointIndex(endpoint, Thermostat::Id, MATTER_DM_THERMOSTAT_CLUSTER_SERVER_ENDPOINT_COUNT); + + if (ep >= ArraySize(mAtomicWriteSessions)) + { + return false; + } + auto & atomicWriteSession = mAtomicWriteSessions[ep]; + if (atomicWriteSession.state != AtomicWriteState::Open) + { + return false; + } + if (!attributeId.HasValue()) + { + return true; + } + for (size_t i = 0; i < atomicWriteSession.attributeIds.AllocatedSize(); ++i) + { + if (atomicWriteSession.attributeIds[i] == attributeId.Value()) + { + return true; + } + } + return false; +} + +bool ThermostatAttrAccess::InAtomicWrite(EndpointId endpoint, const Access::SubjectDescriptor & subjectDescriptor, + Optional attributeId) +{ + if (!InAtomicWrite(endpoint, attributeId)) + { + return false; + } + return subjectDescriptor.authMode == Access::AuthMode::kCase && + GetAtomicWriteOriginatorScopedNodeId(endpoint) == ScopedNodeId(subjectDescriptor.subject, subjectDescriptor.fabricIndex); +} + +bool ThermostatAttrAccess::InAtomicWrite(EndpointId endpoint, CommandHandler * commandObj, Optional attributeId) +{ + if (!InAtomicWrite(endpoint, attributeId)) + { + return false; + } + ScopedNodeId sourceNodeId = GetSourceScopedNodeId(commandObj); + return GetAtomicWriteOriginatorScopedNodeId(endpoint) == sourceNodeId; +} + +bool ThermostatAttrAccess::InAtomicWrite( + EndpointId endpoint, CommandHandler * commandObj, + Platform::ScopedMemoryBufferWithSize & attributeStatuses) +{ + uint16_t ep = + emberAfGetClusterServerEndpointIndex(endpoint, Thermostat::Id, MATTER_DM_THERMOSTAT_CLUSTER_SERVER_ENDPOINT_COUNT); + + if (ep >= ArraySize(mAtomicWriteSessions)) + { + return false; + } + auto & atomicWriteSession = mAtomicWriteSessions[ep]; + if (atomicWriteSession.state != AtomicWriteState::Open) + { + return false; + } + if (atomicWriteSession.attributeIds.AllocatedSize() == 0 || + atomicWriteSession.attributeIds.AllocatedSize() != attributeStatuses.AllocatedSize()) + { + return false; + } + for (size_t i = 0; i < atomicWriteSession.attributeIds.AllocatedSize(); ++i) + { + bool hasAttribute = false; + auto attributeId = atomicWriteSession.attributeIds[i]; + for (size_t j = 0; j < attributeStatuses.AllocatedSize(); ++j) + { + auto & attributeStatus = attributeStatuses[j]; + if (attributeStatus.attributeID == attributeId) + { + hasAttribute = true; + break; + } + } + if (!hasAttribute) + { + return false; + } + } + return true; +} + +bool ThermostatAttrAccess::SetAtomicWrite( + EndpointId endpoint, ScopedNodeId originatorNodeId, AtomicWriteState state, + Platform::ScopedMemoryBufferWithSize & attributeStatuses) +{ + uint16_t ep = + emberAfGetClusterServerEndpointIndex(endpoint, Thermostat::Id, MATTER_DM_THERMOSTAT_CLUSTER_SERVER_ENDPOINT_COUNT); + + if (ep >= ArraySize(mAtomicWriteSessions)) + { + return false; + } + + auto & atomicWriteSession = mAtomicWriteSessions[ep]; + atomicWriteSession.endpointId = endpoint; + if (!atomicWriteSession.attributeIds.Alloc(attributeStatuses.AllocatedSize())) + { + atomicWriteSession.state = AtomicWriteState::Closed; + atomicWriteSession.nodeId = ScopedNodeId(); + return false; + } + + atomicWriteSession.state = state; + atomicWriteSession.nodeId = originatorNodeId; + + for (size_t i = 0; i < attributeStatuses.AllocatedSize(); ++i) + { + atomicWriteSession.attributeIds[i] = attributeStatuses[i].attributeID; + } + return true; +} + +void ThermostatAttrAccess::ResetAtomicWrite(EndpointId endpoint) +{ + auto delegate = GetDelegate(endpoint); + if (delegate != nullptr) + { + delegate->ClearPendingPresetList(); + } + ClearTimer(endpoint); + uint16_t ep = + emberAfGetClusterServerEndpointIndex(endpoint, Thermostat::Id, MATTER_DM_THERMOSTAT_CLUSTER_SERVER_ENDPOINT_COUNT); + + if (ep >= ArraySize(mAtomicWriteSessions)) + { + return; + } + auto & atomicWriteSession = mAtomicWriteSessions[ep]; + atomicWriteSession.state = AtomicWriteState::Closed; + atomicWriteSession.endpointId = endpoint; + atomicWriteSession.nodeId = ScopedNodeId(); + atomicWriteSession.attributeIds.Free(); +} + +ScopedNodeId ThermostatAttrAccess::GetAtomicWriteOriginatorScopedNodeId(const EndpointId endpoint) +{ + ScopedNodeId originatorNodeId = ScopedNodeId(); + uint16_t ep = + emberAfGetClusterServerEndpointIndex(endpoint, Thermostat::Id, MATTER_DM_THERMOSTAT_CLUSTER_SERVER_ENDPOINT_COUNT); + + if (ep < ArraySize(mAtomicWriteSessions)) + { + originatorNodeId = mAtomicWriteSessions[ep].nodeId; + } + return originatorNodeId; +} + +void SendAtomicResponse(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, Status status, + const Platform::ScopedMemoryBufferWithSize & attributeStatuses, + Optional timeout = NullOptional) +{ + Commands::AtomicResponse::Type response; + response.statusCode = to_underlying(status); + response.attributeStatus = + DataModel::List(attributeStatuses.Get(), attributeStatuses.AllocatedSize()); + response.timeout = timeout; + commandObj->AddResponse(commandPath, response); +} + +void ThermostatAttrAccess::BeginAtomicWrite(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Commands::AtomicRequest::DecodableType & commandData) +{ + EndpointId endpoint = commandPath.mEndpointId; + + auto delegate = GetDelegate(endpoint); + + if (delegate == nullptr) + { + ChipLogError(Zcl, "Delegate is null"); + commandObj->AddStatus(commandPath, Status::InvalidInState); + return; + } + + Platform::ScopedMemoryBufferWithSize attributeStatuses; + auto status = BuildAttributeStatuses(endpoint, commandData.attributeRequests, attributeStatuses); + if (status != Status::Success) + { + commandObj->AddStatus(commandPath, status); + return; + } + + if (InAtomicWrite(endpoint, commandObj)) + { + // This client already has an open atomic write + commandObj->AddStatus(commandPath, Status::InvalidInState); + return; + } + + if (!commandData.timeout.HasValue()) + { + commandObj->AddStatus(commandPath, Status::InvalidCommand); + return; + } + + auto maximumTimeout = System::Clock::Milliseconds16(0); + auto attributeIdsIter = commandData.attributeRequests.begin(); + while (attributeIdsIter.Next()) + { + auto & attributeId = attributeIdsIter.GetValue(); + switch (attributeId) + { + case Presets::Id: + case Schedules::Id: + auto attributeTimeout = delegate->GetMaxAtomicWriteTimeout(attributeId); + + if (attributeTimeout.has_value()) + { + // Add to the maximum timeout + maximumTimeout += attributeTimeout.value(); + } + break; + } + } + + status = Status::Success; + for (size_t i = 0; i < attributeStatuses.AllocatedSize(); ++i) + { + auto & attributeStatus = attributeStatuses[i]; + auto statusCode = Status::Success; + switch (attributeStatus.attributeID) + { + case Presets::Id: + case Schedules::Id: + statusCode = InAtomicWrite(endpoint, MakeOptional(attributeStatus.attributeID)) ? Status::Busy : Status::Success; + break; + default: + statusCode = Status::InvalidCommand; + break; + } + if (statusCode != Status::Success) + { + status = Status::Failure; + } + attributeStatus.statusCode = to_underlying(statusCode); + } + + auto timeout = std::min(System::Clock::Milliseconds16(commandData.timeout.Value()), maximumTimeout); + if (timeout.count() == 0) + { + commandObj->AddStatus(commandPath, Status::InvalidInState); + return; + } + + if (status == Status::Success) + { + if (!SetAtomicWrite(endpoint, GetSourceScopedNodeId(commandObj), AtomicWriteState::Open, attributeStatuses)) + { + for (size_t i = 0; i < attributeStatuses.AllocatedSize(); ++i) + { + attributeStatuses[i].statusCode = to_underlying(Status::ResourceExhausted); + } + status = Status::Failure; + } + else + { + // This is a valid request to open an atomic write. Tell the delegate it + // needs to keep track of a pending preset list now. + delegate->InitializePendingPresets(); + ScheduleTimer(endpoint, timeout); + } + } + + SendAtomicResponse(commandObj, commandPath, status, attributeStatuses, MakeOptional(timeout.count())); +} + +void ThermostatAttrAccess::CommitAtomicWrite(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Commands::AtomicRequest::DecodableType & commandData) +{ + EndpointId endpoint = commandPath.mEndpointId; + auto delegate = GetDelegate(endpoint); + + if (delegate == nullptr) + { + ChipLogError(Zcl, "Delegate is null"); + commandObj->AddStatus(commandPath, Status::InvalidInState); + return; + } + + Platform::ScopedMemoryBufferWithSize attributeStatuses; + auto status = BuildAttributeStatuses(endpoint, commandData.attributeRequests, attributeStatuses); + if (status != Status::Success) + { + commandObj->AddStatus(commandPath, status); + return; + } + + if (!InAtomicWrite(endpoint, commandObj, attributeStatuses)) + { + commandObj->AddStatus(commandPath, Status::InvalidInState); + return; + } + + status = Status::Success; + for (size_t i = 0; i < attributeStatuses.AllocatedSize(); ++i) + { + auto & attributeStatus = attributeStatuses[i]; + auto statusCode = Status::Success; + switch (attributeStatus.attributeID) + { + case Presets::Id: + statusCode = PrecommitPresets(endpoint); + break; + case Schedules::Id: + statusCode = Status::Success; + break; + default: + commandObj->AddStatus(commandPath, Status::InvalidInState); + return; + } + attributeStatus.statusCode = to_underlying(statusCode); + if (statusCode != Status::Success) + { + status = Status::Failure; + } + } + + if (status == Status::Success) + { + for (size_t i = 0; i < attributeStatuses.AllocatedSize(); ++i) + { + auto & attributeStatus = attributeStatuses[i]; + auto statusCode = Status::Success; + CHIP_ERROR err; + switch (attributeStatus.attributeID) + { + case Presets::Id: + err = delegate->CommitPendingPresets(); + if (err != CHIP_NO_ERROR) + { + statusCode = Status::InvalidInState; + } + break; + case Schedules::Id: + break; + default: + // Not reachable, since we returned in this situation above. + break; + } + attributeStatus.statusCode = to_underlying(statusCode); + if (statusCode != Status::Success) + { + status = Status::Failure; + } + } + } + + ResetAtomicWrite(endpoint); + SendAtomicResponse(commandObj, commandPath, status, attributeStatuses); +} + +void ThermostatAttrAccess::RollbackAtomicWrite(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Commands::AtomicRequest::DecodableType & commandData) +{ + + EndpointId endpoint = commandPath.mEndpointId; + auto delegate = GetDelegate(endpoint); + + if (delegate == nullptr) + { + ChipLogError(Zcl, "Delegate is null"); + commandObj->AddStatus(commandPath, Status::InvalidInState); + return; + } + + Platform::ScopedMemoryBufferWithSize attributeStatuses; + auto status = BuildAttributeStatuses(endpoint, commandData.attributeRequests, attributeStatuses); + if (status != Status::Success) + { + commandObj->AddStatus(commandPath, status); + return; + } + + if (!InAtomicWrite(endpoint, commandObj, attributeStatuses)) + { + // There's no open atomic write + commandObj->AddStatus(commandPath, Status::InvalidInState); + return; + } + + ResetAtomicWrite(endpoint); + + for (size_t i = 0; i < attributeStatuses.AllocatedSize(); ++i) + { + attributeStatuses[i].statusCode = to_underlying(Status::Success); + } + + SendAtomicResponse(commandObj, commandPath, status, attributeStatuses); +} + +void MatterThermostatClusterServerShutdownCallback(EndpointId endpoint) +{ + ChipLogProgress(Zcl, "Shutting down thermostat server cluster on endpoint %d", endpoint); + gThermostatAttrAccess.ResetAtomicWrite(endpoint); +} + +bool emberAfThermostatClusterAtomicRequestCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Clusters::Thermostat::Commands::AtomicRequest::DecodableType & commandData) +{ + auto & requestType = commandData.requestType; + + // If we've gotten this far, then the client has manage permission to call AtomicRequest, which is also the + // privilege necessary to write to the atomic attributes, so no need to check + + switch (requestType) + { + case Globals::AtomicRequestTypeEnum::kBeginWrite: + gThermostatAttrAccess.BeginAtomicWrite(commandObj, commandPath, commandData); + return true; + case Globals::AtomicRequestTypeEnum::kCommitWrite: + gThermostatAttrAccess.CommitAtomicWrite(commandObj, commandPath, commandData); + return true; + case Globals::AtomicRequestTypeEnum::kRollbackWrite: + gThermostatAttrAccess.RollbackAtomicWrite(commandObj, commandPath, commandData); + return true; + case Globals::AtomicRequestTypeEnum::kUnknownEnumValue: + commandObj->AddStatus(commandPath, Status::InvalidCommand); + return true; + } + + return false; +} + +} // namespace Thermostat +} // namespace Clusters +} // namespace app +} // namespace chip + +bool emberAfThermostatClusterAtomicRequestCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Clusters::Thermostat::Commands::AtomicRequest::DecodableType & commandData) +{ + return Thermostat::emberAfThermostatClusterAtomicRequestCallback(commandObj, commandPath, commandData); +} + +void MatterThermostatClusterServerShutdownCallback(EndpointId endpoint) +{ + Thermostat::MatterThermostatClusterServerShutdownCallback(endpoint); +} diff --git a/src/app/clusters/thermostat-server/thermostat-server-presets.cpp b/src/app/clusters/thermostat-server/thermostat-server-presets.cpp new file mode 100644 index 00000000000000..e57c2f9c95f8fd --- /dev/null +++ b/src/app/clusters/thermostat-server/thermostat-server-presets.cpp @@ -0,0 +1,546 @@ +/** + * + * 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 "thermostat-server.h" + +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::Thermostat; +using namespace chip::app::Clusters::Thermostat::Attributes; +using namespace chip::app::Clusters::Thermostat::Structs; +using namespace chip::app::Clusters::Globals::Structs; +using namespace chip::Protocols::InteractionModel; + +namespace { + +/** + * @brief Check if a preset is valid. + * + * @param[in] preset The preset to check. + * + * @return true If the preset is valid i.e the PresetHandle (if not null) fits within size constraints and the presetScenario enum + * value is valid. Otherwise, return false. + */ +bool IsValidPresetEntry(const PresetStruct::Type & preset) +{ + // Check that the preset handle is not too long. + if (!preset.presetHandle.IsNull() && preset.presetHandle.Value().size() > kPresetHandleSize) + { + return false; + } + + // Ensure we have a valid PresetScenario. + return (preset.presetScenario != PresetScenarioEnum::kUnknownEnumValue); +} + +/** + * @brief Checks if the preset is built-in + * + * @param[in] preset The preset to check. + * + * @return true If the preset is built-in, false otherwise. + */ +bool IsBuiltIn(const PresetStructWithOwnedMembers & preset) +{ + return preset.GetBuiltIn().ValueOr(false); +} + +/** + * @brief Checks if the presets are matching i.e the presetHandles are the same. + * + * @param[in] preset The preset to check. + * @param[in] presetToMatch The preset to match with. + * + * @return true If the presets match, false otherwise. If both preset handles are null, returns false + */ +bool PresetHandlesExistAndMatch(const PresetStructWithOwnedMembers & preset, const PresetStructWithOwnedMembers & presetToMatch) +{ + return !preset.GetPresetHandle().IsNull() && !presetToMatch.GetPresetHandle().IsNull() && + preset.GetPresetHandle().Value().data_equal(presetToMatch.GetPresetHandle().Value()); +} + +/** + * @brief Finds an entry in the pending presets list that matches a preset. + * The presetHandle of the two presets must match. + * + * @param[in] delegate The delegate to use. + * @param[in] presetToMatch The preset to match with. + * + * @return true if a matching entry was found in the pending presets list, false otherwise. + */ +bool MatchingPendingPresetExists(Delegate * delegate, const PresetStructWithOwnedMembers & presetToMatch) +{ + VerifyOrReturnValue(delegate != nullptr, false); + + for (uint8_t i = 0; true; i++) + { + PresetStructWithOwnedMembers preset; + CHIP_ERROR err = delegate->GetPendingPresetAtIndex(i, preset); + + if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) + { + break; + } + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "MatchingPendingPresetExists: GetPendingPresetAtIndex failed with error %" CHIP_ERROR_FORMAT, + err.Format()); + return false; + } + + if (PresetHandlesExistAndMatch(preset, presetToMatch)) + { + return true; + } + } + return false; +} + +/** + * @brief Finds and returns an entry in the Presets attribute list that matches + * a preset, if such an entry exists. The presetToMatch must have a preset handle. + * + * @param[in] delegate The delegate to use. + * @param[in] presetToMatch The preset to match with. + * @param[out] matchingPreset The preset in the Presets attribute list that has the same PresetHandle as the presetToMatch. + * + * @return true if a matching entry was found in the presets attribute list, false otherwise. + */ +bool GetMatchingPresetInPresets(Delegate * delegate, const PresetStruct::Type & presetToMatch, + PresetStructWithOwnedMembers & matchingPreset) +{ + VerifyOrReturnValue(delegate != nullptr, false); + + for (uint8_t i = 0; true; i++) + { + CHIP_ERROR err = delegate->GetPresetAtIndex(i, matchingPreset); + + if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) + { + break; + } + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "GetMatchingPresetInPresets: GetPresetAtIndex failed with error %" CHIP_ERROR_FORMAT, err.Format()); + return false; + } + + // Note: presets coming from our delegate always have a handle. + if (presetToMatch.presetHandle.Value().data_equal(matchingPreset.GetPresetHandle().Value())) + { + return true; + } + } + return false; +} + +/** + * @brief Returns the length of the list of presets if the pending presets were to be applied. The size of the pending presets list + * calculated, after all the constraint checks are done, is the new size of the updated Presets attribute since the pending + * preset list is expected to have all existing presets with or without edits plus new presets. + * This is called before changes are actually applied. + * + * @param[in] delegate The delegate to use. + * + * @return count of the updated Presets attribute if the pending presets were applied to it. Return 0 for error cases. + */ +uint8_t CountNumberOfPendingPresets(Delegate * delegate) +{ + uint8_t numberOfPendingPresets = 0; + + VerifyOrReturnValue(delegate != nullptr, 0); + + for (uint8_t i = 0; true; i++) + { + PresetStructWithOwnedMembers pendingPreset; + CHIP_ERROR err = delegate->GetPendingPresetAtIndex(i, pendingPreset); + + if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) + { + break; + } + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "CountNumberOfPendingPresets: GetPendingPresetAtIndex failed with error %" CHIP_ERROR_FORMAT, + err.Format()); + return 0; + } + numberOfPendingPresets++; + } + + return numberOfPendingPresets; +} + +/** + * @brief Checks if the presetScenario is present in the PresetTypes attribute. + * + * @param[in] delegate The delegate to use. + * @param[in] presetScenario The presetScenario to match with. + * + * @return true if the presetScenario is found, false otherwise. + */ +bool PresetScenarioExistsInPresetTypes(Delegate * delegate, PresetScenarioEnum presetScenario) +{ + VerifyOrReturnValue(delegate != nullptr, false); + + for (uint8_t i = 0; true; i++) + { + PresetTypeStruct::Type presetType; + auto err = delegate->GetPresetTypeAtIndex(i, presetType); + if (err != CHIP_NO_ERROR) + { + return false; + } + + if (presetType.presetScenario == presetScenario) + { + return true; + } + } + return false; +} + +/** + * @brief Returns the count of preset entries in the pending presets list that have the matching presetHandle. + * @param[in] delegate The delegate to use. + * @param[in] presetHandleToMatch The preset handle to match. + * + * @return count of the number of presets found with the matching presetHandle. Returns 0 if no matching presets were found. + */ +uint8_t CountPresetsInPendingListWithPresetHandle(Delegate * delegate, const ByteSpan & presetHandleToMatch) +{ + uint8_t count = 0; + VerifyOrReturnValue(delegate != nullptr, count); + + for (uint8_t i = 0; true; i++) + { + PresetStructWithOwnedMembers preset; + auto err = delegate->GetPendingPresetAtIndex(i, preset); + if (err != CHIP_NO_ERROR) + { + return count; + } + + DataModel::Nullable presetHandle = preset.GetPresetHandle(); + if (!presetHandle.IsNull() && presetHandle.Value().data_equal(presetHandleToMatch)) + { + count++; + } + } + return count; +} + +/** + * @brief Checks if the presetType for the given preset scenario supports name in the presetTypeFeatures bitmap. + * + * @param[in] delegate The delegate to use. + * @param[in] presetScenario The presetScenario to match with. + * + * @return true if the presetType for the given preset scenario supports name, false otherwise. + */ +bool PresetTypeSupportsNames(Delegate * delegate, PresetScenarioEnum scenario) +{ + VerifyOrReturnValue(delegate != nullptr, false); + + for (uint8_t i = 0; true; i++) + { + PresetTypeStruct::Type presetType; + auto err = delegate->GetPresetTypeAtIndex(i, presetType); + if (err != CHIP_NO_ERROR) + { + return false; + } + + if (presetType.presetScenario == scenario) + { + return (presetType.presetTypeFeatures.Has(PresetTypeFeaturesBitmap::kSupportsNames)); + } + } + return false; +} + +/** + * @brief Checks if the given preset handle is present in the presets attribute + * @param[in] delegate The delegate to use. + * @param[in] presetHandleToMatch The preset handle to match with. + * + * @return true if the given preset handle is present in the presets attribute list, false otherwise. + */ +bool IsPresetHandlePresentInPresets(Delegate * delegate, const ByteSpan & presetHandleToMatch) +{ + VerifyOrReturnValue(delegate != nullptr, false); + + PresetStructWithOwnedMembers matchingPreset; + for (uint8_t i = 0; true; i++) + { + CHIP_ERROR err = delegate->GetPresetAtIndex(i, matchingPreset); + + if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) + { + return false; + } + + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "IsPresetHandlePresentInPresets: GetPresetAtIndex failed with error %" CHIP_ERROR_FORMAT, + err.Format()); + return false; + } + + if (!matchingPreset.GetPresetHandle().IsNull() && matchingPreset.GetPresetHandle().Value().data_equal(presetHandleToMatch)) + { + return true; + } + } + return false; +} + +} // namespace + +namespace chip { +namespace app { +namespace Clusters { +namespace Thermostat { + +extern ThermostatAttrAccess gThermostatAttrAccess; +extern int16_t EnforceHeatingSetpointLimits(int16_t HeatingSetpoint, EndpointId endpoint); +extern int16_t EnforceCoolingSetpointLimits(int16_t CoolingSetpoint, EndpointId endpoint); + +Status ThermostatAttrAccess::SetActivePreset(EndpointId endpoint, DataModel::Nullable presetHandle) +{ + + auto delegate = GetDelegate(endpoint); + + if (delegate == nullptr) + { + ChipLogError(Zcl, "Delegate is null"); + return Status::InvalidInState; + } + + // If the preset handle passed in the command is not present in the Presets attribute, return INVALID_COMMAND. + if (!presetHandle.IsNull() && !IsPresetHandlePresentInPresets(delegate, presetHandle.Value())) + { + return Status::InvalidCommand; + } + + CHIP_ERROR err = delegate->SetActivePresetHandle(presetHandle); + + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "Failed to set ActivePresetHandle with error %" CHIP_ERROR_FORMAT, err.Format()); + return StatusIB(err).mStatus; + } + + return Status::Success; +} + +CHIP_ERROR ThermostatAttrAccess::AppendPendingPreset(Thermostat::Delegate * delegate, const PresetStruct::Type & preset) +{ + if (!IsValidPresetEntry(preset)) + { + return CHIP_IM_GLOBAL_STATUS(ConstraintError); + } + + if (preset.presetHandle.IsNull()) + { + if (IsBuiltIn(preset)) + { + return CHIP_IM_GLOBAL_STATUS(ConstraintError); + } + } + else + { + auto & presetHandle = preset.presetHandle.Value(); + + // Per spec we need to check that: + // (a) There is an existing non-pending preset with this handle. + PresetStructWithOwnedMembers matchingPreset; + if (!GetMatchingPresetInPresets(delegate, preset, matchingPreset)) + { + return CHIP_IM_GLOBAL_STATUS(NotFound); + } + + // (b) There is no existing pending preset with this handle. + if (CountPresetsInPendingListWithPresetHandle(delegate, presetHandle) > 0) + { + return CHIP_IM_GLOBAL_STATUS(ConstraintError); + } + + // (c)/(d) The built-in fields do not have a mismatch. + // TODO: What's the story with nullability on the BuiltIn field? + if (!preset.builtIn.IsNull() && !matchingPreset.GetBuiltIn().IsNull() && + preset.builtIn.Value() != matchingPreset.GetBuiltIn().Value()) + { + return CHIP_IM_GLOBAL_STATUS(ConstraintError); + } + } + + if (!PresetScenarioExistsInPresetTypes(delegate, preset.presetScenario)) + { + return CHIP_IM_GLOBAL_STATUS(ConstraintError); + } + + if (preset.name.HasValue() && !PresetTypeSupportsNames(delegate, preset.presetScenario)) + { + return CHIP_IM_GLOBAL_STATUS(ConstraintError); + } + + return delegate->AppendToPendingPresetList(preset); +} + +Status ThermostatAttrAccess::PrecommitPresets(EndpointId endpoint) +{ + auto delegate = GetDelegate(endpoint); + + if (delegate == nullptr) + { + ChipLogError(Zcl, "Delegate is null"); + return Status::InvalidInState; + } + + CHIP_ERROR err = CHIP_NO_ERROR; + + // For each preset in the presets attribute, check that the matching preset in the pending presets list does not + // violate any spec constraints. + for (uint8_t i = 0; true; i++) + { + PresetStructWithOwnedMembers preset; + err = delegate->GetPresetAtIndex(i, preset); + + if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) + { + break; + } + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, + "emberAfThermostatClusterCommitPresetsSchedulesRequestCallback: GetPresetAtIndex failed with error " + "%" CHIP_ERROR_FORMAT, + err.Format()); + return Status::InvalidInState; + } + + bool found = MatchingPendingPresetExists(delegate, preset); + + // If a built in preset in the Presets attribute list is removed and not found in the pending presets list, return + // CONSTRAINT_ERROR. + if (IsBuiltIn(preset) && !found) + { + return Status::ConstraintError; + } + } + + // If there is an ActivePresetHandle set, find the preset in the pending presets list that matches the ActivePresetHandle + // attribute. If a preset is not found with the same presetHandle, return INVALID_IN_STATE. If there is no ActivePresetHandle + // attribute set, continue with other checks. + uint8_t buffer[kPresetHandleSize]; + MutableByteSpan activePresetHandleSpan(buffer); + auto activePresetHandle = DataModel::MakeNullable(activePresetHandleSpan); + + err = delegate->GetActivePresetHandle(activePresetHandle); + + if (err != CHIP_NO_ERROR) + { + return Status::InvalidInState; + } + + if (!activePresetHandle.IsNull()) + { + uint8_t count = CountPresetsInPendingListWithPresetHandle(delegate, activePresetHandle.Value()); + if (count == 0) + { + return Status::InvalidInState; + } + } + + // For each preset in the pending presets list, check that the preset does not violate any spec constraints. + for (uint8_t i = 0; true; i++) + { + PresetStructWithOwnedMembers pendingPreset; + err = delegate->GetPendingPresetAtIndex(i, pendingPreset); + + if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) + { + break; + } + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, + "emberAfThermostatClusterCommitPresetsSchedulesRequestCallback: GetPendingPresetAtIndex failed with error " + "%" CHIP_ERROR_FORMAT, + err.Format()); + return Status::InvalidInState; + } + + // Enforce the Setpoint Limits for both the cooling and heating setpoints in the pending preset. + // TODO: This code does not work, because it's modifying our temporary copy. + Optional coolingSetpointValue = pendingPreset.GetCoolingSetpoint(); + if (coolingSetpointValue.HasValue()) + { + pendingPreset.SetCoolingSetpoint(MakeOptional(EnforceCoolingSetpointLimits(coolingSetpointValue.Value(), endpoint))); + } + + Optional heatingSetpointValue = pendingPreset.GetHeatingSetpoint(); + if (heatingSetpointValue.HasValue()) + { + pendingPreset.SetHeatingSetpoint(MakeOptional(EnforceHeatingSetpointLimits(heatingSetpointValue.Value(), endpoint))); + } + } + + uint8_t totalCount = CountNumberOfPendingPresets(delegate); + + uint8_t numberOfPresetsSupported = delegate->GetNumberOfPresets(); + + if (numberOfPresetsSupported == 0) + { + ChipLogError(Zcl, "emberAfThermostatClusterCommitPresetsSchedulesRequestCallback: Failed to get NumberOfPresets"); + return Status::InvalidInState; + } + + // If the expected length of the presets attribute with the applied changes exceeds the total number of presets supported, + // return RESOURCE_EXHAUSTED. Note that the changes are not yet applied. + if (numberOfPresetsSupported > 0 && totalCount > numberOfPresetsSupported) + { + return Status::ResourceExhausted; + } + + // TODO: Check if the number of presets for each presetScenario exceeds the max number of presets supported for that + // scenario. We plan to support only one preset for each presetScenario for our use cases so defer this for re-evaluation. + return Status::Success; +} + +bool emberAfThermostatClusterSetActivePresetRequestCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Commands::SetActivePresetRequest::DecodableType & commandData) +{ + auto status = gThermostatAttrAccess.SetActivePreset(commandPath.mEndpointId, commandData.presetHandle); + commandObj->AddStatus(commandPath, status); + return true; +} + +} // namespace Thermostat +} // namespace Clusters +} // namespace app +} // namespace chip + +bool emberAfThermostatClusterSetActivePresetRequestCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Commands::SetActivePresetRequest::DecodableType & commandData) +{ + return Thermostat::emberAfThermostatClusterSetActivePresetRequestCallback(commandObj, commandPath, commandData); +} diff --git a/src/app/clusters/thermostat-server/thermostat-server.cpp b/src/app/clusters/thermostat-server/thermostat-server.cpp index 8fe70211eeda1a..fe8ccf8aab3cf0 100644 --- a/src/app/clusters/thermostat-server/thermostat-server.cpp +++ b/src/app/clusters/thermostat-server/thermostat-server.cpp @@ -30,7 +30,6 @@ #include #include #include -#include using namespace chip; using namespace chip::app; @@ -38,8 +37,7 @@ using namespace chip::app::Clusters; using namespace chip::app::Clusters::Thermostat; using namespace chip::app::Clusters::Thermostat::Structs; using namespace chip::app::Clusters::Thermostat::Attributes; - -using imcode = Protocols::InteractionModel::Status; +using namespace Protocols::InteractionModel; constexpr int16_t kDefaultAbsMinHeatSetpointLimit = 700; // 7C (44.5 F) is the default constexpr int16_t kDefaultAbsMaxHeatSetpointLimit = 3000; // 30C (86 F) is the default @@ -69,381 +67,16 @@ constexpr int8_t kDefaultDeadBand = 25; // 2.5C is the default #define FEATURE_MAP_DEFAULT FEATURE_MAP_HEAT | FEATURE_MAP_COOL | FEATURE_MAP_AUTO -namespace { - -ThermostatAttrAccess gThermostatAttrAccess; - static_assert(kThermostatEndpointCount <= kEmberInvalidEndpointIndex, "Thermostat Delegate table size error"); Delegate * gDelegateTable[kThermostatEndpointCount] = { nullptr }; -Delegate * GetDelegate(EndpointId endpoint) -{ - uint16_t ep = - emberAfGetClusterServerEndpointIndex(endpoint, Thermostat::Id, MATTER_DM_THERMOSTAT_CLUSTER_SERVER_ENDPOINT_COUNT); - return (ep >= ArraySize(gDelegateTable) ? nullptr : gDelegateTable[ep]); -} - -/** - * @brief Check if a preset is valid. - * - * @param[in] preset The preset to check. - * - * @return true If the preset is valid i.e the PresetHandle (if not null) fits within size constraints and the presetScenario enum - * value is valid. Otherwise, return false. - */ -bool IsValidPresetEntry(const PresetStruct::Type & preset) -{ - // Check that the preset handle is not too long. - if (!preset.presetHandle.IsNull() && preset.presetHandle.Value().size() > kPresetHandleSize) - { - return false; - } - - // Ensure we have a valid PresetScenario. - return (preset.presetScenario != PresetScenarioEnum::kUnknownEnumValue); -} - -/** - * @brief Callback that is called when the timeout for editing the presets expires. - * - * @param[in] systemLayer The system layer. - * @param[in] callbackContext The context passed to the timer callback. - */ -void TimerExpiredCallback(System::Layer * systemLayer, void * callbackContext) -{ - EndpointId endpoint = static_cast(reinterpret_cast(callbackContext)); - - Delegate * delegate = GetDelegate(endpoint); - VerifyOrReturn(delegate != nullptr, ChipLogError(Zcl, "Delegate is null. Unable to handle timer expired")); - - delegate->ClearPendingPresetList(); - gThermostatAttrAccess.SetAtomicWrite(endpoint, ScopedNodeId(), kAtomicWriteState_Closed); -} - -/** - * @brief Schedules a timer for the given timeout in milliseconds. - * - * @param[in] endpoint The endpoint to use. - * @param[in] timeoutMilliseconds The timeout in milliseconds. - */ -void ScheduleTimer(EndpointId endpoint, System::Clock::Milliseconds16 timeout) -{ - DeviceLayer::SystemLayer().StartTimer(timeout, TimerExpiredCallback, - reinterpret_cast(static_cast(endpoint))); -} - -/** - * @brief Clears the currently scheduled timer. - * - * @param[in] endpoint The endpoint to use. - */ -void ClearTimer(EndpointId endpoint) -{ - DeviceLayer::SystemLayer().CancelTimer(TimerExpiredCallback, reinterpret_cast(static_cast(endpoint))); -} - -/** - * @brief Checks if the preset is built-in - * - * @param[in] preset The preset to check. - * - * @return true If the preset is built-in, false otherwise. - */ -bool IsBuiltIn(const PresetStructWithOwnedMembers & preset) -{ - return preset.GetBuiltIn().ValueOr(false); -} - -/** - * @brief Checks if the presets are matching i.e the presetHandles are the same. - * - * @param[in] preset The preset to check. - * @param[in] presetToMatch The preset to match with. - * - * @return true If the presets match, false otherwise. If both preset handles are null, returns false - */ -bool PresetHandlesExistAndMatch(const PresetStructWithOwnedMembers & preset, const PresetStructWithOwnedMembers & presetToMatch) -{ - return !preset.GetPresetHandle().IsNull() && !presetToMatch.GetPresetHandle().IsNull() && - preset.GetPresetHandle().Value().data_equal(presetToMatch.GetPresetHandle().Value()); -} - -/** - * @brief Get the source scoped node id. - * - * @param[in] commandObj The command handler object. - * - * @return The scoped node id of the source node. If the scoped node id is not retreived, return ScopedNodeId(). - */ -ScopedNodeId GetSourceScopedNodeId(CommandHandler * commandObj) -{ - ScopedNodeId sourceNodeId = ScopedNodeId(); - auto sessionHandle = commandObj->GetExchangeContext()->GetSessionHandle(); - - if (sessionHandle->IsSecureSession()) - { - sourceNodeId = sessionHandle->AsSecureSession()->GetPeer(); - } - else if (sessionHandle->IsGroupSession()) - { - sourceNodeId = sessionHandle->AsIncomingGroupSession()->GetPeer(); - } - return sourceNodeId; -} - -/** - * @brief Discards pending atomic writes and atomic state. - * - * @param[in] delegate The delegate to use. - * @param[in] endpoint The endpoint to use. - * - */ -void resetAtomicWrite(Delegate * delegate, EndpointId endpoint) -{ - if (delegate != nullptr) - { - delegate->ClearPendingPresetList(); - } - ClearTimer(endpoint); - gThermostatAttrAccess.SetAtomicWrite(endpoint, ScopedNodeId(), kAtomicWriteState_Closed); -} - -/** - * @brief Finds an entry in the pending presets list that matches a preset. - * The presetHandle of the two presets must match. - * - * @param[in] delegate The delegate to use. - * @param[in] presetToMatch The preset to match with. - * - * @return true if a matching entry was found in the pending presets list, false otherwise. - */ -bool MatchingPendingPresetExists(Delegate * delegate, const PresetStructWithOwnedMembers & presetToMatch) -{ - VerifyOrReturnValue(delegate != nullptr, false); - - for (uint8_t i = 0; true; i++) - { - PresetStructWithOwnedMembers preset; - CHIP_ERROR err = delegate->GetPendingPresetAtIndex(i, preset); - - if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) - { - break; - } - if (err != CHIP_NO_ERROR) - { - ChipLogError(Zcl, "MatchingPendingPresetExists: GetPendingPresetAtIndex failed with error %" CHIP_ERROR_FORMAT, - err.Format()); - return false; - } - - if (PresetHandlesExistAndMatch(preset, presetToMatch)) - { - return true; - } - } - return false; -} - -/** - * @brief Finds and returns an entry in the Presets attribute list that matches - * a preset, if such an entry exists. The presetToMatch must have a preset handle. - * - * @param[in] delegate The delegate to use. - * @param[in] presetToMatch The preset to match with. - * @param[out] matchingPreset The preset in the Presets attribute list that has the same PresetHandle as the presetToMatch. - * - * @return true if a matching entry was found in the presets attribute list, false otherwise. - */ -bool GetMatchingPresetInPresets(Delegate * delegate, const PresetStruct::Type & presetToMatch, - PresetStructWithOwnedMembers & matchingPreset) -{ - VerifyOrReturnValue(delegate != nullptr, false); - - for (uint8_t i = 0; true; i++) - { - CHIP_ERROR err = delegate->GetPresetAtIndex(i, matchingPreset); - - if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) - { - break; - } - if (err != CHIP_NO_ERROR) - { - ChipLogError(Zcl, "GetMatchingPresetInPresets: GetPresetAtIndex failed with error %" CHIP_ERROR_FORMAT, err.Format()); - return false; - } - - // Note: presets coming from our delegate always have a handle. - if (presetToMatch.presetHandle.Value().data_equal(matchingPreset.GetPresetHandle().Value())) - { - return true; - } - } - return false; -} - -/** - * @brief Checks if the given preset handle is present in the presets attribute - * @param[in] delegate The delegate to use. - * @param[in] presetHandleToMatch The preset handle to match with. - * - * @return true if the given preset handle is present in the presets attribute list, false otherwise. - */ -bool IsPresetHandlePresentInPresets(Delegate * delegate, const ByteSpan & presetHandleToMatch) -{ - VerifyOrReturnValue(delegate != nullptr, false); - - PresetStructWithOwnedMembers matchingPreset; - for (uint8_t i = 0; true; i++) - { - CHIP_ERROR err = delegate->GetPresetAtIndex(i, matchingPreset); - - if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) - { - return false; - } - - if (err != CHIP_NO_ERROR) - { - ChipLogError(Zcl, "IsPresetHandlePresentInPresets: GetPresetAtIndex failed with error %" CHIP_ERROR_FORMAT, - err.Format()); - return false; - } - - if (!matchingPreset.GetPresetHandle().IsNull() && matchingPreset.GetPresetHandle().Value().data_equal(presetHandleToMatch)) - { - return true; - } - } - return false; -} - -/** - * @brief Returns the length of the list of presets if the pending presets were to be applied. The size of the pending presets list - * calculated, after all the constraint checks are done, is the new size of the updated Presets attribute since the pending - * preset list is expected to have all existing presets with or without edits plus new presets. - * This is called before changes are actually applied. - * - * @param[in] delegate The delegate to use. - * - * @return count of the updated Presets attribute if the pending presets were applied to it. Return 0 for error cases. - */ -uint8_t CountNumberOfPendingPresets(Delegate * delegate) -{ - uint8_t numberOfPendingPresets = 0; - - VerifyOrReturnValue(delegate != nullptr, 0); - - for (uint8_t i = 0; true; i++) - { - PresetStructWithOwnedMembers pendingPreset; - CHIP_ERROR err = delegate->GetPendingPresetAtIndex(i, pendingPreset); - - if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) - { - break; - } - if (err != CHIP_NO_ERROR) - { - ChipLogError(Zcl, "CountNumberOfPendingPresets: GetPendingPresetAtIndex failed with error %" CHIP_ERROR_FORMAT, - err.Format()); - return 0; - } - numberOfPendingPresets++; - } - - return numberOfPendingPresets; -} - -/** - * @brief Checks if the presetScenario is present in the PresetTypes attribute. - * - * @param[in] delegate The delegate to use. - * @param[in] presetScenario The presetScenario to match with. - * - * @return true if the presetScenario is found, false otherwise. - */ -bool PresetScenarioExistsInPresetTypes(Delegate * delegate, PresetScenarioEnum presetScenario) -{ - VerifyOrReturnValue(delegate != nullptr, false); - - for (uint8_t i = 0; true; i++) - { - PresetTypeStruct::Type presetType; - auto err = delegate->GetPresetTypeAtIndex(i, presetType); - if (err != CHIP_NO_ERROR) - { - return false; - } - - if (presetType.presetScenario == presetScenario) - { - return true; - } - } - return false; -} - -/** - * @brief Returns the count of preset entries in the pending presets list that have the matching presetHandle. - * @param[in] delegate The delegate to use. - * @param[in] presetHandleToMatch The preset handle to match. - * - * @return count of the number of presets found with the matching presetHandle. Returns 0 if no matching presets were found. - */ -uint8_t CountPresetsInPendingListWithPresetHandle(Delegate * delegate, const ByteSpan & presetHandleToMatch) -{ - uint8_t count = 0; - VerifyOrReturnValue(delegate != nullptr, count); - - for (uint8_t i = 0; true; i++) - { - PresetStructWithOwnedMembers preset; - auto err = delegate->GetPendingPresetAtIndex(i, preset); - if (err != CHIP_NO_ERROR) - { - return count; - } - - DataModel::Nullable presetHandle = preset.GetPresetHandle(); - if (!presetHandle.IsNull() && presetHandle.Value().data_equal(presetHandleToMatch)) - { - count++; - } - } - return count; -} - -/** - * @brief Checks if the presetType for the given preset scenario supports name in the presetTypeFeatures bitmap. - * - * @param[in] delegate The delegate to use. - * @param[in] presetScenario The presetScenario to match with. - * - * @return true if the presetType for the given preset scenario supports name, false otherwise. - */ -bool PresetTypeSupportsNames(Delegate * delegate, PresetScenarioEnum scenario) -{ - VerifyOrReturnValue(delegate != nullptr, false); - - for (uint8_t i = 0; true; i++) - { - PresetTypeStruct::Type presetType; - auto err = delegate->GetPresetTypeAtIndex(i, presetType); - if (err != CHIP_NO_ERROR) - { - return false; - } +namespace chip { +namespace app { +namespace Clusters { +namespace Thermostat { - if (presetType.presetScenario == scenario) - { - return (presetType.presetTypeFeatures.Has(PresetTypeFeaturesBitmap::kSupportsNames)); - } - } - return false; -} +ThermostatAttrAccess gThermostatAttrAccess; int16_t EnforceHeatingSetpointLimits(int16_t HeatingSetpoint, EndpointId endpoint) { @@ -461,7 +94,7 @@ int16_t EnforceHeatingSetpointLimits(int16_t HeatingSetpoint, EndpointId endpoin // Note that the limits are initialized above per the spec limits // if they are not present Get() will not update the value so the defaults are used - imcode status; + Status status; // https://github.com/CHIP-Specifications/connectedhomeip-spec/issues/3724 // behavior is not specified when Abs * values are not present and user values are present @@ -471,24 +104,24 @@ int16_t EnforceHeatingSetpointLimits(int16_t HeatingSetpoint, EndpointId endpoin // if a attribute is not present then it's default shall be used. status = AbsMinHeatSetpointLimit::Get(endpoint, &AbsMinHeatSetpointLimit); - if (status != imcode::Success) + if (status != Status::Success) { ChipLogError(Zcl, "Warning: AbsMinHeatSetpointLimit missing using default"); } status = AbsMaxHeatSetpointLimit::Get(endpoint, &AbsMaxHeatSetpointLimit); - if (status != imcode::Success) + if (status != Status::Success) { ChipLogError(Zcl, "Warning: AbsMaxHeatSetpointLimit missing using default"); } status = MinHeatSetpointLimit::Get(endpoint, &MinHeatSetpointLimit); - if (status != imcode::Success) + if (status != Status::Success) { MinHeatSetpointLimit = AbsMinHeatSetpointLimit; } status = MaxHeatSetpointLimit::Get(endpoint, &MaxHeatSetpointLimit); - if (status != imcode::Success) + if (status != Status::Success) { MaxHeatSetpointLimit = AbsMaxHeatSetpointLimit; } @@ -532,7 +165,7 @@ int16_t EnforceCoolingSetpointLimits(int16_t CoolingSetpoint, EndpointId endpoin // Note that the limits are initialized above per the spec limits // if they are not present Get() will not update the value so the defaults are used - imcode status; + Status status; // https://github.com/CHIP-Specifications/connectedhomeip-spec/issues/3724 // behavior is not specified when Abs * values are not present and user values are present @@ -542,25 +175,25 @@ int16_t EnforceCoolingSetpointLimits(int16_t CoolingSetpoint, EndpointId endpoin // if a attribute is not present then it's default shall be used. status = AbsMinCoolSetpointLimit::Get(endpoint, &AbsMinCoolSetpointLimit); - if (status != imcode::Success) + if (status != Status::Success) { ChipLogError(Zcl, "Warning: AbsMinCoolSetpointLimit missing using default"); } status = AbsMaxCoolSetpointLimit::Get(endpoint, &AbsMaxCoolSetpointLimit); - if (status != imcode::Success) + if (status != Status::Success) { ChipLogError(Zcl, "Warning: AbsMaxCoolSetpointLimit missing using default"); } status = MinCoolSetpointLimit::Get(endpoint, &MinCoolSetpointLimit); - if (status != imcode::Success) + if (status != Status::Success) { MinCoolSetpointLimit = AbsMinCoolSetpointLimit; } status = MaxCoolSetpointLimit::Get(endpoint, &MaxCoolSetpointLimit); - if (status != imcode::Success) + if (status != Status::Success) { MaxCoolSetpointLimit = AbsMaxCoolSetpointLimit; } @@ -587,81 +220,22 @@ int16_t EnforceCoolingSetpointLimits(int16_t CoolingSetpoint, EndpointId endpoin return CoolingSetpoint; } -} // anonymous namespace - -namespace chip { -namespace app { -namespace Clusters { -namespace Thermostat { - -void SetDefaultDelegate(EndpointId endpoint, Delegate * delegate) -{ - uint16_t ep = - emberAfGetClusterServerEndpointIndex(endpoint, Thermostat::Id, MATTER_DM_THERMOSTAT_CLUSTER_SERVER_ENDPOINT_COUNT); - // if endpoint is found, add the delegate in the delegate table - if (ep < ArraySize(gDelegateTable)) - { - gDelegateTable[ep] = delegate; - } -} - -void ThermostatAttrAccess::SetAtomicWrite(EndpointId endpoint, ScopedNodeId originatorNodeId, AtomicWriteState state) -{ - uint16_t ep = - emberAfGetClusterServerEndpointIndex(endpoint, Thermostat::Id, MATTER_DM_THERMOSTAT_CLUSTER_SERVER_ENDPOINT_COUNT); - - if (ep < ArraySize(mAtomicWriteSessions)) - { - mAtomicWriteSessions[ep].state = state; - mAtomicWriteSessions[ep].endpointId = endpoint; - mAtomicWriteSessions[ep].nodeId = originatorNodeId; - } -} - -bool ThermostatAttrAccess::InAtomicWrite(EndpointId endpoint) +Delegate * GetDelegate(EndpointId endpoint) { - bool inAtomicWrite = false; uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, Thermostat::Id, MATTER_DM_THERMOSTAT_CLUSTER_SERVER_ENDPOINT_COUNT); - - if (ep < ArraySize(mAtomicWriteSessions)) - { - inAtomicWrite = (mAtomicWriteSessions[ep].state == kAtomicWriteState_Open); - } - return inAtomicWrite; -} - -bool ThermostatAttrAccess::InAtomicWrite(const Access::SubjectDescriptor & subjectDescriptor, EndpointId endpoint) -{ - if (!InAtomicWrite(endpoint)) - { - return false; - } - return subjectDescriptor.authMode == Access::AuthMode::kCase && - GetAtomicWriteScopedNodeId(endpoint) == ScopedNodeId(subjectDescriptor.subject, subjectDescriptor.fabricIndex); -} - -bool ThermostatAttrAccess::InAtomicWrite(CommandHandler * commandObj, EndpointId endpoint) -{ - if (!InAtomicWrite(endpoint)) - { - return false; - } - ScopedNodeId sourceNodeId = GetSourceScopedNodeId(commandObj); - return GetAtomicWriteScopedNodeId(endpoint) == sourceNodeId; + return (ep >= ArraySize(gDelegateTable) ? nullptr : gDelegateTable[ep]); } -ScopedNodeId ThermostatAttrAccess::GetAtomicWriteScopedNodeId(EndpointId endpoint) +void SetDefaultDelegate(EndpointId endpoint, Delegate * delegate) { - ScopedNodeId originatorNodeId = ScopedNodeId(); uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, Thermostat::Id, MATTER_DM_THERMOSTAT_CLUSTER_SERVER_ENDPOINT_COUNT); - - if (ep < ArraySize(mAtomicWriteSessions)) + // if endpoint is found, add the delegate in the delegate table + if (ep < ArraySize(gDelegateTable)) { - originatorNodeId = mAtomicWriteSessions[ep].nodeId; + gDelegateTable[ep] = delegate; } - return originatorNodeId; } CHIP_ERROR ThermostatAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) @@ -669,7 +243,7 @@ CHIP_ERROR ThermostatAttrAccess::Read(const ConcreteReadAttributePath & aPath, A VerifyOrDie(aPath.mClusterId == Thermostat::Id); uint32_t ourFeatureMap; - bool localTemperatureNotExposedSupported = (FeatureMap::Get(aPath.mEndpointId, &ourFeatureMap) == imcode::Success) && + bool localTemperatureNotExposedSupported = (FeatureMap::Get(aPath.mEndpointId, &ourFeatureMap) == Status::Success) && ((ourFeatureMap & to_underlying(Feature::kLocalTemperatureNotExposed)) != 0); switch (aPath.mAttributeId) @@ -684,8 +258,8 @@ CHIP_ERROR ThermostatAttrAccess::Read(const ConcreteReadAttributePath & aPath, A if (localTemperatureNotExposedSupported) { BitMask valueRemoteSensing; - imcode status = RemoteSensing::Get(aPath.mEndpointId, &valueRemoteSensing); - if (status != imcode::Success) + Status status = RemoteSensing::Get(aPath.mEndpointId, &valueRemoteSensing); + if (status != Status::Success) { StatusIB statusIB(status); return statusIB.ToChipError(); @@ -725,7 +299,7 @@ CHIP_ERROR ThermostatAttrAccess::Read(const ConcreteReadAttributePath & aPath, A VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Delegate is null")); auto & subjectDescriptor = aEncoder.GetSubjectDescriptor(); - if (InAtomicWrite(subjectDescriptor, aPath.mEndpointId)) + if (InAtomicWrite(aPath.mEndpointId, subjectDescriptor, MakeOptional(aPath.mAttributeId))) { return aEncoder.EncodeList([delegate](const auto & encoder) -> CHIP_ERROR { for (uint8_t i = 0; true; i++) @@ -801,12 +375,12 @@ CHIP_ERROR ThermostatAttrAccess::Write(const ConcreteDataAttributePath & aPath, VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Delegate is null")); // Presets are not editable, return INVALID_IN_STATE. - VerifyOrReturnError(InAtomicWrite(endpoint), CHIP_IM_GLOBAL_STATUS(InvalidInState), + VerifyOrReturnError(InAtomicWrite(endpoint, MakeOptional(aPath.mAttributeId)), CHIP_IM_GLOBAL_STATUS(InvalidInState), ChipLogError(Zcl, "Presets are not editable")); // OK, we're in an atomic write, make sure the requesting node is the same one that started the atomic write, // otherwise return BUSY. - if (!InAtomicWrite(subjectDescriptor, endpoint)) + if (!InAtomicWrite(endpoint, subjectDescriptor, MakeOptional(aPath.mAttributeId))) { ChipLogError(Zcl, "Another node is editing presets. Server is busy. Try again later"); return CHIP_IM_GLOBAL_STATUS(Busy); @@ -848,14 +422,14 @@ CHIP_ERROR ThermostatAttrAccess::Write(const ConcreteDataAttributePath & aPath, } // This is not an atomic attribute, so check to make sure we don't have an atomic write going for this client - if (InAtomicWrite(subjectDescriptor, endpoint)) + if (InAtomicWrite(endpoint, subjectDescriptor)) { ChipLogError(Zcl, "Can not write to non-atomic attributes during atomic write"); return CHIP_IM_GLOBAL_STATUS(InvalidInState); } uint32_t ourFeatureMap; - bool localTemperatureNotExposedSupported = (FeatureMap::Get(aPath.mEndpointId, &ourFeatureMap) == imcode::Success) && + bool localTemperatureNotExposedSupported = (FeatureMap::Get(aPath.mEndpointId, &ourFeatureMap) == Status::Success) && ((ourFeatureMap & to_underlying(Feature::kLocalTemperatureNotExposed)) != 0); switch (aPath.mAttributeId) @@ -869,7 +443,7 @@ CHIP_ERROR ThermostatAttrAccess::Write(const ConcreteDataAttributePath & aPath, { return CHIP_IM_GLOBAL_STATUS(ConstraintError); } - imcode status = RemoteSensing::Set(aPath.mEndpointId, valueRemoteSensing); + Status status = RemoteSensing::Set(aPath.mEndpointId, valueRemoteSensing); StatusIB statusIB(status); return statusIB.ToChipError(); } @@ -882,73 +456,14 @@ CHIP_ERROR ThermostatAttrAccess::Write(const ConcreteDataAttributePath & aPath, return CHIP_NO_ERROR; } -CHIP_ERROR ThermostatAttrAccess::AppendPendingPreset(Thermostat::Delegate * delegate, const PresetStruct::Type & preset) -{ - if (!IsValidPresetEntry(preset)) - { - return CHIP_IM_GLOBAL_STATUS(ConstraintError); - } - - if (preset.presetHandle.IsNull()) - { - if (IsBuiltIn(preset)) - { - return CHIP_IM_GLOBAL_STATUS(ConstraintError); - } - } - else - { - auto & presetHandle = preset.presetHandle.Value(); - - // Per spec we need to check that: - // (a) There is an existing non-pending preset with this handle. - PresetStructWithOwnedMembers matchingPreset; - if (!GetMatchingPresetInPresets(delegate, preset, matchingPreset)) - { - return CHIP_IM_GLOBAL_STATUS(NotFound); - } - - // (b) There is no existing pending preset with this handle. - if (CountPresetsInPendingListWithPresetHandle(delegate, presetHandle) > 0) - { - return CHIP_IM_GLOBAL_STATUS(ConstraintError); - } - - // (c)/(d) The built-in fields do not have a mismatch. - // TODO: What's the story with nullability on the BuiltIn field? - if (!preset.builtIn.IsNull() && !matchingPreset.GetBuiltIn().IsNull() && - preset.builtIn.Value() != matchingPreset.GetBuiltIn().Value()) - { - return CHIP_IM_GLOBAL_STATUS(ConstraintError); - } - } - - if (!PresetScenarioExistsInPresetTypes(delegate, preset.presetScenario)) - { - return CHIP_IM_GLOBAL_STATUS(ConstraintError); - } - - if (preset.name.HasValue() && !PresetTypeSupportsNames(delegate, preset.presetScenario)) - { - return CHIP_IM_GLOBAL_STATUS(ConstraintError); - } - - return delegate->AppendToPendingPresetList(preset); -} - void ThermostatAttrAccess::OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) { for (size_t i = 0; i < ArraySize(mAtomicWriteSessions); ++i) { - auto atomicWriteState = mAtomicWriteSessions[i]; - if (atomicWriteState.state == kAtomicWriteState_Open && atomicWriteState.nodeId.GetFabricIndex() == fabricIndex) + auto & atomicWriteState = mAtomicWriteSessions[i]; + if (atomicWriteState.state == AtomicWriteState::Open && atomicWriteState.nodeId.GetFabricIndex() == fabricIndex) { - auto delegate = GetDelegate(atomicWriteState.endpointId); - if (delegate == nullptr) - { - continue; - } - resetAtomicWrite(delegate, atomicWriteState.endpointId); + ResetAtomicWrite(atomicWriteState.endpointId); } } } @@ -1003,7 +518,7 @@ MatterThermostatClusterServerPreAttributeChangedCallback(const app::ConcreteAttr bool CoolSupported = false; bool OccupancySupported = false; - if (FeatureMap::Get(endpoint, &OurFeatureMap) != imcode::Success) + if (FeatureMap::Get(endpoint, &OurFeatureMap) != Status::Success) OurFeatureMap = FEATURE_MAP_DEFAULT; if (OurFeatureMap & 1 << 5) // Bit 5 is Auto Mode supported @@ -1020,63 +535,63 @@ MatterThermostatClusterServerPreAttributeChangedCallback(const app::ConcreteAttr if (AutoSupported) { - if (MinSetpointDeadBand::Get(endpoint, &DeadBand) != imcode::Success) + if (MinSetpointDeadBand::Get(endpoint, &DeadBand) != Status::Success) { DeadBand = kDefaultDeadBand; } DeadBandTemp = static_cast(DeadBand * 10); } - if (AbsMinCoolSetpointLimit::Get(endpoint, &AbsMinCoolSetpointLimit) != imcode::Success) + if (AbsMinCoolSetpointLimit::Get(endpoint, &AbsMinCoolSetpointLimit) != Status::Success) AbsMinCoolSetpointLimit = kDefaultAbsMinCoolSetpointLimit; - if (AbsMaxCoolSetpointLimit::Get(endpoint, &AbsMaxCoolSetpointLimit) != imcode::Success) + if (AbsMaxCoolSetpointLimit::Get(endpoint, &AbsMaxCoolSetpointLimit) != Status::Success) AbsMaxCoolSetpointLimit = kDefaultAbsMaxCoolSetpointLimit; - if (MinCoolSetpointLimit::Get(endpoint, &MinCoolSetpointLimit) != imcode::Success) + if (MinCoolSetpointLimit::Get(endpoint, &MinCoolSetpointLimit) != Status::Success) MinCoolSetpointLimit = AbsMinCoolSetpointLimit; - if (MaxCoolSetpointLimit::Get(endpoint, &MaxCoolSetpointLimit) != imcode::Success) + if (MaxCoolSetpointLimit::Get(endpoint, &MaxCoolSetpointLimit) != Status::Success) MaxCoolSetpointLimit = AbsMaxCoolSetpointLimit; - if (AbsMinHeatSetpointLimit::Get(endpoint, &AbsMinHeatSetpointLimit) != imcode::Success) + if (AbsMinHeatSetpointLimit::Get(endpoint, &AbsMinHeatSetpointLimit) != Status::Success) AbsMinHeatSetpointLimit = kDefaultAbsMinHeatSetpointLimit; - if (AbsMaxHeatSetpointLimit::Get(endpoint, &AbsMaxHeatSetpointLimit) != imcode::Success) + if (AbsMaxHeatSetpointLimit::Get(endpoint, &AbsMaxHeatSetpointLimit) != Status::Success) AbsMaxHeatSetpointLimit = kDefaultAbsMaxHeatSetpointLimit; - if (MinHeatSetpointLimit::Get(endpoint, &MinHeatSetpointLimit) != imcode::Success) + if (MinHeatSetpointLimit::Get(endpoint, &MinHeatSetpointLimit) != Status::Success) MinHeatSetpointLimit = AbsMinHeatSetpointLimit; - if (MaxHeatSetpointLimit::Get(endpoint, &MaxHeatSetpointLimit) != imcode::Success) + if (MaxHeatSetpointLimit::Get(endpoint, &MaxHeatSetpointLimit) != Status::Success) MaxHeatSetpointLimit = AbsMaxHeatSetpointLimit; if (CoolSupported) - if (OccupiedCoolingSetpoint::Get(endpoint, &OccupiedCoolingSetpoint) != imcode::Success) + if (OccupiedCoolingSetpoint::Get(endpoint, &OccupiedCoolingSetpoint) != Status::Success) { ChipLogError(Zcl, "Error: Can not read Occupied Cooling Setpoint"); - return imcode::Failure; + return Status::Failure; } if (HeatSupported) - if (OccupiedHeatingSetpoint::Get(endpoint, &OccupiedHeatingSetpoint) != imcode::Success) + if (OccupiedHeatingSetpoint::Get(endpoint, &OccupiedHeatingSetpoint) != Status::Success) { ChipLogError(Zcl, "Error: Can not read Occupied Heating Setpoint"); - return imcode::Failure; + return Status::Failure; } if (CoolSupported && OccupancySupported) - if (UnoccupiedCoolingSetpoint::Get(endpoint, &UnoccupiedCoolingSetpoint) != imcode::Success) + if (UnoccupiedCoolingSetpoint::Get(endpoint, &UnoccupiedCoolingSetpoint) != Status::Success) { ChipLogError(Zcl, "Error: Can not read Unoccupied Cooling Setpoint"); - return imcode::Failure; + return Status::Failure; } if (HeatSupported && OccupancySupported) - if (UnoccupiedHeatingSetpoint::Get(endpoint, &UnoccupiedHeatingSetpoint) != imcode::Success) + if (UnoccupiedHeatingSetpoint::Get(endpoint, &UnoccupiedHeatingSetpoint) != Status::Success) { ChipLogError(Zcl, "Error: Can not read Unoccupied Heating Setpoint"); - return imcode::Failure; + return Status::Failure; } switch (attributePath.mAttributeId) @@ -1084,143 +599,143 @@ MatterThermostatClusterServerPreAttributeChangedCallback(const app::ConcreteAttr case OccupiedHeatingSetpoint::Id: { requested = static_cast(chip::Encoding::LittleEndian::Get16(value)); if (!HeatSupported) - return imcode::UnsupportedAttribute; + return Status::UnsupportedAttribute; if (requested < AbsMinHeatSetpointLimit || requested < MinHeatSetpointLimit || requested > AbsMaxHeatSetpointLimit || requested > MaxHeatSetpointLimit) - return imcode::InvalidValue; + return Status::InvalidValue; if (AutoSupported) { if (requested > OccupiedCoolingSetpoint - DeadBandTemp) - return imcode::InvalidValue; + return Status::InvalidValue; } - return imcode::Success; + return Status::Success; } case OccupiedCoolingSetpoint::Id: { requested = static_cast(chip::Encoding::LittleEndian::Get16(value)); if (!CoolSupported) - return imcode::UnsupportedAttribute; + return Status::UnsupportedAttribute; if (requested < AbsMinCoolSetpointLimit || requested < MinCoolSetpointLimit || requested > AbsMaxCoolSetpointLimit || requested > MaxCoolSetpointLimit) - return imcode::InvalidValue; + return Status::InvalidValue; if (AutoSupported) { if (requested < OccupiedHeatingSetpoint + DeadBandTemp) - return imcode::InvalidValue; + return Status::InvalidValue; } - return imcode::Success; + return Status::Success; } case UnoccupiedHeatingSetpoint::Id: { requested = static_cast(chip::Encoding::LittleEndian::Get16(value)); if (!(HeatSupported && OccupancySupported)) - return imcode::UnsupportedAttribute; + return Status::UnsupportedAttribute; if (requested < AbsMinHeatSetpointLimit || requested < MinHeatSetpointLimit || requested > AbsMaxHeatSetpointLimit || requested > MaxHeatSetpointLimit) - return imcode::InvalidValue; + return Status::InvalidValue; if (AutoSupported) { if (requested > UnoccupiedCoolingSetpoint - DeadBandTemp) - return imcode::InvalidValue; + return Status::InvalidValue; } - return imcode::Success; + return Status::Success; } case UnoccupiedCoolingSetpoint::Id: { requested = static_cast(chip::Encoding::LittleEndian::Get16(value)); if (!(CoolSupported && OccupancySupported)) - return imcode::UnsupportedAttribute; + return Status::UnsupportedAttribute; if (requested < AbsMinCoolSetpointLimit || requested < MinCoolSetpointLimit || requested > AbsMaxCoolSetpointLimit || requested > MaxCoolSetpointLimit) - return imcode::InvalidValue; + return Status::InvalidValue; if (AutoSupported) { if (requested < UnoccupiedHeatingSetpoint + DeadBandTemp) - return imcode::InvalidValue; + return Status::InvalidValue; } - return imcode::Success; + return Status::Success; } case MinHeatSetpointLimit::Id: { requested = static_cast(chip::Encoding::LittleEndian::Get16(value)); if (!HeatSupported) - return imcode::UnsupportedAttribute; + return Status::UnsupportedAttribute; if (requested < AbsMinHeatSetpointLimit || requested > MaxHeatSetpointLimit || requested > AbsMaxHeatSetpointLimit) - return imcode::InvalidValue; + return Status::InvalidValue; if (AutoSupported) { if (requested > MinCoolSetpointLimit - DeadBandTemp) - return imcode::InvalidValue; + return Status::InvalidValue; } - return imcode::Success; + return Status::Success; } case MaxHeatSetpointLimit::Id: { requested = static_cast(chip::Encoding::LittleEndian::Get16(value)); if (!HeatSupported) - return imcode::UnsupportedAttribute; + return Status::UnsupportedAttribute; if (requested < AbsMinHeatSetpointLimit || requested < MinHeatSetpointLimit || requested > AbsMaxHeatSetpointLimit) - return imcode::InvalidValue; + return Status::InvalidValue; if (AutoSupported) { if (requested > MaxCoolSetpointLimit - DeadBandTemp) - return imcode::InvalidValue; + return Status::InvalidValue; } - return imcode::Success; + return Status::Success; } case MinCoolSetpointLimit::Id: { requested = static_cast(chip::Encoding::LittleEndian::Get16(value)); if (!CoolSupported) - return imcode::UnsupportedAttribute; + return Status::UnsupportedAttribute; if (requested < AbsMinCoolSetpointLimit || requested > MaxCoolSetpointLimit || requested > AbsMaxCoolSetpointLimit) - return imcode::InvalidValue; + return Status::InvalidValue; if (AutoSupported) { if (requested < MinHeatSetpointLimit + DeadBandTemp) - return imcode::InvalidValue; + return Status::InvalidValue; } - return imcode::Success; + return Status::Success; } case MaxCoolSetpointLimit::Id: { requested = static_cast(chip::Encoding::LittleEndian::Get16(value)); if (!CoolSupported) - return imcode::UnsupportedAttribute; + return Status::UnsupportedAttribute; if (requested < AbsMinCoolSetpointLimit || requested < MinCoolSetpointLimit || requested > AbsMaxCoolSetpointLimit) - return imcode::InvalidValue; + return Status::InvalidValue; if (AutoSupported) { if (requested < MaxHeatSetpointLimit + DeadBandTemp) - return imcode::InvalidValue; + return Status::InvalidValue; } - return imcode::Success; + return Status::Success; } case MinSetpointDeadBand::Id: { requested = *value; if (!AutoSupported) - return imcode::UnsupportedAttribute; + return Status::UnsupportedAttribute; if (requested < 0 || requested > 25) - return imcode::InvalidValue; - return imcode::Success; + return Status::InvalidValue; + return Status::Success; } case ControlSequenceOfOperation::Id: { uint8_t requestedCSO; requestedCSO = *value; if (requestedCSO > to_underlying(ControlSequenceOfOperationEnum::kCoolingAndHeatingWithReheat)) - return imcode::InvalidValue; - return imcode::Success; + return Status::InvalidValue; + return Status::Success; } case SystemMode::Id: { ControlSequenceOfOperationEnum ControlSequenceOfOperation; - imcode status = ControlSequenceOfOperation::Get(endpoint, &ControlSequenceOfOperation); - if (status != imcode::Success) + Status status = ControlSequenceOfOperation::Get(endpoint, &ControlSequenceOfOperation); + if (status != Status::Success) { - return imcode::InvalidValue; + return Status::InvalidValue; } auto RequestedSystemMode = static_cast(*value); if (ControlSequenceOfOperation > ControlSequenceOfOperationEnum::kCoolingAndHeatingWithReheat || RequestedSystemMode > SystemModeEnum::kFanOnly) { - return imcode::InvalidValue; + return Status::InvalidValue; } switch (ControlSequenceOfOperation) @@ -1228,22 +743,22 @@ MatterThermostatClusterServerPreAttributeChangedCallback(const app::ConcreteAttr case ControlSequenceOfOperationEnum::kCoolingOnly: case ControlSequenceOfOperationEnum::kCoolingWithReheat: if (RequestedSystemMode == SystemModeEnum::kHeat || RequestedSystemMode == SystemModeEnum::kEmergencyHeat) - return imcode::InvalidValue; + return Status::InvalidValue; else - return imcode::Success; + return Status::Success; case ControlSequenceOfOperationEnum::kHeatingOnly: case ControlSequenceOfOperationEnum::kHeatingWithReheat: if (RequestedSystemMode == SystemModeEnum::kCool || RequestedSystemMode == SystemModeEnum::kPrecooling) - return imcode::InvalidValue; + return Status::InvalidValue; else - return imcode::Success; + return Status::Success; default: - return imcode::Success; + return Status::Success; } } default: - return imcode::Success; + return Status::Success; } } @@ -1279,363 +794,6 @@ bool emberAfThermostatClusterSetActiveScheduleRequestCallback( return false; } -bool emberAfThermostatClusterSetActivePresetRequestCallback( - CommandHandler * commandObj, const ConcreteCommandPath & commandPath, - const Clusters::Thermostat::Commands::SetActivePresetRequest::DecodableType & commandData) -{ - EndpointId endpoint = commandPath.mEndpointId; - Delegate * delegate = GetDelegate(endpoint); - - if (delegate == nullptr) - { - ChipLogError(Zcl, "Delegate is null"); - commandObj->AddStatus(commandPath, imcode::InvalidInState); - return true; - } - - DataModel::Nullable newPresetHandle = commandData.presetHandle; - - // If the preset handle passed in the command is not present in the Presets attribute, return INVALID_COMMAND. - if (!newPresetHandle.IsNull() && !IsPresetHandlePresentInPresets(delegate, newPresetHandle.Value())) - { - commandObj->AddStatus(commandPath, imcode::InvalidCommand); - return true; - } - - CHIP_ERROR err = delegate->SetActivePresetHandle(newPresetHandle); - - if (err != CHIP_NO_ERROR) - { - ChipLogError(Zcl, "Failed to set ActivePresetHandle with error %" CHIP_ERROR_FORMAT, err.Format()); - commandObj->AddStatus(commandPath, StatusIB(err).mStatus); - return true; - } - - commandObj->AddStatus(commandPath, imcode::Success); - return true; -} - -bool validAtomicAttributes(const Commands::AtomicRequest::DecodableType & commandData, bool requireBoth) -{ - auto attributeIdsIter = commandData.attributeRequests.begin(); - bool requestedPresets = false, requestedSchedules = false; - while (attributeIdsIter.Next()) - { - auto & attributeId = attributeIdsIter.GetValue(); - - switch (attributeId) - { - case Presets::Id: - if (requestedPresets) // Double-requesting an attribute is invalid - { - return false; - } - requestedPresets = true; - break; - case Schedules::Id: - if (requestedSchedules) // Double-requesting an attribute is invalid - { - return false; - } - requestedSchedules = true; - break; - default: - return false; - } - } - if (attributeIdsIter.GetStatus() != CHIP_NO_ERROR) - { - return false; - } - if (requireBoth) - { - return (requestedPresets && requestedSchedules); - } - // If the atomic request doesn't contain at least one of these attributes, it's invalid - return (requestedPresets || requestedSchedules); -} - -void sendAtomicResponse(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, imcode status, imcode presetsStatus, - imcode schedulesStatus, Optional timeout = NullOptional) -{ - Commands::AtomicResponse::Type response; - Globals::Structs::AtomicAttributeStatusStruct::Type attributeStatus[] = { - { .attributeID = Presets::Id, .statusCode = to_underlying(presetsStatus) }, - { .attributeID = Schedules::Id, .statusCode = to_underlying(schedulesStatus) } - }; - response.statusCode = to_underlying(status); - response.attributeStatus = attributeStatus; - response.timeout = timeout; - commandObj->AddResponse(commandPath, response); -} - -void handleAtomicBegin(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, - const Commands::AtomicRequest::DecodableType & commandData) -{ - EndpointId endpoint = commandPath.mEndpointId; - - Delegate * delegate = GetDelegate(endpoint); - - if (delegate == nullptr) - { - ChipLogError(Zcl, "Delegate is null"); - commandObj->AddStatus(commandPath, imcode::InvalidInState); - return; - } - - if (gThermostatAttrAccess.InAtomicWrite(commandObj, endpoint)) - { - // This client already has an open atomic write - commandObj->AddStatus(commandPath, imcode::InvalidInState); - return; - } - - if (!commandData.timeout.HasValue()) - { - commandObj->AddStatus(commandPath, imcode::InvalidCommand); - return; - } - - if (!validAtomicAttributes(commandData, false)) - { - commandObj->AddStatus(commandPath, imcode::InvalidCommand); - return; - } - - if (gThermostatAttrAccess.InAtomicWrite(endpoint)) - { - sendAtomicResponse(commandObj, commandPath, imcode::Failure, imcode::Busy, imcode::Busy); - return; - } - - // This is a valid request to open an atomic write. Tell the delegate it - // needs to keep track of a pending preset list now. - delegate->InitializePendingPresets(); - - auto timeout = - delegate->GetAtomicWriteTimeout(commandData.attributeRequests, System::Clock::Milliseconds16(commandData.timeout.Value())); - - if (!timeout.has_value()) - { - commandObj->AddStatus(commandPath, imcode::InvalidCommand); - return; - } - ScheduleTimer(endpoint, timeout.value()); - gThermostatAttrAccess.SetAtomicWrite(endpoint, GetSourceScopedNodeId(commandObj), kAtomicWriteState_Open); - sendAtomicResponse(commandObj, commandPath, imcode::Success, imcode::Success, imcode::Success, - MakeOptional(timeout.value().count())); -} - -imcode commitPresets(Delegate * delegate, EndpointId endpoint) -{ - CHIP_ERROR err = CHIP_NO_ERROR; - - // For each preset in the presets attribute, check that the matching preset in the pending presets list does not - // violate any spec constraints. - for (uint8_t i = 0; true; i++) - { - PresetStructWithOwnedMembers preset; - err = delegate->GetPresetAtIndex(i, preset); - - if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) - { - break; - } - if (err != CHIP_NO_ERROR) - { - ChipLogError(Zcl, - "emberAfThermostatClusterCommitPresetsSchedulesRequestCallback: GetPresetAtIndex failed with error " - "%" CHIP_ERROR_FORMAT, - err.Format()); - return imcode::InvalidInState; - } - - bool found = MatchingPendingPresetExists(delegate, preset); - - // If a built in preset in the Presets attribute list is removed and not found in the pending presets list, return - // CONSTRAINT_ERROR. - if (IsBuiltIn(preset) && !found) - { - return imcode::ConstraintError; - } - } - - // If there is an ActivePresetHandle set, find the preset in the pending presets list that matches the ActivePresetHandle - // attribute. If a preset is not found with the same presetHandle, return INVALID_IN_STATE. If there is no ActivePresetHandle - // attribute set, continue with other checks. - uint8_t buffer[kPresetHandleSize]; - MutableByteSpan activePresetHandleSpan(buffer); - auto activePresetHandle = DataModel::MakeNullable(activePresetHandleSpan); - - err = delegate->GetActivePresetHandle(activePresetHandle); - - if (err != CHIP_NO_ERROR) - { - return imcode::InvalidInState; - } - - if (!activePresetHandle.IsNull()) - { - uint8_t count = CountPresetsInPendingListWithPresetHandle(delegate, activePresetHandle.Value()); - if (count == 0) - { - return imcode::InvalidInState; - } - } - - // For each preset in the pending presets list, check that the preset does not violate any spec constraints. - for (uint8_t i = 0; true; i++) - { - PresetStructWithOwnedMembers pendingPreset; - err = delegate->GetPendingPresetAtIndex(i, pendingPreset); - - if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) - { - break; - } - if (err != CHIP_NO_ERROR) - { - ChipLogError(Zcl, - "emberAfThermostatClusterCommitPresetsSchedulesRequestCallback: GetPendingPresetAtIndex failed with error " - "%" CHIP_ERROR_FORMAT, - err.Format()); - return imcode::InvalidInState; - } - - // Enforce the Setpoint Limits for both the cooling and heating setpoints in the pending preset. - // TODO: This code does not work, because it's modifying our temporary copy. - Optional coolingSetpointValue = pendingPreset.GetCoolingSetpoint(); - if (coolingSetpointValue.HasValue()) - { - pendingPreset.SetCoolingSetpoint(MakeOptional(EnforceCoolingSetpointLimits(coolingSetpointValue.Value(), endpoint))); - } - - Optional heatingSetpointValue = pendingPreset.GetHeatingSetpoint(); - if (heatingSetpointValue.HasValue()) - { - pendingPreset.SetHeatingSetpoint(MakeOptional(EnforceHeatingSetpointLimits(heatingSetpointValue.Value(), endpoint))); - } - } - - uint8_t totalCount = CountNumberOfPendingPresets(delegate); - - uint8_t numberOfPresetsSupported = delegate->GetNumberOfPresets(); - - if (numberOfPresetsSupported == 0) - { - ChipLogError(Zcl, "emberAfThermostatClusterCommitPresetsSchedulesRequestCallback: Failed to get NumberOfPresets"); - return imcode::InvalidInState; - } - - // If the expected length of the presets attribute with the applied changes exceeds the total number of presets supported, - // return RESOURCE_EXHAUSTED. Note that the changes are not yet applied. - if (numberOfPresetsSupported > 0 && totalCount > numberOfPresetsSupported) - { - return imcode::ResourceExhausted; - } - - // TODO: Check if the number of presets for each presetScenario exceeds the max number of presets supported for that - // scenario. We plan to support only one preset for each presetScenario for our use cases so defer this for re-evaluation. - - // Call the delegate API to apply the pending presets to the presets attribute and update it. - err = delegate->ApplyPendingPresets(); - - if (err != CHIP_NO_ERROR) - { - return imcode::InvalidInState; - } - - return imcode::Success; -} - -void handleAtomicCommit(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, - const Commands::AtomicRequest::DecodableType & commandData) -{ - if (!validAtomicAttributes(commandData, true)) - { - commandObj->AddStatus(commandPath, imcode::InvalidCommand); - return; - } - EndpointId endpoint = commandPath.mEndpointId; - bool inAtomicWrite = gThermostatAttrAccess.InAtomicWrite(commandObj, endpoint); - if (!inAtomicWrite) - { - commandObj->AddStatus(commandPath, imcode::InvalidInState); - return; - } - - Delegate * delegate = GetDelegate(endpoint); - - if (delegate == nullptr) - { - ChipLogError(Zcl, "Delegate is null"); - commandObj->AddStatus(commandPath, imcode::InvalidInState); - return; - } - - auto presetsStatus = commitPresets(delegate, endpoint); - // TODO: copy over schedules code - auto schedulesStatus = imcode::Success; - resetAtomicWrite(delegate, endpoint); - imcode status = (presetsStatus == imcode::Success && schedulesStatus == imcode::Success) ? imcode::Success : imcode::Failure; - sendAtomicResponse(commandObj, commandPath, status, presetsStatus, schedulesStatus); -} - -void handleAtomicRollback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, - const Commands::AtomicRequest::DecodableType & commandData) -{ - if (!validAtomicAttributes(commandData, true)) - { - commandObj->AddStatus(commandPath, imcode::InvalidCommand); - return; - } - EndpointId endpoint = commandPath.mEndpointId; - bool inAtomicWrite = gThermostatAttrAccess.InAtomicWrite(commandObj, endpoint); - if (!inAtomicWrite) - { - commandObj->AddStatus(commandPath, imcode::InvalidInState); - return; - } - - Delegate * delegate = GetDelegate(endpoint); - - if (delegate == nullptr) - { - ChipLogError(Zcl, "Delegate is null"); - commandObj->AddStatus(commandPath, imcode::InvalidInState); - return; - } - resetAtomicWrite(delegate, endpoint); - sendAtomicResponse(commandObj, commandPath, imcode::Success, imcode::Success, imcode::Success); -} - -bool emberAfThermostatClusterAtomicRequestCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, - const Clusters::Thermostat::Commands::AtomicRequest::DecodableType & commandData) -{ - auto & requestType = commandData.requestType; - - // If we've gotten this far, then the client has manage permission to call AtomicRequest, which is also the - // privilege necessary to write to the atomic attributes, so no need to check - - switch (requestType) - { - case Globals::AtomicRequestTypeEnum::kBeginWrite: - handleAtomicBegin(commandObj, commandPath, commandData); - return true; - case Globals::AtomicRequestTypeEnum::kCommitWrite: - handleAtomicCommit(commandObj, commandPath, commandData); - return true; - case Globals::AtomicRequestTypeEnum::kRollbackWrite: - handleAtomicRollback(commandObj, commandPath, commandData); - return true; - case Globals::AtomicRequestTypeEnum::kUnknownEnumValue: - commandObj->AddStatus(commandPath, imcode::InvalidCommand); - return true; - } - - return false; -} - bool emberAfThermostatClusterSetpointRaiseLowerCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, const Commands::SetpointRaiseLower::DecodableType & commandData) @@ -1646,9 +804,9 @@ bool emberAfThermostatClusterSetpointRaiseLowerCallback(app::CommandHandler * co EndpointId aEndpointId = commandPath.mEndpointId; int16_t HeatingSetpoint = kDefaultHeatingSetpoint, CoolingSetpoint = kDefaultCoolingSetpoint; // Set to defaults to be safe - imcode status = imcode::Failure; - imcode WriteCoolingSetpointStatus = imcode::Failure; - imcode WriteHeatingSetpointStatus = imcode::Failure; + Status status = Status::Failure; + Status WriteCoolingSetpointStatus = Status::Failure; + Status WriteHeatingSetpointStatus = Status::Failure; int16_t DeadBandTemp = 0; int8_t DeadBand = 0; uint32_t OurFeatureMap; @@ -1656,7 +814,7 @@ bool emberAfThermostatClusterSetpointRaiseLowerCallback(app::CommandHandler * co bool HeatSupported = false; bool CoolSupported = false; - if (FeatureMap::Get(aEndpointId, &OurFeatureMap) != imcode::Success) + if (FeatureMap::Get(aEndpointId, &OurFeatureMap) != Status::Success) OurFeatureMap = FEATURE_MAP_DEFAULT; if (OurFeatureMap & 1 << 5) // Bit 5 is Auto Mode supported @@ -1670,7 +828,7 @@ bool emberAfThermostatClusterSetpointRaiseLowerCallback(app::CommandHandler * co if (AutoSupported) { - if (MinSetpointDeadBand::Get(aEndpointId, &DeadBand) != imcode::Success) + if (MinSetpointDeadBand::Get(aEndpointId, &DeadBand) != Status::Success) DeadBand = kDefaultDeadBand; DeadBandTemp = static_cast(DeadBand * 10); } @@ -1681,13 +839,13 @@ bool emberAfThermostatClusterSetpointRaiseLowerCallback(app::CommandHandler * co if (HeatSupported && CoolSupported) { int16_t DesiredCoolingSetpoint, CoolLimit, DesiredHeatingSetpoint, HeatLimit; - if (OccupiedCoolingSetpoint::Get(aEndpointId, &CoolingSetpoint) == imcode::Success) + if (OccupiedCoolingSetpoint::Get(aEndpointId, &CoolingSetpoint) == Status::Success) { DesiredCoolingSetpoint = static_cast(CoolingSetpoint + amount * 10); CoolLimit = static_cast(DesiredCoolingSetpoint - EnforceCoolingSetpointLimits(DesiredCoolingSetpoint, aEndpointId)); { - if (OccupiedHeatingSetpoint::Get(aEndpointId, &HeatingSetpoint) == imcode::Success) + if (OccupiedHeatingSetpoint::Get(aEndpointId, &HeatingSetpoint) == Status::Success) { DesiredHeatingSetpoint = static_cast(HeatingSetpoint + amount * 10); HeatLimit = static_cast(DesiredHeatingSetpoint - @@ -1709,12 +867,12 @@ bool emberAfThermostatClusterSetpointRaiseLowerCallback(app::CommandHandler * co } } WriteCoolingSetpointStatus = OccupiedCoolingSetpoint::Set(aEndpointId, DesiredCoolingSetpoint); - if (WriteCoolingSetpointStatus != imcode::Success) + if (WriteCoolingSetpointStatus != Status::Success) { ChipLogError(Zcl, "Error: SetOccupiedCoolingSetpoint failed!"); } WriteHeatingSetpointStatus = OccupiedHeatingSetpoint::Set(aEndpointId, DesiredHeatingSetpoint); - if (WriteHeatingSetpointStatus != imcode::Success) + if (WriteHeatingSetpointStatus != Status::Success) { ChipLogError(Zcl, "Error: SetOccupiedHeatingSetpoint failed!"); } @@ -1726,12 +884,12 @@ bool emberAfThermostatClusterSetpointRaiseLowerCallback(app::CommandHandler * co if (CoolSupported && !HeatSupported) { - if (OccupiedCoolingSetpoint::Get(aEndpointId, &CoolingSetpoint) == imcode::Success) + if (OccupiedCoolingSetpoint::Get(aEndpointId, &CoolingSetpoint) == Status::Success) { CoolingSetpoint = static_cast(CoolingSetpoint + amount * 10); CoolingSetpoint = EnforceCoolingSetpointLimits(CoolingSetpoint, aEndpointId); WriteCoolingSetpointStatus = OccupiedCoolingSetpoint::Set(aEndpointId, CoolingSetpoint); - if (WriteCoolingSetpointStatus != imcode::Success) + if (WriteCoolingSetpointStatus != Status::Success) { ChipLogError(Zcl, "Error: SetOccupiedCoolingSetpoint failed!"); } @@ -1740,34 +898,34 @@ bool emberAfThermostatClusterSetpointRaiseLowerCallback(app::CommandHandler * co if (HeatSupported && !CoolSupported) { - if (OccupiedHeatingSetpoint::Get(aEndpointId, &HeatingSetpoint) == imcode::Success) + if (OccupiedHeatingSetpoint::Get(aEndpointId, &HeatingSetpoint) == Status::Success) { HeatingSetpoint = static_cast(HeatingSetpoint + amount * 10); HeatingSetpoint = EnforceHeatingSetpointLimits(HeatingSetpoint, aEndpointId); WriteHeatingSetpointStatus = OccupiedHeatingSetpoint::Set(aEndpointId, HeatingSetpoint); - if (WriteHeatingSetpointStatus != imcode::Success) + if (WriteHeatingSetpointStatus != Status::Success) { ChipLogError(Zcl, "Error: SetOccupiedHeatingSetpoint failed!"); } } } - if ((!HeatSupported || WriteHeatingSetpointStatus == imcode::Success) && - (!CoolSupported || WriteCoolingSetpointStatus == imcode::Success)) - status = imcode::Success; + if ((!HeatSupported || WriteHeatingSetpointStatus == Status::Success) && + (!CoolSupported || WriteCoolingSetpointStatus == Status::Success)) + status = Status::Success; break; case SetpointRaiseLowerModeEnum::kCool: if (CoolSupported) { - if (OccupiedCoolingSetpoint::Get(aEndpointId, &CoolingSetpoint) == imcode::Success) + if (OccupiedCoolingSetpoint::Get(aEndpointId, &CoolingSetpoint) == Status::Success) { CoolingSetpoint = static_cast(CoolingSetpoint + amount * 10); CoolingSetpoint = EnforceCoolingSetpointLimits(CoolingSetpoint, aEndpointId); if (AutoSupported) { // Need to check if we can move the cooling setpoint while maintaining the dead band - if (OccupiedHeatingSetpoint::Get(aEndpointId, &HeatingSetpoint) == imcode::Success) + if (OccupiedHeatingSetpoint::Get(aEndpointId, &HeatingSetpoint) == Status::Success) { if (CoolingSetpoint - HeatingSetpoint < DeadBandTemp) { @@ -1778,10 +936,10 @@ bool emberAfThermostatClusterSetpointRaiseLowerCallback(app::CommandHandler * co { // Desired cooling setpoint is enforcable // Set the new cooling and heating setpoints - if (OccupiedHeatingSetpoint::Set(aEndpointId, HeatingSetpoint) == imcode::Success) + if (OccupiedHeatingSetpoint::Set(aEndpointId, HeatingSetpoint) == Status::Success) { - if (OccupiedCoolingSetpoint::Set(aEndpointId, CoolingSetpoint) == imcode::Success) - status = imcode::Success; + if (OccupiedCoolingSetpoint::Set(aEndpointId, CoolingSetpoint) == Status::Success) + status = Status::Success; } else ChipLogError(Zcl, "Error: SetOccupiedHeatingSetpoint failed!"); @@ -1789,7 +947,7 @@ bool emberAfThermostatClusterSetpointRaiseLowerCallback(app::CommandHandler * co else { ChipLogError(Zcl, "Error: Could Not adjust heating setpoint to maintain dead band!"); - status = imcode::InvalidCommand; + status = Status::InvalidCommand; } } else @@ -1807,20 +965,20 @@ bool emberAfThermostatClusterSetpointRaiseLowerCallback(app::CommandHandler * co ChipLogError(Zcl, "Error: GetOccupiedCoolingSetpoint failed!"); } else - status = imcode::InvalidCommand; + status = Status::InvalidCommand; break; case SetpointRaiseLowerModeEnum::kHeat: if (HeatSupported) { - if (OccupiedHeatingSetpoint::Get(aEndpointId, &HeatingSetpoint) == imcode::Success) + if (OccupiedHeatingSetpoint::Get(aEndpointId, &HeatingSetpoint) == Status::Success) { HeatingSetpoint = static_cast(HeatingSetpoint + amount * 10); HeatingSetpoint = EnforceHeatingSetpointLimits(HeatingSetpoint, aEndpointId); if (AutoSupported) { // Need to check if we can move the cooling setpoint while maintaining the dead band - if (OccupiedCoolingSetpoint::Get(aEndpointId, &CoolingSetpoint) == imcode::Success) + if (OccupiedCoolingSetpoint::Get(aEndpointId, &CoolingSetpoint) == Status::Success) { if (CoolingSetpoint - HeatingSetpoint < DeadBandTemp) { @@ -1831,10 +989,10 @@ bool emberAfThermostatClusterSetpointRaiseLowerCallback(app::CommandHandler * co { // Desired cooling setpoint is enforcable // Set the new cooling and heating setpoints - if (OccupiedCoolingSetpoint::Set(aEndpointId, CoolingSetpoint) == imcode::Success) + if (OccupiedCoolingSetpoint::Set(aEndpointId, CoolingSetpoint) == Status::Success) { - if (OccupiedHeatingSetpoint::Set(aEndpointId, HeatingSetpoint) == imcode::Success) - status = imcode::Success; + if (OccupiedHeatingSetpoint::Set(aEndpointId, HeatingSetpoint) == Status::Success) + status = Status::Success; } else ChipLogError(Zcl, "Error: SetOccupiedCoolingSetpoint failed!"); @@ -1842,7 +1000,7 @@ bool emberAfThermostatClusterSetpointRaiseLowerCallback(app::CommandHandler * co else { ChipLogError(Zcl, "Error: Could Not adjust cooling setpoint to maintain dead band!"); - status = imcode::InvalidCommand; + status = Status::InvalidCommand; } } else @@ -1860,11 +1018,11 @@ bool emberAfThermostatClusterSetpointRaiseLowerCallback(app::CommandHandler * co ChipLogError(Zcl, "Error: GetOccupiedHeatingSetpoint failed!"); } else - status = imcode::InvalidCommand; + status = Status::InvalidCommand; break; default: - status = imcode::InvalidCommand; + status = Status::InvalidCommand; break; } @@ -1877,14 +1035,3 @@ void MatterThermostatPluginServerInitCallback() Server::GetInstance().GetFabricTable().AddFabricDelegate(&gThermostatAttrAccess); AttributeAccessInterfaceRegistry::Instance().Register(&gThermostatAttrAccess); } - -void MatterThermostatClusterServerShutdownCallback(EndpointId endpoint) -{ - ChipLogProgress(Zcl, "Shutting down thermostat server cluster on endpoint %d", endpoint); - Delegate * delegate = GetDelegate(endpoint); - - if (delegate != nullptr) - { - resetAtomicWrite(delegate, endpoint); - } -} diff --git a/src/app/clusters/thermostat-server/thermostat-server.h b/src/app/clusters/thermostat-server/thermostat-server.h index ddede8a9bb13f9..cc941cfa766d92 100644 --- a/src/app/clusters/thermostat-server/thermostat-server.h +++ b/src/app/clusters/thermostat-server/thermostat-server.h @@ -26,6 +26,7 @@ #include "thermostat-delegate.h" +#include #include #include @@ -34,25 +35,62 @@ namespace app { namespace Clusters { namespace Thermostat { +enum class AtomicWriteState +{ + Closed = 0, + Open, +}; + static constexpr size_t kThermostatEndpointCount = MATTER_DM_THERMOSTAT_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; -enum AtomicWriteState -{ - kAtomicWriteState_Closed = 0, - kAtomicWriteState_Open, -}; /** * @brief Thermostat Attribute Access Interface. */ class ThermostatAttrAccess : public chip::app::AttributeAccessInterface, public chip::FabricTable::Delegate { + public: ThermostatAttrAccess() : AttributeAccessInterface(Optional::Missing(), Thermostat::Id) {} CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; CHIP_ERROR Write(const ConcreteDataAttributePath & aPath, chip::app::AttributeValueDecoder & aDecoder) override; +private: + /** + * @brief Set the Active Preset to a given preset handle, or null + * + * @param endpoint The endpoint + * @param presetHandle The handle of the preset to set active, or null to clear the active preset + * @return Success if the active preset was updated, an error code if not + */ + Protocols::InteractionModel::Status SetActivePreset(EndpointId endpoint, DataModel::Nullable presetHandle); + + /** + * @brief Apply a preset to the pending lists of presets during an atomic write + * + * @param delegate The current ThermostatDelegate + * @param preset The preset to append + * @return CHIP_NO_ERROR if successful, an error code if not + */ + CHIP_ERROR AppendPendingPreset(Thermostat::Delegate * delegate, const Structs::PresetStruct::Type & preset); + + /** + * @brief Verifies if the pending presets for a given endpoint are valid + * + * @param endpoint The endpoint + * @return Success if the list of pending presets is valid, an error code if not + */ + Protocols::InteractionModel::Status PrecommitPresets(EndpointId endpoint); + + /** + * @brief Callback for when the server is removed from a given fabric; all associated atomic writes are reset + * + * @param fabricTable The fabric table + * @param fabricIndex The fabric index + */ + void OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) override; + /** * @brief Gets the scoped node id of the originator that sent the last successful * AtomicRequest of type BeginWrite for the given endpoint. @@ -61,7 +99,7 @@ class ThermostatAttrAccess : public chip::app::AttributeAccessInterface, public * * @return the scoped node id for the given endpoint if set. Otherwise returns ScopedNodeId(). */ - ScopedNodeId GetAtomicWriteScopedNodeId(EndpointId endpoint); + ScopedNodeId GetAtomicWriteOriginatorScopedNodeId(EndpointId endpoint); /** * @brief Sets the atomic write state for the given endpoint and originatorNodeId @@ -69,46 +107,119 @@ class ThermostatAttrAccess : public chip::app::AttributeAccessInterface, public * @param[in] endpoint The endpoint. * @param[in] originatorNodeId The originator scoped node id. * @param[in] state Whether or not an atomic write is open or closed. + * @param attributeStatuses The set of attribute status structs the atomic write should be associated with + * @return true if it was able to update the atomic write state + * @return false if it was unable to update the atomic write state */ - void SetAtomicWrite(EndpointId endpoint, ScopedNodeId originatorNodeId, AtomicWriteState state); + bool + SetAtomicWrite(EndpointId endpoint, ScopedNodeId originatorNodeId, AtomicWriteState state, + Platform::ScopedMemoryBufferWithSize & attributeStatuses); /** - * @brief Gets whether an atomic write is in progress for the given endpoint + * @brief Sets the atomic write state for the given endpoint and originatorNodeId * - * @param[in] endpoint The endpoint. + */ + /** + * @brief Resets the atomic write for a given endpoint * - * @return Whether an atomic write is in progress for the given endpoint + * @param endpoint The endpoint */ - bool InAtomicWrite(EndpointId endpoint); + void ResetAtomicWrite(EndpointId endpoint); /** - * @brief Gets whether an atomic write is in progress for the given endpoint + * @brief Checks if a given endpoint has an atomic write open, optionally filtered by an attribute ID * - * @param[in] subjectDescriptor The subject descriptor. - * @param[in] endpoint The endpoint. + * @param endpoint The endpoint + * @param attributeId The optional attribute ID to filter on + * @return true if the endpoint has an open atomic write + * @return false if the endpoint does not have an open atomic write + */ + bool InAtomicWrite(EndpointId endpoint, Optional attributeId = NullOptional); + + /** + * @brief Checks if a given endpoint has an atomic write open for a given subject descriptor, optionally filtered by an + * attribute ID * - * @return Whether an atomic write is in progress for the given endpoint + * @param endpoint The endpoint + * @param subjectDescriptor The subject descriptor for the client making a read or write request + * @param attributeId The optional attribute ID to filter on + * @return true if the endpoint has an open atomic write + * @return false if the endpoint does not have an open atomic write */ - bool InAtomicWrite(const Access::SubjectDescriptor & subjectDescriptor, EndpointId endpoint); + bool InAtomicWrite(EndpointId endpoint, const Access::SubjectDescriptor & subjectDescriptor, + Optional attributeId = NullOptional); /** - * @brief Gets whether an atomic write is in progress for the given endpoint + * @brief Checks if a given endpoint has an atomic write open for a given command invocation, optionally filtered by an + * attribute ID * - * @param[in] commandObj The command handler. - * @param[in] endpoint The endpoint. + * @param endpoint The endpoint + * @param commandObj The CommandHandler for the invoked command + * @param attributeId The optional attribute ID to filter on + * @return true if the endpoint has an open atomic write + * @return false if the endpoint does not have an open atomic write + */ + bool InAtomicWrite(EndpointId endpoint, CommandHandler * commandObj, Optional attributeId = NullOptional); + + /** + * @brief Checks if a given endpoint has an atomic write open for a given command invocation and a list of attributes * - * @return Whether an atomic write is in progress for the given endpoint + * @param endpoint The endpoint + * @param commandObj The CommandHandler for the invoked command + * @param attributeStatuses The list of attribute statuses whose attributeIds must match the open atomic write + * @return true if the endpoint has an open atomic write + * @return false if the endpoint does not have an open atomic write */ - bool InAtomicWrite(CommandHandler * commandObj, EndpointId endpoint); + bool + InAtomicWrite(EndpointId endpoint, CommandHandler * commandObj, + Platform::ScopedMemoryBufferWithSize & attributeStatuses); -private: - CHIP_ERROR AppendPendingPreset(Thermostat::Delegate * delegate, const Structs::PresetStruct::Type & preset); + /** + * @brief Handles an AtomicRequest of type BeginWrite + * + * @param commandObj The AtomicRequest command handler + * @param commandPath The path for the Atomic Request command + * @param commandData The payload data for the Atomic Request + */ + void BeginAtomicWrite(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Commands::AtomicRequest::DecodableType & commandData); - void OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) override; + /** + * @brief Handles an AtomicRequest of type CommitWrite + * + * @param commandObj The AtomicRequest command handler + * @param commandPath The path for the Atomic Request command + * @param commandData The payload data for the Atomic Request + */ + void CommitAtomicWrite(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Commands::AtomicRequest::DecodableType & commandData); + + /** + * @brief Handles an AtomicRequest of type RollbackWrite + * + * @param commandObj The AtomicRequest command handler + * @param commandPath The path for the Atomic Request command + * @param commandData The payload data for the Atomic Request + */ + void RollbackAtomicWrite(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Commands::AtomicRequest::DecodableType & commandData); + + friend void TimerExpiredCallback(System::Layer * systemLayer, void * callbackContext); + + friend void MatterThermostatClusterServerShutdownCallback(EndpointId endpoint); + + friend bool emberAfThermostatClusterSetActivePresetRequestCallback( + CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Clusters::Thermostat::Commands::SetActivePresetRequest::DecodableType & commandData); + + friend bool + emberAfThermostatClusterAtomicRequestCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Clusters::Thermostat::Commands::AtomicRequest::DecodableType & commandData); struct AtomicWriteSession { - AtomicWriteState state = kAtomicWriteState_Closed; + AtomicWriteState state = AtomicWriteState::Closed; + Platform::ScopedMemoryBufferWithSize attributeIds; ScopedNodeId nodeId; EndpointId endpointId = kInvalidEndpointId; }; @@ -124,6 +235,8 @@ class ThermostatAttrAccess : public chip::app::AttributeAccessInterface, public */ void SetDefaultDelegate(EndpointId endpoint, Delegate * delegate); +Delegate * GetDelegate(EndpointId endpoint); + } // namespace Thermostat } // namespace Clusters } // namespace app diff --git a/src/python_testing/TC_TSTAT_4_2.py b/src/python_testing/TC_TSTAT_4_2.py index 5c289ced50604e..563d6f3f2eddfc 100644 --- a/src/python_testing/TC_TSTAT_4_2.py +++ b/src/python_testing/TC_TSTAT_4_2.py @@ -61,15 +61,30 @@ class TC_TSTAT_4_2(MatterBaseTest): def check_atomic_response(self, response: object, expected_status: Status = Status.Success, expected_overall_status: Status = Status.Success, - expected_preset_status: Status = Status.Success): + expected_preset_status: Status = Status.Success, + expected_schedules_status: Status = None, + expected_timeout: int = None): asserts.assert_equal(expected_status, Status.Success, "We expected we had a valid response") asserts.assert_equal(response.statusCode, expected_overall_status, "Response should have the right overall status") found_preset_status = False + found_schedules_status = False for attrStatus in response.attributeStatus: if attrStatus.attributeID == cluster.Attributes.Presets.attribute_id: asserts.assert_equal(attrStatus.statusCode, expected_preset_status, "Preset attribute should have the right status") found_preset_status = True + if attrStatus.attributeID == cluster.Attributes.Schedules.attribute_id: + asserts.assert_equal(attrStatus.statusCode, expected_schedules_status, + "Schedules attribute should have the right status") + found_schedules_status = True + if expected_timeout is not None: + asserts.assert_equal(response.timeout, expected_timeout, + "Timeout should have the right value") + asserts.assert_true(found_preset_status, "Preset attribute should have a status") + if expected_schedules_status is not None: + asserts.assert_true(found_schedules_status, "Schedules attribute should have a status") + asserts.assert_equal(attrStatus.statusCode, expected_schedules_status, + "Schedules attribute should have the right status") asserts.assert_true(found_preset_status, "Preset attribute should have a status") async def write_presets(self, @@ -87,17 +102,21 @@ async def write_presets(self, async def send_atomic_request_begin_command(self, dev_ctrl: ChipDeviceCtrl = None, endpoint: int = None, + timeout: int = 1800, expected_status: Status = Status.Success, expected_overall_status: Status = Status.Success, - expected_preset_status: Status = Status.Success): + expected_preset_status: Status = Status.Success, + expected_schedules_status: Status = None, + expected_timeout: int = None): try: response = await self.send_single_cmd(cmd=cluster.Commands.AtomicRequest(requestType=Globals.Enums.AtomicRequestTypeEnum.kBeginWrite, attributeRequests=[ cluster.Attributes.Presets.attribute_id], - timeout=1800), + timeout=timeout), dev_ctrl=dev_ctrl, endpoint=endpoint) - self.check_atomic_response(response, expected_status, expected_overall_status, expected_preset_status) + self.check_atomic_response(response, expected_status, expected_overall_status, + expected_preset_status, expected_schedules_status, expected_timeout) except InteractionModelError as e: asserts.assert_equal(e.status, expected_status, "Unexpected error returned") @@ -107,13 +126,15 @@ async def send_atomic_request_commit_command(self, endpoint: int = None, expected_status: Status = Status.Success, expected_overall_status: Status = Status.Success, - expected_preset_status: Status = Status.Success): + expected_preset_status: Status = Status.Success, + expected_schedules_status: Status = None): try: response = await self.send_single_cmd(cmd=cluster.Commands.AtomicRequest(requestType=Globals.Enums.AtomicRequestTypeEnum.kCommitWrite, - attributeRequests=[cluster.Attributes.Presets.attribute_id, cluster.Attributes.Schedules.attribute_id]), + attributeRequests=[cluster.Attributes.Presets.attribute_id]), dev_ctrl=dev_ctrl, endpoint=endpoint) - self.check_atomic_response(response, expected_status, expected_overall_status, expected_preset_status) + self.check_atomic_response(response, expected_status, expected_overall_status, + expected_preset_status, expected_schedules_status) except InteractionModelError as e: asserts.assert_equal(e.status, expected_status, "Unexpected error returned") @@ -122,13 +143,16 @@ async def send_atomic_request_rollback_command(self, endpoint: int = None, expected_status: Status = Status.Success, expected_overall_status: Status = Status.Success, - expected_preset_status: Status = Status.Success): + expected_preset_status: Status = Status.Success, + expected_schedules_status: Status = None): try: response = await self.send_single_cmd(cmd=cluster.Commands.AtomicRequest(requestType=Globals.Enums.AtomicRequestTypeEnum.kRollbackWrite, - attributeRequests=[cluster.Attributes.Presets.attribute_id, cluster.Attributes.Schedules.attribute_id]), + attributeRequests=[cluster.Attributes.Presets.attribute_id]), dev_ctrl=dev_ctrl, endpoint=endpoint) - self.check_atomic_response(response, expected_status, expected_overall_status, expected_preset_status) + self.check_atomic_response(response, expected_status, expected_overall_status, + expected_preset_status, expected_schedules_status) + except InteractionModelError as e: asserts.assert_equal(e.status, expected_status, "Unexpected error returned") @@ -219,7 +243,6 @@ async def test_TC_TSTAT_4_2(self): await self.write_presets(endpoint=endpoint, presets=new_presets, expected_status=Status.InvalidInState) self.step("3") - if self.pics_guard(self.check_pics("TSTAT.S.F08") and self.check_pics("TSTAT.S.A0050") and self.check_pics("TSTAT.S.Cfe.Rsp")): await self.send_atomic_request_begin_command() @@ -260,7 +283,7 @@ async def test_TC_TSTAT_4_2(self): if self.pics_guard(self.check_pics("TSTAT.S.F08") and self.check_pics("TSTAT.S.A0050") and self.check_pics("TSTAT.S.Cfe.Rsp")): # Send the AtomicRequest begin command - await self.send_atomic_request_begin_command() + await self.send_atomic_request_begin_command(timeout=5000, expected_timeout=3000) # Write to the presets attribute after removing a built in preset from the list. Remove the first entry. test_presets = new_presets_with_handle.copy() @@ -406,10 +429,7 @@ async def test_TC_TSTAT_4_2(self): self.step("14") if self.pics_guard(self.check_pics("TSTAT.S.F08") and self.check_pics("TSTAT.S.A0050") and self.check_pics("TSTAT.S.Cfe.Rsp")): - - # Send the AtomicRequest begin command await self.send_atomic_request_begin_command() - # Send the AtomicRequest begin command from separate controller, which should receive busy status = await self.send_atomic_request_begin_command(dev_ctrl=secondary_controller, expected_overall_status=Status.Failure, expected_preset_status=Status.Busy)