[HVAC] Sync atomic write error order with spec (#34936)
* Add support for Presets attributes and commands to the Thermostat cluster
Clean up the Thermostat cluster and remove the TemperatureSetpointHoldPolicy attribute
and SetTemperatureSetpointHoldPolicy command
* Restyled by whitespace
* Restyled by clang-format
* Restyled by gn.
* Fix build error for Linux configure build of all-clusters-app
* Fix Darwin CI issues
Editorial fixes
* Restyled by clang-format
* More fixes
* Restyled by clang-format
* BUILD.gn fixes for CI
* Apply suggestions from code review
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Address review comments.
* Restyled by clang-format
* Regenerate Thermostat XML from spec
* Move atomic enum to global-enums.xml, actually
# Conflicts:
# src/app/zap-templates/zcl/data-model/chip/global-structs.xml
* Regenerate XML and convert thermostat-server to atomic writes
* Pull in ACCapacityFormat typo un-fix
* Update Test_TC_TSTAT_1_1 to know about AtomicResponse command.
* Restyled patch
* Fix weird merge with upstream
* Fix emberAfIsTypeSigned not understanding temperature type
* Merge fixes from atomic write branch
* Relocate thermostat-manager sample code to all-clusters-common
* Fix g++ build error on linux
* Fix C formatter for long int, cast whole expression
* Sync cast fix with master
* Add thermostat-common dependency to thermostat app under linux
* Remove MatterPostAttributeChangeCallback from thermostat-manager, as it conflicts with other implementations
* Convert Atomic enums and structs to global
* Restyled patch
* Apply suggestions from code review
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Regen with alchemy 0.6.1
* Updates based on comments
* Add TC_MCORE_FS_1_3.py test implementation (#34650)
* Fix most TC-SWTCH-2.4 remaining issues (#34677)
- Move 2.4 in a better place in the file
- Add test steps properly
- Allow default button press position override
Issue #34656
Testing done:
- Test still passes on DUT with automation
* Initial test script for Fabric Sync TC_MCORE_FS_1_2 (#34675)
* Initial test script for Fabric Sync TC_MCORE_FS_1_2
* Apply suggestions from code review
Co-authored-by: C Freeman <cecille@google.com>
* Address Review Comments
* Address review comments
* Fix default timeout after other timeouts changed
* Restyled by autopep8
* Fix linter error
---------
Co-authored-by: C Freeman <cecille@google.com>
Co-authored-by: Restyled.io <commits@restyled.io>
* Test automation for FabricSync ICD BridgedDeviceBasicInfoCluster (#34628)
* WIP Bridged ICD, commissioning to both fabrics
* wip testing sending KeepActive
* wip most steps implemented
* using SIGSTOP and SIGCONT to control ICD server pausing
* Update src/python_testing/TC_BRBINFO_4_1.py
Co-authored-by: Terence Hampson <thampson@google.com>
* comments addressed
* more comments addressed
* lint pass
* Update src/python_testing/TC_BRBINFO_4_1.py
Co-authored-by: C Freeman <cecille@google.com>
* comments addressed, incl TH_SERVER configurable
* added setupQRCode and setupManualCode as options for DUT commissioning
* Restyled by autopep8
* Restyled by isort
* Update src/python_testing/TC_BRBINFO_4_1.py
Co-authored-by: Terence Hampson <thampson@google.com>
* Update src/python_testing/TC_BRBINFO_4_1.py
Co-authored-by: Terence Hampson <thampson@google.com>
* Update src/python_testing/TC_BRBINFO_4_1.py
Co-authored-by: Terence Hampson <thampson@google.com>
* comments addressed
* Restyled by autopep8
---------
Co-authored-by: Terence Hampson <thampson@google.com>
Co-authored-by: C Freeman <cecille@google.com>
Co-authored-by: Restyled.io <commits@restyled.io>
* ServiceArea test scripts (#34548)
* initial commit
* fix bugs
* fix issues reported by the linter
* fix bug in checking for unique areaDesc
* add TC 1.5
* Update src/python_testing/TC_SEAR_1_2.py
Co-authored-by: William <hicklin@users.noreply.github.com>
* Update src/python_testing/TC_SEAR_1_2.py
Co-authored-by: William <hicklin@users.noreply.github.com>
* address code review comments
* fix issue introduced by the previous commit
* address code review feedback
* Update src/python_testing/TC_SEAR_1_2.py
Co-authored-by: Kiel Oleson <kielo@apple.com>
* address code review feedback
* remove PICS checked by the TC_SEAR_1.6
* more code review updates
* Restyled by autopep8
---------
Co-authored-by: William <hicklin@users.noreply.github.com>
Co-authored-by: Kiel Oleson <kielo@apple.com>
Co-authored-by: Restyled.io <commits@restyled.io>
* Remove manual tests for Thermostat presets (#34679)
* Dump details about leaked ExchangeContexts before aborting (#34617)
* Dump details about leaked ExchangeContexts before aborting
This is implemented via a VerifyOrDieWithObject() variant of the existing
VerifyOrDie() macro that calls a DumpToLog() method on the provided object if
it exists (otherwise this is simply a no-op).
If CHIP_CONFIG_VERBOSE_VERIFY_OR_DIE is not enabled, VerifyOrDieWithObject()
simply behaves like a plain VerifyOrDie(). DumpToLog() implementations can use
ChipLogFormatRtti to log type information about an object (usually a delegate);
if RTTI is disabled this simply outputs whether the object was null or not.
* Address review comments
* Make gcc happy and improve documentation
* Remove unused include
* Fix compile error without CHIP_CONFIG_VERBOSE_VERIFY_OR_DIE
* Avoid unused parameter warning
* [TI] CC13x4_26x4 build fixes (#34682)
* lwip pbuf, map file, and hex creation when OTA is disabled
* added cc13x4 family define around the non OTA hex creation
* whitespace fix
* reversed custom factoy data flash with cc13x4 check
* more whitespace fixes
* [ICD] Add missing polling function to NoWifi connectivity manager (#34684)
* Add missing polling function to NoWifi connectivity manager
* Update GenericConnectivityManagerImpl_NoWiFi.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
---------
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* [OPSTATE] Add Q test script for CountdownTime (#34632)
* Add Q test
* Added test to test set
* Remove unused var
* Restyled by autopep8
* Restyled by isort
* Fix name
* Use pics over other method
* Removed unused stuff
* Added pipe commands
* Fix reset
* Get example to report appropriate changes.
* WiP
* Added some comments
* Changes to make things work
* Removed dev msgs
* Missed some
* Removed dev msgs
* Straggler
* Restyled by clang-format
* Restyled by autopep8
* Restyled by isort
* Commented unused var
* Update examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp
* Fix bug
---------
Co-authored-by: Restyled.io <commits@restyled.io>
* YAML update to BRBINFO, ProductId (#34513)
* Bridged Device Information Cluster, Attribute ProductID test reflects marking as O, not X
* Update src/app/tests/suites/certification/Test_TC_BRBINFO_2_1.yaml
Co-authored-by: Terence Hampson <thampson@google.com>
* corrected pics
* corrected pics
* WIP Bridged ICD, commissioning to both fabrics
* wip testing sending KeepActive
* update to bridged-device-basic-information.xml and zap generated files
* removed unrelated file
---------
Co-authored-by: Terence Hampson <thampson@google.com>
Co-authored-by: Andrei Litvin <andy314@gmail.com>
* Fix simplified Linux tv-casting-app gn build error. (#34692)
* adding parallel execution to restyle-diff (#34663)
* adding parallel execution to restyle-diff
* using xargs to call restyle-paths
* fixing Copyright year
* restyle the restyler
* Add some bits to exercise global structs/enums to Unit Testing cluster. (#34540)
* Adds things to the Unit Testing cluster XML.
* This requires those things to be enabled in all-clusters-app,
all-clusters-minimal-app, and one of the chef contact sensors to pass CI.
* That requires an implementation in test-cluster-server
* At which point might as well add a YAML test to exercise it all.
* [Silabs] Port platform specific Multi-Chip OTA work (#34440)
* Pull request #1836: Cherry multi ota
Merge in WMN_TOOLS/matter from cherry-multi-ota to silabs_slc_1.3
Squashed commit of the following:
commit 4320bb46571658bc44fb82345348265def394991
Author: Michael Rupp <michael.rupp@silabs.com>
Date: Fri May 10 14:26:07 2024 -0400
remove some unwanted diffs in provision files
commit be160931dc600de7e7ead378b70d6a43c3945e46
Author: Michael Rupp <michael.rupp@silabs.com>
Date: Fri May 10 14:24:25 2024 -0400
revert changes to generator.project.mak
commit 14b6605887166e6d5284a61feb2bf407d850bdcf
Author: Michael Rupp <michael.rupp@silabs.com>
Date: Fri May 10 13:06:12 2024 -0400
revert NVM key changes and script changes
... and 8 more commits
* Restyled by whitespace
* Restyled by clang-format
* Restyled by gn
* Restyled by autopep8
* remove unused libs caught by linter
* update doctree with new readmes
* rerun CI, cirque failing for unknown reasons
* fix include guards in provision examples
* Restyled by clang-format
---------
Co-authored-by: Restyled.io <commits@restyled.io>
* Add python tests for Thermostat presets feature (#34693)
* Add python tests for Thermostat presets feature
* Restyled by autopep8
* Restyled by isort
* Update the PICS code for presets attribute
---------
Co-authored-by: Restyled.io <commits@restyled.io>
* removing unneccessary git fetch (#34698)
* Restyle patch
* Regen to fix ordering of global structs
* Apply suggestions from code review
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Return correct AtomicResponse when committing or rolling back
* Patch tests for atomic write of presets
* Fix tests to work with the new setup.
Specific changes:
* Enable SetActivePresetRequest command in all-clusters-app.
* Fix assignment of a PresetStructWithOwnedMembers to another
PresetStructWithOwnedMembers to actually work correctly.
* Move constraint checks that happen on write from commit to write.
* Fix sending of atomic responses to not have use-stack-after-return.
* Fix PICS for the tests involved.
* Fix PICS values for atomic requests
* Remove PresetsSchedulesEditable and QueuedPreset from various places
* Restyled patch
* Restyled patch, again
* Remove PICS value for PresetsSchedulesEditable
* clang-tidy fixes
* clang-tidy fixes
* Clear associated atomic writes when fabric is removed
* Add tests for fabric removal and lockout of clients outside of atomic write
* Python linter
* Restyled patch
* Clear timer when fabric is removed
* Check for open atomic write before resetting
* Revert auto delegate declaration on lines where there's no collision
* Allow Thermostat delegate to provide timeout for atomic requests
* Relocate thermostat example code to thermostat-common
* Remove thermostat-manager code, replace with thermostat delegate
* Sync atomic write error order with spec
* Restyle patch
* Drop memset of atomic write sessions
* Add PreCommit stage to allow rollback of multiple attributes when only one fails
* Separate OnTimerExpired method, vs ResetWrite
* Method documentation
* Apply suggestions from code review
Co-authored-by: Nivi Sarkar <55898241+nivi-apple@users.noreply.github.com>
* Remove unused InWrite check
* Drop imcode alias
* Switch AtomicWriteState to enum class
* DRY up atomic write manager
* Apply suggestions from code review
Co-authored-by: Nivi Sarkar <55898241+nivi-apple@users.noreply.github.com>
* Drop duplicate doc comments
* Rename GetAtomicWriteScopedNodeId to GetAtomicWriteOriginatorScopedNodeId
* Updates based on comments
* Add MatterReportingAttributeChangeCallback calls for updated attributes
* Relocate thermostat example code to thermostat-common, and remove thermostat-manager
* Merge atomic write code back into thermostat-server
* Apply suggestions from code review
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Fix build after suggestions
* Actually track attribute IDs associated with atomic write
* Only commit presets if all attribute precommits were successful
* Fix scope on err
* Add documentation to methods
* Remove duplicate preset check.
* Move various functions into anonymous namespaces, or Thermostat namespace
* Drop impossible non-atomic attribute status after rollback
* Namespace workaround for compilers on other platforms
* Apply suggestions from code review
---------
Co-authored-by: Nivedita Sarkar <nivedita_sarkar@apple.com>
Co-authored-by: Restyled.io <commits@restyled.io>
Co-authored-by: Nivi Sarkar <55898241+nivi-apple@users.noreply.github.com>
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
Co-authored-by: Terence Hampson <thampson@google.com>
Co-authored-by: Tennessee Carmel-Veilleux <tennessee.carmelveilleux@gmail.com>
Co-authored-by: Chris Letnick <cletnick@google.com>
Co-authored-by: C Freeman <cecille@google.com>
Co-authored-by: Douglas Rocha Ferraz <rochaferraz@google.com>
Co-authored-by: Petru Lauric <81822411+plauric@users.noreply.github.com>
Co-authored-by: William <hicklin@users.noreply.github.com>
Co-authored-by: Kiel Oleson <kielo@apple.com>
Co-authored-by: Karsten Sperling <113487422+ksperling-apple@users.noreply.github.com>
Co-authored-by: Anu Biradar <104591549+abiradarti@users.noreply.github.com>
Co-authored-by: mkardous-silabs <84793247+mkardous-silabs@users.noreply.github.com>
Co-authored-by: Rob Bultman <rob.Bultman@gmail.com>
Co-authored-by: Andrei Litvin <andy314@gmail.com>
Co-authored-by: Shao Ling Tan <161761051+shaoltan-amazon@users.noreply.github.com>
Co-authored-by: Amine Alami <43780877+Alami-Amine@users.noreply.github.com>
Co-authored-by: Michael Rupp <95718139+mykrupp@users.noreply.github.com>
diff --git a/examples/all-clusters-app/linux/BUILD.gn b/examples/all-clusters-app/linux/BUILD.gn
index ed228b5..baac520 100644
--- a/examples/all-clusters-app/linux/BUILD.gn
+++ b/examples/all-clusters-app/linux/BUILD.gn
@@ -75,7 +75,7 @@
"${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 @@
"${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 71c0ecc..0683b39 100644
--- a/examples/thermostat/linux/BUILD.gn
+++ b/examples/thermostat/linux/BUILD.gn
@@ -17,11 +17,10 @@
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 @@
"${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 274f66c..0000000
--- 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 <app-common/zap-generated/attributes/Accessors.h>
-
-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 2279f02..b9f8269 100644
--- a/examples/thermostat/linux/main.cpp
+++ b/examples/thermostat/linux/main.cpp
@@ -22,8 +22,6 @@
#include <app/CommandHandler.h>
#include <app/clusters/identify-server/identify-server.h>
-#include "thermostat-manager.h"
-
using namespace chip;
using namespace chip::app;
// using namespace chip::app::Clusters;
@@ -76,19 +74,7 @@
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 ea1f437..0000000
--- 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 <thermostat-delegate-impl.h>
-#include <thermostat-manager.h>
-
-#include <app/clusters/bindings/BindingManager.h>
-#include <app/clusters/thermostat-server/thermostat-server.h>
-#include <controller/ReadInteraction.h>
-#include <platform/PlatformManager.h>
-
-/**********************************************************
- * 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 <typename DecodableAttributeType>
-static void OnAttributeChangeReported(const ConcreteDataAttributePath & path, const DecodableAttributeType & value);
-
-template <>
-void OnAttributeChangeReported<MeasuredValue::TypeInfo::DecodableType>(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 <typename DecodableAttributeType>
-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<DecodableAttributeType>(
- peer_device->GetExchangeManager(), peer_device->GetSecureSession().Value(), binding.remote, clusterId, attributeId,
- &OnAttributeChangeReported<DecodableAttributeType>, &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<MeasuredValue::TypeInfo::DecodableType>(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<intptr_t>(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<SystemModeEnum>(*value);
- ChipLogError(AppServer, "System mode changed to %u (%s)", *value, SystemModeString(mSystemMode));
- EvalThermostatState();
- }
- break;
-
- case ThermostatRunningMode::Id: {
- mRunningMode = static_cast<ThermostatRunningModeEnum>(*value);
- ChipLogError(AppServer, "Running mode changed to %u (%s)", *value, RunningModeString(mRunningMode));
- }
- break;
-
- default: {
- ChipLogError(AppServer, "Unhandled thermostat attribute %u", static_cast<uint>(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<int16_t> 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<int16_t>(mOccupiedSetback * 10);
- const int16_t heatingOffThreshold = mOccupiedHeatingSetpoint + static_cast<int16_t>(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<int16_t>(mOccupiedSetback * 10);
- const int16_t coolingOnThreshold = mOccupiedCoolingSetpoint + static_cast<int16_t>(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 93a0c75..1f8f839 100644
--- a/examples/thermostat/thermostat-common/BUILD.gn
+++ b/examples/thermostat/thermostat-common/BUILD.gn
@@ -16,6 +16,10 @@
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 6bf9d02..9edf13f 100644
--- a/examples/thermostat/linux/include/thermostat-delegate-impl.h
+++ b/examples/thermostat/thermostat-common/include/thermostat-delegate-impl.h
@@ -44,9 +44,7 @@
public:
static inline ThermostatDelegate & GetInstance() { return sInstance; }
- std::optional<System::Clock::Milliseconds16>
- GetAtomicWriteTimeout(DataModel::DecodableList<chip::AttributeId> attributeRequests,
- System::Clock::Milliseconds16 timeoutRequest) override;
+ std::optional<System::Clock::Milliseconds16> GetMaxAtomicWriteTimeout(chip::AttributeId attributeId) override;
CHIP_ERROR GetPresetTypeAtIndex(size_t index, Structs::PresetTypeStruct::Type & presetType) override;
@@ -64,7 +62,7 @@
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 b931db2..8c411cd 100644
--- a/examples/thermostat/linux/thermostat-delegate-impl.cpp
+++ b/examples/thermostat/thermostat-common/src/thermostat-delegate-impl.cpp
@@ -17,7 +17,6 @@
*/
#include <thermostat-delegate-impl.h>
-#include <thermostat-manager.h>
#include <app-common/zap-generated/attributes/Accessors.h>
#include <lib/support/Span.h>
@@ -36,34 +35,12 @@
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 @@
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 @@
return CHIP_NO_ERROR;
}
-std::optional<System::Clock::Milliseconds16>
-ThermostatDelegate::GetAtomicWriteTimeout(DataModel::DecodableList<AttributeId> attributeRequests,
- System::Clock::Milliseconds16 timeoutRequest)
+std::optional<System::Clock::Milliseconds16> ThermostatDelegate::GetMaxAtomicWriteTimeout(chip::AttributeId attributeId)
{
- auto attributeIdsIter = attributeRequests.begin();
- bool requestedPresets = false, requestedSchedules = false;
- while (attributeIdsIter.Next())
+ switch (attributeId)
{
- 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)
- {
+ 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 @@
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 3e4448a..01d47a4 100644
--- a/src/app/chip_data_model.gni
+++ b/src/app/chip_data_model.gni
@@ -428,6 +428,8 @@
]
} 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 0f89f69..ccb690a 100644
--- a/src/app/clusters/thermostat-server/thermostat-delegate.h
+++ b/src/app/clusters/thermostat-server/thermostat-delegate.h
@@ -39,15 +39,12 @@
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<System::Clock::Milliseconds16>
- GetAtomicWriteTimeout(DataModel::DecodableList<AttributeId> attributeRequests,
- System::Clock::Milliseconds16 timeoutRequest) = 0;
+ virtual std::optional<System::Clock::Milliseconds16> GetMaxAtomicWriteTimeout(chip::AttributeId attributeId) = 0;
/**
* @brief Get the preset type at a given index in the PresetTypes attribute
@@ -129,7 +126,7 @@
* @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 0000000..2a6e52e
--- /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 <platform/internal/CHIPDeviceLayerInternal.h>
+
+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<EndpointId>(reinterpret_cast<uintptr_t>(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<void *>(static_cast<uintptr_t>(endpoint)));
+}
+
+/**
+ * @brief Clears the currently scheduled timer.
+ *
+ * @param[in] endpoint The endpoint to use.
+ */
+void ClearTimer(EndpointId endpoint)
+{
+ DeviceLayer::SystemLayer().CancelTimer(TimerExpiredCallback, reinterpret_cast<void *>(static_cast<uintptr_t>(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<chip::AttributeId> 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<chip::AttributeId> attributeRequests,
+ Platform::ScopedMemoryBufferWithSize<AtomicAttributeStatusStruct::Type> & 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> 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> 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> attributeId)
+{
+ if (!InAtomicWrite(endpoint, attributeId))
+ {
+ return false;
+ }
+ ScopedNodeId sourceNodeId = GetSourceScopedNodeId(commandObj);
+ return GetAtomicWriteOriginatorScopedNodeId(endpoint) == sourceNodeId;
+}
+
+bool ThermostatAttrAccess::InAtomicWrite(
+ EndpointId endpoint, CommandHandler * commandObj,
+ Platform::ScopedMemoryBufferWithSize<AtomicAttributeStatusStruct::Type> & 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<AtomicAttributeStatusStruct::Type> & 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<AtomicAttributeStatusStruct::Type> & attributeStatuses,
+ Optional<uint16_t> timeout = NullOptional)
+{
+ Commands::AtomicResponse::Type response;
+ response.statusCode = to_underlying(status);
+ response.attributeStatus =
+ DataModel::List<const AtomicAttributeStatusStruct::Type>(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<AtomicAttributeStatusStruct::Type> 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<AtomicAttributeStatusStruct::Type> 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<AtomicAttributeStatusStruct::Type> 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 0000000..e57c2f9
--- /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 <platform/internal/CHIPDeviceLayerInternal.h>
+
+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<ByteSpan> 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<ByteSpan> 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<int16_t> coolingSetpointValue = pendingPreset.GetCoolingSetpoint();
+ if (coolingSetpointValue.HasValue())
+ {
+ pendingPreset.SetCoolingSetpoint(MakeOptional(EnforceCoolingSetpointLimits(coolingSetpointValue.Value(), endpoint)));
+ }
+
+ Optional<int16_t> 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 8fe7021..fe8ccf8 100644
--- a/src/app/clusters/thermostat-server/thermostat-server.cpp
+++ b/src/app/clusters/thermostat-server/thermostat-server.cpp
@@ -30,7 +30,6 @@
#include <app/server/Server.h>
#include <app/util/endpoint-config-api.h>
#include <lib/core/CHIPEncoding.h>
-#include <platform/internal/CHIPDeviceLayerInternal.h>
using namespace chip;
using namespace chip::app;
@@ -38,8 +37,7 @@
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 @@
#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]);
-}
+namespace chip {
+namespace app {
+namespace Clusters {
+namespace Thermostat {
-/**
- * @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<EndpointId>(reinterpret_cast<uintptr_t>(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<void *>(static_cast<uintptr_t>(endpoint)));
-}
-
-/**
- * @brief Clears the currently scheduled timer.
- *
- * @param[in] endpoint The endpoint to use.
- */
-void ClearTimer(EndpointId endpoint)
-{
- DeviceLayer::SystemLayer().CancelTimer(TimerExpiredCallback, reinterpret_cast<void *>(static_cast<uintptr_t>(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<ByteSpan> 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;
-}
+ThermostatAttrAccess gThermostatAttrAccess;
int16_t EnforceHeatingSetpointLimits(int16_t HeatingSetpoint, EndpointId endpoint)
{
@@ -461,7 +94,7 @@
// 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 @@
// 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 @@
// 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 @@
// 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,12 +220,12 @@
return CoolingSetpoint;
}
-} // anonymous namespace
-
-namespace chip {
-namespace app {
-namespace Clusters {
-namespace Thermostat {
+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]);
+}
void SetDefaultDelegate(EndpointId endpoint, Delegate * delegate)
{
@@ -605,71 +238,12 @@
}
}
-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)
-{
- 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;
-}
-
-ScopedNodeId ThermostatAttrAccess::GetAtomicWriteScopedNodeId(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;
-}
-
CHIP_ERROR ThermostatAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
{
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 @@
if (localTemperatureNotExposedSupported)
{
BitMask<RemoteSensingBitmap> 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 @@
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 @@
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 @@
}
// 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 @@
{
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 @@
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 @@
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 @@
if (AutoSupported)
{
- if (MinSetpointDeadBand::Get(endpoint, &DeadBand) != imcode::Success)
+ if (MinSetpointDeadBand::Get(endpoint, &DeadBand) != Status::Success)
{
DeadBand = kDefaultDeadBand;
}
DeadBandTemp = static_cast<int16_t>(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 @@
case OccupiedHeatingSetpoint::Id: {
requested = static_cast<int16_t>(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<int16_t>(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<int16_t>(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<int16_t>(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<int16_t>(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<int16_t>(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<int16_t>(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<int16_t>(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<SystemModeEnum>(*value);
if (ControlSequenceOfOperation > ControlSequenceOfOperationEnum::kCoolingAndHeatingWithReheat ||
RequestedSystemMode > SystemModeEnum::kFanOnly)
{
- return imcode::InvalidValue;
+ return Status::InvalidValue;
}
switch (ControlSequenceOfOperation)
@@ -1228,22 +743,22 @@
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 @@
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<ByteSpan> 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<uint16_t> 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<int16_t> coolingSetpointValue = pendingPreset.GetCoolingSetpoint();
- if (coolingSetpointValue.HasValue())
- {
- pendingPreset.SetCoolingSetpoint(MakeOptional(EnforceCoolingSetpointLimits(coolingSetpointValue.Value(), endpoint)));
- }
-
- Optional<int16_t> 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 @@
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 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 @@
if (AutoSupported)
{
- if (MinSetpointDeadBand::Get(aEndpointId, &DeadBand) != imcode::Success)
+ if (MinSetpointDeadBand::Get(aEndpointId, &DeadBand) != Status::Success)
DeadBand = kDefaultDeadBand;
DeadBandTemp = static_cast<int16_t>(DeadBand * 10);
}
@@ -1681,13 +839,13 @@
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<int16_t>(CoolingSetpoint + amount * 10);
CoolLimit = static_cast<int16_t>(DesiredCoolingSetpoint -
EnforceCoolingSetpointLimits(DesiredCoolingSetpoint, aEndpointId));
{
- if (OccupiedHeatingSetpoint::Get(aEndpointId, &HeatingSetpoint) == imcode::Success)
+ if (OccupiedHeatingSetpoint::Get(aEndpointId, &HeatingSetpoint) == Status::Success)
{
DesiredHeatingSetpoint = static_cast<int16_t>(HeatingSetpoint + amount * 10);
HeatLimit = static_cast<int16_t>(DesiredHeatingSetpoint -
@@ -1709,12 +867,12 @@
}
}
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 @@
if (CoolSupported && !HeatSupported)
{
- if (OccupiedCoolingSetpoint::Get(aEndpointId, &CoolingSetpoint) == imcode::Success)
+ if (OccupiedCoolingSetpoint::Get(aEndpointId, &CoolingSetpoint) == Status::Success)
{
CoolingSetpoint = static_cast<int16_t>(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 @@
if (HeatSupported && !CoolSupported)
{
- if (OccupiedHeatingSetpoint::Get(aEndpointId, &HeatingSetpoint) == imcode::Success)
+ if (OccupiedHeatingSetpoint::Get(aEndpointId, &HeatingSetpoint) == Status::Success)
{
HeatingSetpoint = static_cast<int16_t>(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<int16_t>(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 @@
{
// 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 @@
else
{
ChipLogError(Zcl, "Error: Could Not adjust heating setpoint to maintain dead band!");
- status = imcode::InvalidCommand;
+ status = Status::InvalidCommand;
}
}
else
@@ -1807,20 +965,20 @@
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<int16_t>(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 @@
{
// 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 @@
else
{
ChipLogError(Zcl, "Error: Could Not adjust cooling setpoint to maintain dead band!");
- status = imcode::InvalidCommand;
+ status = Status::InvalidCommand;
}
}
else
@@ -1860,11 +1018,11 @@
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 @@
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 ddede8a..cc941cf 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 <app-common/zap-generated/callback.h>
#include <app/AttributeAccessInterfaceRegistry.h>
#include <app/CommandHandler.h>
@@ -34,25 +35,62 @@
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<chip::EndpointId>::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<ByteSpan> 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 @@
*
* @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 @@
* @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<Globals::Structs::AtomicAttributeStatusStruct::Type> & 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.
- *
- * @return Whether an atomic write is in progress for the given endpoint
*/
- bool InAtomicWrite(EndpointId endpoint);
+ /**
+ * @brief Resets the atomic write for a given endpoint
+ *
+ * @param endpoint The 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.
- *
- * @return Whether an atomic write is in progress for the given 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(const Access::SubjectDescriptor & subjectDescriptor, EndpointId endpoint);
+ bool InAtomicWrite(EndpointId endpoint, Optional<AttributeId> 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 subject descriptor, optionally filtered by an
+ * attribute ID
*
- * @param[in] commandObj The command handler.
- * @param[in] endpoint The endpoint.
- *
- * @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(CommandHandler * commandObj, EndpointId endpoint);
+ bool InAtomicWrite(EndpointId endpoint, const Access::SubjectDescriptor & subjectDescriptor,
+ Optional<AttributeId> attributeId = NullOptional);
-private:
- CHIP_ERROR AppendPendingPreset(Thermostat::Delegate * delegate, const Structs::PresetStruct::Type & preset);
+ /**
+ * @brief Checks if a given endpoint has an atomic write open for a given command invocation, optionally filtered by an
+ * attribute ID
+ *
+ * @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> attributeId = NullOptional);
- void OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) override;
+ /**
+ * @brief Checks if a given endpoint has an atomic write open for a given command invocation and a list of attributes
+ *
+ * @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(EndpointId endpoint, CommandHandler * commandObj,
+ Platform::ScopedMemoryBufferWithSize<Globals::Structs::AtomicAttributeStatusStruct::Type> & attributeStatuses);
+
+ /**
+ * @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);
+
+ /**
+ * @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<AttributeId> attributeIds;
ScopedNodeId nodeId;
EndpointId endpointId = kInvalidEndpointId;
};
@@ -124,6 +235,8 @@
*/
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 5c289ce..563d6f3 100644
--- a/src/python_testing/TC_TSTAT_4_2.py
+++ b/src/python_testing/TC_TSTAT_4_2.py
@@ -61,15 +61,30 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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)