| /* |
| * |
| * Copyright (c) 2024 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 <AppMain.h> |
| #include <platform/CHIPDeviceLayer.h> |
| #include <platform/PlatformManager.h> |
| |
| #include <app-common/zap-generated/ids/Attributes.h> |
| #include <app-common/zap-generated/ids/Clusters.h> |
| #include <app/AttributeAccessInterfaceRegistry.h> |
| #include <app/ConcreteAttributePath.h> |
| #include <app/EventLogging.h> |
| #include <app/reporting/reporting.h> |
| #include <app/server/Server.h> |
| #include <app/util/af-types.h> |
| #include <app/util/attribute-storage.h> |
| #include <app/util/endpoint-config-api.h> |
| #include <app/util/util.h> |
| #include <credentials/DeviceAttestationCredsProvider.h> |
| #include <credentials/examples/DeviceAttestationCredsExample.h> |
| #include <lib/core/CHIPError.h> |
| #include <lib/support/CHIPMem.h> |
| #include <lib/support/ZclString.h> |
| #include <platform/CommissionableDataProvider.h> |
| #include <setup_payload/QRCodeSetupPayloadGenerator.h> |
| #include <setup_payload/SetupPayload.h> |
| |
| #include "CommissionableInit.h" |
| #include "Device.h" |
| |
| #include <cassert> |
| #include <iostream> |
| #include <string> |
| #include <sys/ioctl.h> |
| #include <thread> |
| |
| using namespace chip; |
| using namespace chip::app; |
| using namespace chip::Credentials; |
| using namespace chip::Inet; |
| using namespace chip::Transport; |
| using namespace chip::DeviceLayer; |
| using namespace chip::app::Clusters; |
| |
| #define POLL_INTERVAL_MS (100) |
| |
| namespace { |
| |
| EndpointId gCurrentEndpointId; |
| EndpointId gFirstDynamicEndpointId; |
| Device * gDevices[CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT + 1]; |
| |
| bool KeyboardHit() |
| { |
| int bytesWaiting; |
| ioctl(0, FIONREAD, &bytesWaiting); |
| return bytesWaiting > 0; |
| } |
| |
| void BridgePollingThread() |
| { |
| while (true) |
| { |
| if (KeyboardHit()) |
| { |
| int ch = getchar(); |
| if (ch == 'e') |
| { |
| ChipLogProgress(DeviceLayer, "Exiting....."); |
| exit(0); |
| } |
| continue; |
| } |
| |
| // Sleep to avoid tight loop reading commands |
| usleep(POLL_INTERVAL_MS * 1000); |
| } |
| } |
| |
| } // namespace |
| |
| // REVISION DEFINITIONS: |
| // ================================================================================= |
| |
| #define ZCL_DESCRIPTOR_CLUSTER_REVISION (1u) |
| #define ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_CLUSTER_REVISION (2u) |
| #define ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_FEATURE_MAP (0u) |
| |
| // --------------------------------------------------------------------------- |
| |
| int AddDeviceEndpoint(Device * dev, EmberAfEndpointType * ep, const Span<const EmberAfDeviceType> & deviceTypeList, |
| const Span<DataVersion> & dataVersionStorage, chip::EndpointId parentEndpointId = chip::kInvalidEndpointId) |
| { |
| uint8_t index = 0; |
| const int maxRetries = 10; // Set the maximum number of retries |
| while (index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) |
| { |
| if (nullptr == gDevices[index]) |
| { |
| gDevices[index] = dev; |
| CHIP_ERROR err; |
| int retryCount = 0; |
| while (retryCount < maxRetries) |
| { |
| DeviceLayer::StackLock lock; |
| dev->SetEndpointId(gCurrentEndpointId); |
| dev->SetParentEndpointId(parentEndpointId); |
| 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; |
| } |
| if (err != CHIP_ERROR_ENDPOINT_EXISTS) |
| { |
| return -1; // Return error as endpoint addition failed due to an error other than endpoint already exists |
| } |
| // Increment the endpoint ID and handle wrap condition |
| if (++gCurrentEndpointId < gFirstDynamicEndpointId) |
| { |
| gCurrentEndpointId = gFirstDynamicEndpointId; |
| } |
| retryCount++; |
| } |
| ChipLogError(DeviceLayer, "Failed to add dynamic endpoint after %d retries", maxRetries); |
| return -1; // Return error as all retries are exhausted |
| } |
| index++; |
| } |
| ChipLogProgress(DeviceLayer, "Failed to add dynamic endpoint: No endpoints available!"); |
| return -1; |
| } |
| |
| int RemoveDeviceEndpoint(Device * dev) |
| { |
| uint8_t index = 0; |
| while (index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) |
| { |
| if (gDevices[index] == dev) |
| { |
| DeviceLayer::StackLock lock; |
| // Silence complaints about unused ep when progress logging |
| // disabled. |
| [[maybe_unused]] EndpointId ep = emberAfClearDynamicEndpoint(index); |
| gDevices[index] = nullptr; |
| ChipLogProgress(DeviceLayer, "Removed device %s from dynamic endpoint %d (index=%d)", dev->GetName(), ep, index); |
| return index; |
| } |
| index++; |
| } |
| return -1; |
| } |
| |
| namespace { |
| void CallReportingCallback(intptr_t closure) |
| { |
| auto path = reinterpret_cast<app::ConcreteAttributePath *>(closure); |
| MatterReportingAttributeChangeCallback(*path); |
| Platform::Delete(path); |
| } |
| |
| void ScheduleReportingCallback(Device * dev, ClusterId cluster, AttributeId attribute) |
| { |
| auto * path = Platform::New<app::ConcreteAttributePath>(dev->GetEndpointId(), cluster, attribute); |
| PlatformMgr().ScheduleWork(CallReportingCallback, reinterpret_cast<intptr_t>(path)); |
| } |
| } // anonymous namespace |
| |
| void HandleDeviceStatusChanged(Device * dev, Device::Changed_t itemChangedMask) |
| { |
| if (itemChangedMask & Device::kChanged_Reachable) |
| { |
| ScheduleReportingCallback(dev, BridgedDeviceBasicInformation::Id, BridgedDeviceBasicInformation::Attributes::Reachable::Id); |
| } |
| |
| if (itemChangedMask & Device::kChanged_Name) |
| { |
| ScheduleReportingCallback(dev, BridgedDeviceBasicInformation::Id, BridgedDeviceBasicInformation::Attributes::NodeLabel::Id); |
| } |
| } |
| |
| Protocols::InteractionModel::Status HandleReadBridgedDeviceBasicAttribute(Device * dev, chip::AttributeId attributeId, |
| uint8_t * buffer, uint16_t maxReadLength) |
| { |
| using namespace BridgedDeviceBasicInformation::Attributes; |
| |
| ChipLogProgress(DeviceLayer, "HandleReadBridgedDeviceBasicAttribute: attrId=%d, maxReadLength=%d", 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)) |
| { |
| uint16_t rev = ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_CLUSTER_REVISION; |
| memcpy(buffer, &rev, sizeof(rev)); |
| } |
| else if ((attributeId == FeatureMap::Id) && (maxReadLength == 4)) |
| { |
| uint32_t featureMap = ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_FEATURE_MAP; |
| memcpy(buffer, &featureMap, sizeof(featureMap)); |
| } |
| else |
| { |
| return Protocols::InteractionModel::Status::Failure; |
| } |
| |
| 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); |
| |
| Protocols::InteractionModel::Status ret = Protocols::InteractionModel::Status::Failure; |
| |
| if ((endpointIndex < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) && (gDevices[endpointIndex] != nullptr)) |
| { |
| Device * dev = gDevices[endpointIndex]; |
| |
| if (clusterId == BridgedDeviceBasicInformation::Id) |
| { |
| ret = HandleReadBridgedDeviceBasicAttribute(dev, attributeMetadata->attributeId, buffer, maxReadLength); |
| } |
| } |
| |
| return ret; |
| } |
| |
| Protocols::InteractionModel::Status emberAfExternalAttributeWriteCallback(EndpointId endpoint, ClusterId clusterId, |
| const EmberAfAttributeMetadata * attributeMetadata, |
| uint8_t * buffer) |
| { |
| uint16_t endpointIndex = emberAfGetDynamicIndexFromEndpoint(endpoint); |
| |
| Protocols::InteractionModel::Status ret = Protocols::InteractionModel::Status::Failure; |
| |
| if (endpointIndex < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) |
| { |
| Device * dev = gDevices[endpointIndex]; |
| |
| if (dev->IsReachable()) |
| { |
| ChipLogProgress(DeviceLayer, "emberAfExternalAttributeWriteCallback: ep=%d, clusterId=%d", endpoint, clusterId); |
| ret = Protocols::InteractionModel::Status::Success; |
| } |
| } |
| |
| return ret; |
| } |
| |
| void ApplicationInit() |
| { |
| // Clear out the device database |
| memset(gDevices, 0, sizeof(gDevices)); |
| |
| // 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; |
| |
| // Start a thread for bridge polling |
| std::thread pollingThread(BridgePollingThread); |
| pollingThread.detach(); |
| } |
| |
| void ApplicationShutdown() {} |
| |
| int main(int argc, char * argv[]) |
| { |
| if (ChipLinuxAppInit(argc, argv) != 0) |
| { |
| return -1; |
| } |
| |
| ChipLinuxAppMainLoop(); |
| |
| return 0; |
| } |