blob: 21f64821e5769bea5f348ba133bddb7227bcab5b [file] [log] [blame] [edit]
/*
* 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 <app/CommandHandlerInterface.h>
#include <app/ConcreteCommandPath.h>
#include <app/data-model-provider/ActionReturnStatus.h>
#include <app/data-model-provider/MetadataTypes.h>
#include <app/data-model-provider/Provider.h>
#include <app/persistence/AttributePersistenceProvider.h>
#include <app/server-cluster/ServerClusterInterface.h>
#include <app/server-cluster/ServerClusterInterfaceRegistry.h>
#include <data-model-providers/codedriven/endpoint/EndpointInterface.h>
#include <data-model-providers/codedriven/endpoint/EndpointInterfaceRegistry.h>
#include <lib/support/ReadOnlyBuffer.h>
namespace chip {
namespace app {
/**
* @brief An implementation of DataModel::Provider that constructs the data model
* programmatically by aggregating EndpointInterface instances.
*
* This provider allows applications to define their Matter device data model (endpoints,
* clusters, attributes, commands) dynamically at runtime. It manages a list of EndpointInterface
* objects, each representing an endpoint on the device.
*
* The expected usage pattern by the application is as follows:
* 1. Instantiate ServerClusterInterface(s) and ServerClusterRegistration(s).
* 2. Instantiate EndpointInterface(s) and EndpointInterfaceRegistration(s).
* 2. Instantiate the CodeDrivenDataModelProvider.
* 3. Register ServerClusterInterfaceRegistration(s) to the CodeDrivenDataModelProvider using AddCluster().
* 4. Register EndpointInterfaceRegistration(s) to the CodeDrivenDataModelProvider using AddEndpoint().
* Note: Step 4 MUST come after Step 3 (Endpoint needs to know about its clusters).
* 5. Call Startup() on the CodeDrivenDataModelProvider.
*
* Note: if the CodeDrivenDataModelProvider has already been started (runtime change to add/remove Endpoints/Clusters),
* the Startup() method on each ServerClusterInterface will be called when the EndpointInterface is added (Step 4).
* If the provider hasn't been started, the Startup() method will be called when the provider is started (Step 5).
*
* TODO: Notify composition changes when the provider is started up and endpoints are added/removed at runtime.
* For now, applications are responsible for handling composition changes and calling markDirty() when needed.
*
* Lifecycle:
* - The CodeDrivenDataModelProvider stores raw pointers to EndpointInterface and ServerClusterInterface.
* It does NOT take ownership. Callers must ensure these instances outlive the provider.
*/
class CodeDrivenDataModelProvider : public DataModel::Provider
{
public:
CodeDrivenDataModelProvider(PersistentStorageDelegate & storage, AttributePersistenceProvider & attributeStorage) :
mPersistentStorageDelegate(storage), mAttributePersistenceProvider(attributeStorage)
{}
/* DataModel::Provider implementation */
CHIP_ERROR Startup(DataModel::InteractionModelContext context) override;
CHIP_ERROR Shutdown() override;
DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request,
AttributeValueEncoder & encoder) override;
DataModel::ActionReturnStatus WriteAttribute(const DataModel::WriteAttributeRequest & request,
AttributeValueDecoder & decoder) override;
void ListAttributeWriteNotification(const ConcreteAttributePath & path, DataModel::ListWriteOperation opType) override;
std::optional<DataModel::ActionReturnStatus> InvokeCommand(const DataModel::InvokeRequest & request,
TLV::TLVReader & input_arguments, CommandHandler * handler) override;
/* ProviderMetadataTree implementation */
CHIP_ERROR Endpoints(ReadOnlyBufferBuilder<DataModel::EndpointEntry> & out) override;
CHIP_ERROR SemanticTags(EndpointId endpointId,
ReadOnlyBufferBuilder<Clusters::Descriptor::Structs::SemanticTagStruct::Type> & out) override;
CHIP_ERROR DeviceTypes(EndpointId endpointId, ReadOnlyBufferBuilder<DataModel::DeviceTypeEntry> & out) override;
CHIP_ERROR ClientClusters(EndpointId endpointId, ReadOnlyBufferBuilder<ClusterId> & out) override;
CHIP_ERROR ServerClusters(EndpointId endpointId, ReadOnlyBufferBuilder<DataModel::ServerClusterEntry> & out) override;
CHIP_ERROR GeneratedCommands(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<CommandId> & out) override;
CHIP_ERROR AcceptedCommands(const ConcreteClusterPath & path,
ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & out) override;
CHIP_ERROR Attributes(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder) override;
CHIP_ERROR EventInfo(const ConcreteEventPath & path, DataModel::EventEntry & eventInfo) override;
void Temporary_ReportAttributeChanged(const AttributePathParams & path) override;
/**
* @brief Adds an endpoint to the data model provider.
*
* This method registers an endpoint, making it part of the device's data model.
* If the provider has already been started, this may trigger a Startup() call on
* each ServerClusterInterface associated with the endpoint.
* The Startup() call on the associated clusters will ONLY happen if this is the first
* endpoint associated with the cluster (i.e. ServerClusterInterface.GetPaths() returns
* at least one path with endpoint ID == registration.endpointEntry.id, and none
* of the other endpoints in GetPaths() are registered yet). This ensures
* the cluster is only started once, even if it is associated with multiple endpoints.
*
* Prerequisites:
* - It MUST be called after all clusters for the endpoint have been registered with
* AddCluster().
* - The provided `registration` (EndpointInterfaceRegistration) must not already be
* part of another list (i.e., `registration.next` must be nullptr).
* - The EndpointInterface within the `registration` must be valid (i.e.,
* `registration.endpointInterface` must not be nullptr).
* - The `registration.endpointEntry.id` must be valid (not `kInvalidEndpointId` and
* not used by another endpoint).
* - The LIFETIME of `registration` must outlive the provider (or the registration must
* be removed using `RemoveEndpoint` before it goes away).
*
* @param registration The registration object for the endpoint, containing a valid
* EndpointInterface and Endpoint ID.
* @return CHIP_NO_ERROR on success.
* CHIP_ERROR_INVALID_ARGUMENT if `registration.next` is not nullptr or
* `registration.endpointInterface` is nullptr or
* `registration.endpointEntry.id` is kInvalidEndpointId.
* CHIP_ERROR_DUPLICATE_KEY_ID if `registration.endpointEntry.id` is already in use.
*/
CHIP_ERROR AddEndpoint(EndpointInterfaceRegistration & registration);
/**
* @brief Removes an endpoint from the data model provider.
*
* This method unregisters an endpoint, removing it from the device's data model.
* If the provider has already been started, this might trigger a Shutdown() call on
* each ServerClusterInterface associated with the endpoint.
* The Shutdown() call on the associated clusters will ONLY happen if this is the last
* endpoint associated with the cluster (i.e. ServerClusterInterface.GetPaths() returns
* no paths with valid Endpoint IDs).
*
* Note: Removing an Endpoint does not remove any clusters associated with the endpoint.
* Those can be removed using RemoveCluster() AFTER the endpoint has been removed.
* Prerequisites:
* - It MUST be called BEFORE removing associted clusters with RemoveCluster() to guarantee
* the endpoint removal is atomic.
* - The endpoint ID must be valid.
*
* @param endpointId The ID of the endpoint to remove.
* @return CHIP_NO_ERROR on success.
* CHIP_ERROR_NOT_FOUND if no endpoint with the given ID is registered.
* CHIP_ERROR_INVALID_ARGUMENT if endpointId is kInvalidEndpointId.
*/
CHIP_ERROR RemoveEndpoint(EndpointId endpointId);
/**
* @brief Add a ServerClusterInterface to the Data Model Provider.
*
* Requirements:
* - entry MUST NOT be part of any other registration
* - paths MUST NOT be part of any other ServerClusterInterface (i.e. only a single
* registration for a given `endpointId/clusterId` path).
* - The LIFETIME of entry must outlive the provider (or the entry must be unregistered
* via RemoveCluster before it goes away).
* - If the provider has already been started, this method must be called prior to
* calling AddEndpoint() (i.e. the `endpointId` of all paths in `entry.GetPaths()`
* must NOT be registered in the provider yet), otherwise this would cause non-atomic
* changes to an endpoint, which is not allowed.
*
* @param entry The ServerClusterRegistration containing the cluster to register.
* @return CHIP_NO_ERROR on success.
* CHIP_ERROR_INVALID_ARGUMENT if `entry.next` is not `nullptr` or
* `entry.serverClusterInterface` is `nullptr` or
* `entry.serverClusterInterface.GetPaths()` is empty/invalid.
* CHIP_ERROR_DUPLICATE_KEY_ID if the cluster is already registered.
* CHIP_ERROR_INCORRECT_STATE if the provider has been started and an endpoint for
* one of the cluster paths has already been registered.
*/
CHIP_ERROR AddCluster(ServerClusterRegistration & entry);
/**
* @brief Remove a ServerClusterInterface from the Data Model Provider.
*
* To avoid violating the requirement of non-atomic changes to endpoints, this SHALL only be
* called after all endpoints associated with the cluster have been removed using RemoveEndpoint().
*
* Requirements:
* - entry MUST be valid
* - The `endpointId` of all paths in `entry.GetPaths()` are no longer registered in the provider.
*
* @param entry The ServerClusterInterface to remove.
* @return CHIP_NO_ERROR on success.
* CHIP_ERROR_INVALID_ARGUMENT if `entry` is nullptr.
* CHIP_ERROR_NOT_FOUND if the entry is not registered.
* CHIP_ERROR_INCORRECT_STATE if an endpoint for one of the cluster paths is still registered.
*/
CHIP_ERROR RemoveCluster(ServerClusterInterface * entry);
private:
EndpointInterfaceRegistry mEndpointInterfaceRegistry;
ServerClusterInterfaceRegistry mServerClusterRegistry;
std::optional<ServerClusterContext> mServerClusterContext;
std::optional<DataModel::InteractionModelContext> mInteractionModelContext;
PersistentStorageDelegate & mPersistentStorageDelegate;
AttributePersistenceProvider & mAttributePersistenceProvider;
/// Return the interface registered for the given endpoint ID or nullptr if one does not exist
EndpointInterface * GetEndpointInterface(EndpointId endpointId);
/// Return the interface registered for the given cluster path or nullptr if one does not exist
ServerClusterInterface * GetServerClusterInterface(const ConcreteClusterPath & path);
};
} // namespace app
} // namespace chip