blob: c2f0fa4610d19f395771594a8a0abcfdafa04931 [file] [log] [blame]
/**
*
* Copyright (c) 2021 Project CHIP Authors
*
* 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.
*/
/**
*
* Copyright (c) 2020 Silicon Labs
*
* 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 "identify-server.h"
#include <app-common/zap-generated/attribute-id.h>
#include <app-common/zap-generated/attribute-type.h>
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/callback.h>
#include <app-common/zap-generated/cluster-id.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <app-common/zap-generated/command-id.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app-common/zap-generated/ids/Clusters.h>
#include <app-common/zap-generated/ids/Commands.h>
#include <app/CommandHandler.h>
#include <app/ConcreteCommandPath.h>
#include <app/util/af.h>
#include <app/util/common.h>
#include <array>
#include <lib/support/CodeUtils.h>
#include <platform/CHIPDeviceLayer.h>
#if CHIP_DEVICE_LAYER_NONE
#error "identify requrires a device layer"
#endif
#ifndef emberAfIdentifyClusterPrintln
#define emberAfIdentifyClusterPrintln(...) ChipLogProgress(Zcl, __VA_ARGS__);
#endif
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters::Identify;
static std::array<Identify *, EMBER_AF_IDENTIFY_CLUSTER_SERVER_ENDPOINT_COUNT> instances = { 0 };
static void onIdentifyClusterTick(chip::System::Layer * systemLayer, void * appState);
static Identify * inst(EndpointId endpoint)
{
for (size_t i = 0; i < instances.size(); i++)
{
if (nullptr != instances[i] && endpoint == instances[i]->mEndpoint)
{
return instances[i];
}
}
return nullptr;
}
static inline void reg(Identify * inst)
{
for (size_t i = 0; i < instances.size(); i++)
{
if (nullptr == instances[i])
{
instances[i] = inst;
break;
}
}
}
static inline void unreg(Identify * inst)
{
for (size_t i = 0; i < instances.size(); i++)
{
if (inst == instances[i])
{
instances[i] = nullptr;
}
}
}
void emberAfIdentifyClusterServerInitCallback(EndpointId endpoint)
{
Identify * identify = inst(endpoint);
if (identify != nullptr)
{
(void) Clusters::Identify::Attributes::IdentifyType::Set(endpoint, identify->mIdentifyType);
}
}
static void onIdentifyClusterTick(chip::System::Layer * systemLayer, void * appState)
{
uint16_t identifyTime = 0;
Identify * identify = reinterpret_cast<Identify *>(appState);
if (nullptr != identify)
{
EndpointId endpoint = identify->mEndpoint;
if (EMBER_ZCL_STATUS_SUCCESS == Clusters::Identify::Attributes::IdentifyTime::Get(endpoint, &identifyTime) &&
0 != identifyTime)
{
identifyTime = static_cast<uint16_t>(identifyTime == 0 ? 0 : identifyTime - 1);
// This tick writes the new attribute, which will trigger the Attribute
// Changed callback.
(void) Clusters::Identify::Attributes::IdentifyTime::Set(endpoint, identifyTime);
}
}
}
static inline void identify_activate(Identify * identify)
{
if (nullptr != identify->mOnIdentifyStart && nullptr != identify->mOnIdentifyStop && false == identify->mActive)
{
identify->mActive = true;
identify->mOnIdentifyStart(identify);
}
}
static inline void identify_deactivate(Identify * identify)
{
if (nullptr != identify->mOnIdentifyStop)
{
identify->mActive = false;
identify->mOnIdentifyStop(identify);
}
}
void MatterIdentifyClusterServerAttributeChangedCallback(const app::ConcreteAttributePath & attributePath)
{
if (attributePath.mAttributeId == Clusters::Identify::Attributes::IdentifyTime::Id)
{
EndpointId endpoint = attributePath.mEndpointId;
Identify * identify = inst(endpoint);
uint16_t identifyTime;
if (identify == nullptr)
{
return;
}
if (EMBER_ZCL_STATUS_SUCCESS == Clusters::Identify::Attributes::IdentifyTime::Get(endpoint, &identifyTime))
{
/* effect identifier changed during identify */
if (identify->mTargetEffectIdentifier != identify->mCurrentEffectIdentifier)
{
identify->mCurrentEffectIdentifier = identify->mTargetEffectIdentifier;
/* finish identify process */
if (EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_FINISH_EFFECT == identify->mCurrentEffectIdentifier && identifyTime > 0)
{
(void) chip::DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds16(1), onIdentifyClusterTick,
identify);
return;
}
/* stop identify process */
else if (EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_STOP_EFFECT == identify->mCurrentEffectIdentifier && identifyTime > 0)
{
Clusters::Identify::Attributes::IdentifyTime::Set(endpoint, 0);
identify_deactivate(identify);
}
/* change from e.g. Breathe to Blink during identify */
else
{
/* cancel identify */
Clusters::Identify::Attributes::IdentifyTime::Set(endpoint, 0);
identify_deactivate(identify);
/* trigger effect identifier callback */
if (nullptr != identify->mOnEffectIdentifier)
identify->mOnEffectIdentifier(identify);
}
}
else if (identifyTime > 0)
{
/* we only start if both callbacks are set */
identify_activate(identify);
(void) chip::DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds16(1), onIdentifyClusterTick, identify);
return;
}
else
{
identify_deactivate(identify);
}
}
(void) chip::DeviceLayer::SystemLayer().CancelTimer(onIdentifyClusterTick, identify);
}
}
bool emberAfIdentifyClusterIdentifyCallback(CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::Identify::DecodableType & commandData)
{
auto & identifyTime = commandData.identifyTime;
// cmd Identify
return EMBER_SUCCESS ==
emberAfSendImmediateDefaultResponse(
Clusters::Identify::Attributes::IdentifyTime::Set(commandPath.mEndpointId, identifyTime));
}
bool emberAfIdentifyClusterIdentifyQueryCallback(CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::IdentifyQuery::DecodableType & commandData)
{
EndpointId endpoint = commandPath.mEndpointId;
// cmd IdentifyQuery
uint16_t identifyTime = 0;
EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS;
EmberStatus sendStatus = EMBER_SUCCESS;
CHIP_ERROR err = CHIP_NO_ERROR;
status = Clusters::Identify::Attributes::IdentifyTime::Get(endpoint, &identifyTime);
if (status != EMBER_ZCL_STATUS_SUCCESS || 0 == identifyTime)
{
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfIdentifyClusterPrintln("Error reading identify time");
}
else
{
emberAfIdentifyClusterPrintln("identifyTime is at 0");
}
emberAfIdentifyClusterPrintln("Sending back default response");
sendStatus = emberAfSendImmediateDefaultResponse(status);
if (EMBER_SUCCESS != sendStatus)
{
emberAfIdentifyClusterPrintln("Identify: failed to send %s response: "
"0x%x",
"default", sendStatus);
}
return true;
}
emberAfIdentifyClusterPrintln("Identifying for %u more seconds", identifyTime);
{
app::ConcreteCommandPath path = { endpoint, Clusters::Identify::Id, ZCL_IDENTIFY_QUERY_RESPONSE_COMMAND_ID };
TLV::TLVWriter * writer = nullptr;
VerifyOrExit(commandObj != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(err = commandObj->PrepareCommand(path));
VerifyOrExit((writer = commandObj->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(err = writer->Put(TLV::ContextTag(0), identifyTime));
SuccessOrExit(err = commandObj->FinishCommand());
}
exit:
if (err != CHIP_NO_ERROR)
{
emberAfIdentifyClusterPrintln("Failed to encode response command.");
}
return true;
}
bool emberAfIdentifyClusterTriggerEffectCallback(CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::TriggerEffect::DecodableType & commandData)
{
auto & effectIdentifier = commandData.effectIdentifier;
auto & effectVariant = commandData.effectVariant;
EndpointId endpoint = commandPath.mEndpointId;
// cmd TriggerEffect
Identify * identify = inst(endpoint);
uint16_t identifyTime = 0;
EmberAfIdentifyEffectIdentifier effectId = static_cast<EmberAfIdentifyEffectIdentifier>(effectIdentifier);
emberAfIdentifyClusterPrintln("RX identify:trigger effect 0x%X variant 0x%X", effectId, effectVariant);
if (identify == nullptr)
{
return false;
}
identify->mTargetEffectIdentifier = effectId;
identify->mEffectVariant = effectVariant;
/* only call the callback if no identify is in progress */
if (nullptr != identify->mOnEffectIdentifier &&
EMBER_ZCL_STATUS_SUCCESS == Clusters::Identify::Attributes::IdentifyTime::Get(endpoint, &identifyTime) && 0 == identifyTime)
{
identify->mCurrentEffectIdentifier = identify->mTargetEffectIdentifier;
identify->mOnEffectIdentifier(identify);
}
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
return true;
}
Identify::Identify(chip::EndpointId endpoint, onIdentifyStartCb onIdentifyStart, onIdentifyStopCb onIdentifyStop,
EmberAfIdentifyIdentifyType identifyType, onEffectIdentifierCb onEffectIdentifier,
EmberAfIdentifyEffectIdentifier effectIdentifier, EmberAfIdentifyEffectVariant effectVariant) :
mEndpoint(endpoint),
mOnIdentifyStart(onIdentifyStart), mOnIdentifyStop(onIdentifyStop), mIdentifyType(identifyType),
mOnEffectIdentifier(onEffectIdentifier), mCurrentEffectIdentifier(effectIdentifier), mTargetEffectIdentifier(effectIdentifier),
mEffectVariant(static_cast<uint8_t>(effectVariant))
{
reg(this);
};
Identify::~Identify()
{
unreg(this);
}
void MatterIdentifyPluginServerInitCallback() {}