blob: fb955a63a49822deacc6c4207f03404d7d2cbfcc [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 "BindingHandler.h"
#include "AppConfig.h"
#include <app/CommandSender.h>
#include <app/clusters/bindings/BindingManager.h>
#include <app/server/Server.h>
#include <controller/InvokeInteraction.h>
#include <controller/ReadInteraction.h>
#include <lib/support/CodeUtils.h>
#include <platform/CHIPDeviceLayer.h>
using namespace chip;
using namespace chip::app;
#if CONFIG_ENABLE_CHIP_SHELL
#include <lib/shell/Engine.h>
#include <lib/shell/commands/Help.h>
using Shell::Engine;
using Shell::shell_command_t;
using Shell::streamer_get;
using Shell::streamer_printf;
Engine sShellSwitchSubCommands;
Engine sShellSwitchOnOffSubCommands;
Engine sShellSwitchGroupsSubCommands;
Engine sShellSwitchGroupsOnOffSubCommands;
Engine sShellSwitchBindingSubCommands;
#endif // defined(ENABLE_CHIP_SHELL)
#if CONFIG_ENABLE_ATTRIBUTE_SUBSCRIBE
extern void UpdateLightingStatetoGUI(EndpointId endpointId, uint8_t status);
#endif
void BindingHandler::Init()
{
// The initialization of binding manager will try establishing connection with unicast peers
// so it requires the Server instance to be correctly initialized. Post the init function to
// the event queue so that everything is ready when initialization is conducted.
chip::DeviceLayer::PlatformMgr().ScheduleWork(InitInternal);
#if CONFIG_ENABLE_CHIP_SHELL
RegisterSwitchCommands();
#endif
}
void BindingHandler::OnInvokeCommandFailure(BindingData & aBindingData, CHIP_ERROR aError)
{
CHIP_ERROR error;
if (aError == CHIP_ERROR_TIMEOUT && !BindingHandler::GetInstance().mCaseSessionRecovered)
{
ChipLogProgress(NotSpecified, "Response timeout for invoked command, trying to recover CASE session.");
// 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 = BindingManager::GetInstance().NotifyBoundClusterChanged(aBindingData.EndpointId, aBindingData.ClusterId,
static_cast<void *>(data));
if (CHIP_NO_ERROR != error)
{
ChipLogProgress(NotSpecified, "NotifyBoundClusterChanged failed due to: %" CHIP_ERROR_FORMAT, error.Format());
return;
}
}
else
{
ChipLogProgress(NotSpecified, "Binding command was not applied! Reason: %" CHIP_ERROR_FORMAT, aError.Format());
}
}
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) {
ChipLogProgress(NotSpecified, "Binding command applied successfully!");
// 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:
ChipLogProgress(NotSpecified, "Invalid binding command data - commandId is not supported");
break;
}
if (CHIP_NO_ERROR != ret)
{
ChipLogProgress(NotSpecified, "Invoke OnOff Command Request ERROR: %s", ErrorStr(ret));
}
}
void BindingHandler::LightSwitchChangedHandler(const EmberBindingTableEntry & aBinding, OperationalDeviceProxy * deviceProxy,
void * context)
{
#if CONFIG_ENABLE_ATTRIBUTE_SUBSCRIBE
if (context == nullptr)
{
chip::EndpointId localEndpointId = aBinding.local;
ChipLogProgress(NotSpecified, "localEndpointId=%d", localEndpointId);
if (aBinding.type == MATTER_UNICAST_BINDING &&
(!aBinding.clusterId.has_value() || aBinding.clusterId.value() == Clusters::OnOff::Id))
{
auto onReport = [localEndpointId](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) {
ChipLogProgress(NotSpecified, "SubscribeAttribute onReport OnOff=%d", dataResponse);
UpdateLightingStatetoGUI(localEndpointId, (uint8_t) dataResponse);
};
auto onError = [](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
ChipLogError(NotSpecified, "SubscribeAttribute failed: %" CHIP_ERROR_FORMAT, aError.Format());
// todo
};
auto onSubscriptionEstablishedCb = [localEndpointId](const app::ReadClient & readClient,
SubscriptionId subscriptionId) {
ChipLogProgress(NotSpecified, "onSubscriptionEstablishedCb %d", localEndpointId); // online
UpdateLightingStatetoGUI(localEndpointId, 3);
};
auto onResubscriptionAttemptCb = [localEndpointId](const app::ReadClient & readClient, CHIP_ERROR aError,
uint32_t aNextResubscribeIntervalMsec) {
ChipLogProgress(NotSpecified, "onResubscriptionAttemptCb %d", localEndpointId); // offline
UpdateLightingStatetoGUI(localEndpointId, 2);
};
Controller::SubscribeAttribute<Clusters::OnOff::Attributes::OnOff::TypeInfo>(
deviceProxy->GetExchangeManager(), deviceProxy->GetSecureSession().Value(), aBinding.remote, onReport, onError, 1,
15, onSubscriptionEstablishedCb, onResubscriptionAttemptCb);
}
return;
}
#endif
VerifyOrReturn(context != nullptr, ChipLogError(NotSpecified, "OnDeviceConnectedFn: context is null"));
BindingData * data = static_cast<BindingData *>(context);
if (aBinding.type == MATTER_MULTICAST_BINDING && data->IsGroup)
{
switch (data->ClusterId)
{
case Clusters::OnOff::Id:
OnOffProcessCommand(data->CommandId, aBinding, nullptr, context);
break;
default:
ChipLogError(NotSpecified, "Invalid binding group command data");
break;
}
}
else if (aBinding.type == MATTER_UNICAST_BINDING && !data->IsGroup)
{
switch (data->ClusterId)
{
case Clusters::OnOff::Id:
OnOffProcessCommand(data->CommandId, aBinding, deviceProxy, context);
break;
default:
ChipLogError(NotSpecified, "Invalid binding unicast command data");
break;
}
}
}
void BindingHandler::LightSwitchContextReleaseHandler(void * context)
{
VerifyOrReturn(context != nullptr, ChipLogError(NotSpecified, "LightSwitchContextReleaseHandler: context is null"));
Platform::Delete(static_cast<BindingData *>(context));
}
void BindingHandler::InitInternal(intptr_t arg)
{
ChipLogProgress(NotSpecified, "Initialize binding Handler");
auto & server = chip::Server::GetInstance();
chip::BindingManager::GetInstance().Init(
{ &server.GetFabricTable(), server.GetCASESessionManager(), &server.GetPersistentStorage() });
chip::BindingManager::GetInstance().RegisterBoundDeviceChangedHandler(LightSwitchChangedHandler);
chip::BindingManager::GetInstance().RegisterBoundDeviceContextReleaseHandler(LightSwitchContextReleaseHandler);
}
#ifdef CONFIG_ENABLE_CHIP_SHELL
/********************************************************
* Switch shell functions
*********************************************************/
CHIP_ERROR BindingHandler::SwitchHelpHandler(int argc, char ** argv)
{
sShellSwitchSubCommands.ForEachCommand(Shell::PrintCommandHelp, nullptr);
return CHIP_NO_ERROR;
}
CHIP_ERROR BindingHandler::SwitchCommandHandler(int argc, char ** argv)
{
if (argc == 0)
{
return SwitchHelpHandler(argc, argv);
}
return sShellSwitchSubCommands.ExecCommand(argc, argv);
}
/********************************************************
* OnOff switch shell functions
*********************************************************/
CHIP_ERROR BindingHandler::OnOffHelpHandler(int argc, char ** argv)
{
sShellSwitchOnOffSubCommands.ForEachCommand(Shell::PrintCommandHelp, nullptr);
return CHIP_NO_ERROR;
}
CHIP_ERROR BindingHandler::OnOffSwitchCommandHandler(int argc, char ** argv)
{
if (argc == 0)
{
return OnOffHelpHandler(argc, argv);
}
return sShellSwitchOnOffSubCommands.ExecCommand(argc, argv);
}
CHIP_ERROR BindingHandler::OnSwitchCommandHandler(int argc, char ** argv)
{
BindingData * data = Platform::New<BindingData>();
data->EndpointId = atoi(argv[0]) + 1;
data->CommandId = Clusters::OnOff::Commands::On::Id;
data->ClusterId = Clusters::OnOff::Id;
DeviceLayer::PlatformMgr().ScheduleWork(SwitchWorkerFunction, reinterpret_cast<intptr_t>(data));
return CHIP_NO_ERROR;
}
CHIP_ERROR BindingHandler::OffSwitchCommandHandler(int argc, char ** argv)
{
BindingData * data = Platform::New<BindingData>();
data->EndpointId = atoi(argv[0]) + 1;
data->CommandId = Clusters::OnOff::Commands::Off::Id;
data->ClusterId = Clusters::OnOff::Id;
DeviceLayer::PlatformMgr().ScheduleWork(SwitchWorkerFunction, reinterpret_cast<intptr_t>(data));
return CHIP_NO_ERROR;
}
CHIP_ERROR BindingHandler::ToggleSwitchCommandHandler(int argc, char ** argv)
{
BindingData * data = Platform::New<BindingData>();
data->EndpointId = atoi(argv[0]) + 1;
data->CommandId = Clusters::OnOff::Commands::Toggle::Id;
data->ClusterId = Clusters::OnOff::Id;
DeviceLayer::PlatformMgr().ScheduleWork(SwitchWorkerFunction, reinterpret_cast<intptr_t>(data));
return CHIP_NO_ERROR;
}
/********************************************************
* bind switch shell functions
*********************************************************/
CHIP_ERROR BindingHandler::BindingHelpHandler(int argc, char ** argv)
{
sShellSwitchBindingSubCommands.ForEachCommand(Shell::PrintCommandHelp, nullptr);
return CHIP_NO_ERROR;
}
CHIP_ERROR BindingHandler::BindingSwitchCommandHandler(int argc, char ** argv)
{
if (argc == 0)
{
return BindingHelpHandler(argc, argv);
}
return sShellSwitchBindingSubCommands.ExecCommand(argc, argv);
}
CHIP_ERROR BindingHandler::BindingGroupBindCommandHandler(int argc, char ** argv)
{
VerifyOrReturnError(argc == 2, CHIP_ERROR_INVALID_ARGUMENT);
EmberBindingTableEntry * entry = Platform::New<EmberBindingTableEntry>();
entry->type = MATTER_MULTICAST_BINDING;
entry->fabricIndex = atoi(argv[0]);
entry->groupId = atoi(argv[1]);
entry->local = 1; // Hardcoded to endpoint 1 for now
entry->clusterId.emplace(6); // Hardcoded to OnOff cluster for now
DeviceLayer::PlatformMgr().ScheduleWork(BindingWorkerFunction, reinterpret_cast<intptr_t>(entry));
return CHIP_NO_ERROR;
}
CHIP_ERROR BindingHandler::BindingUnicastBindCommandHandler(int argc, char ** argv)
{
VerifyOrReturnError(argc == 3, CHIP_ERROR_INVALID_ARGUMENT);
EmberBindingTableEntry * entry = Platform::New<EmberBindingTableEntry>();
entry->type = MATTER_UNICAST_BINDING;
entry->fabricIndex = atoi(argv[0]);
entry->nodeId = atoi(argv[1]);
entry->local = 1; // Hardcoded to endpoint 1 for now
entry->remote = atoi(argv[2]);
entry->clusterId.emplace(6); // Hardcode to OnOff cluster for now
DeviceLayer::PlatformMgr().ScheduleWork(BindingWorkerFunction, reinterpret_cast<intptr_t>(entry));
return CHIP_NO_ERROR;
}
/********************************************************
* Groups switch shell functions
*********************************************************/
CHIP_ERROR BindingHandler::GroupsHelpHandler(int argc, char ** argv)
{
sShellSwitchGroupsSubCommands.ForEachCommand(Shell::PrintCommandHelp, nullptr);
return CHIP_NO_ERROR;
}
CHIP_ERROR BindingHandler::GroupsSwitchCommandHandler(int argc, char ** argv)
{
if (argc == 0)
{
return GroupsHelpHandler(argc, argv);
}
return sShellSwitchGroupsSubCommands.ExecCommand(argc, argv);
}
/********************************************************
* Groups OnOff switch shell functions
*********************************************************/
CHIP_ERROR BindingHandler::GroupsOnOffHelpHandler(int argc, char ** argv)
{
sShellSwitchGroupsOnOffSubCommands.ForEachCommand(Shell::PrintCommandHelp, nullptr);
return CHIP_NO_ERROR;
}
CHIP_ERROR BindingHandler::GroupsOnOffSwitchCommandHandler(int argc, char ** argv)
{
if (argc == 0)
{
return GroupsOnOffHelpHandler(argc, argv);
}
return sShellSwitchGroupsOnOffSubCommands.ExecCommand(argc, argv);
}
CHIP_ERROR BindingHandler::GroupOnSwitchCommandHandler(int argc, char ** argv)
{
BindingData * data = Platform::New<BindingData>();
data->EndpointId = 1;
data->CommandId = Clusters::OnOff::Commands::On::Id;
data->ClusterId = Clusters::OnOff::Id;
data->IsGroup = true;
DeviceLayer::PlatformMgr().ScheduleWork(SwitchWorkerFunction, reinterpret_cast<intptr_t>(data));
return CHIP_NO_ERROR;
}
CHIP_ERROR BindingHandler::GroupOffSwitchCommandHandler(int argc, char ** argv)
{
BindingData * data = Platform::New<BindingData>();
data->EndpointId = 1;
data->CommandId = Clusters::OnOff::Commands::Off::Id;
data->ClusterId = Clusters::OnOff::Id;
data->IsGroup = true;
DeviceLayer::PlatformMgr().ScheduleWork(SwitchWorkerFunction, reinterpret_cast<intptr_t>(data));
return CHIP_NO_ERROR;
}
CHIP_ERROR BindingHandler::GroupToggleSwitchCommandHandler(int argc, char ** argv)
{
BindingData * data = Platform::New<BindingData>();
data->EndpointId = 1;
data->CommandId = Clusters::OnOff::Commands::Toggle::Id;
data->ClusterId = Clusters::OnOff::Id;
data->IsGroup = true;
DeviceLayer::PlatformMgr().ScheduleWork(SwitchWorkerFunction, reinterpret_cast<intptr_t>(data));
return CHIP_NO_ERROR;
}
CHIP_ERROR BindingHandler::TableCommandHelper(int argc, char ** argv)
{
BindingTable & bindingTable = BindingTable::GetInstance();
streamer_printf(streamer_get(), "Binding Table size: [%d]:\r\n", bindingTable.Size());
uint8_t i = 0;
for (auto & entry : bindingTable)
{
switch (entry.type)
{
case MATTER_UNICAST_BINDING:
streamer_printf(streamer_get(), "[%d] UNICAST:\r\n", i++);
streamer_printf(streamer_get(), "\t\t+ Fabric: %d\r\n \
\t+ LocalEndpoint %d \r\n \
\t+ ClusterId %d \r\n \
\t+ RemoteEndpointId %d \r\n \
\t+ NodeId %d\r\n",
(int) entry.fabricIndex, (int) entry.local, (int) entry.clusterId.value_or(kInvalidClusterId),
(int) entry.remote, (int) entry.nodeId);
break;
case MATTER_MULTICAST_BINDING:
streamer_printf(streamer_get(), "[%d] GROUP:\r\n", i++);
streamer_printf(streamer_get(), "\t\t+ Fabric: %d\r\n \
\t+ LocalEndpoint %d \r\n \
\t+ RemoteEndpointId %d \r\n \
\t+ GroupId %d\r\n",
(int) entry.fabricIndex, (int) entry.local, (int) entry.remote, (int) entry.groupId);
break;
case MATTER_UNUSED_BINDING:
streamer_printf(streamer_get(), "[%d] UNUSED\r\n", i++);
break;
default:
break;
}
}
return CHIP_NO_ERROR;
}
/**
* @brief configures switch matter shell
*
*/
void BindingHandler::RegisterSwitchCommands()
{
static const shell_command_t sSwitchSubCommands[] = {
{ &SwitchHelpHandler, "help", "Usage: switch <subcommand>" },
{ &OnOffSwitchCommandHandler, "onoff", " Usage: switch onoff <subcommand>" },
{ &GroupsSwitchCommandHandler, "groups", "Usage: switch groups <subcommand>" },
{ &BindingSwitchCommandHandler, "binding", "Usage: switch binding <subcommand>" },
{ &TableCommandHelper, "table", "Usage: switch table" }
};
static const shell_command_t sSwitchOnOffSubCommands[] = {
{ &OnOffHelpHandler, "help", "Usage : switch ononff <subcommand>" },
{ &OnSwitchCommandHandler, "on", "Sends on command to bound lighting app" },
{ &OffSwitchCommandHandler, "off", "Sends off command to bound lighting app" },
{ &ToggleSwitchCommandHandler, "toggle", "Sends toggle command to bound lighting app" }
};
static const shell_command_t sSwitchGroupsSubCommands[] = { { &GroupsHelpHandler, "help", "Usage: switch groups <subcommand>" },
{ &GroupsOnOffSwitchCommandHandler, "onoff",
"Usage: switch groups onoff <subcommand>" } };
static const shell_command_t sSwitchGroupsOnOffSubCommands[] = {
{ &GroupsOnOffHelpHandler, "help", "Usage: switch groups onoff <subcommand>" },
{ &GroupOnSwitchCommandHandler, "on", "Sends on command to bound group" },
{ &GroupOffSwitchCommandHandler, "off", "Sends off command to bound group" },
{ &GroupToggleSwitchCommandHandler, "toggle", "Sends toggle command to group" }
};
static const shell_command_t sSwitchBindingSubCommands[] = {
{ &BindingHelpHandler, "help", "Usage: switch binding <subcommand>" },
{ &BindingGroupBindCommandHandler, "group", "Usage: switch binding group <fabric index> <group id>" },
{ &BindingUnicastBindCommandHandler, "unicast", "Usage: switch binding unicast <fabric index> <node id> <endpoint>" }
};
static const shell_command_t sSwitchCommand = { &SwitchCommandHandler, "switch",
"Light-switch commands. Usage: switch <subcommand>" };
sShellSwitchGroupsOnOffSubCommands.RegisterCommands(sSwitchGroupsOnOffSubCommands,
MATTER_ARRAY_SIZE(sSwitchGroupsOnOffSubCommands));
sShellSwitchOnOffSubCommands.RegisterCommands(sSwitchOnOffSubCommands, MATTER_ARRAY_SIZE(sSwitchOnOffSubCommands));
sShellSwitchGroupsSubCommands.RegisterCommands(sSwitchGroupsSubCommands, MATTER_ARRAY_SIZE(sSwitchGroupsSubCommands));
sShellSwitchBindingSubCommands.RegisterCommands(sSwitchBindingSubCommands, MATTER_ARRAY_SIZE(sSwitchBindingSubCommands));
sShellSwitchSubCommands.RegisterCommands(sSwitchSubCommands, MATTER_ARRAY_SIZE(sSwitchSubCommands));
Engine::Root().RegisterCommands(&sSwitchCommand, 1);
}
void BindingHandler::BindingWorkerFunction(intptr_t context)
{
VerifyOrReturn(context != 0, ChipLogError(NotSpecified, "BindingWorkerFunction - Invalid work data"));
EmberBindingTableEntry * entry = reinterpret_cast<EmberBindingTableEntry *>(context);
AddBindingEntry(*entry);
Platform::Delete(entry);
}
#endif // ENABLE_CHIP_SHELL
/********************************************************
* Switch functions
*********************************************************/
void BindingHandler::SwitchWorkerFunction(intptr_t context)
{
#if CONFIG_ENABLE_CHIP_SHELL
VerifyOrReturn(context != 0, streamer_printf(streamer_get(), "SwitchWorkerFunction - Invalid work data\r\n"));
#else
VerifyOrReturn(context != 0, ChipLogError(NotSpecified, "SwitchWorkerFunction - Invalid work data"));
#endif
BindingData * data = reinterpret_cast<BindingData *>(context);
#if CONFIG_ENABLE_CHIP_SHELL
streamer_printf(streamer_get(), "Notify Bounded Cluster | endpoint: %d CLuster: %d\r\n", data->EndpointId, data->ClusterId);
#endif
BindingManager::GetInstance().NotifyBoundClusterChanged(data->EndpointId, data->ClusterId, static_cast<void *>(data));
}
void BindingHandler::SwitchWorkerFunction2(int localEndpointId)
{
BindingManager::GetInstance().NotifyBoundClusterChanged(localEndpointId, Clusters::OnOff::Id, nullptr);
}
void BindingHandler::SwitchWorkerFunction3(intptr_t context)
{
SubscribeCommandData * data = reinterpret_cast<SubscribeCommandData *>(context);
chip::app::InteractionModelEngine::GetInstance()->ShutdownSubscriptions(data->fabricIndex, data->nodeId);
}