| /* | |
| * 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. | |
| */ | |
| #include "CodegenIntegration.h" | |
| #include <app-common/zap-generated/attributes/Accessors.h> | |
| #include <app/clusters/mode-select-server/ModeSelectCluster.h> | |
| #include <app/clusters/mode-select-server/SupportedModesManager.h> | |
| #include <app/static-cluster-config/ModeSelect.h> | |
| #include <app/util/attribute-storage.h> | |
| #ifdef MATTER_DM_PLUGIN_SCENES_MANAGEMENT | |
| #include <app/clusters/scenes-server/CodegenIntegration.h> | |
| #endif | |
| #include <app/util/endpoint-config-api.h> | |
| #include <app/util/util.h> | |
| #include <clusters/ModeSelect/Attributes.h> | |
| #include <data-model-providers/codegen/ClusterIntegration.h> | |
| #include <platform/DiagnosticDataProvider.h> | |
| #ifdef MATTER_DM_PLUGIN_ON_OFF_SERVER | |
| #include <app/clusters/on-off-server/on-off-server.h> | |
| #endif | |
| using namespace chip; | |
| using namespace chip::app; | |
| using namespace chip::app::Clusters; | |
| using namespace chip::app::Clusters::ModeSelect; | |
| using namespace chip::app::Clusters::ModeSelect::Attributes; | |
| using Status = Protocols::InteractionModel::Status; | |
| namespace { | |
| // Adapts SupportedModesManager to ModeSelectCluster::Delegate for a single endpoint. | |
| class SupportedModesManagerDelegate : public ModeSelectCluster::Delegate | |
| { | |
| public: | |
| void SetEndpointId(EndpointId endpointId) { mEndpointId = endpointId; } | |
| Span<const ModeSelect::Structs::ModeOptionStruct::Type> GetSupportedModes() const override | |
| { | |
| const SupportedModesManager * mgr = getSupportedModesManager(); | |
| if (mgr == nullptr) | |
| { | |
| return Span<const ModeSelect::Structs::ModeOptionStruct::Type>(); | |
| } | |
| auto provider = mgr->getModeOptionsProvider(mEndpointId); | |
| if (provider.begin() == nullptr) | |
| { | |
| return Span<const ModeSelect::Structs::ModeOptionStruct::Type>(); | |
| } | |
| return Span<const ModeSelect::Structs::ModeOptionStruct::Type>(provider.begin(), | |
| static_cast<size_t>(provider.end() - provider.begin())); | |
| } | |
| private: | |
| EndpointId mEndpointId = kInvalidEndpointId; | |
| }; | |
| // Re-exposes ApplyStartupModeLogic() as public so the OnSupportedModesManagerSet | |
| // callback can call it after the manager becomes available post-Startup. | |
| class CodegenModeSelectCluster : public ModeSelectCluster | |
| { | |
| public: | |
| using ModeSelectCluster::ApplyStartupModeLogic; | |
| using ModeSelectCluster::ModeSelectCluster; | |
| }; | |
| struct ModeSelectEntry | |
| { | |
| SupportedModesManagerDelegate delegate; | |
| LazyRegisteredServerCluster<CodegenModeSelectCluster> server; | |
| }; | |
| constexpr size_t kFixedClusterCount = ModeSelect::StaticApplicationConfig::kFixedClusterConfig.size(); | |
| constexpr size_t kMaxClusterCount = kFixedClusterCount + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; | |
| ModeSelectEntry gEntries[kMaxClusterCount]; | |
| class IntegrationDelegate : public CodegenClusterIntegration::Delegate | |
| { | |
| public: | |
| ServerClusterRegistration & CreateRegistration(EndpointId endpointId, unsigned clusterInstanceIndex, | |
| uint32_t optionalAttributeBits, uint32_t featureMap) override | |
| { | |
| VerifyOrDie(clusterInstanceIndex < kMaxClusterCount); | |
| ModeSelectEntry & entry = gEntries[clusterInstanceIndex]; | |
| entry.delegate.SetEndpointId(endpointId); | |
| ModeSelectCluster::OptionalAttributeSet optionalAttributeSet(optionalAttributeBits); | |
| // Determine if OnMode overrides CurrentMode at startup (OnOff feature startup behavior) | |
| bool onOffValueForStartUp = false; | |
| #ifdef MATTER_DM_PLUGIN_ON_OFF_SERVER | |
| if (BitMask<Feature>(featureMap).Has(Feature::kOnOff) && emberAfContainsServer(endpointId, OnOff::Id) && | |
| emberAfContainsAttribute(endpointId, OnOff::Id, OnOff::Attributes::StartUpOnOff::Id) && | |
| !emberAfIsKnownVolatileAttribute(endpointId, OnOff::Id, OnOff::Attributes::StartUpOnOff::Id)) | |
| { | |
| bool startUpValue = false; | |
| if (OnOffServer::Instance().getOnOffValueForStartUp(endpointId, startUpValue) == Status::Success) | |
| { | |
| onOffValueForStartUp = startUpValue; | |
| } | |
| } | |
| #endif | |
| // Read Description from attribute metadata (points directly to flash). | |
| chip::CharSpan descSpan; | |
| Attributes::Description::GetDefaultOr(endpointId, descSpan, chip::CharSpan()); | |
| // Read StandardNamespace default from attribute metadata (flash; ZAP default = null). | |
| DataModel::Nullable<uint16_t> standardNamespace; | |
| Attributes::StandardNamespace::GetDefaultOr(endpointId, standardNamespace, DataModel::NullNullable); | |
| ModeSelectCluster::Config config{ | |
| .featureMap = BitMask<Feature>(featureMap), | |
| .optionalAttributeSet = optionalAttributeSet, | |
| .description = descSpan, | |
| .standardNamespace = standardNamespace, | |
| .onOffValueForStartUp = onOffValueForStartUp, | |
| .diagnosticDataProvider = DeviceLayer::GetDiagnosticDataProvider(), | |
| }; | |
| entry.server.Create(endpointId, entry.delegate, config); | |
| #ifdef MATTER_DM_PLUGIN_SCENES_MANAGEMENT | |
| ScenesManagement::ScenesServer::Instance().RegisterSceneHandler(endpointId, &entry.server.Cluster()); | |
| #endif | |
| return entry.server.Registration(); | |
| } | |
| ServerClusterInterface * FindRegistration(unsigned clusterInstanceIndex) override | |
| { | |
| VerifyOrReturnValue(clusterInstanceIndex < kMaxClusterCount, nullptr); | |
| VerifyOrReturnValue(gEntries[clusterInstanceIndex].server.IsConstructed(), nullptr); | |
| return &gEntries[clusterInstanceIndex].server.Cluster(); | |
| } | |
| void ReleaseRegistration(unsigned clusterInstanceIndex) override | |
| { | |
| VerifyOrReturn(clusterInstanceIndex < kMaxClusterCount); | |
| if (gEntries[clusterInstanceIndex].server.IsConstructed()) | |
| { | |
| #ifdef MATTER_DM_PLUGIN_SCENES_MANAGEMENT | |
| ModeSelectCluster & cluster = gEntries[clusterInstanceIndex].server.Cluster(); | |
| EndpointId endpointId = cluster.GetPaths()[0].mEndpointId; | |
| ScenesManagement::ScenesServer::Instance().UnregisterSceneHandler(endpointId, &cluster); | |
| #endif | |
| gEntries[clusterInstanceIndex].server.Destroy(); | |
| } | |
| } | |
| }; | |
| } // namespace | |
| // ---- Code-driven init/shutdown callbacks (generated by ZAP CodeDrivenCallback.h) ---- | |
| void MatterModeSelectClusterInitCallback(EndpointId endpointId) | |
| { | |
| IntegrationDelegate integrationDelegate; | |
| CodegenClusterIntegration::RegisterServer( | |
| { | |
| .endpointId = endpointId, | |
| .clusterId = ModeSelect::Id, | |
| .fixedClusterInstanceCount = kFixedClusterCount, | |
| .maxClusterInstanceCount = kMaxClusterCount, | |
| .fetchFeatureMap = true, | |
| .fetchOptionalAttributes = true, | |
| }, | |
| integrationDelegate); | |
| } | |
| void MatterModeSelectClusterShutdownCallback(EndpointId endpointId, MatterClusterShutdownType shutdownType) | |
| { | |
| IntegrationDelegate integrationDelegate; | |
| CodegenClusterIntegration::UnregisterServer( | |
| { | |
| .endpointId = endpointId, | |
| .clusterId = ModeSelect::Id, | |
| .fixedClusterInstanceCount = kFixedClusterCount, | |
| .maxClusterInstanceCount = kMaxClusterCount, | |
| }, | |
| integrationDelegate, shutdownType); | |
| } | |
| // ---- Legacy ember callbacks (stubs) ---- | |
| static void OnSupportedModesManagerSet(SupportedModesManager *) | |
| { | |
| // Apply deferred startup logic for any clusters that started before the manager was set. | |
| for (auto & entry : gEntries) | |
| { | |
| if (entry.server.IsConstructed()) | |
| { | |
| entry.server.Cluster().ApplyStartupModeLogic(); | |
| } | |
| } | |
| } | |
| void MatterModeSelectPluginServerInitCallback() | |
| { | |
| ModeSelect::internal::RegisterOnManagerSetCallback(OnSupportedModesManagerSet); | |
| } | |
| void MatterModeSelectPluginServerShutdownCallback() | |
| { | |
| ModeSelect::internal::RegisterOnManagerSetCallback(nullptr); | |
| } | |
| void emberAfModeSelectClusterServerInitCallback(EndpointId endpointId) {} | |
| void MatterModeSelectClusterServerAttributeChangedCallback(const ConcreteAttributePath &) {} | |
| // ---- Backward-compat global API for app code ---- | |
| namespace chip::app::Clusters::ModeSelect { | |
| ModeSelectCluster * FindClusterOnEndpoint(EndpointId endpointId) | |
| { | |
| IntegrationDelegate integrationDelegate; | |
| ServerClusterInterface * cluster = CodegenClusterIntegration::FindClusterOnEndpoint( | |
| { | |
| .endpointId = endpointId, | |
| .clusterId = ModeSelect::Id, | |
| .fixedClusterInstanceCount = kFixedClusterCount, | |
| .maxClusterInstanceCount = kMaxClusterCount, | |
| }, | |
| integrationDelegate); | |
| return static_cast<ModeSelectCluster *>(cluster); | |
| } | |
| } // namespace chip::app::Clusters::ModeSelect |