Add identify and iaszone cluster implementations
parent
9196ff83cf
commit
8105a9082b
|
|
@ -8,9 +8,11 @@ SOURCES += \
|
|||
backends/deconz/interface/zigbeeinterfacedeconzreply.cpp \
|
||||
backends/deconz/zigbeebridgecontrollerdeconz.cpp \
|
||||
backends/deconz/zigbeenetworkdeconz.cpp \
|
||||
zcl/general/zigbeeclusteridentify.cpp \
|
||||
zcl/general/zigbeeclusteronoff.cpp \
|
||||
zcl/measurement/zigbeeclusterrelativehumiditymeasurement.cpp \
|
||||
zcl/measurement/zigbeeclustertemperaturemeasurement.cpp \
|
||||
zcl/security/zigbeeclusteriaszone.cpp \
|
||||
zcl/zigbeecluster.cpp \
|
||||
zcl/zigbeeclusterattribute.cpp \
|
||||
zcl/zigbeeclusterlibrary.cpp \
|
||||
|
|
@ -52,9 +54,11 @@ HEADERS += \
|
|||
backends/deconz/interface/zigbeeinterfacedeconzreply.h \
|
||||
backends/deconz/zigbeebridgecontrollerdeconz.h \
|
||||
backends/deconz/zigbeenetworkdeconz.h \
|
||||
zcl/general/zigbeeclusteridentify.h \
|
||||
zcl/general/zigbeeclusteronoff.h \
|
||||
zcl/measurement/zigbeeclusterrelativehumiditymeasurement.h \
|
||||
zcl/measurement/zigbeeclustertemperaturemeasurement.h \
|
||||
zcl/security/zigbeeclusteriaszone.h \
|
||||
zcl/zigbeecluster.h \
|
||||
zcl/zigbeeclusterattribute.h \
|
||||
zcl/zigbeeclusterlibrary.h \
|
||||
|
|
|
|||
|
|
@ -0,0 +1,217 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea-zigbee.
|
||||
* This project including source code and documentation is protected by copyright law, and
|
||||
* remains the property of nymea GmbH. All rights, including reproduction, publication,
|
||||
* editing and translation, are reserved. The use of this project is subject to the terms of a
|
||||
* license agreement to be concluded with nymea GmbH in accordance with the terms
|
||||
* of use of nymea GmbH, available under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the terms of the GNU
|
||||
* Lesser General Public License as published by the Free Software Foundation; version 3.
|
||||
* this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along with this project.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under contact@nymea.io
|
||||
* or see our FAQ/Licensing Information on https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "zigbeeclusteridentify.h"
|
||||
#include "zigbeenetworkreply.h"
|
||||
#include "loggingcategory.h"
|
||||
#include "zigbeenetwork.h"
|
||||
|
||||
#include <QDataStream>
|
||||
|
||||
ZigbeeClusterIdentify::ZigbeeClusterIdentify(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, ZigbeeCluster::Direction direction, QObject *parent) :
|
||||
ZigbeeCluster(network, node, endpoint, Zigbee::ClusterIdIdentify, direction, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ZigbeeClusterReply *ZigbeeClusterIdentify::identify(quint16 seconds)
|
||||
{
|
||||
ZigbeeNetworkRequest request = createGeneralRequest();
|
||||
|
||||
// Build ZCL frame
|
||||
|
||||
ZigbeeClusterLibrary::FrameControl frameControl;
|
||||
frameControl.frameType = ZigbeeClusterLibrary::FrameTypeClusterSpecific;
|
||||
frameControl.manufacturerSpecific = false;
|
||||
frameControl.direction = ZigbeeClusterLibrary::DirectionClientToServer;
|
||||
frameControl.disableDefaultResponse = false;
|
||||
|
||||
// ZCL header
|
||||
ZigbeeClusterLibrary::Header header;
|
||||
header.frameControl = frameControl;
|
||||
header.command = ZigbeeClusterIdentify::CommandIdentify;
|
||||
header.transactionSequenceNumber = m_transactionSequenceNumber++;
|
||||
|
||||
// Note: the identify time unit is 0.5 seconds
|
||||
QByteArray payload = ZigbeeDataType(seconds * 2).data();
|
||||
|
||||
// Put them together
|
||||
ZigbeeClusterLibrary::Frame frame;
|
||||
frame.header = header;
|
||||
frame.payload = payload;
|
||||
|
||||
request.setTxOptions(Zigbee::ZigbeeTxOptions(Zigbee::ZigbeeTxOptionAckTransmission));
|
||||
request.setAsdu(ZigbeeClusterLibrary::buildFrame(frame));
|
||||
|
||||
ZigbeeClusterReply *zclReply = createClusterReply(request, frame);
|
||||
ZigbeeNetworkReply *networkReply = m_network->sendRequest(request);
|
||||
connect(networkReply, &ZigbeeNetworkReply::finished, this, [this, networkReply, zclReply](){
|
||||
if (!verifyNetworkError(zclReply, networkReply)) {
|
||||
qCWarning(dcZigbeeClusterLibrary()) << "Failed to send request"
|
||||
<< m_node << networkReply->error()
|
||||
<< networkReply->zigbeeApsStatus();
|
||||
finishZclReply(zclReply);
|
||||
return;
|
||||
}
|
||||
|
||||
// The request was successfully sent to the device
|
||||
// Now check if the expected indication response received already
|
||||
if (zclReply->isComplete()) {
|
||||
finishZclReply(zclReply);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return zclReply;
|
||||
}
|
||||
|
||||
ZigbeeClusterReply *ZigbeeClusterIdentify::identifyQuery()
|
||||
{
|
||||
ZigbeeNetworkRequest request = createGeneralRequest();
|
||||
|
||||
// Build ZCL frame
|
||||
|
||||
ZigbeeClusterLibrary::FrameControl frameControl;
|
||||
frameControl.frameType = ZigbeeClusterLibrary::FrameTypeClusterSpecific;
|
||||
frameControl.manufacturerSpecific = false;
|
||||
frameControl.direction = ZigbeeClusterLibrary::DirectionClientToServer;
|
||||
frameControl.disableDefaultResponse = false;
|
||||
|
||||
// ZCL header
|
||||
ZigbeeClusterLibrary::Header header;
|
||||
header.frameControl = frameControl;
|
||||
header.command = ZigbeeClusterIdentify::CommandIdentifyQuery;
|
||||
header.transactionSequenceNumber = m_transactionSequenceNumber++;
|
||||
|
||||
// No payload
|
||||
|
||||
// Put them together
|
||||
ZigbeeClusterLibrary::Frame frame;
|
||||
frame.header = header;
|
||||
|
||||
request.setTxOptions(Zigbee::ZigbeeTxOptions(Zigbee::ZigbeeTxOptionAckTransmission));
|
||||
request.setAsdu(ZigbeeClusterLibrary::buildFrame(frame));
|
||||
|
||||
ZigbeeClusterReply *zclReply = createClusterReply(request, frame);
|
||||
ZigbeeNetworkReply *networkReply = m_network->sendRequest(request);
|
||||
connect(networkReply, &ZigbeeNetworkReply::finished, this, [this, networkReply, zclReply](){
|
||||
if (!verifyNetworkError(zclReply, networkReply)) {
|
||||
qCWarning(dcZigbeeClusterLibrary()) << "Failed to send request"
|
||||
<< m_node << networkReply->error()
|
||||
<< networkReply->zigbeeApsStatus();
|
||||
finishZclReply(zclReply);
|
||||
return;
|
||||
}
|
||||
|
||||
// The request was successfully sent to the device
|
||||
// Now check if the expected indication response received already
|
||||
if (zclReply->isComplete()) {
|
||||
finishZclReply(zclReply);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return zclReply;
|
||||
}
|
||||
|
||||
ZigbeeClusterReply *ZigbeeClusterIdentify::triggerEffect(ZigbeeClusterIdentify::Effect effect, quint8 effectVariant)
|
||||
{
|
||||
ZigbeeNetworkRequest request = createGeneralRequest();
|
||||
|
||||
// Build ZCL frame
|
||||
|
||||
ZigbeeClusterLibrary::FrameControl frameControl;
|
||||
frameControl.frameType = ZigbeeClusterLibrary::FrameTypeClusterSpecific;
|
||||
frameControl.manufacturerSpecific = false;
|
||||
frameControl.direction = ZigbeeClusterLibrary::DirectionClientToServer;
|
||||
frameControl.disableDefaultResponse = false;
|
||||
|
||||
// ZCL header
|
||||
ZigbeeClusterLibrary::Header header;
|
||||
header.frameControl = frameControl;
|
||||
header.command = ZigbeeClusterIdentify::CommandTriggerEffect;
|
||||
header.transactionSequenceNumber = m_transactionSequenceNumber++;
|
||||
|
||||
QByteArray payload;
|
||||
QDataStream stream(&payload, QIODevice::WriteOnly);
|
||||
stream.setByteOrder(QDataStream::LittleEndian);
|
||||
stream << static_cast<quint8>(effect);
|
||||
stream << static_cast<quint8>(effectVariant);
|
||||
|
||||
// Put them together
|
||||
ZigbeeClusterLibrary::Frame frame;
|
||||
frame.header = header;
|
||||
frame.payload = payload;
|
||||
|
||||
request.setTxOptions(Zigbee::ZigbeeTxOptions(Zigbee::ZigbeeTxOptionAckTransmission));
|
||||
request.setAsdu(ZigbeeClusterLibrary::buildFrame(frame));
|
||||
|
||||
ZigbeeClusterReply *zclReply = createClusterReply(request, frame);
|
||||
ZigbeeNetworkReply *networkReply = m_network->sendRequest(request);
|
||||
connect(networkReply, &ZigbeeNetworkReply::finished, this, [this, networkReply, zclReply](){
|
||||
if (!verifyNetworkError(zclReply, networkReply)) {
|
||||
qCWarning(dcZigbeeClusterLibrary()) << "Failed to send request"
|
||||
<< m_node << networkReply->error()
|
||||
<< networkReply->zigbeeApsStatus();
|
||||
finishZclReply(zclReply);
|
||||
return;
|
||||
}
|
||||
|
||||
// The request was successfully sent to the device
|
||||
// Now check if the expected indication response received already
|
||||
if (zclReply->isComplete()) {
|
||||
finishZclReply(zclReply);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return zclReply;
|
||||
}
|
||||
|
||||
void ZigbeeClusterIdentify::setAttribute(const ZigbeeClusterAttribute &attribute)
|
||||
{
|
||||
qCDebug(dcZigbeeCluster()) << "Update attribute" << m_node << m_endpoint << this << static_cast<Attribute>(attribute.id()) << attribute.dataType();
|
||||
if (hasAttribute(attribute.id())) {
|
||||
m_attributes[attribute.id()] = attribute;
|
||||
emit attributeChanged(attribute);
|
||||
} else {
|
||||
m_attributes.insert(attribute.id(), attribute);
|
||||
emit attributeChanged(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
void ZigbeeClusterIdentify::processDataIndication(ZigbeeClusterLibrary::Frame frame)
|
||||
{
|
||||
qCDebug(dcZigbeeCluster()) << "Processing cluster frame" << m_node << m_endpoint << this << frame;
|
||||
|
||||
// TODO: implement identify query response
|
||||
|
||||
// Increase the tsn for continuouse id increasing on both sides
|
||||
m_transactionSequenceNumber = frame.header.transactionSequenceNumber;
|
||||
|
||||
qCWarning(dcZigbeeCluster()) << "Unhandled ZCL indication in" << m_node << m_endpoint << this << frame;
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea-zigbee.
|
||||
* This project including source code and documentation is protected by copyright law, and
|
||||
* remains the property of nymea GmbH. All rights, including reproduction, publication,
|
||||
* editing and translation, are reserved. The use of this project is subject to the terms of a
|
||||
* license agreement to be concluded with nymea GmbH in accordance with the terms
|
||||
* of use of nymea GmbH, available under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the terms of the GNU
|
||||
* Lesser General Public License as published by the Free Software Foundation; version 3.
|
||||
* this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along with this project.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under contact@nymea.io
|
||||
* or see our FAQ/Licensing Information on https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef ZIGBEECLUSTERIDENTIFY_H
|
||||
#define ZIGBEECLUSTERIDENTIFY_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "zcl/zigbeecluster.h"
|
||||
#include "zcl/zigbeeclusterreply.h"
|
||||
|
||||
class ZigbeeNode;
|
||||
class ZigbeeNetwork;
|
||||
class ZigbeeNodeEndpoint;
|
||||
class ZigbeeNetworkReply;
|
||||
|
||||
class ZigbeeClusterIdentify : public ZigbeeCluster
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
friend class ZigbeeNode;
|
||||
friend class ZigbeeNetwork;
|
||||
|
||||
public:
|
||||
|
||||
enum Attribute {
|
||||
AttributeIdentifyTime = 0x0000
|
||||
};
|
||||
Q_ENUM(Attribute)
|
||||
|
||||
enum Command {
|
||||
CommandIdentify = 0x00,
|
||||
CommandIdentifyQuery = 0x01,
|
||||
CommandTriggerEffect = 0x40
|
||||
};
|
||||
Q_ENUM(Command)
|
||||
|
||||
enum Effect {
|
||||
EffectBlink = 0x00,
|
||||
EffectBreath = 0x01,
|
||||
EffectOkay = 0x02,
|
||||
EffectChannelChange = 0x0b,
|
||||
EffectFinishEffect = 0xfe,
|
||||
EffectStopEffect = 0xff
|
||||
};
|
||||
Q_ENUM(Effect)
|
||||
|
||||
explicit ZigbeeClusterIdentify(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, Direction direction, QObject *parent = nullptr);
|
||||
|
||||
ZigbeeClusterReply *identify(quint16 seconds);
|
||||
ZigbeeClusterReply *identifyQuery();
|
||||
ZigbeeClusterReply *triggerEffect(Effect effect, quint8 effectVariant = 0x00);
|
||||
|
||||
private:
|
||||
void setAttribute(const ZigbeeClusterAttribute &attribute) override;
|
||||
|
||||
protected:
|
||||
void processDataIndication(ZigbeeClusterLibrary::Frame frame) override;
|
||||
|
||||
};
|
||||
|
||||
#endif // ZIGBEECLUSTERIDENTIFY_H
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea-zigbee.
|
||||
* This project including source code and documentation is protected by copyright law, and
|
||||
* remains the property of nymea GmbH. All rights, including reproduction, publication,
|
||||
* editing and translation, are reserved. The use of this project is subject to the terms of a
|
||||
* license agreement to be concluded with nymea GmbH in accordance with the terms
|
||||
* of use of nymea GmbH, available under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the terms of the GNU
|
||||
* Lesser General Public License as published by the Free Software Foundation; version 3.
|
||||
* this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along with this project.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under contact@nymea.io
|
||||
* or see our FAQ/Licensing Information on https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "zigbeeclusteriaszone.h"
|
||||
#include "zigbeenetworkreply.h"
|
||||
#include "loggingcategory.h"
|
||||
#include "zigbeenetwork.h"
|
||||
#include "zigbeeutils.h"
|
||||
|
||||
#include <QDataStream>
|
||||
|
||||
ZigbeeClusterIasZone::ZigbeeClusterIasZone(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, ZigbeeCluster::Direction direction, QObject *parent) :
|
||||
ZigbeeCluster(network, node, endpoint, Zigbee::ClusterIdIasZone, direction, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ZigbeeClusterIasZone::setAttribute(const ZigbeeClusterAttribute &attribute)
|
||||
{
|
||||
qCDebug(dcZigbeeCluster()) << "Update attribute" << m_node << m_endpoint << this << static_cast<Attribute>(attribute.id()) << attribute.dataType();
|
||||
if (hasAttribute(attribute.id())) {
|
||||
m_attributes[attribute.id()] = attribute;
|
||||
emit attributeChanged(attribute);
|
||||
} else {
|
||||
m_attributes.insert(attribute.id(), attribute);
|
||||
emit attributeChanged(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
void ZigbeeClusterIasZone::processDataIndication(ZigbeeClusterLibrary::Frame frame)
|
||||
{
|
||||
qCDebug(dcZigbeeCluster()) << "Processing cluster frame" << m_node << m_endpoint << this << frame;
|
||||
|
||||
// Increase the tsn for continuouse id increasing on both sides
|
||||
m_transactionSequenceNumber = frame.header.transactionSequenceNumber;
|
||||
|
||||
switch (m_direction) {
|
||||
case Client:
|
||||
// TODO: handle client frames
|
||||
break;
|
||||
case Server:
|
||||
// If the client cluster sends data to a server cluster (independent which), the command was executed on the device like button pressed
|
||||
if (frame.header.frameControl.direction == ZigbeeClusterLibrary::DirectionServerToClient) {
|
||||
// Read the payload which is
|
||||
ClientCommand command = static_cast<ClientCommand>(frame.header.command);
|
||||
qCDebug(dcZigbeeCluster()) << "Command received from" << m_node << m_endpoint << this << command;
|
||||
switch (command) {
|
||||
case ClientCommandStatusChangedNotification: {
|
||||
QDataStream stream(frame.payload);
|
||||
stream.setByteOrder(QDataStream::LittleEndian);
|
||||
quint16 zoneStatus = 0; quint8 extendedStatus = 0; quint8 zoneId = 0xff; quint16 delay = 0;
|
||||
stream >> zoneStatus >> extendedStatus >> zoneId >> delay;
|
||||
qCDebug(dcZigbeeCluster()) << "IAS zone status notification from" << m_node << m_endpoint << this
|
||||
<< ZoneStatusFlags(zoneStatus) << "Extended status:" << ZigbeeUtils::convertByteToHexString(extendedStatus)
|
||||
<< "Zone ID:" << ZigbeeUtils::convertByteToHexString(zoneId) << "Delay:" << delay << "[s/4]";
|
||||
emit zoneStatusChanged(ZoneStatusFlags(zoneStatus), extendedStatus, zoneId, delay);
|
||||
break;
|
||||
}
|
||||
case ClientCommandZoneEnrollRequest: {
|
||||
QDataStream stream(frame.payload);
|
||||
stream.setByteOrder(QDataStream::LittleEndian);
|
||||
quint16 zoneTypeInt = 0; quint16 manufacturerCode = 0;
|
||||
stream >> zoneTypeInt >> manufacturerCode;
|
||||
ZoneType zoneType = static_cast<ZoneType>(zoneTypeInt);
|
||||
qCDebug(dcZigbeeCluster()) << "IAS zone enroll request from" << m_node << m_endpoint << this
|
||||
<< zoneType << "Manufacturer code:" << ZigbeeUtils::convertUint16ToHexString(manufacturerCode);
|
||||
emit zoneEnrollRequest(zoneType, manufacturerCode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea-zigbee.
|
||||
* This project including source code and documentation is protected by copyright law, and
|
||||
* remains the property of nymea GmbH. All rights, including reproduction, publication,
|
||||
* editing and translation, are reserved. The use of this project is subject to the terms of a
|
||||
* license agreement to be concluded with nymea GmbH in accordance with the terms
|
||||
* of use of nymea GmbH, available under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the terms of the GNU
|
||||
* Lesser General Public License as published by the Free Software Foundation; version 3.
|
||||
* this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along with this project.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under contact@nymea.io
|
||||
* or see our FAQ/Licensing Information on https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef ZIGBEECLUSTERIASZONE_H
|
||||
#define ZIGBEECLUSTERIASZONE_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "zcl/zigbeecluster.h"
|
||||
#include "zcl/zigbeeclusterreply.h"
|
||||
|
||||
class ZigbeeNode;
|
||||
class ZigbeeNetwork;
|
||||
class ZigbeeNodeEndpoint;
|
||||
class ZigbeeNetworkReply;
|
||||
|
||||
class ZigbeeClusterIasZone : public ZigbeeCluster
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
friend class ZigbeeNode;
|
||||
friend class ZigbeeNetwork;
|
||||
|
||||
public:
|
||||
|
||||
enum Attribute {
|
||||
// Zone information attribute set
|
||||
AttributeZoneState = 0x0000,
|
||||
AttributeZoneType = 0x0001,
|
||||
AttributeZoneStatus = 0x0002,
|
||||
// Zone settings attribute set
|
||||
AttributeCieAddress = 0x0010,
|
||||
AttributeZoneId = 0x0011,
|
||||
AttributeNumberOfZoneSensitivityLevelsSupported = 0x0012,
|
||||
AttributeCurrentZoneSensitivityLevel = 0x0013
|
||||
};
|
||||
Q_ENUM(Attribute)
|
||||
|
||||
enum ZoneState {
|
||||
ZoneStateNotEnrolled = 0x00,
|
||||
ZoneStateEnrolled = 0x01
|
||||
};
|
||||
Q_ENUM(ZoneState)
|
||||
|
||||
enum ZoneType {
|
||||
ZoneTypeStandardCIE = 0x0000,
|
||||
ZoneTypeMotionSensor = 0x000d,
|
||||
ZoneTypeContactSwitch = 0x0015,
|
||||
ZoneTypeFireSensor = 0x0028,
|
||||
ZoneTypeWaterSensor = 0x002a,
|
||||
ZoneTypeCarbonMonoxideSensor = 0x002b,
|
||||
ZoneTypePersonalEmergencyDevice = 0x002c,
|
||||
ZoneTypeVibrationMovementSensor = 0x002d,
|
||||
ZoneTypeRemoteControl = 0x010f,
|
||||
ZoneTypeKeyFob = 0x0115,
|
||||
ZoneTypeKeypad = 0x021d,
|
||||
ZoneTypeStandardWarningDevice = 0x0225,
|
||||
ZoneTypeGlassBreakSensor = 0x0226,
|
||||
ZoneTypeSecurityRepater = 0x0229,
|
||||
ZoneTypeInvalidZone = 0xffff
|
||||
};
|
||||
Q_ENUM(ZoneType)
|
||||
|
||||
enum ZoneStatusFlag {
|
||||
ZoneStatusFlagAlarm1 = 0x0001,
|
||||
ZoneStatusFlagAlarm2 = 0x0002,
|
||||
ZoneStatusFlagTamper = 0x0004,
|
||||
ZoneStatusFlagBattery = 0x0008,
|
||||
ZoneStatusFlagSupervisionReports = 0x0010,
|
||||
ZoneStatusFlagRestoreReports = 0x0020,
|
||||
ZoneStatusFlagTrouble = 0x0040,
|
||||
ZoneStatusFlagAcMains = 0x0080,
|
||||
ZoneStatusFlagTest = 0x0100,
|
||||
ZoneStatusFlagBatteryDefect = 0x0200
|
||||
};
|
||||
Q_ENUM(ZoneStatusFlag)
|
||||
Q_DECLARE_FLAGS(ZoneStatusFlags, ZoneStatusFlag)
|
||||
|
||||
enum EnrollResponseCode {
|
||||
EnrollResponseCodeSuccess = 0x00,
|
||||
EnrollResponseCodeNotSupported = 0x01,
|
||||
EnrollResponseCodeNoEnrollPermit = 0x02,
|
||||
EnrollResponseCodeToManyZones = 0x03
|
||||
};
|
||||
Q_ENUM(EnrollResponseCode)
|
||||
|
||||
enum ClientCommand {
|
||||
ClientCommandStatusChangedNotification = 0x00, // M
|
||||
ClientCommandZoneEnrollRequest = 0x01 // M
|
||||
};
|
||||
Q_ENUM(ClientCommand)
|
||||
|
||||
enum ServerCommand {
|
||||
ServerCommandEnrollResponse = 0x00, // M
|
||||
ServerCommandInitNormalOperationMode = 0x01, // O
|
||||
ServerCommandInitTestMode = 0x02 // O
|
||||
};
|
||||
Q_ENUM(ServerCommand)
|
||||
|
||||
explicit ZigbeeClusterIasZone(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, Direction direction, QObject *parent = nullptr);
|
||||
|
||||
// TODO: write server commands
|
||||
|
||||
private:
|
||||
void setAttribute(const ZigbeeClusterAttribute &attribute) override;
|
||||
|
||||
protected:
|
||||
void processDataIndication(ZigbeeClusterLibrary::Frame frame) override;
|
||||
|
||||
signals:
|
||||
void zoneStatusChanged(ZoneStatusFlags zoneStatus, quint8 extendedStatus, quint8 zoneId, quint16 delay);
|
||||
void zoneEnrollRequest(ZoneType zoneType, quint16 manufacturerCode);
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(ZigbeeClusterIasZone::ZoneStatusFlags)
|
||||
|
||||
#endif // ZIGBEECLUSTERIASZONE_H
|
||||
|
|
@ -218,6 +218,8 @@ void ZigbeeCluster::processDataIndication(ZigbeeClusterLibrary::Frame frame)
|
|||
{
|
||||
// Increase the tsn for continuouse id increasing on both sides
|
||||
m_transactionSequenceNumber = frame.header.transactionSequenceNumber;
|
||||
|
||||
// Warn about the unhandled cluster indication, you can override this method in cluster implementations
|
||||
qCWarning(dcZigbeeCluster()) << "Unhandled ZCL indication in" << m_node << m_endpoint << this << frame;
|
||||
}
|
||||
|
||||
|
|
@ -246,6 +248,9 @@ void ZigbeeCluster::processApsDataIndication(const QByteArray &asdu, const Zigbe
|
|||
}
|
||||
}
|
||||
|
||||
// Increase the tsn for continuouse id increasing on both sides
|
||||
m_transactionSequenceNumber = frame.header.transactionSequenceNumber;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -261,11 +266,14 @@ void ZigbeeCluster::processApsDataIndication(const QByteArray &asdu, const Zigbe
|
|||
stream >> attributeId >> type;
|
||||
ZigbeeDataType dataType = ZigbeeClusterLibrary::readDataType(&stream, static_cast<Zigbee::DataType>(type));
|
||||
setAttribute(ZigbeeClusterAttribute(attributeId, dataType));
|
||||
|
||||
// Increase the tsn for continuouse id increasing on both sides
|
||||
m_transactionSequenceNumber = frame.header.transactionSequenceNumber;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Not for a reply or not an attribute report, let the cluster process this message internally
|
||||
processDataIndication(frame);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -352,8 +352,7 @@ void ZigbeeNetwork::loadNetwork()
|
|||
settings.setArrayIndex(n);
|
||||
Zigbee::ClusterId clusterId = static_cast<Zigbee::ClusterId>(settings.value("clusterId", 0).toUInt());
|
||||
ZigbeeCluster *cluster = endpoint->createCluster(clusterId, ZigbeeCluster::Server);
|
||||
//qCDebug(dcZigbeeNetwork()) << "Created" << cluster;
|
||||
endpoint->m_inputClusters.insert(clusterId, cluster);
|
||||
endpoint->addInputCluster(cluster);
|
||||
}
|
||||
settings.endArray(); // inputClusters
|
||||
|
||||
|
|
@ -362,12 +361,11 @@ void ZigbeeNetwork::loadNetwork()
|
|||
settings.setArrayIndex(n);
|
||||
Zigbee::ClusterId clusterId = static_cast<Zigbee::ClusterId>(settings.value("clusterId", 0).toUInt());
|
||||
ZigbeeCluster *cluster = endpoint->createCluster(clusterId, ZigbeeCluster::Client);
|
||||
//qCDebug(dcZigbeeNetwork()) << "Created" << cluster;
|
||||
endpoint->m_outputClusters.insert(clusterId, cluster);
|
||||
endpoint->addOutputCluster(cluster);
|
||||
}
|
||||
settings.endArray(); // outputClusters
|
||||
|
||||
node->m_endpoints.append(endpoint);
|
||||
node->m_endpoints.insert(endpoint->endpointId(), endpoint);
|
||||
}
|
||||
|
||||
settings.endArray(); // endpoints
|
||||
|
|
|
|||
|
|
@ -68,23 +68,17 @@ ZigbeeAddress ZigbeeNode::extendedAddress() const
|
|||
|
||||
QList<ZigbeeNodeEndpoint *> ZigbeeNode::endpoints() const
|
||||
{
|
||||
return m_endpoints;
|
||||
return m_endpoints.values();
|
||||
}
|
||||
|
||||
bool ZigbeeNode::hasEndpoint(quint8 endpointId) const
|
||||
{
|
||||
return getEndpoint(endpointId) != nullptr;
|
||||
return m_endpoints.keys().contains(endpointId);
|
||||
}
|
||||
|
||||
ZigbeeNodeEndpoint *ZigbeeNode::getEndpoint(quint8 endpointId) const
|
||||
{
|
||||
foreach (ZigbeeNodeEndpoint *endpoint, m_endpoints) {
|
||||
if (endpoint->endpointId() == endpointId) {
|
||||
return endpoint;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return m_endpoints.value(endpointId);
|
||||
}
|
||||
|
||||
ZigbeeNode::NodeType ZigbeeNode::nodeType() const
|
||||
|
|
@ -641,7 +635,7 @@ void ZigbeeNode::initEndpoint(quint8 endpointId)
|
|||
ZigbeeNodeEndpoint *endpoint = nullptr;
|
||||
if (!hasEndpoint(endpointId)) {
|
||||
endpoint = new ZigbeeNodeEndpoint(m_network, this, endpointId, this);
|
||||
m_endpoints.append(endpoint);
|
||||
m_endpoints.insert(endpoint->endpointId(), endpoint);
|
||||
} else {
|
||||
endpoint = getEndpoint(endpointId);
|
||||
}
|
||||
|
|
@ -694,7 +688,7 @@ void ZigbeeNode::initEndpoint(quint8 endpointId)
|
|||
void ZigbeeNode::initBasicCluster()
|
||||
{
|
||||
// FIXME: check if we want to read from all endpoints the basic cluster information or only from the first
|
||||
ZigbeeClusterBasic *basicCluster = m_endpoints.first()->inputCluster<ZigbeeClusterBasic>(Zigbee::ClusterIdBasic);
|
||||
ZigbeeClusterBasic *basicCluster = endpoints().first()->inputCluster<ZigbeeClusterBasic>(Zigbee::ClusterIdBasic);
|
||||
if (!basicCluster) {
|
||||
qCWarning(dcZigbeeNode()) << "Could not find basic cluster on" << this << "Set the node to initialized anyways.";
|
||||
// Set the device initialized any ways since this ist just for convinience
|
||||
|
|
@ -718,7 +712,7 @@ void ZigbeeNode::readManufacturerName(ZigbeeClusterBasic *basicCluster)
|
|||
bool valueOk = false;
|
||||
QString manufacturerName = basicCluster->attribute(attributeId).dataType().toString(&valueOk);
|
||||
if (valueOk) {
|
||||
m_endpoints.first()->m_manufacturerName = manufacturerName;
|
||||
endpoints().first()->m_manufacturerName = manufacturerName;
|
||||
} else {
|
||||
qCWarning(dcZigbeeNode()) << "Could not convert manufacturer name attribute data to string" << basicCluster->attribute(attributeId).dataType();
|
||||
}
|
||||
|
|
@ -742,7 +736,7 @@ void ZigbeeNode::readManufacturerName(ZigbeeClusterBasic *basicCluster)
|
|||
bool valueOk = false;
|
||||
QString manufacturerName = attributeStatusRecord.dataType.toString(&valueOk);
|
||||
if (valueOk) {
|
||||
m_endpoints.first()->m_manufacturerName = manufacturerName;
|
||||
endpoints().first()->m_manufacturerName = manufacturerName;
|
||||
} else {
|
||||
qCWarning(dcZigbeeNode()) << "Could not convert manufacturer name attribute data to string" << attributeStatusRecord.dataType;
|
||||
}
|
||||
|
|
@ -766,7 +760,7 @@ void ZigbeeNode::readModelIdentifier(ZigbeeClusterBasic *basicCluster)
|
|||
bool valueOk = false;
|
||||
QString modelIdentifier = basicCluster->attribute(attributeId).dataType().toString(&valueOk);
|
||||
if (valueOk) {
|
||||
m_endpoints.first()->m_modelIdentifier= modelIdentifier;
|
||||
endpoints().first()->m_modelIdentifier= modelIdentifier;
|
||||
} else {
|
||||
qCWarning(dcZigbeeNode()) << "Could not convert model identifier attribute data to string" << basicCluster->attribute(attributeId).dataType();
|
||||
}
|
||||
|
|
@ -790,7 +784,7 @@ void ZigbeeNode::readModelIdentifier(ZigbeeClusterBasic *basicCluster)
|
|||
bool valueOk = false;
|
||||
QString modelIdentifier = attributeStatusRecord.dataType.toString(&valueOk);
|
||||
if (valueOk) {
|
||||
m_endpoints.first()->m_modelIdentifier = modelIdentifier;
|
||||
endpoints().first()->m_modelIdentifier = modelIdentifier;
|
||||
} else {
|
||||
qCWarning(dcZigbeeNode()) << "Could not convert model identifier attribute data to string" << attributeStatusRecord.dataType;
|
||||
}
|
||||
|
|
@ -820,7 +814,7 @@ void ZigbeeNode::readSoftwareBuildId(ZigbeeClusterBasic *basicCluster)
|
|||
bool valueOk = false;
|
||||
QString softwareBuildId = attributeStatusRecord.dataType.toString(&valueOk);
|
||||
if (valueOk) {
|
||||
m_endpoints.first()->m_softwareBuildId = softwareBuildId;
|
||||
endpoints().first()->m_softwareBuildId = softwareBuildId;
|
||||
} else {
|
||||
qCWarning(dcZigbeeNode()) << "Could not convert software build id attribute data to string" << attributeStatusRecord.dataType;
|
||||
}
|
||||
|
|
@ -845,7 +839,7 @@ void ZigbeeNode::handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataInd
|
|||
endpoint = new ZigbeeNodeEndpoint(m_network, this, indication.sourceEndpoint, this);
|
||||
endpoint->setProfile(static_cast<Zigbee::ZigbeeProfile>(indication.profileId));
|
||||
// Note: the endpoint is not initializd yet, but keep it anyways
|
||||
m_endpoints.append(endpoint);
|
||||
m_endpoints.insert(endpoint->endpointId(), endpoint);
|
||||
}
|
||||
|
||||
endpoint->handleZigbeeClusterLibraryIndication(indication);
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ private:
|
|||
|
||||
ZigbeeNetwork *m_network;
|
||||
ZigbeeDeviceObject *m_deviceObject = nullptr;
|
||||
QList<ZigbeeNodeEndpoint *> m_endpoints;
|
||||
QHash<quint8, ZigbeeNodeEndpoint *> m_endpoints;
|
||||
|
||||
// Node descriptor information
|
||||
QByteArray m_nodeDescriptorRawData;
|
||||
|
|
@ -277,6 +277,7 @@ private:
|
|||
// Init methods
|
||||
int m_requestRetry = 0;
|
||||
QList<quint8> m_uninitializedEndpoints;
|
||||
|
||||
void initNodeDescriptor();
|
||||
void initPowerDescriptor();
|
||||
void initEndpoints();
|
||||
|
|
|
|||
|
|
@ -112,6 +112,11 @@ QList<ZigbeeCluster *> ZigbeeNodeEndpoint::outputClusters() const
|
|||
return m_outputClusters.values();
|
||||
}
|
||||
|
||||
ZigbeeCluster *ZigbeeNodeEndpoint::getOutputCluster(Zigbee::ClusterId clusterId) const
|
||||
{
|
||||
return m_outputClusters.value(clusterId);
|
||||
}
|
||||
|
||||
bool ZigbeeNodeEndpoint::hasOutputCluster(Zigbee::ClusterId clusterId) const
|
||||
{
|
||||
return m_outputClusters.keys().contains(clusterId);
|
||||
|
|
@ -161,18 +166,27 @@ void ZigbeeNodeEndpoint::setSoftwareBuildId(const QString &softwareBuildId)
|
|||
ZigbeeCluster *ZigbeeNodeEndpoint::createCluster(Zigbee::ClusterId clusterId, ZigbeeCluster::Direction direction)
|
||||
{
|
||||
switch (clusterId) {
|
||||
// General
|
||||
case Zigbee::ClusterIdBasic:
|
||||
return new ZigbeeClusterBasic(m_network, m_node, this, direction, this);
|
||||
break;
|
||||
case Zigbee::ClusterIdOnOff:
|
||||
return new ZigbeeClusterOnOff(m_network, m_node, this, direction, this);
|
||||
break;
|
||||
case Zigbee::ClusterIdIdentify:
|
||||
return new ZigbeeClusterIdentify(m_network, m_node, this, direction, this);
|
||||
break;
|
||||
// Measurement
|
||||
case Zigbee::ClusterIdTemperatureMeasurement:
|
||||
return new ZigbeeClusterTemperatureMeasurement(m_network, m_node, this, direction, this);
|
||||
break;
|
||||
case Zigbee::ClusterIdRelativeHumidityMeasurement:
|
||||
return new ZigbeeClusterRelativeHumidityMeasurement(m_network, m_node, this, direction, this);
|
||||
break;
|
||||
// Security
|
||||
case Zigbee::ClusterIdIasZone:
|
||||
return new ZigbeeClusterIasZone(m_network, m_node, this, direction, this);
|
||||
break;
|
||||
default:
|
||||
// Return a default cluster since we have no special implementation for this cluster
|
||||
return new ZigbeeCluster(m_network, m_node, this, clusterId, direction, this);
|
||||
|
|
@ -182,11 +196,13 @@ ZigbeeCluster *ZigbeeNodeEndpoint::createCluster(Zigbee::ClusterId clusterId, Zi
|
|||
void ZigbeeNodeEndpoint::addInputCluster(ZigbeeCluster *cluster)
|
||||
{
|
||||
m_inputClusters.insert(cluster->clusterId(), cluster);
|
||||
emit inputClusterAdded(cluster);
|
||||
}
|
||||
|
||||
void ZigbeeNodeEndpoint::addOutputCluster(ZigbeeCluster *cluster)
|
||||
{
|
||||
m_outputClusters.insert(cluster->clusterId(), cluster);
|
||||
emit outputClusterAdded(cluster);
|
||||
}
|
||||
|
||||
void ZigbeeNodeEndpoint::handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataIndication &indication)
|
||||
|
|
@ -203,7 +219,7 @@ void ZigbeeNodeEndpoint::handleZigbeeClusterLibraryIndication(const Zigbee::Apsd
|
|||
if (!cluster) {
|
||||
cluster = createCluster(static_cast<Zigbee::ClusterId>(indication.clusterId), ZigbeeCluster::Client);
|
||||
qCWarning(dcZigbeeEndpoint()) << "Received a ZCL indication for a cluster which does not exist yet on" << m_node << this << "Creating" << cluster;
|
||||
m_outputClusters.insert(cluster->clusterId(), cluster);
|
||||
addOutputCluster(cluster);
|
||||
}
|
||||
break;
|
||||
case ZigbeeClusterLibrary::DirectionServerToClient:
|
||||
|
|
@ -212,7 +228,7 @@ void ZigbeeNodeEndpoint::handleZigbeeClusterLibraryIndication(const Zigbee::Apsd
|
|||
if (!cluster) {
|
||||
cluster = createCluster(static_cast<Zigbee::ClusterId>(indication.clusterId), ZigbeeCluster::Server);
|
||||
qCWarning(dcZigbeeEndpoint()) << "Received a ZCL indication for a cluster which does not exist yet on" << m_node << this << "Creating" << cluster;
|
||||
m_inputClusters.insert(cluster->clusterId(), cluster);
|
||||
addInputCluster(cluster);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -220,11 +236,6 @@ void ZigbeeNodeEndpoint::handleZigbeeClusterLibraryIndication(const Zigbee::Apsd
|
|||
cluster->processApsDataIndication(indication.asdu, frame);
|
||||
}
|
||||
|
||||
ZigbeeCluster *ZigbeeNodeEndpoint::getOutputCluster(Zigbee::ClusterId clusterId) const
|
||||
{
|
||||
return m_outputClusters.value(clusterId);
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, ZigbeeNodeEndpoint *endpoint)
|
||||
{
|
||||
debug.nospace().noquote() << "ZigbeeNodeEndpoint(" << ZigbeeUtils::convertByteToHexString(endpoint->endpointId());
|
||||
|
|
|
|||
|
|
@ -36,11 +36,16 @@
|
|||
|
||||
// Import all implemented cluster types
|
||||
#include "zcl/zigbeecluster.h"
|
||||
|
||||
#include "zcl/general/zigbeeclusterbasic.h"
|
||||
#include "zcl/general/zigbeeclusteronoff.h"
|
||||
#include "zcl/general/zigbeeclusteridentify.h"
|
||||
|
||||
#include "zcl/measurement/zigbeeclustertemperaturemeasurement.h"
|
||||
#include "zcl/measurement/zigbeeclusterrelativehumiditymeasurement.h"
|
||||
|
||||
#include "zcl/security/zigbeeclusteriaszone.h"
|
||||
|
||||
class ZigbeeNode;
|
||||
class ZigbeeNetwork;
|
||||
|
||||
|
|
@ -82,6 +87,7 @@ public:
|
|||
ZigbeeCluster *getOutputCluster(Zigbee::ClusterId clusterId) const;
|
||||
bool hasOutputCluster(Zigbee::ClusterId clusterId) const;
|
||||
|
||||
// Convinience cast methods for getting a specific cluster object
|
||||
template<typename T>
|
||||
inline T* inputCluster(Zigbee::ClusterId clusterId)
|
||||
{
|
||||
|
|
@ -123,9 +129,6 @@ private:
|
|||
void setModelIdentifier(const QString &modelIdentifier);
|
||||
void setSoftwareBuildId(const QString &softwareBuildId);
|
||||
|
||||
// Cluster commands
|
||||
//virtual void setClusterAttribute(Zigbee::ClusterId clusterId, const ZigbeeClusterAttribute &attribute = ZigbeeClusterAttribute()) = 0;
|
||||
|
||||
ZigbeeCluster *createCluster(Zigbee::ClusterId clusterId, ZigbeeCluster::Direction direction);
|
||||
|
||||
void addInputCluster(ZigbeeCluster *cluster);
|
||||
|
|
@ -134,12 +137,14 @@ private:
|
|||
void handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataIndication &indication);
|
||||
|
||||
signals:
|
||||
void inputClusterAdded(ZigbeeCluster *cluster);
|
||||
void outputClusterAdded(ZigbeeCluster *cluster);
|
||||
|
||||
void clusterAttributeChanged(ZigbeeCluster *cluster, const ZigbeeClusterAttribute &attribute);
|
||||
|
||||
void manufacturerNameChanged(const QString &manufacturerName);
|
||||
void modelIdentifierChanged(const QString &modelIdentifier);
|
||||
void softwareBuildIdChanged(const QString &softwareBuildId);
|
||||
|
||||
};
|
||||
|
||||
QDebug operator<<(QDebug debug, ZigbeeNodeEndpoint *endpoint);
|
||||
|
|
|
|||
Loading…
Reference in New Issue