Skip to content

Commit

Permalink
Merge pull request #5009 from manup/zcl_default_resp
Browse files Browse the repository at this point in the history
Fix handling of ZCL Default Response
  • Loading branch information
manup authored Jun 13, 2021
2 parents 7deefba + 8dde28c commit eb09170
Show file tree
Hide file tree
Showing 22 changed files with 354 additions and 173 deletions.
196 changes: 196 additions & 0 deletions aps_controller_wrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
* Copyright (c) 2021 dresden elektronik ingenieurtechnik gmbh.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
*/

#include <deconz/zdp_profile.h>
#include <deconz/aps_controller.h>
#include <deconz/dbg_trace.h>
#include <deconz/zcl.h>
#include "utils/utils.h"
#include "aps_controller_wrapper.h"

// enable domain specific string literals
using namespace deCONZ::literals;

//! Sends a ZCL Default Response based on parameters from the request in \p ind and \p zclFrame.
static bool ZCL_SendDefaultResponse(deCONZ::ApsController *apsCtrl, const deCONZ::ApsDataIndication &ind, const deCONZ::ZclFrame &zclFrame, quint8 status)
{
deCONZ::ApsDataRequest apsReq;

// ZDP Header
apsReq.dstAddress() = ind.srcAddress();
apsReq.setDstAddressMode(ind.srcAddressMode());
apsReq.setDstEndpoint(ind.srcEndpoint());
apsReq.setSrcEndpoint(ind.dstEndpoint());
apsReq.setProfileId(ind.profileId());
apsReq.setRadius(0);
apsReq.setClusterId(ind.clusterId());
//apsReq.setTxOptions(deCONZ::ApsTxAcknowledgedTransmission);

deCONZ::ZclFrame outZclFrame;
outZclFrame.setSequenceNumber(zclFrame.sequenceNumber());
outZclFrame.setCommandId(deCONZ::ZclDefaultResponseId);

if (zclFrame.frameControl() & deCONZ::ZclFCDirectionServerToClient)
{
outZclFrame.setFrameControl(deCONZ::ZclFCProfileCommand | deCONZ::ZclFCDirectionClientToServer | deCONZ::ZclFCDisableDefaultResponse);
}
else
{
outZclFrame.setFrameControl(deCONZ::ZclFCProfileCommand | deCONZ::ZclFCDirectionServerToClient | deCONZ::ZclFCDisableDefaultResponse);
}

if (zclFrame.manufacturerCode_t() != 0x0000_mfcode)
{
outZclFrame.setFrameControl(outZclFrame.frameControl() | deCONZ::ZclFCManufacturerSpecific);
outZclFrame.setManufacturerCode(zclFrame.manufacturerCode_t());
}

{ // ZCL payload
QDataStream stream(&outZclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << zclFrame.commandId();
stream << status;
}

{ // ZCL frame
QDataStream stream(&apsReq.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
outZclFrame.writeToStream(stream);
}

return apsCtrl->apsdeDataRequest(apsReq) == deCONZ::Success;
}

//! Returns true if \p zclFrame requires a ZCL Default Response.
static bool ZCL_NeedDefaultResponse(const deCONZ::ApsDataIndication &ind, const deCONZ::ZclFrame &zclFrame)
{
if (ind.dstAddressMode() == deCONZ::ApsNwkAddress) // only respond to unicast
{
if (!(zclFrame.frameControl() & deCONZ::ZclFCDisableDefaultResponse))
{
return true;
}
}

return false;
}

//! Returns true if \p req contains a specific or ZCL Default Response for \p indZclFrame.
static bool ZCL_IsResponse(const deCONZ::ZclFrame &indZclFrame, const deCONZ::ApsDataRequest &req)
{
if (req.asdu().size() < 3) // need at least frame control | seqno | command id
{
return false;
}

// frame control | [manufacturer code] | seqno | command id
quint8 seq;
quint8 commandId;

if (req.asdu().size() >= 5 && req.asdu().at(0) & deCONZ::ZclFCManufacturerSpecific)
{
seq = static_cast<quint8>(req.asdu().at(3));
commandId = static_cast<quint8>(req.asdu().at(4));
}
else
{
seq = static_cast<quint8>(req.asdu().at(1));
commandId = static_cast<quint8>(req.asdu().at(2));
}

if (seq == indZclFrame.sequenceNumber())
{
if (commandId == deCONZ::ZclDefaultResponseId)
{
return true;
}

// Request and response command ids can differ, match for sequence number _should_ be fine.
// If we see false positives, mappings need to be created on per cluster base.
return true;
}

return false;
}

ApsControllerWrapper::ApsControllerWrapper(deCONZ::ApsController *ctrl) :
m_apsCtrl(ctrl)
{

}

int ApsControllerWrapper::apsdeDataRequest(const deCONZ::ApsDataRequest &req)
{
if (!m_apsCtrl)
{
return deCONZ::ErrorNotConnected;
}
if (m_zclDefaultResponder)
{
m_zclDefaultResponder->checkApsdeDataRequest(req);
}
return m_apsCtrl->apsdeDataRequest(req);
}

ZclDefaultResponder::ZclDefaultResponder(ApsControllerWrapper *apsCtrlWrapper, const deCONZ::ApsDataIndication &ind, const deCONZ::ZclFrame &zclFrame) :
m_apsCtrlWrapper(apsCtrlWrapper),
m_ind(ind),
m_zclFrame(zclFrame)
{
// ZCL only and ignore OTA commands as these are handled by the OTA plugin
if (m_ind.profileId() != ZDP_PROFILE_ID && m_ind.clusterId() != 0x0019)
{
m_apsCtrlWrapper->registerZclDefaultResponder(this);
m_state = State::Watch;
}
}

/*! When the APS indication function ends this destructor sends the ZCL Default Response if needed (RAII).
*/
ZclDefaultResponder::~ZclDefaultResponder()
{
if (m_state == State::Init) // ZDP indications
{
return;
}

m_apsCtrlWrapper->clearZclDefaultResponder();

if (m_state == State::Watch)
{
if (ZCL_NeedDefaultResponse(m_ind, m_zclFrame))
{
ZCL_SendDefaultResponse(m_apsCtrlWrapper->apsController(), m_ind, m_zclFrame, deCONZ::ZclSuccessStatus);
}
}
}

/*! During life time checks if \p req is a response to the contained request.
*/
void ZclDefaultResponder::checkApsdeDataRequest(const deCONZ::ApsDataRequest &req)
{
if (m_state != State::Watch) { return; }

if (!isSameAddress(m_ind.srcAddress(), req.dstAddress())) { return; }
if (req.profileId() != m_ind.profileId()) { return; }
if (req.clusterId() != m_ind.clusterId()) { return; }

if (ZCL_NeedDefaultResponse(m_ind, m_zclFrame)) // check here since in constructor m_zclFrame isn't parsed yet
{
if (ZCL_IsResponse(m_zclFrame, req))
{
m_state = State::HasResponse;
}
}
else
{
m_state = State::NoResponseNeeded;
}
}
68 changes: 68 additions & 0 deletions aps_controller_wrapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2021 dresden elektronik ingenieurtechnik gmbh.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
*/

#ifndef APS_CONTROLLER_WRAPPER_H
#define APS_CONTROLLER_WRAPPER_H

namespace deCONZ {
class ApsController;
class ApsDataIndication;
class ApsDataRequest;
class ZclFrame;
}

class ApsControllerWrapper;

/*! RAII helper to send a ZCL Default Response after APS indication if needed.
The class observes outgoing APS requests for specific responses for a request and
automatically sends a ZCL Default Response if no specifc response was send.
*/
class ZclDefaultResponder
{
public:
ZclDefaultResponder() = delete;
explicit ZclDefaultResponder(ApsControllerWrapper *apsCtrlWrapper, const deCONZ::ApsDataIndication &ind, const deCONZ::ZclFrame &zclFrame);
~ZclDefaultResponder();
void checkApsdeDataRequest(const deCONZ::ApsDataRequest &req);

private:
enum class State
{
Init,
NoResponseNeeded,
Watch,
HasResponse
};
ApsControllerWrapper *m_apsCtrlWrapper = nullptr;
const deCONZ::ApsDataIndication &m_ind;
const deCONZ::ZclFrame &m_zclFrame;
State m_state = State::Init;
};

/*! Wraps \c deCONZ::ApsController to intercept apsdeDataRequest().
The main purpose is to deterministic send ZCL Default Response if needed.
*/
class ApsControllerWrapper
{
public:
ApsControllerWrapper(deCONZ::ApsController *ctrl);
int apsdeDataRequest(const deCONZ::ApsDataRequest &req);
void registerZclDefaultResponder(ZclDefaultResponder *resp) { m_zclDefaultResponder = resp; }
void clearZclDefaultResponder() { m_zclDefaultResponder = nullptr; };
deCONZ::ApsController *apsController() { return m_apsCtrl; }

private:
deCONZ::ApsController *m_apsCtrl = nullptr;
ZclDefaultResponder *m_zclDefaultResponder = nullptr;
};

#endif // APS_CONTROLLER_WRAPPER_H
2 changes: 1 addition & 1 deletion basic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ void DeRestPluginPrivate::sendBasicClusterResponse(const deCONZ::ApsDataIndicati
outZclFrame.writeToStream(stream);
}

if (apsCtrl && apsCtrl->apsdeDataRequest(req) != deCONZ::Success)
if (apsCtrlWrapper.apsdeDataRequest(req) != deCONZ::Success)
{
DBG_Printf(DBG_INFO, "Basic failed to send reponse\n");
}
Expand Down
15 changes: 4 additions & 11 deletions bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -447,12 +447,6 @@ void DeRestPluginPrivate::handleZclConfigureReportingResponseIndication(const de
allNodes.push_back(&l);
}

// send DefaultResponse if not disabled
if (!(zclFrame.frameControl() & deCONZ::ZclFCDisableDefaultResponse))
{
sendZclDefaultResponse(ind, zclFrame, deCONZ::ZclSuccessStatus);
}

for (RestNodeBase * restNode : allNodes)
{
if (restNode->address().ext() != ind.srcAddress().ext())
Expand Down Expand Up @@ -670,7 +664,7 @@ bool DeRestPluginPrivate::sendBindRequest(BindingTask &bt)
return false;
}

if (apsCtrl && (apsCtrl->apsdeDataRequest(apsReq) == deCONZ::Success))
if (apsCtrlWrapper.apsdeDataRequest(apsReq) == deCONZ::Success)
{
return true;
}
Expand Down Expand Up @@ -827,7 +821,7 @@ bool DeRestPluginPrivate::sendConfigureReportingRequest(BindingTask &bt, const s
}


if (apsCtrl && apsCtrl->apsdeDataRequest(apsReq) == deCONZ::Success)
if (apsCtrlWrapper.apsdeDataRequest(apsReq) == deCONZ::Success)
{
queryTime = queryTime.addSecs(1);
return true;
Expand Down Expand Up @@ -4418,11 +4412,10 @@ void DeRestPluginPrivate::processUbisysC4Configuration(Sensor *sensor)
stream.setByteOrder(QDataStream::LittleEndian);
zclFrame.writeToStream(stream);

if (apsCtrl->apsdeDataRequest(req) == deCONZ::Success)
if (apsCtrlWrapper.apsdeDataRequest(req) == deCONZ::Success)
{

}

}

/*! Process binding related tasks queue every one second. */
Expand Down Expand Up @@ -5048,7 +5041,7 @@ void DeRestPluginPrivate::bindingTableReaderTimerFired()
stream << i->index;

// send
if (apsCtrl && apsCtrl->apsdeDataRequest(apsReq) == deCONZ::Success)
if (apsCtrlWrapper.apsdeDataRequest(apsReq) == deCONZ::Success)
{
DBG_Printf(DBG_ZDP, "Mgmt_Bind_req id: %d to 0x%016llX send\n", i->apsReq.id(), i->apsReq.dstAddress().ext());
i->time.start();
Expand Down
10 changes: 5 additions & 5 deletions change_channel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ bool DeRestPluginPrivate::startChannelChange(quint8 channel)
bool DeRestPluginPrivate::verifyChannel(quint8 channel)
{

DBG_Assert(apsCtrl != 0);
DBG_Assert(apsCtrl != nullptr);
if (!apsCtrl)
{
return false;
Expand Down Expand Up @@ -135,9 +135,9 @@ void DeRestPluginPrivate::changeChannel(quint8 channel)
else if (ccRetries < 3)
{
DBG_Assert(channel >= 11 && channel <= 26);
if (apsCtrl && (channel >= 11) && (channel <= 26))
if (apsCtrl && channel >= 11 && channel <= 26)
{
uint8_t nwkUpdateId = (apsCtrl->getParameter(deCONZ::ParamNetworkUpdateId));
uint8_t nwkUpdateId = apsCtrl->getParameter(deCONZ::ParamNetworkUpdateId);
if (nwkUpdateId < 255)
{
nwkUpdateId++;
Expand Down Expand Up @@ -173,7 +173,7 @@ void DeRestPluginPrivate::changeChannel(quint8 channel)
stream << scanDuration;
stream << nwkUpdateId;

if (apsCtrl && apsCtrl->apsdeDataRequest(req) == deCONZ::Success)
if (apsCtrlWrapper.apsdeDataRequest(req) == deCONZ::Success)
{
channelChangeApsRequestId = req.id();
DBG_Printf(DBG_INFO, "change channel to %d, channel mask = 0x%08lX\n", channel, scanChannels);
Expand Down Expand Up @@ -285,7 +285,7 @@ void DeRestPluginPrivate::checkChannelChangeNetworkDisconnected()
}
else
{
DBG_Assert(apsCtrl != 0);
DBG_Assert(apsCtrl != nullptr);
if (apsCtrl)
{
DBG_Printf(DBG_INFO, "disconnect from network failed, try again\n");
Expand Down
2 changes: 1 addition & 1 deletion de_otau.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ void DeRestPluginPrivate::otauSendStdNotify(LightNode *node)
zclFrame.writeToStream(stream);
}

if (apsCtrl && apsCtrl->apsdeDataRequest(req) != deCONZ::Success)
if (apsCtrlWrapper.apsdeDataRequest(req) != deCONZ::Success)
{
DBG_Printf(DBG_INFO, "otau failed to send image notify request\n");
}
Expand Down
2 changes: 2 additions & 0 deletions de_web.pro
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ DEFINES += GW_MIN_DERFUSB23E0X_FW_VERSION=0x22030300
DEFINES += GW_DEFAULT_NAME=\\\"Phoscon-GW\\\"

HEADERS = bindings.h \
aps_controller_wrapper.h \
backup.h \
button_maps.h \
connectivity.h \
Expand Down Expand Up @@ -142,6 +143,7 @@ HEADERS = bindings.h \
websocket_server.h

SOURCES = air_quality.cpp \
aps_controller_wrapper.cpp \
authorisation.cpp \
backup.cpp \
bindings.cpp \
Expand Down
Loading

0 comments on commit eb09170

Please sign in to comment.