| /** |
| * |
| * Copyright (c) 2025 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 "CommodityTariffConsts.h" |
| #include <app-common/zap-generated/cluster-enums.h> |
| #include <app-common/zap-generated/cluster-objects.h> |
| #include <platform/LockTracker.h> |
| |
| #include <atomic> |
| #include <cassert> |
| #include <map> |
| #include <set> |
| #include <string> |
| #include <unordered_map> |
| #include <unordered_set> |
| |
| namespace chip { |
| |
| // Span comparison operators |
| inline bool operator==(const Span<const char> & a, const Span<const char> & b) |
| { |
| return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin()); |
| } |
| |
| inline bool operator!=(const Span<const char> & a, const Span<const char> & b) |
| { |
| return !(a == b); |
| } |
| |
| inline bool operator==(const Span<const uint32_t> & a, const Span<const uint32_t> & b) |
| { |
| return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin()); |
| } |
| |
| inline bool operator!=(const Span<const uint32_t> & a, const Span<const uint32_t> & b) |
| { |
| return !(a == b); |
| } |
| |
| namespace app { |
| |
| namespace Clusters { |
| namespace Globals { |
| namespace Structs { |
| namespace CurrencyStruct { |
| inline bool operator==(const Type & lhs, const Type & rhs) |
| { |
| return (lhs.currency == rhs.currency) && (lhs.decimalPoints == rhs.decimalPoints); |
| } |
| } // namespace CurrencyStruct |
| |
| namespace PowerThresholdStruct { |
| inline bool operator==(const Type & lhs, const Type & rhs) |
| { |
| return (lhs.powerThresholdSource == rhs.powerThresholdSource) && (lhs.powerThreshold == rhs.powerThreshold) && |
| (lhs.apparentPowerThreshold == rhs.apparentPowerThreshold); |
| } |
| } // namespace PowerThresholdStruct |
| |
| } // namespace Structs |
| } // namespace Globals |
| |
| namespace CommodityTariff { |
| namespace Structs { |
| |
| namespace TariffPriceStruct { |
| inline bool operator==(const Type & lhs, const Type & rhs) |
| { |
| return (lhs.priceType == rhs.priceType) && (lhs.price == rhs.price) && (lhs.priceLevel == rhs.priceLevel); |
| } |
| } // namespace TariffPriceStruct |
| |
| namespace AuxiliaryLoadSwitchSettingsStruct { |
| inline bool operator==(const AuxiliaryLoadSwitchSettingsStruct::Type & lhs, const AuxiliaryLoadSwitchSettingsStruct::Type & rhs) |
| { |
| return (lhs.number == rhs.number) && (lhs.requiredState == rhs.requiredState); |
| } |
| } // namespace AuxiliaryLoadSwitchSettingsStruct |
| |
| // PeakPeriodStruct |
| namespace PeakPeriodStruct { |
| inline bool operator==(const Type & lhs, const Type & rhs) |
| { |
| return (lhs.severity == rhs.severity) && (lhs.peakPeriod == rhs.peakPeriod); |
| } |
| } // namespace PeakPeriodStruct |
| |
| namespace TariffInformationStruct { |
| inline bool operator==(const Type & lhs, const Type & rhs) |
| { |
| return (lhs.currency == rhs.currency) && (lhs.tariffLabel == rhs.tariffLabel) && (lhs.providerName == rhs.providerName) && |
| (lhs.blockMode == rhs.blockMode); |
| } |
| |
| inline bool operator!=(const Type & lhs, const Type & rhs) |
| { |
| return !(lhs == rhs); |
| } |
| } // namespace TariffInformationStruct |
| |
| namespace DayStruct { |
| inline bool operator==(const Type & lhs, const Type & rhs) |
| { |
| return (lhs.date == rhs.date) && (lhs.dayType == rhs.dayType) && (lhs.dayEntryIDs == rhs.dayEntryIDs); |
| } |
| |
| inline bool operator!=(const Type & lhs, const Type & rhs) |
| { |
| return !(lhs == rhs); |
| } |
| } // namespace DayStruct |
| |
| namespace DayEntryStruct { |
| inline bool operator==(const Type & lhs, const Type & rhs) |
| { |
| return (lhs.dayEntryID == rhs.dayEntryID) && (lhs.startTime == rhs.startTime) && (lhs.duration == rhs.duration) && |
| (lhs.randomizationOffset == rhs.randomizationOffset) && (lhs.randomizationType == rhs.randomizationType); |
| } |
| |
| inline bool operator!=(const Type & lhs, const Type & rhs) |
| { |
| return !(lhs == rhs); |
| } |
| } // namespace DayEntryStruct |
| |
| namespace DayPatternStruct { |
| inline bool operator==(const Type & lhs, const Type & rhs) |
| { |
| return (lhs.dayPatternID == rhs.dayPatternID) && (lhs.daysOfWeek.Raw() == rhs.daysOfWeek.Raw()) && |
| (lhs.dayEntryIDs == rhs.dayEntryIDs); |
| } |
| |
| inline bool operator!=(const Type & lhs, const Type & rhs) |
| { |
| return !(lhs == rhs); |
| } |
| } // namespace DayPatternStruct |
| |
| namespace TariffComponentStruct { |
| inline bool operator==(const Type & lhs, const Type & rhs) |
| { |
| return (lhs.label == rhs.label) && (lhs.price == rhs.price) && (lhs.friendlyCredit == rhs.friendlyCredit) && |
| (lhs.auxiliaryLoad == rhs.auxiliaryLoad) && (lhs.peakPeriod == rhs.peakPeriod) && |
| (lhs.powerThreshold == rhs.powerThreshold) && (lhs.predicted == rhs.predicted) && (lhs.threshold == rhs.threshold); |
| } |
| |
| inline bool operator!=(const Type & lhs, const Type & rhs) |
| { |
| return !(lhs == rhs); |
| } |
| } // namespace TariffComponentStruct |
| |
| namespace TariffPeriodStruct { |
| inline bool operator==(const Type & lhs, const Type & rhs) |
| { |
| return (lhs.tariffComponentIDs == rhs.tariffComponentIDs) && (lhs.dayEntryIDs == rhs.dayEntryIDs) && (lhs.label == rhs.label); |
| } |
| |
| inline bool operator!=(const Type & lhs, const Type & rhs) |
| { |
| return !(lhs == rhs); |
| } |
| } // namespace TariffPeriodStruct |
| |
| namespace CalendarPeriodStruct { |
| inline bool operator==(const Type & lhs, const Type & rhs) |
| { |
| return (lhs.startDate == rhs.startDate) && (lhs.dayPatternIDs == rhs.dayPatternIDs); |
| } |
| |
| inline bool operator!=(const Type & lhs, const Type & rhs) |
| { |
| return !(lhs == rhs); |
| } |
| } // namespace CalendarPeriodStruct |
| |
| } // namespace Structs |
| } // namespace CommodityTariff |
| |
| } // namespace Clusters |
| |
| namespace CommodityTariffAttrsDataMgmt { |
| |
| /// @brief Helper for copying spans to Matter data model lists |
| /// @tparam T Type of elements to copy |
| template <typename T> |
| struct SpanCopier |
| { |
| /// @brief Copies span data to a newly allocated list |
| /// @param source Input span to copy from |
| /// @param destination Output list to populate |
| /// @param maxCount Maximum number of elements to copy (default: unlimited) |
| /// @return CHIP_NO_ERROR if copy succeeded, error code on failure |
| static CHIP_ERROR Copy(const Span<const T> & source, DataModel::List<const T> & destination, |
| size_t maxCount = std::numeric_limits<size_t>::max()) |
| { |
| if (source.empty()) |
| { |
| destination = DataModel::List<const T>(); |
| return CHIP_NO_ERROR; |
| } |
| |
| const size_t elementsToCopy = std::min(source.size(), maxCount); |
| auto * buffer = static_cast<T *>(Platform::MemoryCalloc(elementsToCopy, sizeof(T))); |
| |
| if (!buffer) |
| return CHIP_ERROR_NO_MEMORY; |
| |
| std::copy(source.begin(), source.begin() + elementsToCopy, buffer); |
| destination = DataModel::List<const T>(Span<const T>(buffer, elementsToCopy)); |
| return CHIP_NO_ERROR; |
| } |
| }; |
| |
| /// @brief Specialization for character spans with consistent maxCount semantics |
| template <> |
| struct SpanCopier<char> |
| { |
| /// @brief Copies character span to a nullable CharSpan |
| /// @param source Input span to copy from |
| /// @param destination Output span to populate |
| /// @param maxCount Maximum number of characters to copy (default: unlimited) |
| /// @return CHIP_NO_ERROR if copy succeeded, error code on failure |
| static CHIP_ERROR Copy(const CharSpan & source, DataModel::Nullable<CharSpan> & destination, |
| size_t maxCount = std::numeric_limits<size_t>::max()) |
| { |
| if (source.size() > maxCount) |
| { |
| return CHIP_ERROR_INVALID_STRING_LENGTH; |
| } |
| |
| if (source.empty()) |
| { |
| destination.SetNull(); |
| return CHIP_NO_ERROR; |
| } |
| |
| char * buffer = static_cast<char *>(Platform::MemoryCalloc(1, source.size())); |
| if (!buffer) |
| return CHIP_ERROR_NO_MEMORY; |
| |
| std::copy(source.begin(), source.end(), buffer); |
| destination.SetNonNull(CharSpan(buffer, source.size())); |
| return CHIP_NO_ERROR; |
| } |
| }; |
| |
| /// @brief Helper for string to span conversions |
| struct StrToSpan |
| { |
| /// @brief Copies std::string to a CharSpan |
| /// @param source Input string to copy from |
| /// @param destination Output span to populate |
| /// @param maxCount Maximum number of characters to copy (default: unlimited) |
| /// @return CHIP_NO_ERROR on success, error code on failure |
| static CHIP_ERROR Copy(const std::string & source, CharSpan & destination, |
| size_t maxCount = CommodityTariffConsts::kDefaultStringValuesMaxBufLength) |
| { |
| if (source.empty()) |
| { |
| destination = CharSpan(); |
| return CHIP_NO_ERROR; |
| } |
| |
| if (source.size() > maxCount) |
| { |
| return CHIP_ERROR_INVALID_STRING_LENGTH; |
| } |
| |
| char * buffer = static_cast<char *>(Platform::MemoryAlloc(source.size())); |
| if (!buffer) |
| return CHIP_ERROR_NO_MEMORY; |
| |
| memcpy(buffer, source.data(), source.size()); |
| destination = CharSpan(buffer, source.size()); |
| return CHIP_NO_ERROR; |
| } |
| |
| /// @brief Releases memory allocated by a CharSpan |
| static void Release(CharSpan & span) |
| { |
| if (!span.empty()) |
| { |
| Platform::MemoryFree(const_cast<char *>(span.data())); |
| span = CharSpan(); |
| } |
| } |
| }; |
| |
| template <typename T, auto X> |
| void ListToMap(const DataModel::List<T> & aList, std::map<uint32_t, const T *> & aMap) |
| { |
| for (const auto & item : aList) |
| { |
| // Insert into map with specified entry as key |
| aMap.emplace(item.*X, &item); |
| } |
| } |
| |
| template <typename T, auto X> |
| void ListToMap(const DataModel::List<T> & aList, std::unordered_map<uint32_t, const T *> & aMap) |
| { |
| for (const auto & item : aList) |
| { |
| // Insert into map with specified entry as key |
| aMap.emplace(item.*X, &item); |
| } |
| } |
| |
| template <typename T> |
| CHIP_ERROR ValidateListEntry(const T & entryNewValue, void * aCtx); |
| |
| /// @brief Type trait for nullable types |
| template <typename U> |
| struct IsNullable : std::false_type |
| { |
| }; |
| template <typename U> |
| struct IsNullable<DataModel::Nullable<U>> : std::true_type |
| { |
| }; |
| |
| /// @brief Type trait for list types |
| template <typename U> |
| struct IsList : std::false_type |
| { |
| }; |
| template <typename U> |
| struct IsList<DataModel::List<U>> : std::true_type |
| { |
| }; |
| |
| /// @brief Type categorization traits |
| template <typename U> |
| struct IsNumeric : std::integral_constant<bool, std::is_integral<U>::value || std::is_floating_point<U>::value> |
| { |
| }; |
| template <typename U> |
| struct IsEnum : std::is_enum<U> |
| { |
| }; |
| template <typename U> |
| struct IsStruct |
| : std::integral_constant<bool, !IsList<U>::value && !IsNumeric<U>::value && !IsEnum<U>::value && !std::is_pointer<U>::value> |
| { |
| }; |
| |
| /// @brief Type extraction utilities |
| template <typename U> |
| struct ExtractNonNullableType |
| { |
| using type = U; |
| }; |
| template <typename U> |
| struct ExtractNonNullableType<DataModel::Nullable<U>> |
| { |
| using type = typename ExtractNonNullableType<U>::type; |
| }; |
| template <typename U> |
| using ExtractNonNullableType_t = typename ExtractNonNullableType<U>::type; |
| |
| template <typename U> |
| struct ExtractNestedType |
| { |
| using type = U; // Base case - not a wrapper |
| }; |
| template <typename U> |
| struct ExtractNestedType<DataModel::Nullable<U>> |
| { |
| using type = typename ExtractNestedType<U>::type; |
| }; |
| template <typename U> |
| struct ExtractNestedType<DataModel::List<U>> |
| { |
| using type = typename ExtractNestedType<U>::type; |
| }; |
| template <typename U> |
| using ExtractNestedType_t = typename ExtractNestedType<U>::type; |
| |
| /** |
| * @class CTC_BaseDataClass |
| * @tparam T The attribute value type (non-nullable/nullable list, struct or primitive) |
| * @brief Atomic attribute data management with validation support |
| * |
| * @details This template class provides a robust foundation for managing attribute |
| * values in a thread-safe manner with support for atomic updates, validation, |
| * and automatic memory management. It handles three categories of attributes: |
| * 1. Nullable types (DataModel::Nullable<U>, DataModel::Nullable<DataModel::List<U>>) |
| * 2. List types (DataModel::List<U>) |
| * 3. Primitive/struct types |
| * |
| * @section features Key Features |
| * - Atomic value updates with validation |
| * - Change detection and notification |
| * - Automatic memory management for complex types |
| * - Double-buffered storage for thread-safe access |
| * - Strict state machine for update flow control |
| * - Customizable validation hooks |
| * - Support for nullable and list types |
| * |
| * @section type_handling Type Handling |
| * The class automatically adapts its behavior based on the template type: |
| * - For nullable types (DataModel::Nullable<U>): |
| * * Manages null state transitions |
| * * Handles both scalar and complex nullable values |
| * - For list types (DataModel::List<U>): |
| * * Manages memory allocation/deallocation |
| * * Provides element-wise copy and cleanup |
| * - For struct types: |
| * * Uses type-specific CopyData and CleanupStructValue specializations |
| * - For primitive types: |
| * * Simple value storage with atomic update support |
| * |
| * @section memory_management Memory Management |
| * The class handles automatic cleanup for: |
| * - List memory (allocated via Platform::MemoryCalloc) |
| * - Nested structs (via CleanupStructValue template) |
| * - Nullable state transitions |
| * |
| * @section usage_patterns Usage Patterns |
| * |
| * @subsection basic_usage Basic Value Update |
| * @code{.cpp} |
| * CTC_BaseDataClass<Nullable<uint32_t>> data(attrId); |
| * data.SetNewValue(5->aValue) |
| * - CreateNewSingleValue(); // Initialize new value |
| * - GetNewValueRef().SetNonNull(5); // Modify value |
| * - MarkAsAssigned(); // Mark as ready for validation |
| * data.UpdateBegin(nullptr); // Validate (no context needed) |
| * data.UpdateFinish(true); // Commit changes |
| * @endcode |
| * |
| * @subsection list_usage List Value Update |
| * @code{.cpp} |
| * List<DayEntryStruct> mListData = {...} |
| * CTC_BaseDataClass<Nullable<List<DayEntryStruct>>> data(attrId); |
| * data.SetNewValue(mListData->aValue) |
| * - CreateNewListValue(aValue.Size()); // Allocate 3 elements |
| * - Loop for list items: CopyData(src_item[i], dst_item[i]); //copy list elements... |
| * - MarkAsAssigned(); |
| * data.UpdateBegin(validationCtx); |
| * data.UpdateFinish(true); |
| * @endcode |
| * |
| * @section update_flow Update State Machine |
| * The class enforces a strict update workflow: |
| * |
| * @dot |
| * digraph update_flow { |
| * node [shape=box, style=rounded]; |
| * kIdle -> kInitialized [label="CreateNewValue()"]; |
| * kInitialized -> kAssigned [label="MarkAsAssigned()"]; |
| * kAssigned -> kValidated [label="UpdateBegin()"]; |
| * kValidated -> kIdle [label="UpdateFinish() (mandatory)"]; |
| * |
| * // Error/cleanup paths |
| * kInitialized -> kIdle [label="UpdateFinish()" style="dashed"]; |
| * kAssigned -> kIdle [label="UpdateFinish()" style="dashed"]; |
| * kValidated -> kIdle [label="UpdateFinish()" style="dashed"]; |
| * } |
| * @enddot |
| * |
| * @note UpdateFinish() must always be called to complete the update process, |
| * even if validation fails or the update is aborted. |
| */ |
| class CTC_BaseDataClassBase |
| { |
| public: |
| virtual ~CTC_BaseDataClassBase() = default; |
| |
| // Common interface |
| virtual bool IsValid() const = 0; |
| virtual bool HasValue() const = 0; |
| virtual bool HasNewValue() const = 0; |
| virtual CHIP_ERROR MarkAsAssigned() = 0; |
| virtual CHIP_ERROR UpdateBegin(void * aUpdCtx) = 0; |
| virtual bool UpdateFinish(bool aUpdateAllow) = 0; |
| virtual bool Cleanup() = 0; |
| virtual AttributeId GetAttrId() const = 0; |
| |
| // Type-erased methods for generic access |
| virtual CHIP_ERROR GetValueAsVoid(void *& outValue) = 0; |
| virtual CHIP_ERROR GetNewValueAsVoid(void *& outValue) = 0; |
| virtual CHIP_ERROR SetNewValueFromVoid(const void * value) = 0; |
| }; |
| |
| template <typename T> |
| class CTC_BaseDataClass : public CTC_BaseDataClassBase |
| { |
| /// @brief Internal storage states |
| enum class StorageState : uint8_t |
| { |
| kEmpty, // Value not initialized (default state) |
| kHold, // Storage holds valid data |
| }; |
| |
| /// @brief Update process states |
| enum class UpdateState : uint8_t |
| { |
| kIdle, // No active update |
| kInitialized, // New value initialized but not populated |
| kAssigned, // New value populated but not validated |
| kValidated, // New value validated and ready for commit |
| }; |
| |
| T mValueStorage[2]; ///< Double-buffered value storage |
| StorageState mHoldState[2] = { StorageState::kEmpty }; ///< Storage state tracking |
| std::atomic<UpdateState> mUpdateState{ UpdateState::kIdle }; ///< Current update state |
| std::atomic<uint8_t> mActiveValueIdx{ 0 }; ///< Index of active value storage |
| public: |
| /// The exposed attribute value type |
| using ValueType = T; |
| |
| using DataType = ExtractNonNullableType_t<ValueType>; |
| |
| using ListEntryType = std::conditional_t<IsList<DataType>::value, |
| ExtractNestedType_t<DataType>, // Extract the list element type |
| void *>; |
| |
| using StructType = std::conditional_t<IsList<DataType>::value, |
| ListEntryType, // Extract the list element type |
| DataType>; |
| |
| /** |
| * @brief Construct a new data class instance |
| * @param[in] aAttrId Attribute ID for identification and callbacks |
| * |
| * @post Initializes storage based on type: |
| * - Nullable types: set to null |
| * - List types: initialized as empty list |
| * - Others: default initialized |
| * - Update state set to kIdle |
| */ |
| explicit CTC_BaseDataClass(AttributeId aAttrId) : mAttrId(aAttrId) |
| { |
| if constexpr (TypeIsNullable<ValueType>()) |
| { |
| GetValueRef().SetNull(); |
| } |
| else if constexpr (TypeIsList<ValueType>()) |
| { |
| GetValueRef() = ValueType(); |
| } |
| } |
| |
| /// @brief Virtual destructor ensures proper cleanup of resources |
| virtual ~CTC_BaseDataClass() { Cleanup(); } |
| |
| /** |
| * @brief Get mutable reference to the active value |
| * @return Reference to the currently active value storage |
| * @warning Modifying the returned reference directly bypasses |
| * the update state machine and validation. Prefer using |
| * the proper update workflow. |
| */ |
| ValueType & GetValue() { return GetValueRef(); } |
| |
| /** |
| * @brief Get mutable reference to the new value (during update) |
| * @return Reference to the new value storage |
| * @pre Must be in kInitialized or kAssigned state |
| */ |
| ValueType & GetNewValue() { return GetNewValueRef(); } |
| |
| /** |
| * @brief Check if current update is validated |
| * @return true if in kValidated state, false otherwise |
| */ |
| bool IsValid() const override { return (mUpdateState.load() == UpdateState::kValidated); } |
| |
| /** |
| * @brief Check if active storage contains valid data |
| * @return true if storage is in kHold state, false otherwise |
| */ |
| bool HasValue() const override { return (mHoldState[mActiveValueIdx.load()] == StorageState::kHold); } |
| |
| /** |
| * @brief Check if new value storage contains valid data |
| * @return true if new storage is in kHold state, false otherwise |
| */ |
| bool HasNewValue() const override { return (mHoldState[1 - mActiveValueIdx.load()] == StorageState::kHold); } |
| |
| /** |
| * @brief Prepares a new list value for modification |
| * @param[in] size Number of elements to allocate |
| * @return CHIP_NO_ERROR on success |
| * @retval CHIP_ERROR_INCORRECT_STATE if called during active update |
| * @retval CHIP_ERROR_NO_MEMORY if allocation fails |
| * @retval CHIP_ERROR_INTERNAL if called on non-list type |
| * |
| * @post On success: |
| * - Allocates memory for list elements |
| * - Initializes new value storage |
| * - Transitions state to kInitialized |
| * |
| * @note For zero size, creates an empty list or null value |
| */ |
| CHIP_ERROR CreateNewListValue(size_t size) |
| { |
| if (mUpdateState.load() != UpdateState::kIdle) |
| { |
| return CHIP_ERROR_INCORRECT_STATE; |
| } |
| |
| if constexpr (TypeIsList<DataType>()) |
| { |
| if (size >= 1) |
| { |
| auto * buffer = static_cast<ListEntryType *>(Platform::MemoryCalloc(size, sizeof(ListEntryType))); |
| if (!buffer) |
| { |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| if constexpr (TypeIsNullable<ValueType>()) |
| { |
| GetNewValueRef().SetNonNull(DataModel::List<ListEntryType>(buffer, size)); |
| } |
| else |
| { |
| GetNewValueRef() = DataModel::List<ListEntryType>(buffer, size); |
| } |
| } |
| else |
| { |
| return CHIP_ERROR_INVALID_LIST_LENGTH; |
| } |
| } |
| else |
| { |
| return CHIP_ERROR_INTERNAL; |
| } |
| |
| mUpdateState.store(UpdateState::kInitialized); |
| return CHIP_NO_ERROR; |
| } |
| |
| /** |
| * @brief Prepares a new non-list value for modification |
| * @return CHIP_NO_ERROR on success |
| * @retval CHIP_ERROR_INCORRECT_STATE if called during active update |
| * @retval CHIP_ERROR_INTERNAL if called on list type |
| * |
| * @post On success: |
| * - Initializes new value storage |
| * - Transitions state to kInitialized |
| */ |
| CHIP_ERROR CreateNewSingleValue() |
| { |
| if (mUpdateState.load() != UpdateState::kIdle) |
| { |
| return CHIP_ERROR_INCORRECT_STATE; |
| } |
| |
| if constexpr (TypeIsList<DataType>()) |
| { |
| return CHIP_ERROR_INTERNAL; |
| } |
| |
| if constexpr (TypeIsNullable<ValueType>()) |
| { |
| GetNewValueRef().SetNull(); |
| } |
| else |
| { |
| GetNewValueRef() = ValueType(); |
| } |
| |
| mUpdateState.store(UpdateState::kInitialized); |
| return CHIP_NO_ERROR; |
| } |
| |
| /** |
| * @brief Sets a new value from external source |
| * @param[in] aValue The new value to set |
| * @return CHIP_NO_ERROR on success |
| * |
| * @note Handles all type cases (nullable, list, struct, scalar) |
| * @note Performs proper copy and memory management |
| */ |
| CHIP_ERROR SetNewValue(const ValueType & aValue) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| // Handle nullable case first - early return for null |
| if constexpr (TypeIsNullable<ValueType>()) |
| { |
| if (aValue.IsNull()) |
| { |
| mUpdateState.store(UpdateState::kInitialized); |
| return MarkAsAssigned(); |
| } |
| } |
| |
| // Get reference to the actual value (non-nullable part) |
| const auto & actualValue = [&]() -> const auto & { |
| if constexpr (TypeIsNullable<ValueType>()) |
| { |
| return aValue.Value(); |
| } |
| else |
| { |
| return aValue; |
| } |
| }(); |
| |
| // Get reference to the storage location |
| [[maybe_unused]] const auto getStorageRef = [this]() -> auto & { |
| if constexpr (TypeIsNullable<ValueType>()) |
| { |
| return GetNewValueRef().Value(); |
| } |
| else |
| { |
| return GetNewValueRef(); |
| } |
| }; |
| |
| [[maybe_unused]] const auto assignStorageVal = [this](auto && value) -> auto & { |
| if constexpr (TypeIsNullable<ValueType>()) |
| { |
| return GetNewValueRef().SetNonNull(value); |
| } |
| else |
| { |
| return GetNewValueRef() = value; |
| } |
| }; |
| |
| if constexpr (TypeIsList<DataType>()) |
| { |
| assertChipStackLockedByCurrentThread(); |
| |
| err = CreateNewListValue(actualValue.size()); |
| |
| if (CHIP_NO_ERROR == err) |
| { |
| auto buffer = getStorageRef().data(); |
| for (size_t idx = 0; idx < actualValue.size(); idx++) |
| { |
| if constexpr (TypeIsStruct<ListEntryType>()) |
| { |
| if (CHIP_NO_ERROR == (err = CopyData(actualValue[idx], buffer[idx]))) |
| { |
| continue; |
| } |
| break; |
| } |
| else |
| { |
| buffer[idx] = actualValue[idx]; |
| } |
| } |
| } |
| } |
| else if constexpr (TypeIsStruct<DataType>()) |
| { |
| err = CreateNewSingleValue(); |
| |
| if (CHIP_NO_ERROR == err) |
| { |
| assignStorageVal(DataType()); // Default construct in place |
| err = CopyData(actualValue, getStorageRef()); |
| } |
| } |
| else if constexpr (TypeIsScalar<DataType>()) |
| { |
| err = CreateNewSingleValue(); |
| |
| if (CHIP_NO_ERROR == err) |
| { |
| assignStorageVal(actualValue); |
| } |
| } |
| |
| ReturnErrorOnFailure(err); |
| return MarkAsAssigned(); |
| } |
| |
| /** |
| * @brief Signals completion of new value modifications |
| * @return CHIP_NO_ERROR on success |
| * @retval CHIP_ERROR_INCORRECT_STATE if not in kInitialized state |
| * |
| * @post Transitions state to kAssigned if successful |
| */ |
| CHIP_ERROR MarkAsAssigned() override |
| { |
| if (mUpdateState.load() != UpdateState::kInitialized) |
| { |
| return CHIP_ERROR_INCORRECT_STATE; |
| } |
| mUpdateState.store(UpdateState::kAssigned); |
| mHoldState[1 - mActiveValueIdx.load()] = StorageState::kHold; |
| return CHIP_NO_ERROR; |
| } |
| |
| /** |
| * @brief Validates and prepares the new value for commit |
| * @param[in] aUpdCtx Context data for validation (may be nullptr) |
| * @return CHIP_NO_ERROR if validation succeeds |
| * @retval CHIP_ERROR_INCORRECT_STATE if not in kAssigned state |
| * @retval Other validation errors if validation fails |
| * |
| * @post On success, transitions state to kValidated |
| * @note If aUpdCtx is nullptr, skips context validation |
| */ |
| CHIP_ERROR UpdateBegin(void * aUpdCtx) override |
| { |
| /* Skip if the attribute object has no new attached data */ |
| if (mUpdateState.load() == UpdateState::kIdle) |
| { |
| return CHIP_NO_ERROR; |
| } |
| |
| if (mUpdateState.load() != UpdateState::kAssigned) |
| { |
| return CHIP_ERROR_INCORRECT_STATE; |
| } |
| |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| if (aUpdCtx != nullptr) |
| { |
| mAuxData = aUpdCtx; |
| err = ValidateNewValue(); |
| } |
| |
| if (err == CHIP_NO_ERROR) |
| { |
| mUpdateState.store(UpdateState::kValidated); |
| } |
| else |
| { |
| ChipLogError(AppServer, "The value of attr %" PRIu32 " is not valid!", mAttrId); |
| } |
| |
| return err; |
| } |
| |
| /** |
| * @brief Completes the update process |
| * @param[in] aUpdateAllow Whether to commit the changes |
| * @return true if value changed, false otherwise |
| * |
| * @post Always transitions state to kIdle |
| * @note Performs cleanup of unused storage |
| */ |
| bool UpdateFinish(bool aUpdateAllow) override |
| { |
| bool ret = false; |
| /* Skip if the attribute object has no new attached data */ |
| if (mUpdateState.load() == UpdateState::kIdle) |
| { |
| return false; |
| } |
| |
| if (aUpdateAllow && (mUpdateState.load() == UpdateState::kValidated)) |
| { |
| SwapActiveValueStorage(); |
| |
| ret = HasChanged(); |
| } |
| |
| CleanupByIdx(1 - mActiveValueIdx.load()); |
| |
| mUpdateState.store(UpdateState::kIdle); |
| |
| return ret; |
| } |
| |
| /** |
| * @brief Full cleanup of all value storage |
| * @return true if active value was changed, false otherwise |
| */ |
| bool Cleanup() override |
| { |
| bool ret = false; |
| if (HasValue()) |
| { |
| ret = true; |
| } |
| |
| CleanupByIdx(1 - mActiveValueIdx.load()); |
| CleanupByIdx(mActiveValueIdx.load()); |
| |
| return ret; |
| } |
| |
| /** |
| * @brief Cleans up an external list entry |
| * @param[in,out] entry The list entry to clean up |
| */ |
| void CleanupExtListEntry(ListEntryType & entry) { CleanupStruct(entry); } |
| |
| /** |
| * @brief Gets the attribute ID |
| * @return The attribute ID this instance manages |
| */ |
| AttributeId GetAttrId() const override { return mAttrId; } |
| |
| // Type-erased implementations |
| CHIP_ERROR GetValueAsVoid(void *& outValue) override |
| { |
| outValue = static_cast<void *>(&GetValueRef()); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR GetNewValueAsVoid(void *& outValue) override |
| { |
| outValue = static_cast<void *>(&GetNewValueRef()); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR SetNewValueFromVoid(const void * value) override |
| { |
| if (value == nullptr) |
| { |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| const ValueType * typedValue = static_cast<const ValueType *>(value); |
| return SetNewValue(*typedValue); |
| } |
| |
| private: |
| /** |
| * @brief Gets reference to active value storage |
| * @return Reference to active storage |
| */ |
| ValueType & GetValueRef() { return mValueStorage[mActiveValueIdx.load()]; } |
| |
| /** |
| * @brief Gets reference to new value storage |
| * @return Reference to new storage |
| */ |
| ValueType & GetNewValueRef() { return mValueStorage[1 - mActiveValueIdx.load()]; } |
| |
| /** |
| * @brief Swaps active and new value storage indices |
| */ |
| void SwapActiveValueStorage() { mActiveValueIdx.store(1 - mActiveValueIdx.load()); } |
| |
| /** |
| * @brief Checks if value type is nullable |
| * @return true if nullable, false otherwise |
| */ |
| template <typename U> |
| static constexpr bool TypeIsNullable() |
| { |
| return IsNullable<U>::value; |
| } |
| |
| /** |
| * @brief Checks if value type is a list |
| * @return true if list type, false otherwise |
| */ |
| template <typename U> |
| static constexpr bool TypeIsList() |
| { |
| return IsList<U>::value; |
| } |
| |
| /** |
| * @brief Checks if value type is a struct |
| * @return true if struct type, false otherwise |
| */ |
| template <typename U> |
| static constexpr bool TypeIsStruct() |
| { |
| return IsStruct<U>::value; |
| } |
| |
| /** |
| * @brief Checks if value type is scalar (numeric or enum) |
| * @return true if scalar type, false otherwise |
| */ |
| template <typename U> |
| static constexpr bool TypeIsScalar() |
| { |
| return (IsNumeric<U>::value || IsEnum<U>::value); |
| } |
| |
| // Internal implementation methods... |
| |
| CHIP_ERROR CopyData(const StructType & input, StructType & output); |
| |
| /** |
| * @brief Validates the new value using type-specific validation |
| * @return CHIP_ERROR Validation result |
| */ |
| CHIP_ERROR ValidateNewValue(); |
| |
| /** |
| * @brief Compares two lists for equality |
| * @param source First list to compare |
| * @param destination Second list to compare |
| * @return true if lists differ, false if identical |
| */ |
| bool ListsNotEqual(const DataModel::List<ListEntryType> & source, const DataModel::List<ListEntryType> & destination) |
| { |
| if (source.size() != destination.size()) |
| { |
| return true; |
| } |
| |
| assertChipStackLockedByCurrentThread(); |
| |
| for (size_t i = 0; i < source.size(); i++) |
| { |
| if (source[i] != destination[i]) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * @brief Compares two nullable values for equality |
| * @param a First value to compare |
| * @param b Second value to compare |
| * @return true if values differ, false if identical |
| */ |
| bool NullableNotEqual(const ValueType & a, const ValueType & b) |
| { |
| bool is_neq = false; |
| if (a.IsNull() || b.IsNull()) |
| { |
| is_neq = a.IsNull() != b.IsNull(); |
| } |
| else |
| { |
| if constexpr (IsList<DataType>::value) |
| { |
| is_neq = ListsNotEqual(a.Value(), b.Value()); |
| } |
| else |
| { |
| is_neq = (a.Value() != b.Value()); |
| } |
| } |
| |
| return is_neq; |
| } |
| |
| /** |
| * @brief Checks if new value differs from current value |
| * @return true if values differ, false if identical |
| */ |
| bool HasChanged() |
| { |
| if constexpr (TypeIsNullable<ValueType>()) |
| { |
| return NullableNotEqual(GetNewValueRef(), GetValueRef()); |
| } |
| else if constexpr (TypeIsList<ValueType>()) |
| { |
| return ListsNotEqual(GetNewValueRef(), GetValueRef()); |
| } |
| else |
| { |
| return GetNewValueRef() != GetValueRef(); |
| } |
| } |
| |
| /** |
| * @brief Cleans up storage at specified index |
| * @param aIdx Storage index to clean (0 or 1) |
| */ |
| void CleanupByIdx(uint8_t aIdx) |
| { |
| if (mActiveValueIdx == aIdx) |
| { |
| CleanupValueByRef(mValueStorage[mActiveValueIdx.load()]); |
| mHoldState[mActiveValueIdx.load()] = StorageState::kEmpty; |
| } |
| else |
| { |
| CleanupValueByRef(mValueStorage[1 - mActiveValueIdx.load()]); |
| mHoldState[1 - mActiveValueIdx.load()] = StorageState::kEmpty; |
| } |
| } |
| |
| /** |
| * @brief Cleans up a value reference |
| * @param aValue Reference to value to clean up |
| */ |
| void CleanupValueByRef(ValueType & aValue) |
| { |
| if constexpr (TypeIsNullable<ValueType>()) |
| { |
| if (!aValue.IsNull()) |
| { |
| if constexpr (TypeIsList<DataType>()) |
| { |
| CleanupList(aValue.Value()); |
| } |
| else if constexpr (TypeIsStruct<DataType>()) |
| { |
| CleanupStruct(aValue.Value()); |
| } |
| } |
| aValue.SetNull(); |
| } |
| else if constexpr (TypeIsList<ValueType>()) |
| { |
| CleanupList(aValue); |
| } |
| } |
| |
| /** |
| * @brief Cleans up a list and its elements |
| * @param list List to clean up |
| */ |
| void CleanupList(DataModel::List<ListEntryType> & list) |
| { |
| assertChipStackLockedByCurrentThread(); |
| |
| if constexpr (TypeIsStruct<ListEntryType>()) |
| { |
| |
| for (auto & item : list) |
| { |
| CleanupStruct(item); |
| } |
| } |
| |
| if (list.data()) |
| { |
| Platform::MemoryFree(list.data()); |
| list = DataModel::List<ListEntryType>(); |
| } |
| } |
| |
| /** |
| * @brief Cleans up a struct value |
| * @param aValue Struct to clean up |
| */ |
| void CleanupStruct(StructType & aValue); |
| |
| //{ |
| // CleanupStructValue<StructType>(aValue); |
| //} |
| |
| void * mAuxData = nullptr; ///< Validation context data |
| const AttributeId mAttrId; ///< Managed attribute ID |
| }; |
| |
| } // namespace CommodityTariffAttrsDataMgmt |
| |
| namespace Clusters { |
| namespace CommodityTariff { |
| /** |
| * @struct TariffUpdateCtx |
| * @brief Context for validating tariff attribute updates and maintaining referential integrity |
| * |
| * This structure tracks relationships between tariff components during attribute updates |
| * to ensure all references are valid and consistent. It serves as a validation context |
| * that collects all IDs and references before checking their consistency. |
| * |
| * @section references Referential Integrity Tracking |
| * The context maintains several sets of IDs to validate that: |
| * - All referenced DayEntry IDs exist in the master set |
| * - All referenced TariffComponent IDs exist in the master set |
| * - All referenced DayPattern IDs exist in the master set |
| * - No dangling references exist between tariff components |
| * |
| * @section lifecycle Lifecycle |
| * - Created at the start of a tariff update operation |
| * - Populated during attribute parsing/processing |
| * - Used for validation before committing changes |
| * - Destroyed after update completion |
| */ |
| struct TariffUpdateCtx |
| { |
| BlockModeEnum blockMode; |
| |
| /** |
| * @brief Reference to the tariff's start timestamp |
| * @note This is a reference to allow validation against the actual attribute value |
| */ |
| DataModel::Nullable<uint32_t> & TariffStartTimestamp; |
| |
| /// @name DayEntry ID Tracking |
| /// @{ |
| /** |
| * @brief Master set of all valid DayEntry IDs |
| * @details Contains all DayEntry IDs that exist in the tariff definition |
| */ |
| std::unordered_set<uint32_t> DayEntryKeyIDs; |
| |
| /** |
| * @brief DayEntry IDs referenced by DayPattern items |
| * @details Collected separately for reference validation |
| */ |
| std::unordered_set<uint32_t> DayPatternsDayEntryIDs; |
| |
| /** |
| * @brief DayEntry IDs referenced by IndividualDays items |
| * @details Collected separately for reference validation |
| */ |
| std::unordered_set<uint32_t> IndividualDaysDayEntryIDs; |
| |
| /** |
| * @brief DayEntry IDs referenced by TariffPeriod items |
| * @details Collected separately for reference validation |
| */ |
| std::unordered_set<uint32_t> TariffPeriodsDayEntryIDs; |
| |
| /// @} |
| |
| /// @name TariffComponent ID Tracking |
| /// @{ |
| /** |
| * @brief Master set of all valid TariffComponent IDs |
| * @details Contains all TariffComponent IDs that exist in the tariff definition |
| */ |
| std::unordered_map<uint32_t, uint32_t> TariffComponentKeyIDsFeatureMap; |
| |
| /** |
| * @brief TariffComponent IDs referenced by TariffPeriod items |
| * @details Collected for validating period->component references |
| */ |
| std::unordered_set<uint32_t> TariffPeriodsTariffComponentIDs; |
| /// @} |
| |
| /// @name DayPattern ID Tracking |
| /// @{ |
| /** |
| * @brief Master set of all valid DayPattern IDs |
| * @details Contains all DayPattern IDs that exist in the tariff definition |
| */ |
| std::unordered_set<uint32_t> DayPatternKeyIDs; |
| |
| /** |
| * @brief DayPattern IDs referenced by CalendarPeriod items |
| * @details Collected for validating calendar->pattern references |
| */ |
| std::unordered_set<uint32_t> CalendarPeriodsDayPatternIDs; |
| /// @} |
| |
| /** |
| * @brief Bitmask of active tariff features |
| * @details Used to validate feature-dependent constraints |
| */ |
| BitMask<Feature> mFeature; |
| |
| /** |
| * @brief Timestamp when the tariff update was initiated |
| * @note Used for change tracking and versioning |
| */ |
| uint32_t TariffUpdateTimestamp; |
| }; |
| |
| /** |
| * @brief Primary attributes for Commodity Tariff |
| * |
| * Primary attributes represent the fundamental tariff configuration that can only |
| * be changed by authorized tariff updates. These are typically set by utility providers. |
| */ |
| |
| // Generated classes for each attribute: |
| |
| class TariffUnitDataClass : public CommodityTariffAttrsDataMgmt::CTC_BaseDataClass<DataModel::Nullable<Globals::TariffUnitEnum>> |
| { |
| public: |
| TariffUnitDataClass() : CTC_BaseDataClass<DataModel::Nullable<Globals::TariffUnitEnum>>(Attributes::TariffUnit::Id) {} |
| ~TariffUnitDataClass() override = default; |
| }; |
| |
| class StartDateDataClass : public CommodityTariffAttrsDataMgmt::CTC_BaseDataClass<DataModel::Nullable<uint32_t>> |
| { |
| public: |
| StartDateDataClass() : CTC_BaseDataClass<DataModel::Nullable<uint32_t>>(Attributes::StartDate::Id) {} |
| ~StartDateDataClass() override = default; |
| }; |
| |
| class DefaultRandomizationOffsetDataClass : public CommodityTariffAttrsDataMgmt::CTC_BaseDataClass<DataModel::Nullable<int16_t>> |
| { |
| public: |
| DefaultRandomizationOffsetDataClass() : |
| CTC_BaseDataClass<DataModel::Nullable<int16_t>>(Attributes::DefaultRandomizationOffset::Id) |
| {} |
| ~DefaultRandomizationOffsetDataClass() override = default; |
| }; |
| |
| class DefaultRandomizationTypeDataClass |
| : public CommodityTariffAttrsDataMgmt::CTC_BaseDataClass<DataModel::Nullable<DayEntryRandomizationTypeEnum>> |
| { |
| public: |
| DefaultRandomizationTypeDataClass() : |
| CTC_BaseDataClass<DataModel::Nullable<DayEntryRandomizationTypeEnum>>(Attributes::DefaultRandomizationType::Id) |
| {} |
| ~DefaultRandomizationTypeDataClass() override = default; |
| }; |
| |
| class TariffInfoDataClass |
| : public CommodityTariffAttrsDataMgmt::CTC_BaseDataClass<DataModel::Nullable<Structs::TariffInformationStruct::Type>> |
| { |
| public: |
| TariffInfoDataClass() : |
| CTC_BaseDataClass<DataModel::Nullable<Structs::TariffInformationStruct::Type>>(Attributes::TariffInfo::Id) |
| {} |
| ~TariffInfoDataClass() override = default; |
| }; |
| |
| class DayEntriesDataClass |
| : public CommodityTariffAttrsDataMgmt::CTC_BaseDataClass<DataModel::Nullable<DataModel::List<Structs::DayEntryStruct::Type>>> |
| { |
| public: |
| DayEntriesDataClass() : |
| CTC_BaseDataClass<DataModel::Nullable<DataModel::List<Structs::DayEntryStruct::Type>>>(Attributes::DayEntries::Id) |
| {} |
| ~DayEntriesDataClass() override = default; |
| }; |
| |
| class DayPatternsDataClass |
| : public CommodityTariffAttrsDataMgmt::CTC_BaseDataClass<DataModel::Nullable<DataModel::List<Structs::DayPatternStruct::Type>>> |
| { |
| public: |
| DayPatternsDataClass() : |
| CTC_BaseDataClass<DataModel::Nullable<DataModel::List<Structs::DayPatternStruct::Type>>>(Attributes::DayPatterns::Id) |
| {} |
| ~DayPatternsDataClass() override = default; |
| }; |
| |
| class TariffComponentsDataClass : public CommodityTariffAttrsDataMgmt::CTC_BaseDataClass< |
| DataModel::Nullable<DataModel::List<Structs::TariffComponentStruct::Type>>> |
| { |
| public: |
| TariffComponentsDataClass() : |
| CTC_BaseDataClass<DataModel::Nullable<DataModel::List<Structs::TariffComponentStruct::Type>>>( |
| Attributes::TariffComponents::Id) |
| {} |
| ~TariffComponentsDataClass() override = default; |
| }; |
| |
| class TariffPeriodsDataClass : public CommodityTariffAttrsDataMgmt::CTC_BaseDataClass< |
| DataModel::Nullable<DataModel::List<Structs::TariffPeriodStruct::Type>>> |
| { |
| public: |
| TariffPeriodsDataClass() : |
| CTC_BaseDataClass<DataModel::Nullable<DataModel::List<Structs::TariffPeriodStruct::Type>>>(Attributes::TariffPeriods::Id) |
| {} |
| ~TariffPeriodsDataClass() override = default; |
| }; |
| |
| class IndividualDaysDataClass |
| : public CommodityTariffAttrsDataMgmt::CTC_BaseDataClass<DataModel::Nullable<DataModel::List<Structs::DayStruct::Type>>> |
| { |
| public: |
| IndividualDaysDataClass() : |
| CTC_BaseDataClass<DataModel::Nullable<DataModel::List<Structs::DayStruct::Type>>>(Attributes::IndividualDays::Id) |
| {} |
| ~IndividualDaysDataClass() override = default; |
| }; |
| |
| class CalendarPeriodsDataClass : public CommodityTariffAttrsDataMgmt::CTC_BaseDataClass< |
| DataModel::Nullable<DataModel::List<Structs::CalendarPeriodStruct::Type>>> |
| { |
| public: |
| CalendarPeriodsDataClass() : |
| CTC_BaseDataClass<DataModel::Nullable<DataModel::List<Structs::CalendarPeriodStruct::Type>>>( |
| Attributes::CalendarPeriods::Id) |
| {} |
| ~CalendarPeriodsDataClass() override = default; |
| }; |
| |
| } // namespace CommodityTariff |
| } // namespace Clusters |
| } // namespace app |
| } // namespace chip |