From 1268601943ab6d69a0197abb7f1e65cbb940d365 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Thu, 6 Jan 2022 15:53:22 -0500 Subject: [PATCH] Implement persistent storage for attributes with "NVM" storage selected. (#13172) General changes: 1. Set the CurrentLevel attribute in all-clusters app to be NVM and default to 254. This requires some YAML changes to expect the new value. 2. Remove the hardcoded setting of that attribute to 255 during boot in the ESP32 all-clusters app. 3. Define and implement AttributePersistenceProvider to read/write persistent attributes. 4. Fix bug in ServerStorageDelegate::SyncGetKeyValue: it was not setting the size outparam to the actual size read. 5. Move EmberAfAttributeMetadata and a few functions for working with strings into a new header that can be included in places where generated headers (which af.h and af-types.h include) can't be. 6. Add those functions to out mock attribute-storage to fix linking of some tests. Fix mistyped "darwin" in gn files so the tests get run on Darwin too, not just on Linux. 7. Rearrange the logic in emAfLoadAttributeDefaults a bit: instead of loading the default value into the RAM store and then checking for a persisted value, check for a persisted value first (if the attribute might have one), and only look for a default value if there is no persisted value. This removes the need for a separate emAfLoadAttributesFromStorage pass (which made sense back when that code was all generated, but it's not anymore). 8. Fix emAfSaveAttributeToStorageIfNeeded to actually store the value. --- .../all-clusters-common/all-clusters-app.zap | 4 +- examples/all-clusters-app/esp32/main/main.cpp | 10 - src/app/AttributePersistenceProvider.h | 95 ++++++++ src/app/BUILD.gn | 2 + .../DefaultAttributePersistenceProvider.cpp | 89 +++++++ src/app/DefaultAttributePersistenceProvider.h | 50 ++++ src/app/server/Server.cpp | 8 +- src/app/server/Server.h | 12 +- src/app/tests/BUILD.gn | 2 +- .../suites/certification/Test_TC_LVL_2_1.yaml | 6 +- .../suites/certification/Test_TC_LVL_3_1.yaml | 25 +- .../suites/certification/Test_TC_LVL_4_1.yaml | 4 +- src/app/util/af-types.h | 164 +------------ src/app/util/af.h | 16 -- src/app/util/attribute-metadata.h | 217 ++++++++++++++++++ src/app/util/attribute-storage.cpp | 128 +++++++---- src/app/util/attribute-storage.h | 4 - src/app/util/mock/attribute-storage.cpp | 33 +++ .../Framework/CHIPTests/CHIPClustersTests.m | 49 +++- src/lib/support/DefaultStorageKeyAllocator.h | 8 + src/platform/BUILD.gn | 2 +- .../zap-generated/endpoint_config.h | 14 +- .../chip-tool/zap-generated/test/Commands.h | 61 ++++- 23 files changed, 728 insertions(+), 275 deletions(-) create mode 100644 src/app/AttributePersistenceProvider.h create mode 100644 src/app/DefaultAttributePersistenceProvider.cpp create mode 100644 src/app/DefaultAttributePersistenceProvider.h create mode 100644 src/app/util/attribute-metadata.h diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap index 29ab97bdd7c445..4c425d54ebba90 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap @@ -8035,10 +8035,10 @@ "mfgCode": null, "side": "server", "included": 1, - "storageOption": "RAM", + "storageOption": "NVM", "singleton": 0, "bounded": 0, - "defaultValue": "0x00", + "defaultValue": "0xFE", "reportable": 1, "minInterval": 0, "maxInterval": 65344, diff --git a/examples/all-clusters-app/esp32/main/main.cpp b/examples/all-clusters-app/esp32/main/main.cpp index 262b2c06da1fa3..c1ac76f8c57818 100644 --- a/examples/all-clusters-app/esp32/main/main.cpp +++ b/examples/all-clusters-app/esp32/main/main.cpp @@ -428,14 +428,6 @@ class CustomScreen : public Screen #endif // CONFIG_DEVICE_TYPE_M5STACK -void SetupInitialLevelControlValues(chip::EndpointId endpointId) -{ - uint8_t level = UINT8_MAX; - - emberAfWriteAttribute(endpointId, ZCL_LEVEL_CONTROL_CLUSTER_ID, ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, &level, - ZCL_INT8U_ATTRIBUTE_TYPE); -} - void SetupPretendDevices() { AddDevice("Watch"); @@ -523,8 +515,6 @@ static void InitServer(intptr_t context) SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider()); SetupPretendDevices(); - SetupInitialLevelControlValues(/* endpointId = */ 1); - SetupInitialLevelControlValues(/* endpointId = */ 2); } extern "C" void app_main() diff --git a/src/app/AttributePersistenceProvider.h b/src/app/AttributePersistenceProvider.h new file mode 100644 index 00000000000000..62f97a3dccfb59 --- /dev/null +++ b/src/app/AttributePersistenceProvider.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include + +namespace chip { +namespace app { + +/** + * Interface for persisting attribute values. + */ + +class AttributePersistenceProvider +{ +public: + virtual ~AttributePersistenceProvider() = default; + AttributePersistenceProvider() = default; + + /** + * Write an attribute value from the attribute store (i.e. not a struct or + * list) to non-volatile memory. + * + * @param [in] aPath the attribute path for the data being written. + * @param [in] aMetadata the attribute metadata, as a convenience. + * @param [in] aValue the data to write. Integers and floats are + * represented in native endianness. Strings are represented + * as Pascal-style strings, as in ZCL, with a length prefix + * whose size depends on the actual string type. The length is + * stored as little-endian. + * + * Integer and float values have a size that matches the `size` + * member of aMetadata. + * + * String values have a size that corresponds to the actual size + * of the data in the string (including the length prefix), + * which is no larger than the `size` member of aMetadata. + */ + virtual CHIP_ERROR WriteValue(const ConcreteAttributePath & aPath, const EmberAfAttributeMetadata * aMetadata, + const ByteSpan & aValue) = 0; + + /** + * Read an attribute value from non-volatile memory. + * + * @param [in] aPath the attribute path for the data being persisted. + * @param [in] aMetadata the attribute metadata, as a convenience. + * @param [in,out] aValue where to place the data. The size of the buffer + * will be equal to `size` member of aMetadata. + * + * The data is expected to be in native endianness for + * integers and floats. For strings, see the string + * representation description in the WriteValue + * documentation. + */ + virtual CHIP_ERROR ReadValue(const ConcreteAttributePath & aPath, const EmberAfAttributeMetadata * aMetadata, + MutableByteSpan & aValue) = 0; +}; + +/** + * Instance getter for the global AttributePersistenceProvider. + * + * Callers have to externally synchronize usage of this function. + * + * @return The global AttributePersistenceProvider. This must never be null. + */ +AttributePersistenceProvider * GetAttributePersistenceProvider(); + +/** + * Instance setter for the global AttributePersistenceProvider. + * + * Callers have to externally synchronize usage of this function. + * + * If the `provider` is nullptr, the value is not changed. + * + * @param[in] provider the AttributePersistenceProvider implementation to use. + */ +void SetAttributePersistenceProvider(AttributePersistenceProvider * aProvider); + +} // namespace app +} // namespace chip diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index 15e996c2d140a6..01506f2c38036e 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -41,6 +41,7 @@ static_library("app") { "AttributePathExpandIterator.cpp", "AttributePathExpandIterator.h", "AttributePathParams.h", + "AttributePersistenceProvider.h", "BufferedReadCallback.cpp", "CASEClient.cpp", "CASEClient.h", @@ -49,6 +50,7 @@ static_library("app") { "CASESessionManager.h", "CommandHandler.cpp", "CommandSender.cpp", + "DefaultAttributePersistenceProvider.cpp", "DeviceProxy.cpp", "DeviceProxy.h", "EventManagement.cpp", diff --git a/src/app/DefaultAttributePersistenceProvider.cpp b/src/app/DefaultAttributePersistenceProvider.cpp new file mode 100644 index 00000000000000..c589c1d36c14c9 --- /dev/null +++ b/src/app/DefaultAttributePersistenceProvider.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2021 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 +#include +#include +#include + +namespace chip { +namespace app { + +CHIP_ERROR DefaultAttributePersistenceProvider::WriteValue(const ConcreteAttributePath & aPath, + const EmberAfAttributeMetadata * aMetadata, const ByteSpan & aValue) +{ + // TODO: we may want to have a small cache for values that change a lot, so + // we only write them once a bunch of changes happen or on timer or + // shutdown. + DefaultStorageKeyAllocator key; + if (!CanCastTo(aValue.size())) + { + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + return mStorage.SyncSetKeyValue(key.AttributeValue(aPath), aValue.data(), static_cast(aValue.size())); +} + +CHIP_ERROR DefaultAttributePersistenceProvider::ReadValue(const ConcreteAttributePath & aPath, + const EmberAfAttributeMetadata * aMetadata, MutableByteSpan & aValue) +{ + DefaultStorageKeyAllocator key; + uint16_t size = static_cast(min(aValue.size(), static_cast(UINT16_MAX))); + ReturnErrorOnFailure(mStorage.SyncGetKeyValue(key.AttributeValue(aPath), aValue.data(), size)); + EmberAfAttributeType type = aMetadata->attributeType; + if (emberAfIsStringAttributeType(type)) + { + // Ensure that we've read enough bytes that we are not ending up with + // un-initialized memory. Should have read length + 1 (for the length + // byte). + VerifyOrReturnError(size >= emberAfStringLength(aValue.data()) + 1, CHIP_ERROR_INCORRECT_STATE); + } + else if (emberAfIsLongStringAttributeType(type)) + { + // Ensure that we've read enough bytes that we are not ending up with + // un-initialized memory. Should have read length + 2 (for the length + // bytes). + VerifyOrReturnError(size >= emberAfLongStringLength(aValue.data()) + 2, CHIP_ERROR_INCORRECT_STATE); + } + else + { + // Ensure we got the expected number of bytes for all other types. + VerifyOrReturnError(size == aMetadata->size, CHIP_ERROR_INCORRECT_STATE); + } + aValue.reduce_size(size); + return CHIP_NO_ERROR; +} + +namespace { + +AttributePersistenceProvider * gAttributeSaver = nullptr; + +} // anonymous namespace + +AttributePersistenceProvider * GetAttributePersistenceProvider() +{ + return gAttributeSaver; +} + +void SetAttributePersistenceProvider(AttributePersistenceProvider * aProvider) +{ + if (aProvider != nullptr) + { + gAttributeSaver = aProvider; + } +} + +} // namespace app +} // namespace chip diff --git a/src/app/DefaultAttributePersistenceProvider.h b/src/app/DefaultAttributePersistenceProvider.h new file mode 100644 index 00000000000000..bc538ad6130261 --- /dev/null +++ b/src/app/DefaultAttributePersistenceProvider.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include + +namespace chip { +namespace app { + +/** + * Default implementation of AttributePersistenceProvider. This uses + * PersistentStorageDelegate to store the attribute values. + * + * NOTE: SetAttributePersistenceProvider must still be called with an instance + * of this class, since it can't be constructed automatically without knowing + * what PersistentStorageDelegate is to be used. + */ +class DefaultAttributePersistenceProvider : public AttributePersistenceProvider +{ +public: + // aStorage must outlive this object. + DefaultAttributePersistenceProvider(PersistentStorageDelegate & aStorage) : mStorage(aStorage) {} + + // AttributePersistenceProvider implementation. + CHIP_ERROR WriteValue(const ConcreteAttributePath & aPath, const EmberAfAttributeMetadata * aMetadata, + const ByteSpan & aValue) override; + CHIP_ERROR ReadValue(const ConcreteAttributePath & aPath, const EmberAfAttributeMetadata * aMetadata, + MutableByteSpan & aValue) override; + +private: + PersistentStorageDelegate & mStorage; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index c07c35833ed32a..1364d11ecd956c 100644 --- a/src/app/server/Server.cpp +++ b/src/app/server/Server.cpp @@ -92,7 +92,8 @@ Server::Server() : .dnsCache = nullptr, .devicePool = &mDevicePool, .dnsResolver = nullptr, - }), mCommissioningWindowManager(this), mGroupsProvider(mGroupsStorage) + }), mCommissioningWindowManager(this), mGroupsProvider(mGroupsStorage), + mAttributePersister(mServerStorage) {} CHIP_ERROR Server::Init(AppDelegate * delegate, uint16_t secureServicePort, uint16_t unsecureServicePort) @@ -106,6 +107,11 @@ CHIP_ERROR Server::Init(AppDelegate * delegate, uint16_t secureServicePort, uint mCommissioningWindowManager.SetAppDelegate(delegate); mCommissioningWindowManager.SetSessionIDAllocator(&mSessionIDAllocator); + + // Set up attribute persistence before we try to bring up the data model + // handler. + SetAttributePersistenceProvider(&mAttributePersister); + InitDataModelHandler(&mExchangeMgr); #if CHIP_DEVICE_LAYER_TARGET_DARWIN diff --git a/src/app/server/Server.h b/src/app/server/Server.h index 06590b73b116f7..33c56610cded88 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -98,8 +100,15 @@ class Server { CHIP_ERROR SyncGetKeyValue(const char * key, void * buffer, uint16_t & size) override { - ReturnErrorOnFailure(DeviceLayer::PersistedStorage::KeyValueStoreMgr().Get(key, buffer, size)); + size_t bytesRead; + ReturnErrorOnFailure(DeviceLayer::PersistedStorage::KeyValueStoreMgr().Get(key, buffer, size, &bytesRead)); + if (!CanCastTo(bytesRead)) + { + ChipLogDetail(AppServer, "%zu is too big to fit in uint16_t", bytesRead); + return CHIP_ERROR_BUFFER_TOO_SMALL; + } ChipLogProgress(AppServer, "Retrieved from server storage: %s", key); + size = static_cast(bytesRead); return CHIP_NO_ERROR; } @@ -159,6 +168,7 @@ class Server // (https://github.com/project-chip/connectedhomeip/issues/12174) TestPersistentStorageDelegate mGroupsStorage; Credentials::GroupDataProviderImpl mGroupsProvider; + app::DefaultAttributePersistenceProvider mAttributePersister; // TODO @ceille: Maybe use OperationalServicePort and CommissionableServicePort uint16_t mSecuredServicePort; diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 9f7f4281d5c365..2bfb555d602a69 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -83,7 +83,7 @@ chip_test_suite("tests") { ] if (chip_config_network_layer_ble && - (chip_device_platform == "linux" || chip_device_platform == "Darwin")) { + (chip_device_platform == "linux" || chip_device_platform == "darwin")) { test_sources += [ "TestCommissionManager.cpp" ] public_deps += [ "${chip_root}/src/app/server" ] } diff --git a/src/app/tests/suites/certification/Test_TC_LVL_2_1.yaml b/src/app/tests/suites/certification/Test_TC_LVL_2_1.yaml index 05407ec4697ac9..17b33244e8919c 100644 --- a/src/app/tests/suites/certification/Test_TC_LVL_2_1.yaml +++ b/src/app/tests/suites/certification/Test_TC_LVL_2_1.yaml @@ -27,7 +27,7 @@ tests: command: "readAttribute" attribute: "current Level" response: - value: 0 + value: 254 - label: "sends a Move to level command" command: "MoveToLevel" @@ -116,12 +116,12 @@ tests: response: value: 254 - - label: "Reset level to 0" + - label: "Reset level to 254" command: "MoveToLevel" arguments: values: - name: "level" - value: 0 + value: 254 - name: "transitionTime" value: 0 - name: "optionMask" diff --git a/src/app/tests/suites/certification/Test_TC_LVL_3_1.yaml b/src/app/tests/suites/certification/Test_TC_LVL_3_1.yaml index 246ee1e3515685..8db9e58750485a 100644 --- a/src/app/tests/suites/certification/Test_TC_LVL_3_1.yaml +++ b/src/app/tests/suites/certification/Test_TC_LVL_3_1.yaml @@ -27,7 +27,7 @@ tests: command: "readAttribute" attribute: "current level" response: - value: 0 + value: 254 - label: "reads max level attribute from DUT" command: "readAttribute" @@ -112,7 +112,7 @@ tests: arguments: values: - name: "moveMode" - value: 1 + value: 0 - name: "rate" value: 255 - name: "optionMask" @@ -134,3 +134,24 @@ tests: response: constraints: notValue: 255 + + - label: "Reset level to 254" + command: "MoveToLevel" + arguments: + values: + - name: "level" + value: 254 + - name: "transitionTime" + value: 0 + - name: "optionMask" + value: 1 + - name: "optionOverride" + value: 1 + + - label: "Wait 100ms" + cluster: "DelayCommands" + command: "WaitForMs" + arguments: + values: + - name: "ms" + value: 100 diff --git a/src/app/tests/suites/certification/Test_TC_LVL_4_1.yaml b/src/app/tests/suites/certification/Test_TC_LVL_4_1.yaml index 43da0afcf0dcea..147ddcaf5dfb27 100644 --- a/src/app/tests/suites/certification/Test_TC_LVL_4_1.yaml +++ b/src/app/tests/suites/certification/Test_TC_LVL_4_1.yaml @@ -32,9 +32,9 @@ tests: arguments: values: - name: "stepMode" - value: 0 + value: 1 - name: "stepSize" - value: 128 + value: 126 - name: "transitionTime" value: 20 - name: "optionMask" diff --git a/src/app/util/af-types.h b/src/app/util/af-types.h index 55101cd9833673..cd838654dbfa55 100644 --- a/src/app/util/af-types.h +++ b/src/app/util/af-types.h @@ -55,6 +55,8 @@ #include #include // For various types. +#include // EmberAfAttributeMetadata + #include #include #include @@ -63,21 +65,11 @@ #include "app/util/ezsp/ezsp-enum.h" #endif -/** - * @brief Type for referring to ZCL attribute type - */ -typedef uint8_t EmberAfAttributeType; - /** * @brief Type for the cluster mask */ typedef uint8_t EmberAfClusterMask; -/** - * @brief Type for the attribute mask - */ -typedef uint8_t EmberAfAttributeMask; - /** * @brief Generic function type, used for either of the cluster function. * @@ -92,158 +84,6 @@ typedef void (*EmberAfGenericClusterFunction)(void); */ #define EMBER_AF_NULL_MANUFACTURER_CODE 0x0000 -/** - * @brief Type for default values. - * - * Default value is either a value itself, if it is 2 bytes or less, - * or a pointer to the value itself, if attribute type is longer than - * 2 bytes. - */ -union EmberAfDefaultAttributeValue -{ - constexpr EmberAfDefaultAttributeValue(const uint8_t * ptr) : ptrToDefaultValue(ptr) {} - constexpr EmberAfDefaultAttributeValue(uint16_t val) : defaultValue(val) {} - - /** - * Points to data if size is more than 2 bytes. - * If size is more than 2 bytes, and this value is NULL, - * then the default value is all zeroes. - */ - const uint8_t * ptrToDefaultValue; - - /** - * Actual default value if the attribute size is 2 bytes or less. - */ - uint16_t defaultValue; -}; - -/** - * @brief Type describing the attribute default, min and max values. - * - * This struct is required if the attribute mask specifies that this - * attribute has a known min and max values. - */ -typedef struct -{ - /** - * Default value of the attribute. - */ - EmberAfDefaultAttributeValue defaultValue; - /** - * Minimum allowed value - */ - EmberAfDefaultAttributeValue minValue; - /** - * Maximum allowed value. - */ - EmberAfDefaultAttributeValue maxValue; -} EmberAfAttributeMinMaxValue; - -/** - * @brief Union describing the attribute default/min/max values. - */ -union EmberAfDefaultOrMinMaxAttributeValue -{ - constexpr EmberAfDefaultOrMinMaxAttributeValue(const uint8_t * ptr) : ptrToDefaultValue(ptr) {} - constexpr EmberAfDefaultOrMinMaxAttributeValue(uint16_t val) : defaultValue(val) {} - constexpr EmberAfDefaultOrMinMaxAttributeValue(const EmberAfAttributeMinMaxValue * ptr) : ptrToMinMaxValue(ptr) {} - - /** - * Points to data if size is more than 2 bytes. - * If size is more than 2 bytes, and this value is NULL, - * then the default value is all zeroes. - */ - const uint8_t * ptrToDefaultValue; - /** - * Actual default value if the attribute size is 2 bytes or less. - */ - uint16_t defaultValue; - /** - * Points to the min max attribute value structure, if min/max is - * supported for this attribute. - */ - const EmberAfAttributeMinMaxValue * ptrToMinMaxValue; -}; - -// Attribute masks modify how attributes are used by the framework -// -// Attribute that has this mask is NOT read-only -#define ATTRIBUTE_MASK_WRITABLE (0x01) -// Attribute that has this mask is saved in non-volatile memory -#define ATTRIBUTE_MASK_NONVOLATILE (0x02) -// Alias until ZAP gets updated to output ATTRIBUTE_MASK_NONVOLATILE -#define ATTRIBUTE_MASK_TOKENIZE ATTRIBUTE_MASK_NONVOLATILE -// Attribute that has this mask has a min/max values -#define ATTRIBUTE_MASK_MIN_MAX (0x04) -// Attribute requires a timed interaction to write -#define ATTRIBUTE_MASK_MUST_USE_TIMED_WRITE (0x08) -// Attribute deferred to external storage -#define ATTRIBUTE_MASK_EXTERNAL_STORAGE (0x10) -// Attribute is singleton -#define ATTRIBUTE_MASK_SINGLETON (0x20) -// Attribute is a client attribute -#define ATTRIBUTE_MASK_CLIENT (0x40) -// Attribute is nullable -#define ATTRIBUTE_MASK_NULLABLE (0x80) - -/** - * @brief Each attribute has it's metadata stored in such struct. - * - * There is only one of these per attribute across all endpoints. - */ -struct EmberAfAttributeMetadata -{ - /** - * Attribute ID, according to ZCL specs. - */ - chip::AttributeId attributeId; - /** - * Attribute type, according to ZCL specs. - */ - EmberAfAttributeType attributeType; - /** - * Size of this attribute in bytes. - */ - uint16_t size; - /** - * Attribute mask, tagging attribute with specific - * functionality. - */ - EmberAfAttributeMask mask; - /** - * Pointer to the default value union. Actual value stored - * depends on the mask. - */ - EmberAfDefaultOrMinMaxAttributeValue defaultValue; - - /** - * Check whether this attribute is nullable. - */ - bool IsNullable() const { return mask & ATTRIBUTE_MASK_NULLABLE; } - - /** - * Check whether this attribute is readonly. - */ - bool IsReadOnly() const { return !(mask & ATTRIBUTE_MASK_WRITABLE); } - - /** - * Check whether this attribute requires a timed write. - */ - bool MustUseTimedWrite() const { return mask & ATTRIBUTE_MASK_MUST_USE_TIMED_WRITE; } - - /** - * Check whether this attibute's storage is managed outside the built-in - * attribute store. - */ - bool IsExternal() const { return mask & ATTRIBUTE_MASK_EXTERNAL_STORAGE; } - - /** - * Check whether this attribute is automatically stored in non-volatile - * memory. - */ - bool IsNonVolatile() const { return (mask & ATTRIBUTE_MASK_NONVOLATILE) && !IsExternal(); } -}; - /** * @brief Struct describing cluster */ diff --git a/src/app/util/af.h b/src/app/util/af.h index 16f7be2c4e1dad..382fa86d3fe9be 100644 --- a/src/app/util/af.h +++ b/src/app/util/af.h @@ -552,16 +552,6 @@ void emberAfCopyString(uint8_t * dest, const uint8_t * src, size_t size); * destination buffer not including the length bytes. */ void emberAfCopyLongString(uint8_t * dest, const uint8_t * src, size_t size); -/* - * @brief Function that determines the length of a zigbee Cluster Library string - * (where the first byte is assumed to be the length). - */ -uint8_t emberAfStringLength(const uint8_t * buffer); -/* - * @brief Function that determines the length of a zigbee Cluster Library long string. - * (where the first two bytes are assumed to be the length). - */ -uint16_t emberAfLongStringLength(const uint8_t * buffer); /* * @brief Function that determines the size of a zigbee Cluster Library @@ -634,12 +624,6 @@ bool emberAfEndpointIndexIsEnabled(uint16_t index); */ bool emberAfIsThisDataTypeAStringType(EmberAfAttributeType dataType); -/** @brief Returns true if the given attribute type is a string. */ -bool emberAfIsStringAttributeType(EmberAfAttributeType attributeType); - -/** @brief Returns true if the given attribute type is a long string. */ -bool emberAfIsLongStringAttributeType(EmberAfAttributeType attributeType); - /** @brief Returns true if a given ZCL data type is a list type. */ bool emberAfIsThisDataTypeAListType(EmberAfAttributeType dataType); diff --git a/src/app/util/attribute-metadata.h b/src/app/util/attribute-metadata.h new file mode 100644 index 00000000000000..01987935589115 --- /dev/null +++ b/src/app/util/attribute-metadata.h @@ -0,0 +1,217 @@ +/** + * + * Copyright (c) 2020 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. + */ + +/** + * + * Copyright (c) 2020 Silicon Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +#include + +/** + * @brief Type for referring to ZCL attribute type + */ +typedef uint8_t EmberAfAttributeType; + +/** + * @brief Type for the attribute mask + */ +typedef uint8_t EmberAfAttributeMask; + +/** + * @brief Type for default values. + * + * Default value is either a value itself, if it is 2 bytes or less, + * or a pointer to the value itself, if attribute type is longer than + * 2 bytes. + */ +union EmberAfDefaultAttributeValue +{ + constexpr EmberAfDefaultAttributeValue(const uint8_t * ptr) : ptrToDefaultValue(ptr) {} + constexpr EmberAfDefaultAttributeValue(uint16_t val) : defaultValue(val) {} + + /** + * Points to data if size is more than 2 bytes. + * If size is more than 2 bytes, and this value is NULL, + * then the default value is all zeroes. + */ + const uint8_t * ptrToDefaultValue; + + /** + * Actual default value if the attribute size is 2 bytes or less. + */ + uint16_t defaultValue; +}; + +/** + * @brief Type describing the attribute default, min and max values. + * + * This struct is required if the attribute mask specifies that this + * attribute has a known min and max values. + */ +typedef struct +{ + /** + * Default value of the attribute. + */ + EmberAfDefaultAttributeValue defaultValue; + /** + * Minimum allowed value + */ + EmberAfDefaultAttributeValue minValue; + /** + * Maximum allowed value. + */ + EmberAfDefaultAttributeValue maxValue; +} EmberAfAttributeMinMaxValue; + +/** + * @brief Union describing the attribute default/min/max values. + */ +union EmberAfDefaultOrMinMaxAttributeValue +{ + constexpr EmberAfDefaultOrMinMaxAttributeValue(const uint8_t * ptr) : ptrToDefaultValue(ptr) {} + constexpr EmberAfDefaultOrMinMaxAttributeValue(uint16_t val) : defaultValue(val) {} + constexpr EmberAfDefaultOrMinMaxAttributeValue(const EmberAfAttributeMinMaxValue * ptr) : ptrToMinMaxValue(ptr) {} + + /** + * Points to data if size is more than 2 bytes. + * If size is more than 2 bytes, and this value is NULL, + * then the default value is all zeroes. + */ + const uint8_t * ptrToDefaultValue; + /** + * Actual default value if the attribute size is 2 bytes or less. + */ + uint16_t defaultValue; + /** + * Points to the min max attribute value structure, if min/max is + * supported for this attribute. + */ + const EmberAfAttributeMinMaxValue * ptrToMinMaxValue; +}; + +// Attribute masks modify how attributes are used by the framework +// +// Attribute that has this mask is NOT read-only +#define ATTRIBUTE_MASK_WRITABLE (0x01) +// Attribute that has this mask is saved in non-volatile memory +#define ATTRIBUTE_MASK_NONVOLATILE (0x02) +// Alias until ZAP gets updated to output ATTRIBUTE_MASK_NONVOLATILE +#define ATTRIBUTE_MASK_TOKENIZE ATTRIBUTE_MASK_NONVOLATILE +// Attribute that has this mask has a min/max values +#define ATTRIBUTE_MASK_MIN_MAX (0x04) +// Attribute requires a timed interaction to write +#define ATTRIBUTE_MASK_MUST_USE_TIMED_WRITE (0x08) +// Attribute deferred to external storage +#define ATTRIBUTE_MASK_EXTERNAL_STORAGE (0x10) +// Attribute is singleton +#define ATTRIBUTE_MASK_SINGLETON (0x20) +// Attribute is a client attribute +#define ATTRIBUTE_MASK_CLIENT (0x40) +// Attribute is nullable +#define ATTRIBUTE_MASK_NULLABLE (0x80) + +/** + * @brief Each attribute has it's metadata stored in such struct. + * + * There is only one of these per attribute across all endpoints. + */ +struct EmberAfAttributeMetadata +{ + /** + * Attribute ID, according to ZCL specs. + */ + chip::AttributeId attributeId; + /** + * Attribute type, according to ZCL specs. + */ + EmberAfAttributeType attributeType; + /** + * Size of this attribute in bytes. + */ + uint16_t size; + /** + * Attribute mask, tagging attribute with specific + * functionality. + */ + EmberAfAttributeMask mask; + /** + * Pointer to the default value union. Actual value stored + * depends on the mask. + */ + EmberAfDefaultOrMinMaxAttributeValue defaultValue; + + /** + * Check whether this attribute is nullable. + */ + bool IsNullable() const { return mask & ATTRIBUTE_MASK_NULLABLE; } + + /** + * Check whether this attribute is readonly. + */ + bool IsReadOnly() const { return !(mask & ATTRIBUTE_MASK_WRITABLE); } + + /** + * Check whether this attribute requires a timed write. + */ + bool MustUseTimedWrite() const { return mask & ATTRIBUTE_MASK_MUST_USE_TIMED_WRITE; } + + /** + * Check whether this attibute's storage is managed outside the built-in + * attribute store. + */ + bool IsExternal() const { return mask & ATTRIBUTE_MASK_EXTERNAL_STORAGE; } + + /** + * Check whether this attribute is automatically stored in non-volatile + * memory. + */ + bool IsNonVolatile() const { return (mask & ATTRIBUTE_MASK_NONVOLATILE) && !IsExternal(); } +}; + +/** @brief Returns true if the given attribute type is a string. */ +bool emberAfIsStringAttributeType(EmberAfAttributeType attributeType); + +/** @brief Returns true if the given attribute type is a long string. */ +bool emberAfIsLongStringAttributeType(EmberAfAttributeType attributeType); + +/* + * @brief Function that determines the length of a zigbee Cluster Library string + * (where the first byte is assumed to be the length). + */ +uint8_t emberAfStringLength(const uint8_t * buffer); +/* + * @brief Function that determines the length of a zigbee Cluster Library long string. + * (where the first two bytes are assumed to be the length). + */ +uint16_t emberAfLongStringLength(const uint8_t * buffer); diff --git a/src/app/util/attribute-storage.cpp b/src/app/util/attribute-storage.cpp index 077a9e63645dca..c1ed396263662d 100644 --- a/src/app/util/attribute-storage.cpp +++ b/src/app/util/attribute-storage.cpp @@ -40,6 +40,7 @@ ******************************************************************************/ #include "app/util/common.h" +#include #include #include #include @@ -1207,6 +1208,8 @@ void emAfLoadAttributeDefaults(EndpointId endpoint, bool ignoreStorage) uint16_t attr; uint8_t * ptr; uint16_t epCount = emberAfEndpointCount(); + uint8_t attrData[ATTRIBUTE_LARGEST]; + auto * attrStorage = ignoreStorage ? nullptr : app::GetAttributePersistenceProvider(); for (ep = 0; ep < epCount; ep++) { @@ -1241,7 +1244,30 @@ void emAfLoadAttributeDefaults(EndpointId endpoint, bool ignoreStorage) for (attr = 0; attr < cluster->attributeCount; attr++) { EmberAfAttributeMetadata * am = &(cluster->attributes[attr]); - if (!(am->mask & ATTRIBUTE_MASK_EXTERNAL_STORAGE)) + ptr = nullptr; // Will get set to the value to write, as needed. + + // First check for a persisted value. + if (!ignoreStorage && am->IsNonVolatile()) + { + MutableByteSpan bytes(attrData); + CHIP_ERROR err = attrStorage->ReadValue( + app::ConcreteAttributePath(de->endpoint, cluster->clusterId, am->attributeId), am, bytes); + if (err == CHIP_NO_ERROR) + { + ptr = attrData; + } + else + { + ChipLogDetail(DataManagement, + "Failed to read stored attribute (%" PRIu16 ", " ChipLogFormatMEI ", " ChipLogFormatMEI + ": %" CHIP_ERROR_FORMAT, + de->endpoint, ChipLogValueMEI(cluster->clusterId), ChipLogValueMEI(am->attributeId), + err.Format()); + // Just fall back to default value. + } + } + + if (!am->IsExternal()) { EmberAfAttributeSearchRecord record; record.endpoint = de->endpoint; @@ -1249,41 +1275,46 @@ void emAfLoadAttributeDefaults(EndpointId endpoint, bool ignoreStorage) record.clusterMask = (emberAfAttributeIsClient(am) ? CLUSTER_MASK_CLIENT : CLUSTER_MASK_SERVER); record.attributeId = am->attributeId; record.manufacturerCode = EMBER_AF_NULL_MANUFACTURER_CODE; - if ((am->mask & ATTRIBUTE_MASK_MIN_MAX) != 0U) + + if (ptr == nullptr) { - if (emberAfAttributeSize(am) <= 2) + if ((am->mask & ATTRIBUTE_MASK_MIN_MAX) != 0U) { - ptr = (uint8_t *) &(am->defaultValue.ptrToMinMaxValue->defaultValue.defaultValue); + if (emberAfAttributeSize(am) <= 2) + { + ptr = (uint8_t *) &(am->defaultValue.ptrToMinMaxValue->defaultValue.defaultValue); + } + else + { + ptr = (uint8_t *) am->defaultValue.ptrToMinMaxValue->defaultValue.ptrToDefaultValue; + } } else { - ptr = (uint8_t *) am->defaultValue.ptrToMinMaxValue->defaultValue.ptrToDefaultValue; - } - } - else - { - if (emberAfAttributeSize(am) <= 2) - { - ptr = (uint8_t *) &(am->defaultValue.defaultValue); + if (emberAfAttributeSize(am) <= 2) + { + ptr = (uint8_t *) &(am->defaultValue.defaultValue); + } + else + { + ptr = (uint8_t *) am->defaultValue.ptrToDefaultValue; + } } - else + // At this point, ptr either points to a default value, or is NULL, in which case + // it should be treated as if it is pointing to an array of all zeroes. + +#if (BIGENDIAN_CPU) + // The default value for one- and two-byte attributes is stored in an + // uint16_t. On big-endian platforms, a pointer to the default value of + // a one-byte attribute will point to the wrong byte. So, for those + // cases, nudge the pointer forward so it points to the correct byte. + if (emberAfAttributeSize(am) == 1 && ptr != NULL) { - ptr = (uint8_t *) am->defaultValue.ptrToDefaultValue; + *ptr++; } +#endif // BIGENDIAN } - // At this point, ptr either points to a default value, or is NULL, in which case - // it should be treated as if it is pointing to an array of all zeroes. -#if (BIGENDIAN_CPU) - // The default value for one- and two-byte attributes is stored in an - // uint16_t. On big-endian platforms, a pointer to the default value of - // a one-byte attribute will point to the wrong byte. So, for those - // cases, nudge the pointer forward so it points to the correct byte. - if (emberAfAttributeSize(am) == 1 && ptr != NULL) - { - *ptr++; - } -#endif // BIGENDIAN emAfReadOrWriteAttribute(&record, NULL, // metadata - unused ptr, @@ -1301,20 +1332,6 @@ void emAfLoadAttributeDefaults(EndpointId endpoint, bool ignoreStorage) break; } } - - if (!ignoreStorage) - { - emAfLoadAttributesFromStorage(endpoint); - } -} - -void emAfLoadAttributesFromStorage(EndpointId endpoint) -{ - // On EZSP host we currently do not support this. We need to come up with some - // callbacks. -#ifndef EZSP_HOST - GENERATED_TOKEN_LOADER(endpoint); -#endif // EZSP_HOST } // 'data' argument may be null, since we changed the ptrToDefaultValue @@ -1329,11 +1346,32 @@ void emAfSaveAttributeToStorageIfNeeded(uint8_t * data, EndpointId endpoint, Clu return; } -// On EZSP host we currently do not support this. We need to come up with some -// callbacks. -#ifndef EZSP_HOST - GENERATED_TOKEN_SAVER; -#endif // EZSP_HOST + // TODO: Maybe we should have a separate constant for the size of the + // largest non-volatile attribute? + uint8_t allZeroData[ATTRIBUTE_LARGEST] = { 0 }; + if (data == nullptr) + { + data = allZeroData; + } + + size_t dataSize; + EmberAfAttributeType type = metadata->attributeType; + if (emberAfIsStringAttributeType(type)) + { + dataSize = emberAfStringLength(data) + 1; + } + else if (emberAfIsLongStringAttributeType(type)) + { + dataSize = emberAfLongStringLength(data) + 2; + } + else + { + dataSize = metadata->size; + } + + auto * attrStorage = app::GetAttributePersistenceProvider(); + attrStorage->WriteValue(app::ConcreteAttributePath(endpoint, clusterId, metadata->attributeId), metadata, + ByteSpan(data, dataSize)); } // This function returns the actual function point from the array, diff --git a/src/app/util/attribute-storage.h b/src/app/util/attribute-storage.h index 14284313e249b4..19972b0cd9a20e 100644 --- a/src/app/util/attribute-storage.h +++ b/src/app/util/attribute-storage.h @@ -213,10 +213,6 @@ void emberAfResetAttributes(chip::EndpointId endpoint); // non-volatile attributes will be overwritten with those defaults. void emAfLoadAttributeDefaults(chip::EndpointId endpoint, bool ignoreStorage); -// This function loads from storage all the attributes that -// are defined to be non-volatile. -void emAfLoadAttributesFromStorage(chip::EndpointId endpoint); - // After the RAM value has changed, code should call this function. If this // attribute has been tagged as non-volatile, its value will be stored. void emAfSaveAttributeToStorageIfNeeded(uint8_t * data, chip::EndpointId endpoint, chip::ClusterId clusterId, diff --git a/src/app/util/mock/attribute-storage.cpp b/src/app/util/mock/attribute-storage.cpp index 6966b0ce4ae7da..c8d576ee618004 100644 --- a/src/app/util/mock/attribute-storage.cpp +++ b/src/app/util/mock/attribute-storage.cpp @@ -29,6 +29,7 @@ */ #include +#include #include #include #include @@ -41,12 +42,15 @@ #include #include #include +#include #include #include #include #include #include +#include + typedef uint8_t EmberAfClusterMask; using namespace chip; @@ -209,6 +213,35 @@ uint8_t emberAfClusterIndex(chip::EndpointId endpoint, chip::ClusterId cluster, return UINT8_MAX; } +// This duplication of basic utilities is really unfortunate, but we can't link +// to the normal attribute-storage.cpp because we redefine some of its symbols +// above. +bool emberAfIsStringAttributeType(EmberAfAttributeType attributeType) +{ + return (attributeType == ZCL_OCTET_STRING_ATTRIBUTE_TYPE || attributeType == ZCL_CHAR_STRING_ATTRIBUTE_TYPE); +} + +bool emberAfIsLongStringAttributeType(EmberAfAttributeType attributeType) +{ + return (attributeType == ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE || attributeType == ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE); +} + +// And we don't have a good way to link to message.cpp either. +uint8_t emberAfStringLength(const uint8_t * buffer) +{ + // The first byte specifies the length of the string. A length of 0xFF means + // the string is invalid and there is no character data. + return (buffer[0] == 0xFF ? 0 : buffer[0]); +} + +uint16_t emberAfLongStringLength(const uint8_t * buffer) +{ + // The first two bytes specify the length of the long string. A length of + // 0xFFFF means the string is invalid and there is no character data. + uint16_t length = Encoding::LittleEndian::Get16(buffer); + return (length == 0xFFFF ? 0 : length); +} + namespace chip { namespace Test { diff --git a/src/darwin/Framework/CHIPTests/CHIPClustersTests.m b/src/darwin/Framework/CHIPTests/CHIPClustersTests.m index cfb6aac8859b70..6551da3af93b10 100644 --- a/src/darwin/Framework/CHIPTests/CHIPClustersTests.m +++ b/src/darwin/Framework/CHIPTests/CHIPClustersTests.m @@ -10229,7 +10229,7 @@ - (void)testSendClusterTest_TC_LVL_2_1_000001_ReadAttribute { id actualValue = value; - XCTAssertEqual([actualValue unsignedCharValue], 0); + XCTAssertEqual([actualValue unsignedCharValue], 254); } [expectation fulfill]; @@ -10434,7 +10434,7 @@ - (void)testSendClusterTest_TC_LVL_2_1_000011_ReadAttribute } - (void)testSendClusterTest_TC_LVL_2_1_000012_MoveToLevel { - XCTestExpectation * expectation = [self expectationWithDescription:@"Reset level to 0"]; + XCTestExpectation * expectation = [self expectationWithDescription:@"Reset level to 254"]; CHIPDevice * device = GetConnectedDevice(); dispatch_queue_t queue = dispatch_get_main_queue(); @@ -10442,13 +10442,13 @@ - (void)testSendClusterTest_TC_LVL_2_1_000012_MoveToLevel XCTAssertNotNil(cluster); __auto_type * params = [[CHIPLevelControlClusterMoveToLevelParams alloc] init]; - params.level = [NSNumber numberWithUnsignedChar:0]; + params.level = [NSNumber numberWithUnsignedChar:254]; params.transitionTime = [NSNumber numberWithUnsignedShort:0U]; params.optionMask = [NSNumber numberWithUnsignedChar:1]; params.optionOverride = [NSNumber numberWithUnsignedChar:1]; [cluster moveToLevelWithParams:params completionHandler:^(NSError * _Nullable err) { - NSLog(@"Reset level to 0 Error: %@", err); + NSLog(@"Reset level to 254 Error: %@", err); XCTAssertEqual([CHIPErrorTestUtils errorToZCLErrorCode:err], 0); @@ -10490,7 +10490,7 @@ - (void)testSendClusterTest_TC_LVL_3_1_000001_ReadAttribute { id actualValue = value; - XCTAssertEqual([actualValue unsignedCharValue], 0); + XCTAssertEqual([actualValue unsignedCharValue], 254); } [expectation fulfill]; @@ -10717,7 +10717,7 @@ - (void)testSendClusterTest_TC_LVL_3_1_000012_Move XCTAssertNotNil(cluster); __auto_type * params = [[CHIPLevelControlClusterMoveParams alloc] init]; - params.moveMode = [NSNumber numberWithUnsignedChar:1]; + params.moveMode = [NSNumber numberWithUnsignedChar:0]; params.rate = [NSNumber numberWithUnsignedChar:255]; params.optionMask = [NSNumber numberWithUnsignedChar:1]; params.optionOverride = [NSNumber numberWithUnsignedChar:1]; @@ -10766,6 +10766,39 @@ - (void)testSendClusterTest_TC_LVL_3_1_000014_ReadAttribute [self waitForExpectationsWithTimeout:kTimeoutInSeconds handler:nil]; } +- (void)testSendClusterTest_TC_LVL_3_1_000015_MoveToLevel +{ + XCTestExpectation * expectation = [self expectationWithDescription:@"Reset level to 254"]; + + CHIPDevice * device = GetConnectedDevice(); + dispatch_queue_t queue = dispatch_get_main_queue(); + CHIPTestLevelControl * cluster = [[CHIPTestLevelControl alloc] initWithDevice:device endpoint:1 queue:queue]; + XCTAssertNotNil(cluster); + + __auto_type * params = [[CHIPLevelControlClusterMoveToLevelParams alloc] init]; + params.level = [NSNumber numberWithUnsignedChar:254]; + params.transitionTime = [NSNumber numberWithUnsignedShort:0U]; + params.optionMask = [NSNumber numberWithUnsignedChar:1]; + params.optionOverride = [NSNumber numberWithUnsignedChar:1]; + [cluster moveToLevelWithParams:params + completionHandler:^(NSError * _Nullable err) { + NSLog(@"Reset level to 254 Error: %@", err); + + XCTAssertEqual([CHIPErrorTestUtils errorToZCLErrorCode:err], 0); + + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:kTimeoutInSeconds handler:nil]; +} +- (void)testSendClusterTest_TC_LVL_3_1_000016_WaitForMs +{ + XCTestExpectation * expectation = [self expectationWithDescription:@"Wait 100ms"]; + + dispatch_queue_t queue = dispatch_get_main_queue(); + WaitForMs(expectation, queue, 100); + [self waitForExpectationsWithTimeout:kTimeoutInSeconds handler:nil]; +} - (void)testSendClusterTest_TC_LVL_4_1_000000_WaitForCommissionee { @@ -10804,8 +10837,8 @@ - (void)testSendClusterTest_TC_LVL_4_1_000002_Step XCTAssertNotNil(cluster); __auto_type * params = [[CHIPLevelControlClusterStepParams alloc] init]; - params.stepMode = [NSNumber numberWithUnsignedChar:0]; - params.stepSize = [NSNumber numberWithUnsignedChar:128]; + params.stepMode = [NSNumber numberWithUnsignedChar:1]; + params.stepSize = [NSNumber numberWithUnsignedChar:126]; params.transitionTime = [NSNumber numberWithUnsignedShort:20U]; params.optionMask = [NSNumber numberWithUnsignedChar:0]; params.optionOverride = [NSNumber numberWithUnsignedChar:0]; diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h index c469f04ba94efc..08af377b7ef511 100644 --- a/src/lib/support/DefaultStorageKeyAllocator.h +++ b/src/lib/support/DefaultStorageKeyAllocator.h @@ -16,6 +16,7 @@ */ #pragma once +#include #include #include #include @@ -47,6 +48,13 @@ class DefaultStorageKeyAllocator } const char * FabricKeyset(chip::FabricIndex fabric, uint16_t keyset) { return Format("f/%x/k/%x", fabric, keyset); } + const char * AttributeValue(const app::ConcreteAttributePath & aPath) + { + // Needs at most 24 chars: 4 for "a///", 4 for the endpoint id, 8 each + // for the cluster and attribute ids. + return Format("a/%" PRIx16 "/%" PRIx32 "/%" PRIx32, aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId); + } + private: static const size_t kKeyLengthMax = 32; diff --git a/src/platform/BUILD.gn b/src/platform/BUILD.gn index 37dd34ab3f86d8..741134a0c931fc 100644 --- a/src/platform/BUILD.gn +++ b/src/platform/BUILD.gn @@ -25,7 +25,7 @@ import("device.gni") if (chip_enable_openthread) { import("//build_overrides/openthread.gni") - if (chip_device_platform == "linux" || chip_device_platform == "Darwin") { + if (chip_device_platform == "linux" || chip_device_platform == "darwin") { import("//build_overrides/ot_br_posix.gni") } } diff --git a/zzz_generated/all-clusters-app/zap-generated/endpoint_config.h b/zzz_generated/all-clusters-app/zap-generated/endpoint_config.h index 71521a5022f08a..8a2cc85b40c21b 100644 --- a/zzz_generated/all-clusters-app/zap-generated/endpoint_config.h +++ b/zzz_generated/all-clusters-app/zap-generated/endpoint_config.h @@ -1711,13 +1711,13 @@ { 0xFFFD, ZAP_TYPE(INT16U), 2, 0, ZAP_SIMPLE_DEFAULT(1) }, /* ClusterRevision */ \ \ /* Endpoint: 1, Cluster: Level Control (server) */ \ - { 0x0000, ZAP_TYPE(INT8U), 1, 0, ZAP_SIMPLE_DEFAULT(0x00) }, /* current level */ \ - { 0x0001, ZAP_TYPE(INT16U), 2, 0, ZAP_SIMPLE_DEFAULT(0x0000) }, /* remaining time */ \ - { 0x0002, ZAP_TYPE(INT8U), 1, 0, ZAP_SIMPLE_DEFAULT(0x00) }, /* min level */ \ - { 0x0003, ZAP_TYPE(INT8U), 1, 0, ZAP_SIMPLE_DEFAULT(0xFF) }, /* max level */ \ - { 0x0004, ZAP_TYPE(INT16U), 2, 0, ZAP_SIMPLE_DEFAULT(0x0000) }, /* current frequency */ \ - { 0x0005, ZAP_TYPE(INT16U), 2, 0, ZAP_SIMPLE_DEFAULT(0x0000) }, /* min frequency */ \ - { 0x0006, ZAP_TYPE(INT16U), 2, 0, ZAP_SIMPLE_DEFAULT(0x0000) }, /* max frequency */ \ + { 0x0000, ZAP_TYPE(INT8U), 1, ZAP_ATTRIBUTE_MASK(TOKENIZE), ZAP_SIMPLE_DEFAULT(0xFE) }, /* current level */ \ + { 0x0001, ZAP_TYPE(INT16U), 2, 0, ZAP_SIMPLE_DEFAULT(0x0000) }, /* remaining time */ \ + { 0x0002, ZAP_TYPE(INT8U), 1, 0, ZAP_SIMPLE_DEFAULT(0x00) }, /* min level */ \ + { 0x0003, ZAP_TYPE(INT8U), 1, 0, ZAP_SIMPLE_DEFAULT(0xFF) }, /* max level */ \ + { 0x0004, ZAP_TYPE(INT16U), 2, 0, ZAP_SIMPLE_DEFAULT(0x0000) }, /* current frequency */ \ + { 0x0005, ZAP_TYPE(INT16U), 2, 0, ZAP_SIMPLE_DEFAULT(0x0000) }, /* min frequency */ \ + { 0x0006, ZAP_TYPE(INT16U), 2, 0, ZAP_SIMPLE_DEFAULT(0x0000) }, /* max frequency */ \ { 0x000F, ZAP_TYPE(BITMAP8), 1, ZAP_ATTRIBUTE_MASK(MIN_MAX) | ZAP_ATTRIBUTE_MASK(WRITABLE), \ ZAP_MIN_MAX_DEFAULTS_INDEX(3) }, /* options */ \ { 0x0010, ZAP_TYPE(INT16U), 2, ZAP_ATTRIBUTE_MASK(WRITABLE), \ diff --git a/zzz_generated/chip-tool/zap-generated/test/Commands.h b/zzz_generated/chip-tool/zap-generated/test/Commands.h index 9cf7742fb3394a..c6c9fc930f5f44 100644 --- a/zzz_generated/chip-tool/zap-generated/test/Commands.h +++ b/zzz_generated/chip-tool/zap-generated/test/Commands.h @@ -16216,8 +16216,8 @@ class Test_TC_LVL_2_1 : public TestCommand err = TestReadsCurrentLevelAttributeFromDut_11(); break; case 12: - ChipLogProgress(chipTool, " ***** Test Step 12 : Reset level to 0\n"); - err = TestResetLevelTo0_12(); + ChipLogProgress(chipTool, " ***** Test Step 12 : Reset level to 254\n"); + err = TestResetLevelTo254_12(); break; case 13: ChipLogProgress(chipTool, " ***** Test Step 13 : Wait 100ms\n"); @@ -16311,7 +16311,7 @@ class Test_TC_LVL_2_1 : public TestCommand void OnSuccessResponse_1(uint8_t currentLevel) { - VerifyOrReturn(CheckValue("currentLevel", currentLevel, 0)); + VerifyOrReturn(CheckValue("currentLevel", currentLevel, 254)); NextTest(); } @@ -16495,13 +16495,13 @@ class Test_TC_LVL_2_1 : public TestCommand NextTest(); } - CHIP_ERROR TestResetLevelTo0_12() + CHIP_ERROR TestResetLevelTo254_12() { const chip::EndpointId endpoint = mEndpointId.HasValue() ? mEndpointId.Value() : 1; using RequestType = chip::app::Clusters::LevelControl::Commands::MoveToLevel::Type; RequestType request; - request.level = 0; + request.level = 254; request.transitionTime = 0U; request.optionMask = 1; request.optionOverride = 1; @@ -16619,6 +16619,14 @@ class Test_TC_LVL_3_1 : public TestCommand ChipLogProgress(chipTool, " ***** Test Step 14 : reads current level attribute from DUT\n"); err = TestReadsCurrentLevelAttributeFromDut_14(); break; + case 15: + ChipLogProgress(chipTool, " ***** Test Step 15 : Reset level to 254\n"); + err = TestResetLevelTo254_15(); + break; + case 16: + ChipLogProgress(chipTool, " ***** Test Step 16 : Wait 100ms\n"); + err = TestWait100ms_16(); + break; } if (CHIP_NO_ERROR != err) @@ -16630,7 +16638,7 @@ class Test_TC_LVL_3_1 : public TestCommand private: std::atomic_uint16_t mTestIndex; - const uint16_t mTestCount = 15; + const uint16_t mTestCount = 17; static void OnFailureCallback_1(void * context, EmberAfStatus status) { @@ -16734,7 +16742,7 @@ class Test_TC_LVL_3_1 : public TestCommand void OnSuccessResponse_1(uint8_t currentLevel) { - VerifyOrReturn(CheckValue("currentLevel", currentLevel, 0)); + VerifyOrReturn(CheckValue("currentLevel", currentLevel, 254)); NextTest(); } @@ -16930,7 +16938,7 @@ class Test_TC_LVL_3_1 : public TestCommand using RequestType = chip::app::Clusters::LevelControl::Commands::Move::Type; RequestType request; - request.moveMode = static_cast(1); + request.moveMode = static_cast(0); request.rate = 255; request.optionMask = 1; request.optionOverride = 1; @@ -16976,6 +16984,39 @@ class Test_TC_LVL_3_1 : public TestCommand NextTest(); } + + CHIP_ERROR TestResetLevelTo254_15() + { + const chip::EndpointId endpoint = mEndpointId.HasValue() ? mEndpointId.Value() : 1; + using RequestType = chip::app::Clusters::LevelControl::Commands::MoveToLevel::Type; + + RequestType request; + request.level = 254; + request.transitionTime = 0U; + request.optionMask = 1; + request.optionOverride = 1; + + auto success = [](void * context, const typename RequestType::ResponseType & data) { + (static_cast(context))->OnSuccessResponse_15(); + }; + + auto failure = [](void * context, EmberAfStatus status) { + (static_cast(context))->OnFailureResponse_15(status); + }; + + ReturnErrorOnFailure(chip::Controller::InvokeCommand(mDevices[kIdentityAlpha], this, success, failure, endpoint, request)); + return CHIP_NO_ERROR; + } + + void OnFailureResponse_15(EmberAfStatus status) { ThrowFailureResponse(); } + + void OnSuccessResponse_15() { NextTest(); } + + CHIP_ERROR TestWait100ms_16() + { + SetIdentity(kIdentityAlpha); + return WaitForMs(100); + } }; class Test_TC_LVL_4_1 : public TestCommand @@ -17138,8 +17179,8 @@ class Test_TC_LVL_4_1 : public TestCommand using RequestType = chip::app::Clusters::LevelControl::Commands::Step::Type; RequestType request; - request.stepMode = static_cast(0); - request.stepSize = 128; + request.stepMode = static_cast(1); + request.stepSize = 126; request.transitionTime = 20U; request.optionMask = 0; request.optionOverride = 0;