/* * * Copyright (c) 2022 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 "controller/ReadInteraction.h" #include #include #include using namespace chip; using namespace chip::app; void BindingHandler::Init() { DeviceLayer::PlatformMgr().ScheduleWork(InitInternal); } void BindingHandler::OnInvokeCommandFailure(BindingData & aBindingData, CHIP_ERROR aError) { CHIP_ERROR error; if (aError == CHIP_ERROR_TIMEOUT && !BindingHandler::GetInstance().mCaseSessionRecovered) { printf("Response timeout for invoked command, trying to recover CASE session.\n"); // Set flag to not try recover session multiple times. BindingHandler::GetInstance().mCaseSessionRecovered = true; // Allocate new object to make sure its life time will be appropriate. BindingHandler::BindingData * data = Platform::New(); *data = aBindingData; // Establish new CASE session and retrasmit command that was not applied. error = chip::BindingManager::GetInstance().NotifyBoundClusterChanged(aBindingData.EndpointId, aBindingData.ClusterId, static_cast(data)); if (CHIP_NO_ERROR != error) { printf("NotifyBoundClusterChanged failed due to: %" CHIP_ERROR_FORMAT, error.Format()); return; } } else { printf("Binding command was not applied! Reason: %" CHIP_ERROR_FORMAT, aError.Format()); } } void ProcessIdentifyUnicastBindingRead(BindingHandler::BindingData * data, const EmberBindingTableEntry & binding, OperationalDeviceProxy * peer_device) { auto onSuccess = [](const ConcreteDataAttributePath & attributePath, const auto & dataResponse) { ChipLogProgress(NotSpecified, "Read Identify attribute succeeds"); }; auto onFailure = [](const ConcreteDataAttributePath * attributePath, CHIP_ERROR error) { ChipLogError(NotSpecified, "Read Identify attribute failed: %" CHIP_ERROR_FORMAT, error.Format()); }; VerifyOrDie(peer_device != nullptr && peer_device->ConnectionReady()); switch (data->attributeId) { case Clusters::Identify::Attributes::AttributeList::Id: Controller::ReadAttribute( peer_device->GetExchangeManager(), peer_device->GetSecureSession().Value(), binding.remote, onSuccess, onFailure); break; case Clusters::Identify::Attributes::FeatureMap::Id: Controller::ReadAttribute( peer_device->GetExchangeManager(), peer_device->GetSecureSession().Value(), binding.remote, onSuccess, onFailure); break; } } void BindingHandler::OnOffProcessCommand(CommandId aCommandId, const EmberBindingTableEntry & aBinding, OperationalDeviceProxy * aDevice, void * aContext) { CHIP_ERROR ret = CHIP_NO_ERROR; BindingData * data = reinterpret_cast(aContext); auto onSuccess = [](const ConcreteCommandPath & commandPath, const StatusIB & status, const auto & dataResponse) { printf("Binding command applied successfully!\n"); // If session was recovered and communication works, reset flag to the initial state. if (BindingHandler::GetInstance().mCaseSessionRecovered) BindingHandler::GetInstance().mCaseSessionRecovered = false; }; auto onFailure = [dataRef = *data](CHIP_ERROR aError) mutable { BindingHandler::OnInvokeCommandFailure(dataRef, aError); }; if (aDevice) { // We are validating connection is ready once here instead of multiple times in each case statement below. VerifyOrDie(aDevice->ConnectionReady()); } switch (aCommandId) { case Clusters::OnOff::Commands::Toggle::Id: Clusters::OnOff::Commands::Toggle::Type toggleCommand; if (aDevice) { ret = Controller::InvokeCommandRequest(aDevice->GetExchangeManager(), aDevice->GetSecureSession().Value(), aBinding.remote, toggleCommand, onSuccess, onFailure); } else { Messaging::ExchangeManager & exchangeMgr = Server::GetInstance().GetExchangeManager(); ret = Controller::InvokeGroupCommandRequest(&exchangeMgr, aBinding.fabricIndex, aBinding.groupId, toggleCommand); } break; case Clusters::OnOff::Commands::On::Id: Clusters::OnOff::Commands::On::Type onCommand; if (aDevice) { ret = Controller::InvokeCommandRequest(aDevice->GetExchangeManager(), aDevice->GetSecureSession().Value(), aBinding.remote, onCommand, onSuccess, onFailure); } else { Messaging::ExchangeManager & exchangeMgr = Server::GetInstance().GetExchangeManager(); ret = Controller::InvokeGroupCommandRequest(&exchangeMgr, aBinding.fabricIndex, aBinding.groupId, onCommand); } break; case Clusters::OnOff::Commands::Off::Id: Clusters::OnOff::Commands::Off::Type offCommand; if (aDevice) { ret = Controller::InvokeCommandRequest(aDevice->GetExchangeManager(), aDevice->GetSecureSession().Value(), aBinding.remote, offCommand, onSuccess, onFailure); } else { Messaging::ExchangeManager & exchangeMgr = Server::GetInstance().GetExchangeManager(); ret = Controller::InvokeGroupCommandRequest(&exchangeMgr, aBinding.fabricIndex, aBinding.groupId, offCommand); } break; default: printf("Invalid binding command data - commandId is not supported\n"); break; } if (CHIP_NO_ERROR != ret) { printf("Invoke OnOff Command Request ERROR: %s\n", ErrorStr(ret)); } } void BindingHandler::LevelControlProcessCommand(CommandId aCommandId, const EmberBindingTableEntry & aBinding, OperationalDeviceProxy * aDevice, void * aContext) { BindingData * data = reinterpret_cast(aContext); auto onSuccess = [](const ConcreteCommandPath & commandPath, const StatusIB & status, const auto & dataResponse) { printf("Binding command applied successfully!\n"); // If session was recovered and communication works, reset flag to the initial state. if (BindingHandler::GetInstance().mCaseSessionRecovered) BindingHandler::GetInstance().mCaseSessionRecovered = false; }; auto onFailure = [dataRef = *data](CHIP_ERROR aError) mutable { BindingHandler::OnInvokeCommandFailure(dataRef, aError); }; CHIP_ERROR ret = CHIP_NO_ERROR; if (aDevice) { // We are validating connection is ready once here instead of multiple times in each case statement below. VerifyOrDie(aDevice->ConnectionReady()); } switch (aCommandId) { case Clusters::LevelControl::Commands::MoveToLevel::Id: { Clusters::LevelControl::Commands::MoveToLevel::Type moveToLevelCommand; moveToLevelCommand.level = data->Value; if (aDevice) { ret = Controller::InvokeCommandRequest(aDevice->GetExchangeManager(), aDevice->GetSecureSession().Value(), aBinding.remote, moveToLevelCommand, onSuccess, onFailure); } else { Messaging::ExchangeManager & exchangeMgr = Server::GetInstance().GetExchangeManager(); ret = Controller::InvokeGroupCommandRequest(&exchangeMgr, aBinding.fabricIndex, aBinding.groupId, moveToLevelCommand); } } break; default: printf("Invalid binding command data - commandId is not supported\n"); break; } if (CHIP_NO_ERROR != ret) { printf("Invoke Group Command Request ERROR: %s\n", ErrorStr(ret)); } } void BindingHandler::LightSwitchChangedHandler(const EmberBindingTableEntry & binding, OperationalDeviceProxy * deviceProxy, void * context) { VerifyOrReturn(context != nullptr, printf("Invalid context for Light switch handler\n");); BindingData * data = static_cast(context); if (binding.type == MATTER_MULTICAST_BINDING && data->IsGroup) { switch (data->ClusterId) { case Clusters::OnOff::Id: OnOffProcessCommand(data->CommandId, binding, nullptr, context); break; case Clusters::LevelControl::Id: LevelControlProcessCommand(data->CommandId, binding, nullptr, context); break; default: ChipLogError(NotSpecified, "Invalid binding group command data"); break; } } else if (binding.type == MATTER_UNICAST_BINDING && !data->IsGroup) { switch (data->ClusterId) { case Clusters::OnOff::Id: OnOffProcessCommand(data->CommandId, binding, deviceProxy, context); break; case Clusters::LevelControl::Id: LevelControlProcessCommand(data->CommandId, binding, deviceProxy, context); break; case Clusters::Identify::Id: ProcessIdentifyUnicastBindingRead(data, binding, deviceProxy); break; default: ChipLogError(NotSpecified, "Invalid binding unicast command data"); break; } } } void BindingHandler::LightSwitchContextReleaseHandler(void * context) { VerifyOrReturn(context != nullptr, printf("Invalid context for Light switch context release handler\n");); Platform::Delete(static_cast(context)); } void BindingHandler::InitInternal(intptr_t aArg) { CHIP_ERROR ret = CHIP_NO_ERROR; auto & server = Server::GetInstance(); ret = BindingManager::GetInstance().Init( { &server.GetFabricTable(), server.GetCASESessionManager(), &server.GetPersistentStorage() }); if (CHIP_NO_ERROR != ret) { printf("BindingHandler::InitInternal() run fail, err_code: 0x%" CHIP_ERROR_FORMAT, ret.Format()); printf("\n"); } else { BindingManager::GetInstance().RegisterBoundDeviceChangedHandler(LightSwitchChangedHandler); BindingManager::GetInstance().RegisterBoundDeviceContextReleaseHandler(LightSwitchContextReleaseHandler); BindingHandler::GetInstance().PrintBindingTable(); } } bool BindingHandler::IsGroupBound() { BindingTable & bindingTable = BindingTable::GetInstance(); for (auto & entry : bindingTable) { if (MATTER_MULTICAST_BINDING == entry.type) { return true; } } return false; } void BindingHandler::PrintBindingTable() { BindingTable & bindingTable = BindingTable::GetInstance(); printf("Binding Table size: [%d]:\n", bindingTable.Size()); uint8_t i = 0; for (auto & entry : bindingTable) { switch (entry.type) { case MATTER_UNICAST_BINDING: printf("[%d] UNICAST:", i++); printf("\t\t+ Fabric: %d\n \ \t+ LocalEndpoint %d \n \ \t+ ClusterId %d \n \ \t+ RemoteEndpointId %d \n \ \t+ NodeId %d \n", (int) entry.fabricIndex, (int) entry.local, (int) entry.clusterId.value_or(kInvalidClusterId), (int) entry.remote, (int) entry.nodeId); break; case MATTER_MULTICAST_BINDING: printf("[%d] GROUP:", i++); printf("\t\t+ Fabric: %d\n \ \t+ LocalEndpoint %d \n \ \t+ RemoteEndpointId %d \n \ \t+ GroupId %d \n", (int) entry.fabricIndex, (int) entry.local, (int) entry.remote, (int) entry.groupId); break; case MATTER_UNUSED_BINDING: printf("[%d] UNUSED", i++); break; default: break; } } } void BindingHandler::SwitchWorkerHandler(intptr_t context) { VerifyOrReturn(context != 0, printf("BindingHandler::Invalid switch work data\n")); BindingHandler::BindingData * data = reinterpret_cast(context); printf("Notify Bounded Cluster | endpoint: %d cluster: %ld\n", data->EndpointId, data->ClusterId); BindingManager::GetInstance().NotifyBoundClusterChanged(data->EndpointId, data->ClusterId, static_cast(data)); Platform::Delete(data); } void BindingHandler::BindingWorkerHandler(intptr_t context) { VerifyOrReturn(context != 0, ChipLogError(NotSpecified, "BindingHandler::Invalid binding work data\n")); EmberBindingTableEntry * entry = reinterpret_cast(context); AddBindingEntry(*entry); Platform::Delete(entry); }