| /* |
| * |
| * Copyright (c) 2021 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 "SubDeviceManager.h" |
| #include "SubDevice.h" |
| #include <app-common/zap-generated/cluster-objects.h> |
| #include <app-common/zap-generated/ids/Attributes.h> |
| #include <app-common/zap-generated/ids/Clusters.h> |
| #include <app/ConcreteAttributePath.h> |
| #include <app/InteractionModelEngine.h> |
| #include <app/clusters/identify-server/identify-server.h> |
| #include <app/reporting/reporting.h> |
| #include <app/server/OnboardingCodesUtil.h> |
| #include <app/util/attribute-storage.h> |
| #include <assert.h> |
| #include <lib/core/CHIPError.h> |
| #include <lib/core/ErrorStr.h> |
| #include <lib/support/CHIPMem.h> |
| #include <lib/support/CHIPMemString.h> |
| #include <lib/support/ZclString.h> |
| #include <platform/CHIPDeviceLayer.h> |
| |
| using namespace ::chip; |
| using namespace ::chip::Credentials; |
| using namespace ::chip::DeviceLayer; |
| using namespace ::chip::System; |
| using namespace ::chip::Platform; |
| using namespace ::chip::app::Clusters; |
| |
| static EndpointId gCurrentEndpointId; |
| static EndpointId gFirstDynamicEndpointId; |
| |
| static SubDevice * gSubDevices[CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT]; // number of dynamic endpoints count |
| |
| int AddDeviceEndpoint(SubDevice * dev, EmberAfEndpointType * ep, const Span<const EmberAfDeviceType> & deviceTypeList, |
| const Span<DataVersion> & dataVersionStorage, chip::EndpointId parentEndpointId) |
| { |
| uint8_t index = 0; |
| while (index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) |
| { |
| if (NULL == gSubDevices[index]) |
| { |
| gSubDevices[index] = dev; |
| CHIP_ERROR err; |
| while (1) |
| { |
| dev->SetEndpointId(gCurrentEndpointId); |
| err = |
| emberAfSetDynamicEndpoint(index, gCurrentEndpointId, ep, dataVersionStorage, deviceTypeList, parentEndpointId); |
| if (err == CHIP_NO_ERROR) |
| { |
| ChipLogProgress(DeviceLayer, "Added device %s to dynamic endpoint %d (index=%d)", dev->GetName(), |
| gCurrentEndpointId, index); |
| return index; |
| } |
| else if (err != CHIP_ERROR_ENDPOINT_EXISTS) |
| { |
| return -1; |
| } |
| // Handle wrap condition |
| if (++gCurrentEndpointId < gFirstDynamicEndpointId) |
| { |
| gCurrentEndpointId = gFirstDynamicEndpointId; |
| } |
| } |
| } |
| index++; |
| } |
| ChipLogProgress(DeviceLayer, "Failed to add dynamic endpoint: No endpoints available!"); |
| return -1; |
| } |
| |
| CHIP_ERROR RemoveDeviceEndpoint(SubDevice * dev) |
| { |
| for (uint8_t index = 0; index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; index++) |
| { |
| if (gSubDevices[index] == dev) |
| { |
| // Silence complaints about unused ep when progress logging |
| // disabled. |
| [[maybe_unused]] EndpointId ep = emberAfClearDynamicEndpoint(index); |
| gSubDevices[index] = NULL; |
| ChipLogProgress(DeviceLayer, "Removed device %s from dynamic endpoint %d (index=%d)", dev->GetName(), ep, index); |
| return CHIP_NO_ERROR; |
| } |
| } |
| return CHIP_ERROR_INTERNAL; |
| } |
| |
| Protocols::InteractionModel::Status HandleReadBridgedDeviceBasicAttribute(SubDevice * dev, chip::AttributeId attributeId, |
| uint8_t * buffer, uint16_t maxReadLength) |
| { |
| using namespace BridgedDeviceBasicInformation::Attributes; |
| ChipLogProgress(DeviceLayer, "HandleReadBridgedDeviceBasicAttribute: attrId=%" PRIu32 ", maxReadLength=%u", attributeId, |
| maxReadLength); |
| |
| if ((attributeId == Reachable::Id) && (maxReadLength == 1)) |
| { |
| *buffer = dev->IsReachable() ? 1 : 0; |
| } |
| else if ((attributeId == NodeLabel::Id) && (maxReadLength == 32)) |
| { |
| MutableByteSpan zclNameSpan(buffer, maxReadLength); |
| MakeZclCharString(zclNameSpan, dev->GetName()); |
| } |
| else if ((attributeId == ClusterRevision::Id) && (maxReadLength == 2)) |
| { |
| *buffer = (uint16_t) ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_REVISION; |
| } |
| else |
| { |
| return Protocols::InteractionModel::Status::Failure; |
| } |
| |
| return Protocols::InteractionModel::Status::Success; |
| } |
| |
| Protocols::InteractionModel::Status HandleReadOnOffAttribute(SubDevice * dev, chip::AttributeId attributeId, uint8_t * buffer, |
| uint16_t maxReadLength) |
| { |
| ChipLogProgress(DeviceLayer, "HandleReadOnOffAttribute: attrId=%" PRIu32 ", maxReadLength=%u", attributeId, maxReadLength); |
| |
| if ((attributeId == OnOff::Attributes::OnOff::Id) && (maxReadLength == 1)) |
| { |
| *buffer = dev->IsOn() ? 1 : 0; |
| } |
| else if ((attributeId == OnOff::Attributes::ClusterRevision::Id) && (maxReadLength == 2)) |
| { |
| *buffer = (uint16_t) ZCL_ON_OFF_CLUSTER_REVISION; |
| } |
| else |
| { |
| return Protocols::InteractionModel::Status::Failure; |
| } |
| |
| return Protocols::InteractionModel::Status::Success; |
| } |
| |
| Protocols::InteractionModel::Status HandleWriteOnOffAttribute(SubDevice * dev, chip::AttributeId attributeId, uint8_t * buffer) |
| { |
| ChipLogProgress(DeviceLayer, "HandleWriteOnOffAttribute: attrId=%" PRIu32, attributeId); |
| |
| ReturnErrorCodeIf((attributeId != OnOff::Attributes::OnOff::Id) || (!dev->IsReachable()), |
| Protocols::InteractionModel::Status::Failure); |
| dev->SetOnOff(*buffer == 1); |
| return Protocols::InteractionModel::Status::Success; |
| } |
| |
| Protocols::InteractionModel::Status emberAfExternalAttributeReadCallback(EndpointId endpoint, ClusterId clusterId, |
| const EmberAfAttributeMetadata * attributeMetadata, |
| uint8_t * buffer, uint16_t maxReadLength) |
| { |
| uint16_t endpointIndex = emberAfGetDynamicIndexFromEndpoint(endpoint); |
| |
| if ((endpointIndex < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) && (gSubDevices[endpointIndex] != NULL)) |
| { |
| SubDevice * dev = gSubDevices[endpointIndex]; |
| |
| // if (clusterId == BridgedDeviceBasic::Id) |
| if (clusterId == BridgedDeviceBasicInformation::Id) |
| { |
| return HandleReadBridgedDeviceBasicAttribute(dev, attributeMetadata->attributeId, buffer, maxReadLength); |
| } |
| else if (clusterId == OnOff::Id) |
| { |
| return HandleReadOnOffAttribute(dev, attributeMetadata->attributeId, buffer, maxReadLength); |
| } |
| } |
| |
| return Protocols::InteractionModel::Status::Failure; |
| } |
| |
| Protocols::InteractionModel::Status emberAfExternalAttributeWriteCallback(EndpointId endpoint, ClusterId clusterId, |
| const EmberAfAttributeMetadata * attributeMetadata, |
| uint8_t * buffer) |
| { |
| uint16_t endpointIndex = emberAfGetDynamicIndexFromEndpoint(endpoint); |
| |
| if (endpointIndex < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) |
| { |
| SubDevice * dev = gSubDevices[endpointIndex]; |
| |
| if ((dev->IsReachable()) && (clusterId == OnOff::Id)) |
| { |
| return HandleWriteOnOffAttribute(dev, attributeMetadata->attributeId, buffer); |
| } |
| } |
| |
| return Protocols::InteractionModel::Status::Failure; |
| } |
| |
| namespace { |
| void CallReportingCallback(intptr_t closure) |
| { |
| auto path = reinterpret_cast<app::ConcreteAttributePath *>(closure); |
| MatterReportingAttributeChangeCallback(*path); |
| Platform::Delete(path); |
| } |
| |
| void ScheduleReportingCallback(SubDevice * dev, ClusterId cluster, AttributeId attribute) |
| { |
| auto * path = Platform::New<app::ConcreteAttributePath>(dev->GetEndpointId(), cluster, attribute); |
| DeviceLayer::PlatformMgr().ScheduleWork(CallReportingCallback, reinterpret_cast<intptr_t>(path)); |
| } |
| } // anonymous namespace |
| |
| void HandleDeviceStatusChanged(SubDevice * dev, SubDevice::Changed_t itemChangedMask) |
| { |
| if (itemChangedMask & SubDevice::kChanged_Reachable) |
| { |
| // ScheduleReportingCallback(dev, BridgedDeviceBasic::Id, BridgedDeviceBasic::Attributes::Reachable::Id); |
| ScheduleReportingCallback(dev, BridgedDeviceBasicInformation::Id, BridgedDeviceBasicInformation::Attributes::Reachable::Id); |
| } |
| |
| if (itemChangedMask & SubDevice::kChanged_State) |
| { |
| ScheduleReportingCallback(dev, OnOff::Id, OnOff::Attributes::OnOff::Id); |
| } |
| |
| if (itemChangedMask & SubDevice::kChanged_Name) |
| { |
| // ScheduleReportingCallback(dev, BridgedDeviceBasic::Id, BridgedDeviceBasic::Attributes::NodeLabel::Id); |
| ScheduleReportingCallback(dev, BridgedDeviceBasicInformation::Id, BridgedDeviceBasicInformation::Attributes::NodeLabel::Id); |
| } |
| } |
| |
| const EmberAfDeviceType gRootDeviceTypes[] = { { DEVICE_TYPE_ROOT_NODE, DEVICE_VERSION_DEFAULT } }; |
| const EmberAfDeviceType gAggregateNodeDeviceTypes[] = { { DEVICE_TYPE_BRIDGE, DEVICE_VERSION_DEFAULT } }; |
| |
| bool emberAfActionsClusterInstantActionCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, |
| const Actions::Commands::InstantAction::DecodableType & commandData) |
| { |
| // No actions are implemented, just return status NotFound. |
| commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::NotFound); |
| return true; |
| } |
| |
| void Init_Bridge_Endpoint() |
| { |
| // bridge will have own database named gSubDevices. |
| // Clear database |
| memset(gSubDevices, 0, sizeof(gSubDevices)); |
| |
| // Set starting endpoint id where dynamic endpoints will be assigned, which |
| // will be the next consecutive endpoint id after the last fixed endpoint. |
| gFirstDynamicEndpointId = static_cast<chip::EndpointId>( |
| static_cast<int>(emberAfEndpointFromIndex(static_cast<uint16_t>(emberAfFixedEndpointCount() - 1))) + 1); |
| gCurrentEndpointId = gFirstDynamicEndpointId; |
| |
| // Disable last fixed endpoint, which is used as a placeholder for all of the |
| // supported clusters so that ZAP will generated the requisite code. |
| emberAfEndpointEnableDisable(emberAfEndpointFromIndex(static_cast<uint16_t>(emberAfFixedEndpointCount() - 1)), false); |
| |
| // A bridge has root node device type on EP0 and aggregate node device type (bridge) at EP1 |
| emberAfSetDeviceTypeList(0, Span<const EmberAfDeviceType>(gRootDeviceTypes)); |
| emberAfSetDeviceTypeList(1, Span<const EmberAfDeviceType>(gAggregateNodeDeviceTypes)); |
| } |