blob: 9b03d14745cdf490b050975d3cd3a928cefb8a34 [file] [log] [blame]
/*
*
* 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 <AppShellCommands.h>
#include <BindingHandler.h>
#include <app/clusters/bindings/bindings.h>
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<BindingHandler::BindingData>();
*data = aBindingData;
// Establish new CASE session and retrasmit command that was not applied.
error = chip::BindingManager::GetInstance().NotifyBoundClusterChanged(aBindingData.EndpointId, aBindingData.ClusterId,
static_cast<void *>(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<Clusters::Identify::Attributes::AttributeList::TypeInfo>(
peer_device->GetExchangeManager(), peer_device->GetSecureSession().Value(), binding.remote, onSuccess, onFailure);
break;
case Clusters::Identify::Attributes::FeatureMap::Id:
Controller::ReadAttribute<Clusters::Identify::Attributes::FeatureMap::TypeInfo>(
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<BindingData *>(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<BindingData *>(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<BindingData *>(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<BindingData *>(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<BindingHandler::BindingData *>(context);
printf("Notify Bounded Cluster | endpoint: %d cluster: %ld\n", data->EndpointId, data->ClusterId);
BindingManager::GetInstance().NotifyBoundClusterChanged(data->EndpointId, data->ClusterId, static_cast<void *>(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<EmberBindingTableEntry *>(context);
AddBindingEntry(*entry);
Platform::Delete(entry);
}