/* * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 & deviceTypeList, const Span & 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); VerifyOrReturnError((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(closure); MatterReportingAttributeChangeCallback(*path); Platform::Delete(path); } void ScheduleReportingCallback(SubDevice * dev, ClusterId cluster, AttributeId attribute) { auto * path = Platform::New(dev->GetEndpointId(), cluster, attribute); DeviceLayer::PlatformMgr().ScheduleWork(CallReportingCallback, reinterpret_cast(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( static_cast(emberAfEndpointFromIndex(static_cast(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(emberAfFixedEndpointCount() - 1)), false); // A bridge has root node device type on EP0 and aggregate node device type (bridge) at EP1 emberAfSetDeviceTypeList(0, Span(gRootDeviceTypes)); emberAfSetDeviceTypeList(1, Span(gAggregateNodeDeviceTypes)); }