├── .gitignore ├── deconz_cli.pro ├── deconz_cli_plugin.h ├── README_v2.04.35.md ├── README.md └── deconz_cli_plugin.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | Makefile.Debug 3 | Makefile.Release 4 | release 5 | .qmake.stash 6 | -------------------------------------------------------------------------------- /deconz_cli.pro: -------------------------------------------------------------------------------- 1 | TARGET = deconz_cli_plugin 2 | 3 | # common configuration for deCONZ plugins 4 | 5 | TARGET = $$qtLibraryTarget($$TARGET) 6 | 7 | DEFINES += DECONZ_DLLSPEC=Q_DECL_IMPORT 8 | 9 | win32:LIBS+= -L../.. -ldeCONZ1 10 | unix:LIBS+= -L../.. -ldeCONZ -lcrypt 11 | win32:CONFIG += dll 12 | 13 | TEMPLATE = lib 14 | CONFIG += plugin \ 15 | += debug_and_release 16 | 17 | INCLUDEPATH += ../.. \ 18 | ../../common 19 | 20 | QMAKE_CXXFLAGS += -Wno-attributes 21 | 22 | HEADERS = deconz_cli_plugin.h 23 | 24 | SOURCES = deconz_cli_plugin.cpp 25 | 26 | QT += network -------------------------------------------------------------------------------- /deconz_cli_plugin.h: -------------------------------------------------------------------------------- 1 | /* 2 | * deconz_cli_plugin.h 3 | * 4 | * Created on: Dec 18, 2016 5 | * Author: ma-ca 6 | * 7 | */ 8 | 9 | #ifndef DECONZ_CLI_PLUGIN_H_ 10 | #define DECONZ_CLI_PLUGIN_H_ 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #if DECONZ_LIB_VERSION < 0x010100 17 | #error "The basic aps plugin requires at least deCONZ library version 1.1.0." 18 | #endif 19 | 20 | class QTcpServer; 21 | 22 | /*! \class CliPlugin 23 | Plugin to cli support for sending and receiving APS frames via 24 | APSDE-DATA.request, APSDE-DATA.confirm and APSDE-DATA.indication primitives. 25 | The plugin opens a TCP socket on port 5008 26 | 27 | */ 28 | class CliPlugin : public QObject, public deCONZ::NodeInterface { 29 | Q_OBJECT 30 | Q_INTERFACES(deCONZ::NodeInterface) 31 | #if QT_VERSION >= 0x050000 32 | Q_PLUGIN_METADATA(IID "CliPlugin") 33 | #endif 34 | 35 | public: 36 | explicit CliPlugin(QObject *parent = 0); 37 | ~CliPlugin(); 38 | // node interface 39 | const char *name(); 40 | bool hasFeature(Features feature); 41 | 42 | public Q_SLOTS: 43 | void apsdeDataIndication(const deCONZ::ApsDataIndication &ind); 44 | void apsdeDataConfirm(const deCONZ::ApsDataConfirm &conf); 45 | std::string getApsIndSrcAddr(const deCONZ::ApsDataIndication &ind); 46 | const char *getClusterName(uint16_t clusterid); 47 | const char *getAttributeTypeIdName(uint8_t attrtypeid); 48 | void handleZdpSimpleResponse(const deCONZ::ApsDataIndication &ind); 49 | void handleZdpActiveEpResponse(const deCONZ::ApsDataIndication &ind); 50 | void handleZdpMatchResponse(const deCONZ::ApsDataIndication &ind); 51 | void handleZdpLqiResponse(const deCONZ::ApsDataIndication &ind); 52 | void handleZdpBindResponse(const deCONZ::ApsDataIndication &ind); 53 | void handleZdpPowerResponse(const deCONZ::ApsDataIndication &ind); 54 | void handleZdpNwkAddrResponse(const deCONZ::ApsDataIndication &ind); 55 | void handleZclReadAttrbuteResponse(const deCONZ::ApsDataIndication &ind, const deCONZ::ZclFrame &zclframe); 56 | void handleZclReportConfigResponse(const deCONZ::ApsDataIndication &ind, const deCONZ::ZclFrame &zclframe); 57 | void handleZclReportAttributeResponse(const deCONZ::ApsDataIndication &ind, const deCONZ::ZclFrame &zclframe); 58 | void handleZclDiscoverAttributesResponse(const deCONZ::ApsDataIndication &ind, const deCONZ::ZclFrame &zclframe); 59 | void handleZclServerToClientResponse(const deCONZ::ApsDataIndication &ind, const deCONZ::ZclFrame &zclframe); 60 | void clientDisconnected(); 61 | void receiveCommand(); 62 | void readReceivedBytes(); 63 | void writeToConnectedClients(char *data); 64 | bool sendZclReadAttributeRequest(int shortaddr, int ep, int cluster, int attrid); 65 | bool sendZclAttributeGenericRequest(int shortaddr, int ep, int cluster, QByteArray payload, int manu); 66 | bool sendZclAttributeRequest(int shortaddr, int ep, int cluster, QByteArray payload); 67 | bool sendZclAttributeManuSpecRequest(int shortaddr, int ep, int cluster, QByteArray payload, int manu); 68 | bool sendZclCmdGenericRequest(deCONZ::Address dstAddress, uint8_t ep, uint16_t cluster, QByteArray payload, int manu); 69 | bool sendZclCmdRequest(deCONZ::Address dstAddress, uint8_t ep, uint16_t cluster, QByteArray payload); 70 | bool sendZclCmdManuSpecRequest(deCONZ::Address dstAddress, uint8_t ep, uint16_t cluster, QByteArray payload, int manu); 71 | bool sendZclDefaultResponse(const deCONZ::ApsDataIndication &ind, const deCONZ::ZclFrame &zclFrame, quint8 status); 72 | bool sendZdpMatchCmdRequest(int profile, int cluster); 73 | bool sendZdpPermitJoinCmdRequest(int shortaddr); 74 | bool sendZdpCmdRequest(deCONZ::Address dstAddress, uint16_t cluster, QByteArray asdu); 75 | bool sendZclTimeAttributes(int shortaddr, int ep); 76 | void get_dst_start_end(int finddst /* 0 or 1 */, time_t *time_dst_start, time_t *time_dst_end, int *time_dst_shift); 77 | void get_dst(time_t *time_dst_start, time_t *time_dst_end, int *time_dst_shift); 78 | long get_epoch_since_2000(time_t tinput); 79 | int get_gmtdiff(); 80 | void printhelp(); 81 | 82 | private: 83 | std::list m_apsReqQueue; //!< queue of active APS requests 84 | deCONZ::ApsController *m_apsCtrl; //!< pointer to ApsController instance 85 | QTcpServer *tcpServer; 86 | QList clientConnection; 87 | quint8 m_zclSeq; //!< ZCL sequence number of Zcl_req 88 | int m_shortaddr; //!< short address 89 | int m_ep; //!< endpoint id 90 | int m_attrid; //!< attribute id 91 | int m_profile; 92 | int m_cluster; 93 | bool m_zdpmatchreq; 94 | bool m_readattr; //!< read basic cluster attributes 95 | int m_readclust; //!< read basic attributes on cluster 96 | bool m_isconnected; 97 | }; 98 | 99 | #endif /* DECONZ_CLI_PLUGIN_H_ */ 100 | -------------------------------------------------------------------------------- /README_v2.04.35.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | The deconz-cli-plugin provides an API to access devices with ZigBee Home Automation (HA) and ZigBee Light Link (ZLL). It can be used as command line interface (cli) to send and receive raw ZigBee commands with deCONZ software. 5 | 6 | As hardware the [RaspBee](http://www.dresden-elektronik.de/funktechnik/solutions/wireless-light-control/raspbee?L=1) ZigBee Shield for Raspberry Pi is used to directly communicate with the ZigBee devices. 7 | 8 | The deconz-cli-plugin requires the [deCONZ software](http://www.dresden-elektronik.de/funktechnik/products/software/pc/deconz?L=1). 9 | 10 | The deconz-cli-plugin opens a socket on port TCP 5008 and listens for incoming connections. The plugin allows to send commands and receive responses to and from ZigBee devices like Philips Hue lights. The plugin translates the incoming commands to APS-Data and passes the data to the RaspBee firmware. 11 | 12 | ## Preparation and configuration steps 13 | 14 | In the standard Raspbian Linux distribution the serial interface /dev/ttyAMA0 is set up as a serial console, i.e. for boot message output. Since the RaspBee ZigBee firmware uses the same interface to communicate with the control software, the following changes must be done to “free” the UART. 15 | 16 | In the file /boot/cmdline.txt: 17 | 18 | If present remove the text `console=serial0,115200` or `console=ttyAMA0,115200` 19 | 20 | Raspbian wheezy: comment out the following line in `/etc/inittab` 21 | 22 | #Spawn a getty on Raspberry Pi serial line 23 | T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100 24 | 25 | Raspian jessie: 26 | 27 | sudo systemctl disable serial-getty@ttyAMA0.service 28 | 29 | Raspberry Pi3 jessie: 30 | in the file /boot/config.txt add the line 31 | 32 | enable_uart=1 33 | 34 | Reboot 35 | 36 | ## Installation and compilation steps 37 | 38 | ##### Install deCONZ and development package 39 | Download and install Qt 4.8 40 | 41 | sudo apt-get install libqt4-core 42 | 43 | 1. Download deCONZ package 44 | 45 | 46 | wget http://www.dresden-elektronik.de/rpi/deconz/deconz-latest.deb 47 | -or- 48 | wget http://www.dresden-elektronik.de/rpi/deconz/deconz-2.04.35.deb 49 | 50 | 2. Install deCONZ package 51 | 52 | 53 | sudo dpkg -i deconz-2.04.35.deb 54 | 55 | 3. Download deCONZ development package 56 | 57 | 58 | wget http://www.dresden-elektronik.de/rpi/deconz-dev/deconz-dev-latest.deb 59 | -or- 60 | wget http://www.dresden-elektronik.de/rpi/deconz-dev/deconz-dev-2.04.35.deb 61 | 62 | 4. Install deCONZ development package 63 | 64 | 65 | sudo dpkg -i deconz-dev-2.04.35.deb 66 | 67 | 5. Install Qt4 development packages 68 | 69 | 70 | sudo apt-get install qt4-qmake libqt4-dev 71 | 72 | 6. (Optional) Install GCFFlasher tool to install new firmware or restart RaspBee 73 | 74 | 75 | wget http://www.dresden-elektronik.de/rpi/gcfflasher/gcfflasher-latest.deb 76 | -or- 77 | wget http://www.dresden-elektronik.de/rpi/gcfflasher/gcfflasher-2.10.deb 78 | sudo dpkg -i gcfflasher-2.10.deb 79 | 80 | 81 | ##### Compile the plugin 82 | 83 | 1. Compile the plugin 84 | 85 | 86 | cd libs/zigbee 87 | qmake-qt4 && make 88 | 89 | 2. Copy the plugin to the deConz plugins folder 90 | 91 | 92 | cp libpilight_plugin.so /usr/share/deCONZ/plugins 93 | 94 | 3. Optional remove or move the existing rest plugin (because for some strange reason the rest plugin removes bindings on all devices which makes reporting impossible) 95 | 96 | 97 | mv /usr/share/deCONZ/plugins/libde_rest_plugin.so /usr/share/deCONZ/libde_rest_plugin.so_NOT_USED 98 | 99 | 4. Copy startup script 100 | 101 | 102 | sudo cp deconz /etc/init.d 103 | sudo chmod +x /etc/init.d/deconz 104 | sudo update-rc.d deconz defaults 105 | 106 | 5. Start deConz (parameter --dbg-info=1 only needed for debug messages). It might be necessary to start with UI when starting and forming the ZigBee network for the first time. Thereafter the UI is not needed anymore. 107 | 108 | with UI (requires X-server) 109 | 110 | deCONZ --dbg-info=1 111 | 112 | without UI 113 | 114 | sudo service deconz start 115 | 116 | -or- 117 | 118 | DISPLAY=:0.0 deCONZ --dbg-info=1 119 | 120 | Hardware requirements 121 | --------------------- 122 | 123 | * Raspberry Pi 124 | * [RaspBee](http://www.dresden-elektronik.de/funktechnik/solutions/wireless-light-control/raspbee?L=1) ZigBee Shield for Raspberry Pi 125 | * or a [ConBee](https://www.dresden-elektronik.de/funktechnik/solutions/wireless-light-control/conbee/?L=1) USB dongle 126 | 127 | Usage 128 | ===== 129 | 130 | The plugin is started when the deConz software is started. The deConz software must be running before starting pilight. 131 | 132 | 133 | Run netcat to connect to port 5008. Type `help` to show usage. 134 | 135 | nc localhost 5008 136 | 137 | 138 | Command | Description 139 | ------- | ----------- 140 | `r ` | read attributes 141 | `b ` | read basic attributes 142 | `b ` | read basic attributes on cluster 143 | `m ` | send match descriptor request (discover cluster) 144 | `p ` | permit Joining on device (coordinator = 0) 145 | `zclattr ` | send ZCL attribute request 146 | `zclcmd ` | send ZCL command request 147 | `zclcmdgrp ` | send ZCL command request to group 148 | `zdpcmd ` | send ZDP command request 149 | `zclattrmanu ` | send ZCL attribute request manufacturer specific 150 | `zclcmdmanu ` | send ZCL command request manufacturer specific 151 | 152 | 153 | Response | Description 154 | --------| ----------- 155 | <-LQI 0x84182600xxxxxxxx 06 0 1 0x001FEE00xxxxxxxx 0x4157 1 1 2 01 02 56 | LQI neighbor reponse 156 | <-ZCL attribute report 0x001FEE00xxxxxxxx 0x0006 1 00 00 10 00 | ZCL attribute report (from cluster 0x0006) 157 | <-ZCL serverToClient 0x00124B00xxxxxxxx 1 for cluster 0x0500 10 00 00 00 00 00 | ZCL attribute (with extaddr) 158 | <-ZCL serverToClient 0x3B58 1 for cluster 0x0500 10 00 00 00 00 00 | ZCL attribute (with shortaddr) 159 | <-APS attr 0x001FEE00xxxxxxxx 5 0x0702 0x0000 0x25 0E DA 76 57 00 00 00 04 00 2A 00 00 00 | APS data 160 | 161 | <-LQI [source addr] [neighborTableEntries] [start] [count] [extaddr] [shortaddr] [device type] [rxOnWhenIdle] [relationship] [permitJoin] [depth] [lqi] 162 | <-APS attr [source extaddr] [endpoint] [cluster] [attributeid] [typeid] [attr value] [...more attributes] 163 | 164 | 165 | 166 | LQI | Description 167 | --- | ----------- 168 | source addr | LQI neighbor table from 169 | neighborTableEntries | total table entries 170 | start | current table entry start index 171 | count | number of table entries in this frame 172 | extaddr | table entry extended address 173 | shortaddr | table entry short address 174 | device type | 0 = coordinator, 1 = router, 2 = end device, 3 = unknown 175 | rxOnWhenIdle | receiver on when idle 0 or 1 176 | relationship | relationship 0 = neighbor is the parent, 1 = child, 2 = sibling, 3 = None of the above, 4 = previous child 177 | permitJoin | permit Join 0 = neighbor is not accepting join requests, 1 = accepting join requests 178 | depth | The tree depth of the neighbor device 179 | lqi | The estimated link quality (range 0x00 - 0xff) 180 | 181 | 182 | Supported ZigBee devices 183 | ======================== 184 | 185 | Successfully tested with follwing ZigBee devices. 186 | 187 | Device | Vendor 188 | ------ | ------ 189 | Light Philps Hue White | Philips, LWB006 190 | Light | OSRAM, Classic B40 TW - LIGHTIFY 191 | Smart Plug | OSRAM, Plug 01 192 | Movement Sensor | Bitron Home, 902010/22 193 | Motion Sensor | Philips Hue motion sensor 194 | Smoke Detektor with siren | Bitron Home, 902010/24 195 | Smart Plug with Metering | Bitron Home, 902010/25 196 | Thermostat | Bitron Home, 902010/32 197 | Switch | ubisys, S2 (5502) 198 | Button | Philips Hue dimmer switch 199 | Button | [Xiaomi Smart Wireless Switch](https://www.banggood.com/Original-Xiaomi-Smart-Wireless-Switch-p-1045081.html) 200 | Temperature | Xiaomi Temperature and Humidity Smart Sensor 201 | 202 | Reference Manuals 203 | ----------------- 204 | [Bitron](http://www.bitronvideo.eu/index.php/service/bitron-home/downloads/) 205 | 206 | [Ubisys](http://www.ubisys.de/en/smarthome/products-s2.html) 207 | 208 | Reset ZigBee device to factory default 209 | -------------------------------------- 210 | 211 | [Philips Hue Light](http://www2.meethue.com/de-de/support/faq-detail/?productCategoryId=131288) 212 | Can I factory reset a Hue light with the Hue dimmer switch? 213 | Yes you can, press and hold the ON and OFF button simultaneously until the LED light on the dimmer switch turns green (Note: the lamp is blinking during this process. Hue Beyond and Hue Phoenix are excluded). 214 | 215 | Osram Lightify Light turn the light on and off five times and wait 5 seconds inbetween. 216 | 217 | 218 | [Bitron Video](http://www.bitronvideo.eu/index.php/service/bitron-home/zb-registrierung/) 219 | Press and hold the button for 10 seconds. 220 | 221 | 222 | Troubleshooting 223 | --------------- 224 | 225 | After a while (rarely and randomly) a device keeps responding with an error status 0xD0 when reading attributes or sending other commands. Sometimes this just gets resolved without doing anything but waiting. However, sometimes it turns out that the device shortaddress has changed for some unknown reason. Oberserving the LQI neibortable entries shows the new shortaddress. 226 | 227 |
228 | zclattr 0x4157 5 0x0702 0000000004 --> send OK
229 | <-APS-DATA.confirm FAILED status 0xD0, id = 0x25, srcEp = 0x01, dstcEp = 0x05, dstAddr = 0x4157
230 | <-LQI 0x00178801xxxxxxxx   07 0 1 0x001FEE00xxxxxxxx 0xA855 1 1 3 01 01 F7
231 | zclattr 0xA855 5 0x0702 0000000004 --> send OK
232 | 
233 | 234 | Restart RaspBee 235 | --------------- 236 | 237 | 238 | sudo service deconz stop; sudo GCFFlasher -r; sudo service deconz start 239 | 240 | 241 | Compilation Error 242 | ---------------- 243 | 244 | ``` 245 | g++ -c -pipe -Wno-attributes -Wall -O2 -Wall -W -D_REENTRANT -fPIC -DDECONZ_DLLSPEC=Q_DECL_IMPORT -DARCH_ARM -DARCH_ARMV7 -DQT_NO_DEBUG -DQT_PLUGIN -DQT_GUI_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -I. -I../.. -I../../common -isystem /usr/include -isystem /usr/include/arm-linux-gnueabihf/qt5 -isystem /usr/include/arm-linux-gnueabihf/qt5/QtGui -isystem /usr/include/arm-linux-gnueabihf/qt5/QtNetwork -isystem /usr/include/arm-linux-gnueabihf/qt5/QtCore -Irelease -I/usr/lib/arm-linux-gnueabihf/qt5/mkspecs/linux-g++ -o release/deconz_cli_plugin.o deconz_cli_plugin.cpp 246 | In file included from /usr/include/c++/6/bits/stl_algo.h:59:0, 247 | from /usr/include/c++/6/algorithm:62, 248 | from /usr/include/arm-linux-gnueabihf/qt5/QtCore/qglobal.h:94, 249 | from /usr/include/arm-linux-gnueabihf/qt5/QtCore/qnamespace.h:43, 250 | from /usr/include/arm-linux-gnueabihf/qt5/QtCore/qobjectdefs.h:48, 251 | from /usr/include/arm-linux-gnueabihf/qt5/QtCore/qobject.h:46, 252 | from /usr/include/arm-linux-gnueabihf/qt5/QtCore/qplugin.h:43, 253 | from /usr/include/arm-linux-gnueabihf/qt5/QtCore/QtPlugin:1, 254 | from deconz_cli_plugin.cpp:8: 255 | /usr/include/c++/6/cstdlib:75:25: fatal error: stdlib.h: No such file or directory 256 | #include_next 257 | ^ 258 | compilation terminated. 259 | ``` 260 | 261 | Generated Makefile should be modified in the following way: 262 | 263 | INCPATH = -I. -isystem /usr/include 264 | --> new 265 | INCPATH = -I. -I/usr/include 266 | 267 | unix:INCLUDEPATH += /usr/include 268 | --> new 269 | unix:INCLUDEPATH += -I/usr/include 270 | 271 | 272 | g++ -c -pipe -Wno-attributes -Wall -O2 -Wall -W -D_REENTRANT -fPIC -DDECONZ_DLLSPEC=Q_DECL_IMPORT -DARCH_ARM -DARCH_ARMV7 -DQT_NO_DEBUG -DQT_PLUGIN -DQT_GUI_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -I. -I../.. -I../../common -I/usr/include -isystem /usr/include/arm-linux-gnueabihf/qt5 -isystem /usr/include/arm-linux-gnueabihf/qt5/QtGui -isystem /usr/include/arm-linux-gnueabihf/qt5/QtNetwork -isystem /usr/include/arm-linux-gnueabihf/qt5/QtCore -Irelease -I/usr/lib/arm-linux-gnueabihf/qt5/mkspecs/linux-g++ -o release/deconz_cli_plugin.o deconz_cli_plugin.cpp 273 | 274 | 275 | 276 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | The deconz-cli-plugin provides an API to access devices with ZigBee Home Automation (HA) and ZigBee Light Link (ZLL). It can be used as command line interface (cli) to send and receive raw ZigBee commands with deCONZ software. 5 | 6 | As hardware the [RaspBee](http://www.dresden-elektronik.de/funktechnik/solutions/wireless-light-control/raspbee?L=1) ZigBee Shield for Raspberry Pi is used to directly communicate with the ZigBee devices. 7 | 8 | The deconz-cli-plugin requires the [deCONZ software](http://www.dresden-elektronik.de/funktechnik/products/software/pc/deconz?L=1). 9 | 10 | The deconz-cli-plugin opens a socket on port TCP 5008 and listens for incoming connections. The plugin allows to send commands and receive responses to and from ZigBee devices like Philips Hue lights. The plugin translates the incoming commands to APS-Data and passes the data to the RaspBee firmware. 11 | 12 | ## Preparation and configuration steps 13 | 14 | In the standard Raspbian Linux distribution the serial interface /dev/ttyAMA0 is set up as a serial console, i.e. for boot message output. Since the RaspBee ZigBee firmware uses the same interface to communicate with the control software, the following changes must be done to “free” the UART. 15 | 16 | In the file /boot/cmdline.txt: 17 | 18 | If present remove the text `console=serial0,115200` or `console=ttyAMA0,115200` 19 | 20 | Raspbian wheezy: comment out the following line in `/etc/inittab` 21 | 22 | #Spawn a getty on Raspberry Pi serial line 23 | T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100 24 | 25 | Raspian jessie: 26 | 27 | sudo systemctl disable serial-getty@ttyAMA0.service 28 | 29 | Raspberry Pi3 jessie: 30 | in the file /boot/config.txt add the line 31 | 32 | enable_uart=1 33 | 34 | Reboot 35 | 36 | ## Installation and compilation steps 37 | 38 | ##### Install deCONZ and development package 39 | 40 | 1. Download deCONZ package 41 | 42 | 43 | wget http://www.dresden-elektronik.de/rpi/deconz/deconz-latest.deb 44 | -or- 45 | wget http://www.dresden-elektronik.de/rpi/deconz/deconz-2.05.32.deb 46 | 47 | 2. Install deCONZ package 48 | 49 | 50 | sudo dpkg -i deconz-2.05.32-qt5.deb 51 | sudo apt update 52 | sudo apt install -f 53 | 54 | 3. Download deCONZ development package 55 | 56 | 57 | wget http://www.dresden-elektronik.de/rpi/deconz-dev/deconz-dev-latest.deb 58 | -or- 59 | wget http://www.dresden-elektronik.de/rpi/deconz-dev/deconz-dev-2.05.32.deb 60 | 61 | 4. Install deCONZ development package 62 | 63 | 64 | sudo dpkg -i deconz-dev-2.05.32.deb 65 | sudo apt install -f 66 | 67 | 6. (Optional) Install GCFFlasher tool to install new firmware or restart RaspBee 68 | 69 | 70 | wget http://www.dresden-elektronik.de/rpi/gcfflasher/gcfflasher-latest.deb 71 | sudo dpkg -i gcfflasher-latest.deb 72 | sudo apt update 73 | sudo apt install -f 74 | 75 | 76 | ##### Compile the plugin 77 | 78 | 1. Compile the plugin 79 | 80 | git clone https://github.com/ma-ca/deconz-cli-plugin.git 81 | cd deconz-cli-plugin 82 | qmake && make 83 | 84 | 2. Copy the plugin to the deConz plugins folder 85 | 86 | 87 | sudo cp libdeconz_cli_plugin.so /usr/share/deCONZ/plugins 88 | 89 | Start deConz (parameter --dbg-info=1 only needed for debug messages). It might be necessary to start with UI when starting and forming the ZigBee network for the first time. Thereafter the UI is not needed anymore. 90 | 91 | with UI (requires X-server) 92 | 93 | sudo systemctl enable deconz-gui 94 | sudo systemctl start deconz 95 | 96 | -or- 97 | 98 | deCONZ --http-port=80 99 | 100 | deCONZ --http-port=80 --dbg-info=1 --dbg-aps=2 --dbg-zcl=1 101 | 102 | 103 | without UI 104 | 105 | sudo systemctl enable deconz 106 | sudo systemctl start deconz 107 | 108 | -or- 109 | 110 | deCONZ -platform minimal --http-port=80 111 | 112 | 113 | 114 | Hardware requirements 115 | --------------------- 116 | 117 | * Raspberry Pi 118 | * [RaspBee](http://www.dresden-elektronik.de/funktechnik/solutions/wireless-light-control/raspbee?L=1) ZigBee Shield for Raspberry Pi 119 | * or a [ConBee](https://www.dresden-elektronik.de/funktechnik/solutions/wireless-light-control/conbee/?L=1) USB dongle 120 | 121 | Usage 122 | ===== 123 | 124 | The plugin is started when the deConz software is started. The deConz software must be running before starting pilight. 125 | 126 | 127 | Run netcat to connect to port 5008. Type `help` to show usage. 128 | 129 | nc localhost 5008 130 | 131 | 132 | Command | Description 133 | ------- | ----------- 134 | `r ` | read attributes 135 | `b ` | read basic attributes 136 | `b ` | read basic attributes on cluster 137 | `m ` | send match descriptor request (discover cluster) 138 | `p ` | permit Joining on device (coordinator = 0) 139 | `zclattr ` | send ZCL attribute request 140 | `zclcmd ` | send ZCL command request 141 | `zclcmdgrp ` | send ZCL command request to group 142 | `zdpcmd ` | send ZDP command request 143 | `zclattrmanu ` | send ZCL attribute request manufacturer specific 144 | `zclcmdmanu ` | send ZCL command request manufacturer specific 145 | `sendtime ` | send ZCL time attributes to Time_Cluster 0x000A 146 | 147 | 148 | Response | Description 149 | --------| ----------- 150 | <-LQI 0x84182600xxxxxxxx 06 0 1 0x001FEE00xxxxxxxx 0x4157 1 1 2 01 02 56 | LQI neighbor reponse 151 | <-ZCL attribute report 0x001FEE00xxxxxxxx 0x0006 1 00 00 10 00 | ZCL attribute report (from cluster 0x0006) 152 | <-ZCL serverToClient 0x00124B00xxxxxxxx 1 for cluster 0x0500 10 00 00 00 00 00 | ZCL attribute (with extaddr) 153 | <-ZCL serverToClient 0x3B58 1 for cluster 0x0500 10 00 00 00 00 00 | ZCL attribute (with shortaddr) 154 | <-APS attr 0x001FEE00xxxxxxxx 5 0x0702 0x0000 0x25 0E DA 76 57 00 00 00 04 00 2A 00 00 00 | APS data 155 | 156 | <-LQI [source addr] [neighborTableEntries] [start] [count] [extaddr] [shortaddr] [device type] [rxOnWhenIdle] [relationship] [permitJoin] [depth] [lqi] 157 | <-APS attr [source extaddr] [endpoint] [cluster] [attributeid] [typeid] [attr value] [...more attributes] 158 | 159 | 160 | 161 | LQI | Description 162 | --- | ----------- 163 | source addr | LQI neighbor table from 164 | neighborTableEntries | total table entries 165 | start | current table entry start index 166 | count | number of table entries in this frame 167 | extaddr | table entry extended address 168 | shortaddr | table entry short address 169 | device type | 0 = coordinator, 1 = router, 2 = end device, 3 = unknown 170 | rxOnWhenIdle | receiver on when idle 0 or 1 171 | relationship | relationship 0 = neighbor is the parent, 1 = child, 2 = sibling, 3 = None of the above, 4 = previous child 172 | permitJoin | permit Join 0 = neighbor is not accepting join requests, 1 = accepting join requests 173 | depth | The tree depth of the neighbor device 174 | lqi | The estimated link quality (range 0x00 - 0xff) 175 | 176 | 177 | Supported ZigBee devices 178 | ======================== 179 | 180 | Successfully tested with follwing ZigBee devices. 181 | 182 | Device | Vendor 183 | ------ | ------ 184 | Light Philps Hue White | Philips, LWB006 185 | Light | OSRAM, Classic B40 TW - LIGHTIFY 186 | Smart Plug | OSRAM, Plug 01 187 | Movement Sensor | Bitron Home, 902010/22 188 | Motion Sensor | Philips Hue motion sensor 189 | Smoke Detektor with siren | Bitron Home, 902010/24 190 | Smart Plug with Metering | Bitron Home, 902010/25 191 | Thermostat | Bitron Home, 902010/32 192 | Switch | ubisys, S2 (5502) 193 | Button | Philips Hue dimmer switch 194 | Button | [Xiaomi Smart Wireless Switch](https://www.banggood.com/Original-Xiaomi-Smart-Wireless-Switch-p-1045081.html) 195 | Temperature | Xiaomi Temperature and Humidity Smart Sensor 196 | 197 | Reference Manuals 198 | ----------------- 199 | [Bitron](http://www.bitronvideo.eu/index.php/service/bitron-home/downloads/) 200 | 201 | [Ubisys](http://www.ubisys.de/en/smarthome/products-s2.html) 202 | 203 | Reset ZigBee device to factory default 204 | -------------------------------------- 205 | 206 | [Philips Hue Light](http://www2.meethue.com/de-de/support/faq-detail/?productCategoryId=131288) 207 | Can I factory reset a Hue light with the Hue dimmer switch? 208 | Yes you can, press and hold the ON and OFF button simultaneously until the LED light on the dimmer switch turns green (Note: the lamp is blinking during this process. Hue Beyond and Hue Phoenix are excluded). 209 | 210 | Osram Lightify Light turn the light on and off five times and wait 5 seconds inbetween. 211 | 212 | 213 | [Bitron Video](http://www.bitronvideo.eu/index.php/service/bitron-home/zb-registrierung/) 214 | Press and hold the button for 10 seconds. 215 | 216 | 217 | Troubleshooting 218 | --------------- 219 | 220 | After a while (rarely and randomly) a device keeps responding with an error status 0xD0 when reading attributes or sending other commands. Sometimes this just gets resolved without doing anything but waiting. However, sometimes it turns out that the device shortaddress has changed for some unknown reason. Oberserving the LQI neibortable entries shows the new shortaddress. 221 | 222 |
223 | zclattr 0x4157 5 0x0702 0000000004 --> send OK
224 | <-APS-DATA.confirm FAILED status 0xD0, id = 0x25, srcEp = 0x01, dstcEp = 0x05, dstAddr = 0x4157
225 | <-LQI 0x00178801xxxxxxxx   07 0 1 0x001FEE00xxxxxxxx 0xA855 1 1 3 01 01 F7
226 | zclattr 0xA855 5 0x0702 0000000004 --> send OK
227 | 
228 | 229 | Restart RaspBee 230 | --------------- 231 | 232 | 233 | sudo pkill deCONZ; sudo GCFFlasher -r; sudo systemctl start deconz 234 | 235 | Example Commands 236 | ---------------- 237 | 238 | Send match descriptor to Profile 0x0104 (ZHA) and Cluster 0x0102 (Window Covering Cluster) 239 | 240 | m 0x0104 0x0102 241 | 242 | --> send OK 243 | <-ZDP match 0x001FEE000000253B 0xD1F3 1 0x0102 0x0104 244 | b 0xD1F3 1 0x0102 245 | --> send OK 246 | <-ZCL attr 0xD1F3 1 0x0102 0x0004 ubisys 247 | <-ZCL attr 0xD1F3 1 0x0102 0x0005 J1 (5502) 248 | <-ZCL attr 0xD1F3 1 0x0102 0x0006 20170712-DE-FB0 249 | <-ZCL attr 0xD1F3 1 0x0102 0x4000 unknown 250 | 251 | send match descriptor to Profile 0xC05E (ZLL) and Cluster 0x0006 (OnOff Cluster) 252 | 253 | m 0xC05E 0x0006 254 | 255 | send match descriptor request to Profile 0x0104 (ZHA) and Cluster 0x0006 (OnOff Cluster) 256 | 257 | m 0x0104 0x0006 258 | 259 | get group membership 260 | 261 | zclcmd 0xC05E 11 0x0004 0200 262 | 263 | switch on / off 264 | 265 | zclcmd 0x6C25 266 | 267 | read S2 ubisys config 268 | 269 | zclattr 0x6C25 232 0xFC00 000100 270 | 271 | --> send OK 272 | <-APS attr 0x001FEE0000001739 232 0xFC00 0x0001 0x48 41 04 00 06 00 0D 03 06 00 02 06 01 0D 04 06 00 02 06 00 03 03 06 00 02 06 01 03 04 06 00 02 273 | 274 | zclattr 0xAF73 232 0xFC00 000100 275 | 276 | --> send OK 277 | <-APS attr 0x001FEE000000170A 232 0xFC00 0x0001 0x48 41 05 00 06 00 0D 03 06 00 02 09 00 07 03 05 00 05 01 00 01 06 00 0B 03 06 00 02 06 01 0D 04 06 00 02 06 01 03 04 06 00 02 278 | 279 | write S2 ubisys settings rocker switch (two 280 | stable positions). 281 | 282 |
283 | zclattr 0x6C25 232 0xFC00 0201004841040006000D0306000206010D040600020600030306000206010304060002
284 |                           ..............----||-------|----||-------|----||-------|----||-------|
285 |                           ..............----0D------02----0D------02----03------02----03------02
286 | 0D = Transition: released->pressed
287 | 03 = Transition: ignore->released
288 | 02 = ZCL Command: Toggle
289 | 
290 | 291 | write S2 ubisys default settings push-button momentary switch (one 292 | stable position). 293 | 294 |
295 | zclattr 0x6C25 232 0xFC00 0201004841040006000D0306000206010D040600020600030306000206010D04060002
296 |                           ..............----||-------|----||-------|----||-------|----||-------|
297 |                           ..............----0D------02----0D------02----03------02----0D------02
298 | 0D = Transition: released->pressed
299 | 02 = ZCL Command: Toggle
300 | 
301 | 302 | read J1 ubisys with default configuration which is aimed at dual push-button 303 | operation (momentary, one stable position): 304 | A short press will move up/down and stop when released, 305 | while a long press will move up/down 306 | without stopping before the fully open or fully closed 307 | position is reached, respectively. 308 | 309 | zclattr 0xD1F3 232 0xFC00 000100 310 | --> send OK 311 | <-APS attr 0x001FEE000000253B 232 0xFC00 0x0001 0x48 41 04 00 06 00 0D 02 02 01 00 06 00 07 02 02 01 02 06 01 0D 02 02 01 01 06 01 07 02 02 01 02 312 | 313 | write J1 ubisys configuration: Here, the blind moves as long as either switch is turned on. 314 | As soon as it is turned off, motion stops. 315 | 316 |
317 | zclattr 0xD1F3 232 0xFC00 0201004841040006000D020201000600030202010206010D0202010106010302020102 
318 |                           ..............----||-------|----||-------|----||-------|----||-------|
319 |                           ..............----0D------00----03------02----0D------01----03------02
320 | 
321 | 0D = Transition: released -> pressed
322 | 07 = Transition: pressed -> released
323 | 03 = Transition: ignore->released
324 |  
325 | 00 = ZCL Command: Move up/open
326 | 01 = ZCL Command: Move down/close
327 | 02 = ZCL Command: Stop
328 | 
329 | zclattr 0xD1F3 232 0xFC00 000100
330 | --> send OK
331 | <-APS attr 0x001FEE000000253B 232 0xFC00 0x0001 0x48 41 04 00 06 00 0D 02 02 01 00 06 00 03 02 02 01 02 06 01 0D 02 02 01 01 06 01 03 02 02 01 02
332 | 
333 | 334 | permit join 335 | 336 | p 0xFFFF 337 | 338 | discover attributes 339 | 340 | zclattr 0x%04X %d %s 0C00000D 341 | 342 | 343 | read reporting configuration 344 | 345 | zclattr 0x%04X %d %s 0800%s 346 | 347 | show bindings 348 | 349 | zdpcmd 0x%04X 0x0033 00 350 | 351 | NWK_addr_req get short address (ext_addr in reverse byte order) 352 | 353 | zdpcmd 0xFFFD 0x0000 %s0000 354 | 355 | read attributes 0x0000 (Temperature), 0x0012 (Heating Setpoint), 0x0025 (Operation Mode) on cluster 0x0201 (Thermostat Cluster) on addresses 0xFFFF (broadcast), 0x8655, 0xE087 356 | 357 | zclattr 0xFFFF 1 0x0201 000000120025002900 358 | zclattr 0x8655 1 0x0201 000000120025002900 359 | zclattr 0xE087 1 0x0201 000000120025002900 360 | 361 | 362 | -------------------------------------------------------------------------------- /deconz_cli_plugin.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * pilight_plugin.ccp 3 | * 4 | * Created on: Dec 18, 2016 5 | * Author: ma-ca 6 | */ 7 | 8 | #include 9 | #include 10 | #include "deconz_cli_plugin.h" 11 | 12 | #define MAX_STR 1000 13 | #define TMP_STR 100 14 | #define MAX_ATTR 0x0006 15 | #define TCP_PORT 5008 16 | 17 | /*! Plugin constructor. 18 | \param parent - the parent object 19 | */ 20 | CliPlugin::CliPlugin(QObject *parent) : QObject(parent) { 21 | // keep a pointer to the ApsController 22 | m_apsCtrl = deCONZ::ApsController::instance(); 23 | DBG_Assert(m_apsCtrl != 0); 24 | 25 | // APSDE-DATA.confirm handler 26 | connect(m_apsCtrl, SIGNAL(apsdeDataConfirm(const deCONZ::ApsDataConfirm&)), 27 | this, SLOT(apsdeDataConfirm(const deCONZ::ApsDataConfirm&))); 28 | 29 | // APSDE-DATA.indication handler 30 | connect(m_apsCtrl, SIGNAL(apsdeDataIndication(const deCONZ::ApsDataIndication&)), 31 | this, SLOT(apsdeDataIndication(const deCONZ::ApsDataIndication&))); 32 | 33 | m_isconnected = false; 34 | m_readattr = false; 35 | m_readclust = 0; 36 | m_zdpmatchreq = false; 37 | m_shortaddr = 0; 38 | m_attrid = -1; 39 | m_cluster = 0; 40 | m_profile = 0; 41 | m_zclSeq = 0; 42 | m_ep = 0; 43 | tcpServer = new QTcpServer(this); 44 | if (!tcpServer->listen(QHostAddress::Any, TCP_PORT)) { // hardcoded listen port 5008 45 | DBG_Printf(DBG_INFO, "Unable to start the server: %s\n", tcpServer->errorString().toStdString().data()); 46 | } 47 | QString ipAddress; 48 | QList ipAddressesList = QNetworkInterface::allAddresses(); 49 | // use the first non-localhost IPv4 address 50 | for (int i = 0; i < ipAddressesList.size(); ++i) { 51 | if (ipAddressesList.at(i) != QHostAddress::LocalHost && 52 | ipAddressesList.at(i).toIPv4Address()) { 53 | ipAddress = ipAddressesList.at(i).toString(); 54 | break; 55 | } 56 | } 57 | // if we did not find one, use IPv4 localhost 58 | if (ipAddress.isEmpty()) 59 | ipAddress = QHostAddress(QHostAddress::LocalHost).toString(); 60 | 61 | DBG_Printf(DBG_INFO, "The server is running on\n\nIP: %s port: %d\n\n", 62 | ipAddress.toStdString().data(), tcpServer->serverPort()); 63 | 64 | connect(tcpServer, SIGNAL(newConnection()), this, SLOT(receiveCommand())); 65 | } 66 | 67 | /*! Deconstructor for plugin. 68 | */ 69 | CliPlugin::~CliPlugin() { 70 | m_apsCtrl = 0; 71 | } 72 | 73 | /*! APSDE-DATA.indication callback. 74 | \param ind - the indication primitive 75 | \note Will be called from the main application for every incoming indication. 76 | Any filtering for nodes, profiles, clusters must be handled by this plugin. 77 | */ 78 | void CliPlugin::apsdeDataIndication(const deCONZ::ApsDataIndication &ind) { 79 | DBG_Printf(DBG_INFO, 80 | "profileid %04X, clusterid %04X, srcEndpoint %02X, dstEndpoint %02X, status 0x%02X, securityStatus %02X\n", 81 | ind.profileId(), ind.clusterId(), ind.srcEndpoint(), ind.dstEndpoint(), ind.status(), ind.securityStatus()); 82 | 83 | unsigned int i; 84 | unsigned int length = ind.asdu().length(); 85 | int strlen = length * 3; // print each byte in hex (2 bytes) and one white space 86 | char rawstr[strlen+1]; 87 | char strout[MAX_STR]; 88 | char strtmp[TMP_STR]; 89 | 90 | memset(rawstr, '\0', strlen + 1); 91 | memset(strout, '\0', MAX_STR); 92 | memset(strtmp, '\0', TMP_STR); 93 | 94 | const char *raw = ind.asdu().data(); 95 | for (i = 0; i < length; i++) { 96 | char rawtmp[4]; 97 | memset(rawtmp, '\0', 4); 98 | snprintf(rawtmp, 4, "%02X ", *raw++); 99 | strcat(rawstr, rawtmp); 100 | } 101 | strcat(rawstr, "\n"); 102 | 103 | DBG_Printf(DBG_INFO, "APS Ind %d, %s: %s", length, getApsIndSrcAddr(ind).c_str(), rawstr); 104 | if (ind.status() != deCONZ::ApsSuccessStatus) { 105 | DBG_Printf(DBG_INFO, "APS Ind ERROR status 0x%02X\n", ind.status()); 106 | return; 107 | } 108 | 109 | if (ind.clusterId() == 0x000A) { 110 | DBG_Printf(DBG_INFO, "<-=======> handle Time Cluster <=================== \n"); 111 | 112 | } 113 | 114 | if (ind.profileId() == ZDP_PROFILE_ID) { // handle ZDP response 115 | switch(ind.clusterId()) { 116 | case ZDP_SIMPLE_DESCRIPTOR_RSP_CLID: 117 | handleZdpSimpleResponse(ind); 118 | break; 119 | case ZDP_ACTIVE_ENDPOINTS_RSP_CLID: 120 | handleZdpActiveEpResponse(ind); 121 | break; 122 | case ZDP_MATCH_DESCRIPTOR_RSP_CLID: 123 | handleZdpMatchResponse(ind); 124 | break; 125 | case ZDP_MGMT_LQI_RSP_CLID: 126 | handleZdpLqiResponse(ind); 127 | break; 128 | case ZDP_MGMT_BIND_RSP_CLID: 129 | handleZdpBindResponse(ind); 130 | break; 131 | case ZDP_POWER_DESCRIPTOR_RSP_CLID: 132 | handleZdpPowerResponse(ind); 133 | break; 134 | case ZDP_NWK_ADDR_RSP_CLID: 135 | handleZdpNwkAddrResponse(ind); 136 | break; 137 | default: 138 | DBG_Printf(DBG_INFO, "ZDP clusterId = 0x%04X \n", ind.clusterId()); 139 | break; 140 | } 141 | return; // end ZDP response 142 | } 143 | 144 | // handle ZCL response 145 | QDataStream stream(ind.asdu()); 146 | stream.setByteOrder(QDataStream::LittleEndian); 147 | deCONZ::ZclFrame zclframe; 148 | zclframe.readFromStream(stream); 149 | 150 | if ((zclframe.frameControl() & 0x09) == 0x09 /*== 0x19 or 0x1D*/) { // ZclFCClusterCommand and ZclFCDirectionServerToClient or ZclFCManufacturerSpecific 151 | handleZclServerToClientResponse(ind, zclframe); 152 | return; 153 | } 154 | if ((zclframe.frameControl() & 0x08) == 0x08 /*== 0x18 or 0x1C*/) { // ZclFCProfileCommand or ZclFCManufacturerSpecific 155 | switch(zclframe.commandId()) { 156 | case deCONZ::ZclReadAttributesResponseId: // 0x01 157 | handleZclReadAttrbuteResponse(ind, zclframe); 158 | break; 159 | case deCONZ::ZclReadReportingConfigResponseId: // 0x09 160 | handleZclReportConfigResponse(ind, zclframe); 161 | break; 162 | case deCONZ::ZclReportAttributesId: // 0x0A 163 | handleZclReportAttributeResponse(ind, zclframe); 164 | break; 165 | case deCONZ::ZclDiscoverAttributesResponseId: // 0x0D 166 | handleZclDiscoverAttributesResponse(ind, zclframe); 167 | break; 168 | default: 169 | DBG_Printf(DBG_INFO, "ZCL commandId = 0x%02X \n", zclframe.commandId()); 170 | break; 171 | } 172 | return; // end ZclFCProfileCommand 173 | } 174 | } 175 | 176 | /*! APSDE-DATA.confirm callback. 177 | \param conf - the confirm primitive 178 | \note Will be called from the main application for each incoming confirmation, 179 | even if the APSDE-DATA.request was not issued by this plugin. 180 | */ 181 | void CliPlugin::apsdeDataConfirm(const deCONZ::ApsDataConfirm &conf) { 182 | std::list::iterator i = m_apsReqQueue.begin(); 183 | std::list::iterator end = m_apsReqQueue.end(); 184 | 185 | deCONZ::Address srcAddress = conf.dstAddress(); 186 | const deCONZ::ApsAddressMode srcAddressMode = conf.dstAddressMode(); 187 | 188 | char strtmp[TMP_STR], data[MAX_STR]; 189 | if (srcAddressMode == 0x3) { //!< 64-bit extended IEEE address mode 190 | snprintf(strtmp, TMP_STR, "0x%016llX", srcAddress.ext()); 191 | } else if (srcAddressMode == 0x2) { //!< 16-bit short network address mode 192 | snprintf(strtmp, TMP_STR, "0x%04X", srcAddress.nwk()); 193 | } 194 | 195 | // search the list of currently active requests 196 | // and check if the confirmation belongs to one of them 197 | for (; i != end; ++i) { 198 | if (i->id() == conf.id()) { 199 | m_apsReqQueue.erase(i); 200 | 201 | snprintf(data, MAX_STR, 202 | "APS-DATA.confirm status 0x%02X, id = 0x%02X, srcEp = 0x%02X, dstcEp = 0x%02X, dstAddr = %s\n", 203 | conf.status(), conf.id(), conf.srcEndpoint(), conf.dstEndpoint(), strtmp); 204 | 205 | if (conf.status() != deCONZ::ApsSuccessStatus) { 206 | snprintf(data, MAX_STR, 207 | "<-APS-DATA.confirm FAILED status 0x%02X, id = 0x%02X, srcEp = 0x%02X, dstcEp = 0x%02X, dstAddr = %s\n", 208 | conf.status(), conf.id(), conf.srcEndpoint(), conf.dstEndpoint(), strtmp); 209 | writeToConnectedClients(data); 210 | } 211 | DBG_Printf(DBG_INFO, "%s", data); 212 | 213 | return; 214 | } 215 | } 216 | } 217 | 218 | /*! get APS Ind source address in 64-Bit or 16-Bit string 219 | */ 220 | std::string CliPlugin::getApsIndSrcAddr(const deCONZ::ApsDataIndication &ind) { 221 | char straddr[19]; 222 | if (ind.srcAddressMode() == 0x3) { //!< 64-bit extended IEEE address mode 223 | sprintf(straddr, "0x%016llX", ind.srcAddress().ext()); 224 | return std::string(straddr); 225 | } else if (ind.srcAddressMode() == 0x2) { //!< 16-bit short network address mode 226 | sprintf(straddr, "0x%04X", ind.srcAddress().nwk()); 227 | return std::string(straddr); 228 | } 229 | 230 | return std::string(); // empty string 231 | } 232 | 233 | /*! get Cluster ID name 234 | */ 235 | const char *CliPlugin::getClusterName(uint16_t clusterid) { 236 | switch (clusterid) { 237 | case 0x0000: return "BASIC_CLUSTER_ID"; //! 2) { 350 | simpleraw++; // skip sequence number 351 | uint8_t zdpstatus = *simpleraw++; //!< Result of a ZDP request. 352 | uint16_t *nwkAddr = (uint16_t *) simpleraw; //!< NWK address of the simple descriptor request 353 | simpleraw += 2; 354 | uint8_t length = *simpleraw; //!< Length 355 | if (length > 0) { 356 | simpleraw++; 357 | uint8_t endpoint = *simpleraw++; //!< 358 | uint16_t *AppProfileId = (uint16_t *) simpleraw; //!< The application profile identifier field 359 | simpleraw += 2; 360 | uint16_t *AppDeviceId = (uint16_t *) simpleraw; //!< The application device identifier 361 | simpleraw += 2; 362 | uint8_t AppDeviceVersion = *simpleraw++; //!< The application device identifier field 363 | uint8_t AppInClustersCount = *simpleraw++; //!< The application input cluster count 364 | snprintf(strtmp, TMP_STR, "<-CLUSTER %s 0x%04X ep 0x%02X profile 0x%04X deviceid 0x%04X deviceversion 0x%02X\n", 365 | getApsIndSrcAddr(ind).c_str(), *nwkAddr, endpoint, *AppProfileId, *AppDeviceId, AppDeviceVersion); 366 | int x = snprintf(strout, MAX_STR, strtmp); 367 | for (i = 0; i < AppInClustersCount; i++) { 368 | uint16_t *clusterid = (uint16_t *) simpleraw; 369 | snprintf(strtmp, TMP_STR, "<-CLUSTER %s 0x%04X 0x%02X In 0x%04X %s\n", 370 | getApsIndSrcAddr(ind).c_str(), *nwkAddr, endpoint, *clusterid, getClusterName(*clusterid)); 371 | x += snprintf(&strout[x], MAX_STR-x, strtmp); 372 | simpleraw += 2; 373 | } 374 | uint8_t AppOutClustersCount = *simpleraw++; //!< The application output cluster count 375 | for (i = 0; i < AppOutClustersCount; i++) { 376 | uint16_t *clusterid = (uint16_t *) simpleraw; 377 | snprintf(strtmp, TMP_STR, "<-CLUSTER %s 0x%04X 0x%02X Out 0x%04X %s\n", 378 | getApsIndSrcAddr(ind).c_str(), *nwkAddr, endpoint, *clusterid, getClusterName(*clusterid)); 379 | x += snprintf(&strout[x], MAX_STR-x, strtmp); 380 | simpleraw += 2; 381 | } 382 | } else { 383 | snprintf(strtmp, TMP_STR, "<-CLUSTER %s response error 0x%04X length = %d ", getApsIndSrcAddr(ind).c_str(), *nwkAddr, length); 384 | strcat(strout, strtmp); 385 | switch(zdpstatus) { 386 | case 0x00: snprintf(strtmp, TMP_STR, "SUCCESS\n"); break; 387 | case 0x80: snprintf(strtmp, TMP_STR, "INVALID_REQUEST\n"); break; 388 | case 0x81: snprintf(strtmp, TMP_STR, "DEVICE_NOT_FOUND\n"); break; 389 | case 0x82: snprintf(strtmp, TMP_STR, "INVALID_EP\n"); break; 390 | case 0x83: snprintf(strtmp, TMP_STR, "NOT_ACTIVE\n"); break; 391 | case 0x89: snprintf(strtmp, TMP_STR, "NO_DESCRIPTOR\n"); break; 392 | default: snprintf(strtmp, TMP_STR, "status = 0x%02X\n", zdpstatus); 393 | } 394 | strcat(strout, strtmp); 395 | } 396 | DBG_Printf(DBG_INFO, "%s", strout); 397 | if (m_isconnected) { 398 | writeToConnectedClients(strout); 399 | } 400 | } else { 401 | snprintf(strtmp, TMP_STR, "<-CLUSTER %s response error\n", getApsIndSrcAddr(ind).c_str()); 402 | strcat(strout, strtmp); 403 | } 404 | } 405 | 406 | /*! Handles an active endpoint request response. 407 | \param ind a ZDP Active_EP_rsp ZigBee specification 2.4.4.1.6 408 | */ 409 | void CliPlugin::handleZdpActiveEpResponse(const deCONZ::ApsDataIndication &ind) { 410 | unsigned int i; 411 | char strout[MAX_STR]; 412 | memset(strout, '\0', MAX_STR); 413 | char strtmp[TMP_STR]; 414 | memset(strtmp, '\0', TMP_STR); 415 | const char *epraw = ind.asdu().data(); 416 | if (ind.asdu().length() > 2) { 417 | epraw += 2; 418 | uint16_t *nwkAddr = (uint16_t *) epraw; //!< NWK address of the active endpoints request 419 | epraw += 2; 420 | uint8_t activeEPCount = *epraw; //!< Count of active endpoints on the remote device. 421 | for (i = 0; i < activeEPCount; i++) { 422 | epraw++; 423 | snprintf(strtmp, TMP_STR, "<-EP %s 0x%04X %3d (%02X)\n", getApsIndSrcAddr(ind).c_str(), *nwkAddr, *epraw, *epraw); 424 | strcat(strout, strtmp); 425 | } 426 | DBG_Printf(DBG_INFO, "%s", strout); 427 | if (m_isconnected) { 428 | writeToConnectedClients(strout); 429 | } 430 | } 431 | } 432 | 433 | /*! Handles an match request response. 434 | \param ind a ZDP Match_Desc_rsp ZigBee specification 2.4.4.1.7 435 | */ 436 | void CliPlugin::handleZdpMatchResponse(const deCONZ::ApsDataIndication &ind) { 437 | unsigned int i; 438 | char strout[MAX_STR]; 439 | memset(strout, '\0', MAX_STR); 440 | char strtmp[TMP_STR]; 441 | memset(strtmp, '\0', TMP_STR); 442 | const char *matchraw = ind.asdu().data(); 443 | if (ind.asdu().length() >= 2) { 444 | if (ind.asdu().length() >= 6) { 445 | matchraw += 2; // skip 2 bytes 446 | uint16_t *nwkAddr = (uint16_t *) matchraw; 447 | matchraw += 2; 448 | uint8_t matchLength = *matchraw; 449 | for (i = 0; i < matchLength; i++) { 450 | matchraw++; 451 | snprintf(strtmp, TMP_STR, "<-ZDP match %s 0x%04X %3d 0x%04X 0x%04X\n", 452 | getApsIndSrcAddr(ind).c_str(), *nwkAddr, *matchraw, m_cluster, m_profile); 453 | strcat(strout, strtmp); 454 | } 455 | } else { 456 | uint8_t seqNum = *matchraw++; //!< Sequence number of a ZDP command 457 | uint8_t zdpstatus = *matchraw; //!< Result of a ZDP request. 458 | snprintf(strout, MAX_STR, "<-ZDP match %s seqNum = 0x%02X zdpstatus = 0x%02X\n", 459 | getApsIndSrcAddr(ind).c_str(), seqNum, zdpstatus); 460 | } 461 | DBG_Printf(DBG_INFO, "%s", strout); 462 | if (m_isconnected) { 463 | writeToConnectedClients(strout); 464 | } 465 | } 466 | } 467 | 468 | /*! Handles an LQI request response. 469 | \param ind a ZDP Mgmt_Lqi_rsp ZigBee specification 2.4.4.3.2 470 | */ 471 | void CliPlugin::handleZdpLqiResponse(const deCONZ::ApsDataIndication &ind) { 472 | unsigned int i; 473 | char strout[MAX_STR]; 474 | memset(strout, '\0', MAX_STR); 475 | char strtmp[TMP_STR]; 476 | memset(strtmp, '\0', TMP_STR); 477 | snprintf(strout, MAX_STR, "<-LQI %s %3d", getApsIndSrcAddr(ind).c_str(), ind.srcEndpoint()); 478 | const char *lqiraw = ind.asdu().data(); 479 | if (ind.asdu().length() >= 27) { 480 | lqiraw += 2 ; // skip 2 bytes 481 | uint8_t neighborTableEntries = *lqiraw++; 482 | uint8_t startIndex = *lqiraw++; 483 | uint8_t neighborTableListCount = *lqiraw++; 484 | lqiraw += 8; // skip Ext PANID 485 | uint64_t *extAddr = (uint64_t *) lqiraw; 486 | lqiraw += 8; 487 | uint16_t *networkAddr = (uint16_t *) lqiraw; 488 | lqiraw += 2; 489 | snprintf(strtmp, TMP_STR, "%d %d %d 0x%016llX 0x%04X ", 490 | neighborTableEntries, startIndex, neighborTableListCount, *extAddr, *networkAddr); 491 | strcat(strout, strtmp); 492 | 493 | uint8_t deviceType = *lqiraw & 0x03; // 0000 00xx 0=CO, 1=RD, 2=ED, 3=unknown 494 | uint8_t rxOnWhenIdle = (*lqiraw >> 2) & 0x03; // 0000 xx00 0=no, 1=yes, 2=unknown 495 | uint8_t relationship = (*lqiraw >> 4) & 0x07; // 0xxx 0000 parent/child/sibling/none/previous child 496 | snprintf(strtmp, TMP_STR, "%u %u %u ", deviceType, rxOnWhenIdle, relationship); 497 | strcat(strout, strtmp); 498 | 499 | lqiraw++; 500 | for (i = 0; i < 3; i++) { // depth lqi 501 | snprintf(strtmp, TMP_STR, "%02X ", *lqiraw++); // print hex 502 | strcat(strout, strtmp); 503 | } 504 | 505 | strcat(strout, "\n"); 506 | DBG_Printf(DBG_INFO, "%s", strout); 507 | if (m_isconnected) { 508 | writeToConnectedClients(strout); 509 | } 510 | } 511 | } 512 | 513 | /*! Handles an Bind request response. 514 | \param ind a ZDP Mgmt_Bind_rsp ZigBee specification 2.4.4.3.4 515 | */ 516 | void CliPlugin::handleZdpBindResponse(const deCONZ::ApsDataIndication &ind) { 517 | unsigned int i; 518 | char strout[MAX_STR]; 519 | memset(strout, '\0', MAX_STR); 520 | char strtmp[TMP_STR]; 521 | memset(strtmp, '\0', TMP_STR); 522 | const char *bindraw = ind.asdu().data(); 523 | if (ind.asdu().length() >= 18) { 524 | bindraw += 2 ; // skip 2 bytes 525 | uint8_t bindingTableEntries = *bindraw++; 526 | uint8_t startIndex = *bindraw++; 527 | uint8_t bindingTableListCount = *bindraw; 528 | 529 | for (i = 0; i < bindingTableListCount; i++) { // read bind table entries 530 | snprintf(strout, MAX_STR, "<-BIND %s ", getApsIndSrcAddr(ind).c_str()); 531 | bindraw++; 532 | uint64_t *srcAddr = (uint64_t *) bindraw; 533 | bindraw += 8; 534 | uint8_t srcEndpoint = *bindraw++; 535 | uint16_t *clusterId = (uint16_t *) bindraw; 536 | bindraw += 2; 537 | uint8_t dstAddrMode = *bindraw++; 538 | snprintf(strtmp, TMP_STR, "%d %d %d 0x%016llX 0x%02X 0x%04X ", 539 | bindingTableEntries, startIndex, bindingTableListCount, *srcAddr, srcEndpoint, *clusterId); 540 | strcat(strout, strtmp); 541 | if (dstAddrMode == 0x03) { // 0x03 = 64-bit extended address for dstAddr and dstEndpoint present 542 | uint64_t *dstExtAddr = (uint64_t *) bindraw; 543 | bindraw += 8; 544 | uint8_t dstEndpoint = *bindraw; 545 | snprintf(strtmp, TMP_STR, "0x%016llX 0x%02X", *dstExtAddr, dstEndpoint); 546 | strcat(strout, strtmp); 547 | } else if (dstAddrMode == 0x01) { //0x01 = 16-bit group address for dstAddr and dstEndoint not present 548 | uint16_t *dstGroupAddr = (uint16_t *) bindraw; 549 | snprintf(strtmp, TMP_STR, "0x%04X", *dstGroupAddr); 550 | strcat(strout, strtmp); 551 | bindraw++; // move pointer to end 552 | } 553 | 554 | strcat(strout, "\n"); 555 | DBG_Printf(DBG_INFO, "%s", strout); 556 | if (m_isconnected) { 557 | writeToConnectedClients(strout); 558 | } 559 | } 560 | } else if (ind.asdu().length() > 2) { 561 | bindraw += 2; 562 | unsigned int bindrawlen = ind.asdu().length() - 2; 563 | snprintf(strout, MAX_STR, "BIND %s ", getApsIndSrcAddr(ind).c_str()); 564 | for (i = 0; i < bindrawlen; i++) { 565 | bindraw++; 566 | snprintf(strtmp, TMP_STR, "%02X ", *bindraw); 567 | strcat(strout, strtmp); 568 | } 569 | strcat(strout, "\n"); 570 | DBG_Printf(DBG_INFO, "%s", strout); 571 | if (m_isconnected) { 572 | writeToConnectedClients(strout); 573 | } 574 | } 575 | } 576 | 577 | /*! Handles an power descriptor request response. 578 | \param ind a ZDP Power_Desc_rsp ZigBee specification 2.4.4.2.4 579 | */ 580 | void CliPlugin::handleZdpPowerResponse(const deCONZ::ApsDataIndication &ind) { 581 | char strout[MAX_STR]; 582 | memset(strout, '\0', MAX_STR); 583 | char strtmp[TMP_STR]; 584 | memset(strtmp, '\0', TMP_STR); 585 | snprintf(strout, MAX_STR, "<-POWER %s ", getApsIndSrcAddr(ind).c_str()); 586 | const char *powraw = ind.asdu().data(); 587 | if (ind.asdu().length() > 2) { 588 | powraw++; // skip sequence number 589 | uint8_t zdpstatus = *powraw++; //!< Result of a ZDP request. 590 | uint16_t *nwkAddr = (uint16_t *) powraw; //!< NWK address of the active endpoints request 591 | powraw += 2; 592 | snprintf(strtmp, TMP_STR, "0x%04X", *nwkAddr); 593 | strcat(strout, strtmp); 594 | if (zdpstatus != 0) { 595 | // SUCCESS, DEVICE_NOT_FOUND, INV_REQUESTTYPE or NO_DESCRIPTOR 596 | switch(zdpstatus) { 597 | case 0x80: 598 | snprintf(strtmp, TMP_STR, " INV_REQUESTTYPE"); 599 | break; 600 | case 0x81: 601 | snprintf(strtmp, TMP_STR, " DEVICE_NOT_FOUND"); 602 | break; 603 | case 0x89: 604 | snprintf(strtmp, TMP_STR, " NO_DESCRIPTOR"); 605 | break; 606 | default: 607 | break; 608 | } 609 | } else { 610 | // Power descriptor 4 x 4 bits 611 | int powmode = *powraw & 0x0F; // Current power mode 612 | int availsrc = (*(powraw++) & 0xF0) >> 4; // Available power sources 613 | int cursrc = *powraw & 0x0F; // Current power source 614 | int powlevel= (*powraw & 0xF0) >> 4; // Current power source level 615 | snprintf(strtmp, TMP_STR, " %X %X %X", powmode, availsrc, cursrc); 616 | strcat(strout, strtmp); 617 | // 0, 33%, 66%, 100% 618 | switch (powlevel) { 619 | case 0x00: 620 | snprintf(strtmp, TMP_STR, " 0"); 621 | break; 622 | case 0x04: 623 | snprintf(strtmp, TMP_STR, " 33"); 624 | break; 625 | case 0x08: 626 | snprintf(strtmp, TMP_STR, " 66"); 627 | break; 628 | case 0x0C: 629 | snprintf(strtmp, TMP_STR, " 100"); 630 | break; 631 | default: 632 | break; 633 | } 634 | } 635 | strcat(strout, strtmp); 636 | snprintf(strtmp, TMP_STR, "\n"); 637 | strcat(strout, strtmp); 638 | DBG_Printf(DBG_INFO, "%s", strout); 639 | if (m_isconnected) { 640 | writeToConnectedClients(strout); 641 | } 642 | } 643 | } 644 | 645 | /*! Handles a NWK_addr_req (ClusterID=0x0000) request response. 646 | \param ind a ZDP NWK_addr_resp ZigBee specification 2.4.4.2.1 647 | */ 648 | void CliPlugin::handleZdpNwkAddrResponse(const deCONZ::ApsDataIndication &ind) { 649 | char strout[MAX_STR]; 650 | memset(strout, '\0', MAX_STR); 651 | char strtmp[TMP_STR]; 652 | memset(strtmp, '\0', TMP_STR); 653 | snprintf(strout, MAX_STR, "<-NWK %s ", getApsIndSrcAddr(ind).c_str()); 654 | const char *nwkraw = ind.asdu().data(); 655 | if (ind.asdu().length() > 2) { 656 | nwkraw++; // skip sequence number 657 | uint8_t zdpstatus = *nwkraw++; //!< Result of a ZDP request. 658 | if (zdpstatus != 0) { 659 | // SUCCESS, INV_REQUESTTYPE, or DEVICE_NOT_FOUND 660 | switch(zdpstatus) { 661 | case 0x80: 662 | snprintf(strtmp, TMP_STR, "INV_REQUESTTYPE "); 663 | break; 664 | case 0x81: 665 | snprintf(strtmp, TMP_STR, "DEVICE_NOT_FOUND "); 666 | break; 667 | default: 668 | break; 669 | } 670 | strcat(strout, strtmp); 671 | } 672 | uint64_t *extAddr = (uint64_t *) nwkraw; // IEEEAddr RemoteDev 673 | nwkraw += 8; 674 | uint16_t *nwkAddr = (uint16_t *) nwkraw; //!< NWK address of the remote device 675 | snprintf(strtmp, TMP_STR, "0x%016llX 0x%04X", *extAddr, *nwkAddr); 676 | strcat(strout, strtmp); 677 | snprintf(strtmp, TMP_STR, "\n"); 678 | strcat(strout, strtmp); 679 | DBG_Printf(DBG_INFO, "%s", strout); 680 | if (m_isconnected) { 681 | writeToConnectedClients(strout); 682 | } 683 | } 684 | } 685 | 686 | /*! Handles a ZCL Read Attribute Response 687 | \param ind a ZDP Active_EP_rsp 688 | */ 689 | void CliPlugin::handleZclReadAttrbuteResponse(const deCONZ::ApsDataIndication &ind, 690 | const deCONZ::ZclFrame &zclframe) { 691 | char strout[MAX_STR]; 692 | memset(strout, '\0', MAX_STR); 693 | char strtmp[TMP_STR]; 694 | memset(strtmp, '\0', TMP_STR); 695 | const char *respvalue = zclframe.payload().data(); 696 | const char *endvalue = respvalue + zclframe.payload().length(); 697 | uint16_t *attrid = (uint16_t *) respvalue; 698 | respvalue += 3; // skip attribute id and status 699 | bool writestrout = true; 700 | if (zclframe.payload().length() > 3) { 701 | uint8_t type = *respvalue++; // ZCL attribute typeid 702 | if (m_readattr && ind.clusterId() == 0x0000 && m_zclSeq == zclframe.sequenceNumber()) { 703 | snprintf(strout, MAX_STR, "<-ZCL attr 0x%04X %d 0x%04X 0x%04X ", 704 | m_shortaddr, ind.srcEndpoint(), m_readclust, *attrid); 705 | } else { 706 | snprintf(strout, MAX_STR, "<-APS attr %s %d 0x%04X 0x%04X 0x%02X ", 707 | getApsIndSrcAddr(ind).c_str(), ind.srcEndpoint(), ind.clusterId(), *attrid, type); 708 | } 709 | if (0x42 == type) { // type 0x42 string 710 | uint8_t stringlength = *respvalue++; 711 | const char *endstring = respvalue + stringlength; 712 | while (respvalue < endstring) { 713 | snprintf(strtmp, TMP_STR, "%c", *respvalue++); // print char 714 | strcat(strout, strtmp); 715 | } 716 | } else { 717 | while (respvalue < endvalue) { 718 | snprintf(strtmp, TMP_STR, "%02X ", *respvalue++); // print hex 719 | strcat(strout, strtmp); 720 | } 721 | } 722 | } else { // error 723 | if (m_readattr && ind.clusterId() == 0x0000 && m_zclSeq == zclframe.sequenceNumber()) { 724 | snprintf(strout, MAX_STR, "<-ZCL attr 0x%04X %d 0x%04X 0x%04X unknown", 725 | m_shortaddr, ind.srcEndpoint(), m_readclust, *attrid); 726 | } else { 727 | writestrout = false; 728 | snprintf(strout, MAX_STR, "APS attr %s %d 0x%04X 0x%04X error ", 729 | strtmp, ind.srcEndpoint(), ind.clusterId(), *attrid); 730 | } 731 | } 732 | strcat(strout, "\n"); 733 | DBG_Printf(DBG_INFO, "%s", strout); 734 | if (m_isconnected && writestrout) { 735 | writeToConnectedClients(strout); 736 | } 737 | 738 | // start next read attribute request if Basic Cluster 739 | if (m_readattr 740 | && ind.clusterId() == 0x0000 741 | && *attrid == m_attrid 742 | && m_zclSeq == zclframe.sequenceNumber()) { 743 | if (m_attrid < MAX_ATTR) { 744 | m_attrid++; 745 | sendZclReadAttributeRequest(m_shortaddr, m_ep, 0x0000, m_attrid); 746 | } else if (m_attrid != 0x4000) { 747 | m_attrid = 0x4000; 748 | sendZclReadAttributeRequest(m_shortaddr, m_ep, 0x0000, m_attrid); 749 | } else { 750 | m_readattr = false; 751 | m_readclust = 0; 752 | } 753 | } 754 | 755 | } 756 | 757 | /*! Handles a ZCL Report Config request response. 758 | \param ind a ZDP Active_EP_rsp 759 | */ 760 | void CliPlugin::handleZclReportConfigResponse(const deCONZ::ApsDataIndication &ind, 761 | const deCONZ::ZclFrame &zclframe) { 762 | char strout[MAX_STR]; 763 | memset(strout, '\0', MAX_STR); 764 | char strtmp[TMP_STR]; 765 | memset(strtmp, '\0', TMP_STR); 766 | const char *respvalue = zclframe.payload().data(); 767 | const char *endvalue = respvalue + zclframe.payload().length(); 768 | snprintf(strout, MAX_STR, "<-REP config %s %d 0x%04X ", 769 | getApsIndSrcAddr(ind).c_str(), ind.srcEndpoint(), ind.clusterId()); 770 | uint8_t zclstatus = *respvalue++; 771 | if (zclstatus != 0x00) { 772 | switch(zclstatus) { 773 | case 0x86: 774 | snprintf(strtmp, TMP_STR, "UNSUPPORTED_ATTRIBUTE "); 775 | break; 776 | case 0x8C: 777 | snprintf(strtmp, TMP_STR, "UNREPORTABLE_ATTRIBUTE "); 778 | break; 779 | default: 780 | snprintf(strtmp, TMP_STR, "zcl error status = 0x%02X: ", zclstatus); 781 | break; 782 | } 783 | strcat(strout, strtmp); 784 | } else { 785 | uint8_t direction = *respvalue++; 786 | uint16_t *attributeId = (uint16_t *) respvalue; 787 | respvalue += 2; 788 | uint8_t attributeType = *respvalue++; 789 | uint16_t *minReportingInterval = (uint16_t *) respvalue; 790 | respvalue += 2; 791 | uint16_t *maxReportingInterval = (uint16_t *) respvalue; 792 | respvalue += 2; 793 | snprintf(strtmp, TMP_STR, "dir %02X min %d max %d id 0x%04X type 0x%02X ", 794 | direction, *minReportingInterval, *maxReportingInterval, *attributeId, attributeType); 795 | strcat(strout, strtmp); 796 | } 797 | while (respvalue < endvalue) { 798 | snprintf(strtmp, TMP_STR, "%02X ", *respvalue++); // print hex 799 | strcat(strout, strtmp); 800 | } 801 | strcat(strout, "\n"); 802 | DBG_Printf(DBG_INFO, "%s", strout); 803 | if (m_isconnected) { 804 | writeToConnectedClients(strout); 805 | } 806 | } 807 | 808 | /*! Handles a ZCL Report Attribute response. 809 | \param ind a ZDP Active_EP_rsp 810 | */ 811 | void CliPlugin::handleZclReportAttributeResponse(const deCONZ::ApsDataIndication &ind, 812 | const deCONZ::ZclFrame &zclframe) { 813 | char strout[MAX_STR]; 814 | memset(strout, '\0', MAX_STR); 815 | char strtmp[TMP_STR]; 816 | memset(strtmp, '\0', TMP_STR); 817 | const char *respvalue = zclframe.payload().data(); 818 | const char *endvalue = respvalue + zclframe.payload().length(); 819 | snprintf(strout, MAX_STR, 820 | "<-ZCL attribute report %s 0x%04X %d ", getApsIndSrcAddr(ind).c_str(), ind.clusterId(), ind.srcEndpoint()); 821 | while (respvalue < endvalue) { 822 | snprintf(strtmp, TMP_STR, "%02X ", *respvalue++); // print hex 823 | strcat(strout, strtmp); 824 | } 825 | strcat(strout, "\n"); 826 | DBG_Printf(DBG_INFO, "%s", strout); 827 | if (m_isconnected) { 828 | writeToConnectedClients(strout); 829 | } 830 | if (!(zclframe.frameControl() & deCONZ::ZclFCDisableDefaultResponse)) { 831 | sendZclDefaultResponse(ind, zclframe, deCONZ::ZclSuccessStatus); 832 | } 833 | } 834 | 835 | /*! Handles a ZCL Discover Attributes response. 836 | \param ind ZCL 837 | */ 838 | void CliPlugin::handleZclDiscoverAttributesResponse(const deCONZ::ApsDataIndication &ind, 839 | const deCONZ::ZclFrame &zclframe) { 840 | char strout[MAX_STR]; 841 | memset(strout, '\0', MAX_STR); 842 | char strtmp[TMP_STR]; 843 | memset(strtmp, '\0', TMP_STR); 844 | const char *respvalue = zclframe.payload().data(); 845 | const char *endvalue = respvalue + zclframe.payload().length(); 846 | 847 | uint8_t discoveryComplete = *respvalue; //!< A value of 0 indicates that there are some more attributes to be discovered 848 | int x = snprintf(strout, MAX_STR, "<-ZCL discover attr %s for cluster 0x%04X discoveryComplete = %s\n ", 849 | getApsIndSrcAddr(ind).c_str(), ind.clusterId(), discoveryComplete ? "Yes" : "No"); 850 | while (respvalue + 3 < endvalue) { 851 | respvalue++; 852 | uint16_t *attributeId = (uint16_t *) respvalue; 853 | respvalue += 2; 854 | uint8_t typeId = *respvalue; 855 | snprintf(strtmp, TMP_STR, "<-ZCL discover attr %s 0x%04X 0x%04X 0x%02X %s\n ", 856 | getApsIndSrcAddr(ind).c_str(), ind.clusterId(), *attributeId, typeId, getAttributeTypeIdName(typeId)); 857 | x += snprintf(&strout[x], MAX_STR-x, strtmp); 858 | } 859 | DBG_Printf(DBG_INFO, "%s", strout); 860 | if (m_isconnected) { 861 | writeToConnectedClients(strout); 862 | } 863 | } 864 | 865 | /*! Handles a ZCL Server To Client response. 866 | \param ind ZCL 867 | */ 868 | void CliPlugin::handleZclServerToClientResponse(const deCONZ::ApsDataIndication &ind, 869 | const deCONZ::ZclFrame &zclframe) { 870 | char strout[MAX_STR]; 871 | memset(strout, '\0', MAX_STR); 872 | char strtmp[TMP_STR]; 873 | memset(strtmp, '\0', TMP_STR); 874 | const char *respvalue = zclframe.payload().data(); 875 | const char *endvalue = respvalue + zclframe.payload().length(); 876 | 877 | snprintf(strout, MAX_STR, 878 | "<-ZCL serverToClient %s %d for cluster 0x%04X ", 879 | getApsIndSrcAddr(ind).c_str(), ind.srcEndpoint(), ind.clusterId()); 880 | if (zclframe.frameControl() & 0x04) { // manufacturer specific 881 | snprintf(strtmp, TMP_STR, "manufacturer 0x%04X ", zclframe.manufacturerCode()); 882 | strcat(strout, strtmp); 883 | } 884 | while (respvalue < endvalue) { 885 | snprintf(strtmp, TMP_STR, "%02X ", *respvalue++); // print hex 886 | strcat(strout, strtmp); 887 | } 888 | strcat(strout, "\n"); 889 | DBG_Printf(DBG_INFO, "%s", strout); 890 | if (m_isconnected) { 891 | writeToConnectedClients(strout); 892 | } 893 | } 894 | 895 | /*! deCONZ will ask this plugin which features are supported. 896 | \param feature - feature to be checked 897 | \return true if supported 898 | */ 899 | bool CliPlugin::hasFeature(Features feature) { 900 | switch (feature) 901 | { 902 | default: 903 | break; 904 | } 905 | 906 | return false; 907 | } 908 | 909 | /*! Returns the name of this plugin. 910 | */ 911 | const char *CliPlugin::name() { 912 | return "deConz CLI Plugin"; 913 | } 914 | 915 | /*! Process incoming TCP request 916 | */ 917 | void CliPlugin::receiveCommand() { 918 | DBG_Printf(DBG_INFO, "receiveCommand\n"); 919 | 920 | QTcpSocket *socket = tcpServer->nextPendingConnection(); 921 | clientConnection.append(socket); 922 | 923 | connect(socket, SIGNAL(readyRead()), 924 | this, SLOT(readReceivedBytes())); 925 | connect(socket, SIGNAL(disconnected()), 926 | this, SLOT(clientDisconnected())); 927 | connect(socket, SIGNAL(disconnected()), 928 | socket, SLOT(deleteLater())); 929 | 930 | m_isconnected = true; 931 | } 932 | 933 | /*! Client has disconnected 934 | */ 935 | void CliPlugin::clientDisconnected() { 936 | for (int i = 0; i < clientConnection.size(); i++) { 937 | if (clientConnection.at(i)->state() != QAbstractSocket::ConnectedState) { 938 | DBG_Printf(DBG_INFO, "clientDisconnected %d\n", i); 939 | clientConnection.takeAt(i); 940 | } 941 | } 942 | if (clientConnection.isEmpty()) { 943 | m_isconnected = false; 944 | } 945 | } 946 | 947 | /*! Read from data from socket 948 | */ 949 | void CliPlugin::readReceivedBytes() { 950 | deCONZ::Address addr; 951 | bool result = false; 952 | char data[MAX_STR]; 953 | memset(data, '\0', MAX_STR); 954 | DBG_Printf(DBG_INFO, "readReceivedBytes clientConnection.size() = %d \n", clientConnection.size()); 955 | QTcpSocket *socket = static_cast(sender()); 956 | if (socket->bytesAvailable() > 0) { 957 | int len = socket->read(data, MAX_STR); 958 | DBG_Printf(DBG_INFO, "readReceivedBytes %d bytes: %s\n", len, data); 959 | } 960 | writeToConnectedClients(data); 961 | 962 | char command[MAX_STR]; 963 | memset(command, '\0', MAX_STR); 964 | int shortaddr = -1, profile = -1, ep = -1, cluster = -1, attrid = -1, manu = 0; 965 | 966 | if (sscanf(data, "r %x %d %x %x", &shortaddr, &ep, &cluster, &attrid) == 4) { 967 | DBG_Printf(DBG_INFO, "read attribute on %x %d %x %x\n", shortaddr, ep, cluster, attrid); 968 | result = sendZclReadAttributeRequest(shortaddr, ep, cluster, attrid); 969 | } 970 | if (sscanf(data, "zclattr %x %d %x %s", &shortaddr, &ep, &cluster, command) == 4) { 971 | DBG_Printf(DBG_INFO, "zclattr on %x %d %x %s\n", shortaddr, ep, cluster, command); 972 | addr.setNwk(shortaddr); 973 | result = sendZclAttributeRequest(shortaddr, ep, cluster, QByteArray::fromHex(command)); 974 | } 975 | if (sscanf(data, "zclattrmanu %x %d %x %x %s", &shortaddr, &ep, &cluster, &manu, command) == 5) { 976 | DBG_Printf(DBG_INFO, "zclattrmanu on %x %d %x %x %s\n", shortaddr, ep, cluster, manu, command); 977 | addr.setNwk(shortaddr); 978 | result = sendZclAttributeManuSpecRequest(shortaddr, ep, cluster, QByteArray::fromHex(command), manu); 979 | } 980 | if (sscanf(data, "zclcmd %x %d %x %s", &shortaddr, &ep, &cluster, command) == 4) { 981 | DBG_Printf(DBG_INFO, "zclcmd on %x %d %x %s\n", shortaddr, ep, cluster, command); 982 | addr.setNwk(shortaddr); 983 | result = sendZclCmdRequest(addr, ep, cluster, QByteArray::fromHex(command)); 984 | } 985 | if (sscanf(data, "zclcmdgrp %x %d %x %s", &shortaddr, &ep, &cluster, command) == 4) { 986 | DBG_Printf(DBG_INFO, "zclcmdgrp on %x %d %x %s\n", shortaddr, ep, cluster, command); 987 | addr.setGroup(shortaddr); 988 | result = sendZclCmdRequest(addr, ep, cluster, QByteArray::fromHex(command)); 989 | } 990 | if (sscanf(data, "zclcmdmanu %x %d %x %x %s", &shortaddr, &ep, &cluster, &manu, command) == 5) { 991 | DBG_Printf(DBG_INFO, "zclcmdmanu on %x %d %x %s\n", shortaddr, ep, cluster, command, manu); 992 | addr.setNwk(shortaddr); 993 | result = sendZclCmdManuSpecRequest(addr, ep, cluster, QByteArray::fromHex(command), manu); 994 | } 995 | if (sscanf(data, "b %x %d %x", &shortaddr, &ep, &cluster) == 3) { 996 | DBG_Printf(DBG_INFO, "read basic attributes on %x %d 0x%04X\n", shortaddr, ep, cluster); 997 | m_readclust = cluster; 998 | } 999 | if (sscanf(data, "b %x %d", &shortaddr, &ep) == 2) { 1000 | DBG_Printf(DBG_INFO, "read basic attributes on %x %d\n", shortaddr, ep); 1001 | m_readattr = true; 1002 | m_shortaddr = shortaddr; 1003 | m_ep = ep; 1004 | m_attrid = 0x0004; // start attribute id 0x0004 1005 | cluster = 0x0000; // Basic Cluster Id 1006 | attrid = m_attrid; 1007 | result = sendZclReadAttributeRequest(shortaddr, ep, cluster, attrid); 1008 | } 1009 | if (sscanf(data, "m %x %x", &profile, &cluster) == 2) { 1010 | DBG_Printf(DBG_INFO, "zdp match request on cluster %x\n", cluster); 1011 | result = sendZdpMatchCmdRequest(profile, cluster); 1012 | } 1013 | if (sscanf(data, "p %x", &shortaddr) == 1) { 1014 | DBG_Printf(DBG_INFO, "permit join on %x\n", shortaddr); 1015 | result = sendZdpPermitJoinCmdRequest(shortaddr); 1016 | } 1017 | if (sscanf(data, "zdpcmd %x %x %s", &shortaddr, &cluster, command) == 3) { 1018 | DBG_Printf(DBG_INFO, "ZDP request on 0x%04X, commandId = 0x%04X, payload = %s\n", shortaddr, cluster, command); 1019 | addr.setNwk(shortaddr); 1020 | result = sendZdpCmdRequest(addr, (uint16_t) cluster, QByteArray::fromHex(command)); 1021 | } 1022 | if (sscanf(data, "sendtime %x %d", &shortaddr, &ep) == 2) { 1023 | DBG_Printf(DBG_INFO, "send time to shortaddr = %x, ep = %d\n", shortaddr, ep); 1024 | result = sendZclTimeAttributes(shortaddr, ep); 1025 | } 1026 | if (strstr(data, "help") != NULL) { 1027 | printhelp(); 1028 | result = true; 1029 | } 1030 | 1031 | if (result) { 1032 | sprintf(data, " --> send OK\n"); 1033 | } else { 1034 | sprintf(data, " --> send Failed\n"); 1035 | } 1036 | writeToConnectedClients(data); 1037 | } 1038 | 1039 | /*! Write to connected clients 1040 | */ 1041 | void CliPlugin::writeToConnectedClients(char *data) { 1042 | for (int i = 0; i < clientConnection.size(); i++) { 1043 | clientConnection.at(i)->write(data); 1044 | } 1045 | } 1046 | 1047 | /*! Send ZCL Read Attribute Request 1048 | \return true if request was added to queue 1049 | */ 1050 | bool CliPlugin::sendZclReadAttributeRequest(int shortaddr, int ep, int cluster, int attrid) { 1051 | QByteArray payload; 1052 | QDataStream zclstream(&payload, QIODevice::WriteOnly); 1053 | zclstream.setByteOrder(QDataStream::LittleEndian); 1054 | zclstream << (qint8) deCONZ::ZclReadAttributesId; 1055 | zclstream << (quint16) attrid; // Attribute ID 1056 | return sendZclAttributeRequest(shortaddr, ep, cluster, payload); 1057 | } 1058 | 1059 | // General ZCL command ids every cluster shall support. 1060 | // enum ZclGeneralCommandId 1061 | // { 1062 | // ZclReadAttributesId = 0x00, 1063 | // ZclReadAttributesResponseId = 0x01, 1064 | // ZclWriteAttributesId = 0x02, 1065 | // ZclWriteAttributesUndividedId = 0x03, 1066 | // ZclWriteAttributesResponseId = 0x04, 1067 | // ZclWriteAttributesNoResponseId = 0x05, 1068 | // ZclConfigureReportingId = 0x06, 1069 | // ZclConfigureReportingResponseId = 0x07, 1070 | // ZclReadReportingConfigId = 0x08, 1071 | // ZclReadReportingConfigResponseId = 0x09, 1072 | // ZclReportAttributesId = 0x0a, 1073 | // ZclDefaultResponseId = 0x0b, 1074 | // ZclDiscoverAttributesId = 0x0c, 1075 | // ZclDiscoverAttributesResponseId = 0x0d 1076 | // }; 1077 | 1078 | /*! Send ZCL Attribute Request ProfileCommand (Read / Write / Report configuration) 1079 | * with or without Manufacturer Specific Attributes 1080 | \return true if request was added to queue 1081 | */ 1082 | bool CliPlugin::sendZclAttributeGenericRequest(int shortaddr, int ep, int cluster, QByteArray payload, int manu) { 1083 | char strtmp[TMP_STR]; 1084 | int rc = -1; 1085 | 1086 | if (m_apsCtrl->networkState() != deCONZ::InNetwork) { 1087 | return false; 1088 | } 1089 | DBG_Printf(DBG_INFO, "-------------> sendZclReadAttributeRequest\n"); 1090 | 1091 | // generate and remember a new ZDP transaction sequence number 1092 | m_zclSeq = (uint8_t) qrand(); 1093 | 1094 | uint8_t zclattrcmd = payload.at(0); // first byte is commandId 1095 | payload.remove(0,1); // remaining bytes are payload 1096 | 1097 | deCONZ::ZclFrame zclReq; 1098 | uint8_t framecontrol = deCONZ::ZclFCProfileCommand; 1099 | if (manu > 0) { 1100 | framecontrol |= deCONZ::ZclFCManufacturerSpecific; 1101 | DBG_Printf(DBG_INFO, "framecontrol = %02x\n", framecontrol); 1102 | zclReq.setManufacturerCode(manu); 1103 | 1104 | } 1105 | zclReq.setFrameControl(framecontrol); 1106 | zclReq.setSequenceNumber(m_zclSeq); 1107 | zclReq.setCommandId(zclattrcmd); // for example deCONZ::ZclReadAttributesId 1108 | 1109 | // prepare ZCL payload 1110 | zclReq.setPayload(payload); 1111 | 1112 | deCONZ::ApsDataRequest apsReq; 1113 | 1114 | // set destination addressing 1115 | apsReq.setDstAddressMode(deCONZ::ApsNwkAddress); 1116 | apsReq.dstAddress().setNwk(shortaddr); 1117 | apsReq.setDstEndpoint(ep); 1118 | apsReq.setSrcEndpoint(0x01); // Source Endpoint 0x01 1119 | apsReq.setProfileId(HA_PROFILE_ID); 1120 | apsReq.setClusterId(cluster); 1121 | 1122 | // prepare APS payload 1123 | QDataStream stream(&apsReq.asdu(), QIODevice::WriteOnly); 1124 | stream.setByteOrder(QDataStream::LittleEndian); 1125 | 1126 | zclReq.writeToStream(stream); 1127 | 1128 | unsigned int length = apsReq.asdu().length(); 1129 | int strlen = length * 3; // print each byte in hex (2 bytes) and white space 1130 | char rawstr[strlen+1]; 1131 | memset(rawstr, '\0', strlen + 1); 1132 | 1133 | QDataStream asdustream(apsReq.asdu()); 1134 | asdustream.setByteOrder(QDataStream::LittleEndian); 1135 | uint8_t asdudata; 1136 | while (!asdustream.atEnd()) { 1137 | asdustream >> asdudata; 1138 | char rawtmp[4]; 1139 | memset(rawtmp, '\0', 4); 1140 | snprintf(rawtmp, 4, "%02X ", asdudata); 1141 | strcat(rawstr, rawtmp); 1142 | } 1143 | DBG_Printf(DBG_INFO, "APS Req id 0x%02X %d profile 0x%04X %s, shortaddr = 0x%04X, ep = %d\n", 1144 | apsReq.id(), length, apsReq.profileId(), rawstr, shortaddr, ep); 1145 | 1146 | if (m_apsCtrl && ((rc = m_apsCtrl->apsdeDataRequest(apsReq)) == deCONZ::Success)) { 1147 | // remember request 1148 | m_apsReqQueue.push_back(apsReq); 1149 | return true; 1150 | } else { 1151 | // Success = 0, //!< Success 1152 | // ErrorNotConnected, //!< Not connected (device or network) 1153 | // ErrorQueueIsFull, //!< Queue is full 1154 | // ErrorNodeIsZombie, //!< Node is not reachable 1155 | // ErrorNotFound //!< Not found 1156 | switch(rc) { 1157 | case -1: snprintf(strtmp, TMP_STR, "controller not connected "); break; 1158 | case deCONZ::ErrorNotConnected: snprintf(strtmp, TMP_STR, " ErrorNotConnected "); break; 1159 | case deCONZ::ErrorQueueIsFull: snprintf(strtmp, TMP_STR, " ErrorQueueIsFull "); break; 1160 | case deCONZ::ErrorNodeIsZombie: snprintf(strtmp, TMP_STR, " ErrorNodeIsZombie "); break; 1161 | case deCONZ::ErrorNotFound: snprintf(strtmp, TMP_STR, " ErrorNotFound "); break; 1162 | default: snprintf(strtmp, TMP_STR, " Error %d ", rc); 1163 | 1164 | } 1165 | writeToConnectedClients(strtmp); 1166 | } 1167 | 1168 | return false; 1169 | } 1170 | 1171 | /*! Send ZCL Attribute Request ProfileCommand (Read / Write / Report configuration) 1172 | \return true if request was added to queue 1173 | */ 1174 | bool CliPlugin::sendZclAttributeRequest(int shortaddr, int ep, int cluster, QByteArray payload) { 1175 | return sendZclAttributeGenericRequest(shortaddr, ep, cluster, payload, 0); 1176 | } 1177 | 1178 | /*! Send ZCL Attribute Manufacturer Specific Request ProfileCommand (Read / Write / Report configuration) 1179 | \return true if request was added to queue 1180 | */ 1181 | bool CliPlugin::sendZclAttributeManuSpecRequest(int shortaddr, int ep, int cluster, QByteArray payload, int manu) { 1182 | return sendZclAttributeGenericRequest(shortaddr, ep, cluster, payload, manu); 1183 | } 1184 | 1185 | /*! Send ZCL Request ClusterCommand (Send Command) 1186 | \return true if request was added to queue 1187 | */ 1188 | bool CliPlugin::sendZclCmdGenericRequest(deCONZ::Address dstAddress, 1189 | uint8_t ep, uint16_t cluster, QByteArray payload, int manu) { 1190 | char strtmp[TMP_STR]; 1191 | int rc = -1; 1192 | 1193 | if (m_apsCtrl->networkState() != deCONZ::InNetwork) { 1194 | return false; 1195 | } 1196 | DBG_Printf(DBG_INFO, "-------------> sendZclCmdRequest\n"); 1197 | 1198 | uint8_t commandId = payload.at(0); // zcl command 1199 | payload.remove(0,1); 1200 | 1201 | deCONZ::ZclFrame zclReq; 1202 | uint8_t framecontrol = deCONZ::ZclFCClusterCommand|deCONZ::ZclFCEnableDefaultResponse; 1203 | if (manu > 0) { 1204 | framecontrol |= deCONZ::ZclFCManufacturerSpecific; 1205 | DBG_Printf(DBG_INFO, "framecontrol = %02x\n", framecontrol); 1206 | zclReq.setManufacturerCode(manu); 1207 | } 1208 | zclReq.setFrameControl(framecontrol); 1209 | zclReq.setSequenceNumber((uint8_t) qrand()); 1210 | zclReq.setCommandId(commandId); 1211 | zclReq.setPayload(payload); 1212 | 1213 | deCONZ::ApsDataRequest apsReq; 1214 | 1215 | // set destination addressing 1216 | if (dstAddress.hasNwk()) { 1217 | apsReq.setDstAddressMode(deCONZ::ApsNwkAddress); 1218 | apsReq.dstAddress().setNwk(dstAddress.nwk()); 1219 | } else if (dstAddress.hasGroup()) { 1220 | apsReq.setDstAddressMode(deCONZ::ApsGroupAddress); 1221 | apsReq.dstAddress().setGroup(dstAddress.group()); 1222 | } else if (dstAddress.hasExt()) { 1223 | apsReq.setDstAddressMode(deCONZ::ApsExtAddress); 1224 | apsReq.dstAddress().setExt(dstAddress.ext()); 1225 | } 1226 | apsReq.setDstEndpoint(ep); 1227 | apsReq.setSrcEndpoint(0x1 /*ZDO_ENDPOINT*/); 1228 | apsReq.setProfileId(HA_PROFILE_ID); 1229 | apsReq.setClusterId(cluster); 1230 | 1231 | // prepare payload 1232 | QDataStream stream(&apsReq.asdu(), QIODevice::WriteOnly); 1233 | stream.setByteOrder(QDataStream::LittleEndian); 1234 | zclReq.writeToStream(stream); 1235 | 1236 | unsigned int length = apsReq.asdu().length(); 1237 | int strlen = length * 3; // print each byte in hex (2 bytes) and white space 1238 | char rawstr[strlen+1]; 1239 | memset(rawstr, '\0', strlen + 1); 1240 | 1241 | QDataStream asdustream(apsReq.asdu()); 1242 | asdustream.setByteOrder(QDataStream::LittleEndian); 1243 | uint8_t asdudata; 1244 | while (!asdustream.atEnd()) { 1245 | asdustream >> asdudata; 1246 | char rawtmp[4]; 1247 | memset(rawtmp, '\0', 4); 1248 | snprintf(rawtmp, 4, "%02X ", asdudata); 1249 | strcat(rawstr, rawtmp); 1250 | } 1251 | DBG_Printf(DBG_INFO, "APS Req id 0x%02X %d: %s. dstAddr = 0x%04X dstEp = 0x%02X done\n", 1252 | apsReq.id(), length, rawstr, dstAddress.nwk(), ep); 1253 | 1254 | if (m_apsCtrl && ((rc = m_apsCtrl->apsdeDataRequest(apsReq)) == deCONZ::Success)) { 1255 | // remember request 1256 | m_apsReqQueue.push_back(apsReq); 1257 | return true; 1258 | } else { 1259 | // Success = 0, //!< Success 1260 | // ErrorNotConnected, //!< Not connected (device or network) 1261 | // ErrorQueueIsFull, //!< Queue is full 1262 | // ErrorNodeIsZombie, //!< Node is not reachable 1263 | // ErrorNotFound //!< Not found 1264 | switch(rc) { 1265 | case -1: snprintf(strtmp, TMP_STR, "controller not connected "); break; 1266 | case deCONZ::ErrorNotConnected: snprintf(strtmp, TMP_STR, " ErrorNotConnected "); break; 1267 | case deCONZ::ErrorQueueIsFull: snprintf(strtmp, TMP_STR, " ErrorQueueIsFull "); break; 1268 | case deCONZ::ErrorNodeIsZombie: snprintf(strtmp, TMP_STR, " ErrorNodeIsZombie "); break; 1269 | case deCONZ::ErrorNotFound: snprintf(strtmp, TMP_STR, " ErrorNotFound "); break; 1270 | default: snprintf(strtmp, TMP_STR, " Error %d ", rc); 1271 | 1272 | } 1273 | writeToConnectedClients(strtmp); 1274 | } 1275 | 1276 | return false; 1277 | } 1278 | 1279 | /*! Send ZCL Request ClusterCommand (Send Command) 1280 | \return true if request was added to queue 1281 | */ 1282 | bool CliPlugin::sendZclCmdRequest(deCONZ::Address dstAddress, 1283 | uint8_t ep, uint16_t cluster, QByteArray payload) { 1284 | return sendZclCmdGenericRequest(dstAddress, ep, cluster, payload, 0); 1285 | } 1286 | 1287 | /*! Send ZCL Request ClusterCommand (Send Command) Manufacturer Specific 1288 | \return true if request was added to queue 1289 | */ 1290 | bool CliPlugin::sendZclCmdManuSpecRequest(deCONZ::Address dstAddress, 1291 | uint8_t ep, uint16_t cluster, QByteArray payload, int manu) { 1292 | return sendZclCmdGenericRequest(dstAddress, ep, cluster, payload, manu); 1293 | } 1294 | 1295 | /*! Send ZCL Default Response 1296 | \return true if request was added to queue 1297 | */ 1298 | bool CliPlugin::sendZclDefaultResponse(const deCONZ::ApsDataIndication &ind, const deCONZ::ZclFrame &zclFrame, quint8 status) { 1299 | char strtmp[TMP_STR]; 1300 | int rc = -1; 1301 | deCONZ::ApsDataRequest apsReq; 1302 | 1303 | // ZDP Header 1304 | apsReq.dstAddress() = ind.srcAddress(); 1305 | apsReq.setDstAddressMode(ind.srcAddressMode()); 1306 | apsReq.setDstEndpoint(ind.srcEndpoint()); 1307 | apsReq.setSrcEndpoint(ind.dstEndpoint()); 1308 | apsReq.setProfileId(ind.profileId()); 1309 | apsReq.setRadius(0); 1310 | apsReq.setClusterId(ind.clusterId()); 1311 | 1312 | deCONZ::ZclFrame outZclFrame; 1313 | outZclFrame.setSequenceNumber(zclFrame.sequenceNumber()); 1314 | outZclFrame.setCommandId(deCONZ::ZclDefaultResponseId); 1315 | outZclFrame.setFrameControl(deCONZ::ZclFCProfileCommand | 1316 | deCONZ::ZclFCDirectionClientToServer | 1317 | deCONZ::ZclFCDisableDefaultResponse); 1318 | 1319 | { // ZCL payload 1320 | QDataStream stream(&outZclFrame.payload(), QIODevice::WriteOnly); 1321 | stream.setByteOrder(QDataStream::LittleEndian); 1322 | stream << zclFrame.commandId(); 1323 | stream << status; 1324 | } 1325 | 1326 | { // ZCL frame 1327 | QDataStream stream(&apsReq.asdu(), QIODevice::WriteOnly); 1328 | stream.setByteOrder(QDataStream::LittleEndian); 1329 | outZclFrame.writeToStream(stream); 1330 | } 1331 | 1332 | snprintf(strtmp, TMP_STR, "->ZCL default response seq id 0x%02X command id 0x%02X, status 0x%02X\n", 1333 | zclFrame.sequenceNumber(), zclFrame.commandId(), status); 1334 | DBG_Printf(DBG_INFO, strtmp); 1335 | 1336 | if (m_apsCtrl && ((rc = m_apsCtrl->apsdeDataRequest(apsReq)) == deCONZ::Success)) { 1337 | // remember request 1338 | m_apsReqQueue.push_back(apsReq); 1339 | writeToConnectedClients(strtmp); 1340 | return true; 1341 | } else { 1342 | // Success = 0, //!< Success 1343 | // ErrorNotConnected, //!< Not connected (device or network) 1344 | // ErrorQueueIsFull, //!< Queue is full 1345 | // ErrorNodeIsZombie, //!< Node is not reachable 1346 | // ErrorNotFound //!< Not found 1347 | switch(rc) { 1348 | case -1: snprintf(strtmp, TMP_STR, "controller not connected "); break; 1349 | case deCONZ::ErrorNotConnected: snprintf(strtmp, TMP_STR, " ErrorNotConnected "); break; 1350 | case deCONZ::ErrorQueueIsFull: snprintf(strtmp, TMP_STR, " ErrorQueueIsFull "); break; 1351 | case deCONZ::ErrorNodeIsZombie: snprintf(strtmp, TMP_STR, " ErrorNodeIsZombie "); break; 1352 | case deCONZ::ErrorNotFound: snprintf(strtmp, TMP_STR, " ErrorNotFound "); break; 1353 | default: snprintf(strtmp, TMP_STR, " Error %d ", rc); 1354 | } 1355 | writeToConnectedClients(strtmp); 1356 | } 1357 | return false; 1358 | 1359 | } 1360 | 1361 | /*! Send ZDP Match Request 1362 | \return true if request was added to queue 1363 | */ 1364 | bool CliPlugin::sendZdpMatchCmdRequest(int profile, int cluster) { 1365 | deCONZ::Address dstAddress; 1366 | dstAddress.setNwk(deCONZ::BroadcastRxOnWhenIdle); 1367 | uint16_t zdpcommand = ZDP_MATCH_DESCRIPTOR_CLID; 1368 | QByteArray asdu; 1369 | QDataStream stream(&asdu, QIODevice::WriteOnly); 1370 | stream.setByteOrder(QDataStream::LittleEndian); 1371 | 1372 | // write payload according to ZigBee specification (2.4.3.1.7 Match_Descr_req) 1373 | // NOTE: explicit castings ensure correct size of the fields 1374 | stream << (quint16) deCONZ::BroadcastRxOnWhenIdle; // NWKAddrOfInterest 1375 | stream << (quint16) profile; //ZLL_PROFILE_ID or HA_PROFILE_ID; 1376 | stream << (quint8) 0x01; // NumInClusters 1377 | stream << (quint16) cluster; // OnOff ClusterID 1378 | stream << (quint8) 0x00; // NumOutClusters 1379 | 1380 | bool result = sendZdpCmdRequest(dstAddress, zdpcommand, asdu); 1381 | 1382 | if (result) { 1383 | m_profile = profile; 1384 | m_cluster = cluster; 1385 | m_zdpmatchreq = true; 1386 | } 1387 | return result; 1388 | } 1389 | 1390 | /*! Send ZDP Permit Join Request 1391 | \return true if request was added to queue 1392 | */ 1393 | bool CliPlugin::sendZdpPermitJoinCmdRequest(int shortaddr) { 1394 | deCONZ::Address dstAddress; 1395 | dstAddress.setNwk(shortaddr); 1396 | uint16_t zdpcommand = ZDP_MGMT_PERMIT_JOINING_REQ_CLID; //ZDP_MATCH_DESCRIPTOR_CLID; 1397 | QByteArray asdu; 1398 | QDataStream stream(&asdu, QIODevice::WriteOnly); 1399 | stream.setByteOrder(QDataStream::LittleEndian); 1400 | 1401 | // write payload according to ZigBee specification (2.4.3.3.7 Mgmt_Permit_Joining_req) 1402 | // NOTE: explicit castings ensure correct size of the fields 1403 | stream << (quint8) 180; // Permit joining for 180 seconds 1404 | stream << (quint8) 0x00; // TC_Significance always 0x00 or 0x01 1405 | 1406 | DBG_Printf(DBG_INFO, "Permit Join reguest payload = %s\n", asdu.toHex().data()); 1407 | m_apsCtrl->setPermitJoin(180); // set permitJoin on coordinator 1408 | 1409 | return sendZdpCmdRequest(dstAddress, zdpcommand, asdu); 1410 | } 1411 | 1412 | 1413 | /*! Send ZDP Request (for example Zdp_match_req) 1414 | \return true if request was added to queue 1415 | */ 1416 | bool CliPlugin::sendZdpCmdRequest(deCONZ::Address dstAddress, uint16_t zdpcommand, QByteArray asdu) { 1417 | deCONZ::ApsDataRequest apsReq; 1418 | char strtmp[TMP_STR]; 1419 | int rc = -1; 1420 | 1421 | // set destination addressing 1422 | if (dstAddress.hasNwk()) { 1423 | apsReq.dstAddress().setNwk(dstAddress.nwk() /*deCONZ::BroadcastRxOnWhenIdle*/); 1424 | apsReq.setDstAddressMode(deCONZ::ApsNwkAddress); 1425 | } else if (dstAddress.hasGroup()) { 1426 | apsReq.dstAddress().setGroup(dstAddress.group()); 1427 | apsReq.setDstAddressMode(deCONZ::ApsGroupAddress); 1428 | } else if (dstAddress.hasExt()) { 1429 | apsReq.dstAddress().setExt(dstAddress.ext()); 1430 | apsReq.setDstAddressMode(deCONZ::ApsExtAddress); 1431 | } 1432 | apsReq.setDstEndpoint(ZDO_ENDPOINT); 1433 | apsReq.setSrcEndpoint(ZDO_ENDPOINT); 1434 | apsReq.setProfileId(ZDP_PROFILE_ID); 1435 | apsReq.setClusterId(zdpcommand); 1436 | 1437 | char zdpseq = (char) qrand(); // ZDP transaction sequence number 1438 | asdu.prepend(zdpseq); // insert at beginning 1439 | apsReq.setAsdu(asdu); 1440 | DBG_Printf(DBG_INFO, "APS ZDP Req id 0x%02X %s\n", apsReq.id(), asdu.toHex().toUpper().data()); 1441 | 1442 | if (m_apsCtrl && ((rc = m_apsCtrl->apsdeDataRequest(apsReq)) == deCONZ::Success)) { 1443 | // remember request 1444 | m_apsReqQueue.push_back(apsReq); 1445 | return true; 1446 | } else { 1447 | // Success = 0, //!< Success 1448 | // ErrorNotConnected, //!< Not connected (device or network) 1449 | // ErrorQueueIsFull, //!< Queue is full 1450 | // ErrorNodeIsZombie, //!< Node is not reachable 1451 | // ErrorNotFound //!< Not found 1452 | switch(rc) { 1453 | case -1: snprintf(strtmp, TMP_STR, "controller not connected "); break; 1454 | case deCONZ::ErrorNotConnected: snprintf(strtmp, TMP_STR, " ErrorNotConnected "); break; 1455 | case deCONZ::ErrorQueueIsFull: snprintf(strtmp, TMP_STR, " ErrorQueueIsFull "); break; 1456 | case deCONZ::ErrorNodeIsZombie: snprintf(strtmp, TMP_STR, " ErrorNodeIsZombie "); break; 1457 | case deCONZ::ErrorNotFound: snprintf(strtmp, TMP_STR, " ErrorNotFound "); break; 1458 | default: snprintf(strtmp, TMP_STR, " Error %d ", rc); 1459 | } 1460 | writeToConnectedClients(strtmp); 1461 | } 1462 | return false; 1463 | } 1464 | 1465 | /*! Send ZCL Write Attributes to Time_Cluster 0x000A 1466 | * Time Cluster Attributes in 3.12.2.2 1467 | * write Attributes 0x0000 Time, 0x0001 TimeStatus (synchronized) 1468 | * TimeZone 0x0002 , DstStart 0x0003, DstEnd 0x0004, DstShift 0x0005 1469 | * \return true if request was added to queue 1470 | */ 1471 | bool CliPlugin::sendZclTimeAttributes(int shortaddr, int ep) { 1472 | time_t time_dst_start, time_dst_end, time_now, time_utc_2000; 1473 | int time_dst_shift, time_gmt_offset; 1474 | int x; 1475 | char data[MAX_STR]; 1476 | memset(data, '\0', MAX_STR); 1477 | 1478 | time_now = time(NULL); 1479 | time_utc_2000 = 946684800; // on 1. Jan 2000 UTC since epoch 1480 | get_dst(&time_dst_start, &time_dst_end, &time_dst_shift); 1481 | time_gmt_offset = get_gmtdiff(); 1482 | 1483 | DBG_Printf(DBG_INFO, "<- time_now %ld, %s", (long) time_now, ctime(&time_now)); 1484 | DBG_Printf(DBG_INFO, "<- time_dst_start %ld, %s", (long) time_dst_start, ctime(&time_dst_start)); 1485 | DBG_Printf(DBG_INFO, "<- time_dst_end %ld, %s", (long) time_dst_end, ctime(&time_dst_end)); 1486 | DBG_Printf(DBG_INFO, "<- time_dst_shift %d\n", time_dst_shift); 1487 | DBG_Printf(DBG_INFO, "<- time_zone %d\n", time_gmt_offset); 1488 | x = sprintf(data, "<- time_now %ld, %s", (long) time_now, ctime(&time_now) ); 1489 | x += sprintf(&data[x], "<- time_dst_start %ld, %s", (long) time_dst_start, ctime(&time_dst_start)); 1490 | x += sprintf(&data[x], "<- time_dst_end %ld, %s", (long) time_dst_end, ctime(&time_dst_end)); 1491 | x += sprintf(&data[x], "<- time_dst_shift %d\n", time_dst_shift); 1492 | x += sprintf(&data[x], "<- time_zone %d\n", time_gmt_offset); 1493 | writeToConnectedClients(data); 1494 | 1495 | // convert time to epoch seconds since 1. Jan 2000 UTC 1496 | uint32_t timenow = (uint32_t) difftime(time_now, time_utc_2000); 1497 | uint8_t timestatus = 0x02; // Synchronized bit-1 1498 | int32_t timezone = (int32_t) time_gmt_offset; 1499 | uint32_t dststart = (uint32_t) difftime(time_dst_start, time_utc_2000); 1500 | uint32_t dstend = (uint32_t) difftime(time_dst_end, time_utc_2000); 1501 | int32_t dstshift = (int32_t) time_dst_shift; 1502 | 1503 | // generate and remember a new ZDP transaction sequence number 1504 | m_zclSeq = (uint8_t) qrand(); 1505 | 1506 | int cluster = 0x000A; // Time cluster 1507 | uint8_t zclattrcmd = deCONZ::ZclWriteAttributesId; 1508 | 1509 | QByteArray zclpayload; 1510 | QDataStream zclstream(&zclpayload, QIODevice::WriteOnly); 1511 | zclstream.setByteOrder(QDataStream::LittleEndian); 1512 | 1513 | zclstream << (quint8) zclattrcmd; // first byte is command id 1514 | 1515 | zclstream << (quint16) 0x0000; // Attribute ID 1516 | zclstream << (quint8) 0xE2; // Attribute Type 0xE2 UTC Time 1517 | zclstream << (quint32) timenow; // Time 1518 | 1519 | zclstream << (quint16) 0x0001; // Attribute ID 1520 | zclstream << (quint8) 0x18; // Attribute Type 0x18 bit8 1521 | zclstream << (quint8) timestatus; // Time Status 1522 | 1523 | zclstream << (quint16) 0x0002; // Attribute ID 1524 | zclstream << (quint8) 0x2B; // Attribute Type 0x2B 1525 | zclstream << (qint32) timezone; // Timezone 1526 | 1527 | zclstream << (quint16) 0x0003; // Attribute ID 1528 | zclstream << (quint8) 0x23; // Attribute Type 0x23 1529 | zclstream << (quint32) dststart; // Daylight Saving Start 1530 | 1531 | zclstream << (quint16) 0x0004; // Attribute ID 1532 | zclstream << (quint8) 0x23; // Attribute Type 0x23 1533 | zclstream << (quint32) dstend; // Daylight Saving End 1534 | 1535 | zclstream << (quint16) 0x0005; // Attribute ID 1536 | zclstream << (quint8) 0x2B; // Attribute Type 0x2B 1537 | zclstream << (qint32) dstshift; // Daylight Saving Time Shift 1538 | 1539 | return sendZclAttributeRequest(shortaddr, ep, cluster, zclpayload); 1540 | } 1541 | 1542 | /*! get dst daylight saving start, end, shift 1543 | * 1544 | */ 1545 | void CliPlugin::get_dst_start_end(int finddst /* 0 or 1 */, time_t *time_dst_start, time_t *time_dst_end, int *time_dst_shift) { 1546 | struct tm tm1, tm2; 1547 | time_t tstart, time_dst_start_end; 1548 | int i_start, i, j, k; 1549 | i_start = 1; 1550 | tstart = time(NULL); 1551 | tm1 = *gmtime(&tstart); 1552 | tm1.tm_mon = tm1.tm_hour = tm1.tm_min = tm1.tm_sec = 0; // reset to Januar midnight 1553 | if (finddst == 0) { 1554 | tstart = *time_dst_start; 1555 | tm1 = *gmtime(&tstart); 1556 | i_start = tm1.tm_mday + 1; 1557 | } 1558 | for (i = i_start; i < 364; i++) { // step over every day 1559 | tm1.tm_mday = i; 1560 | tm2 = tm1; 1561 | time_dst_start_end = mktime(&tm2); 1562 | if (tm2.tm_isdst == finddst /* 1 or 0*/) { // found the day after dst start/end 1563 | tm1 = tm2; 1564 | for (j = 0; j < 24; j++) { 1565 | tm1.tm_hour--; 1566 | tm2 = tm1; 1567 | time_dst_start_end = mktime(&tm2); 1568 | if (tm2.tm_isdst == (1 - finddst) /* 0 or 1*/) { // found the hour before dst start/end 1569 | for (k = 0; k < 60; k++) { 1570 | tm1.tm_min++; 1571 | tm2 = tm1; 1572 | time_dst_start_end = mktime(&tm2); 1573 | if (tm2.tm_isdst == finddst /* 1 or 0*/) { // found the first minute with dst start/end 1574 | if (finddst == 1) { 1575 | *time_dst_start = time_dst_start_end; 1576 | struct tm time_gmt_tm = *gmtime(&time_dst_start_end); 1577 | struct tm time_local_tm = *localtime(&time_dst_start_end); 1578 | time_t time_gmt_t = mktime(&time_gmt_tm); 1579 | time_t time_local_t = mktime(&time_local_tm); 1580 | *time_dst_shift = (int) difftime(time_local_t, time_gmt_t); 1581 | } else { 1582 | *time_dst_end = time_dst_start_end; 1583 | } 1584 | break; 1585 | } 1586 | } 1587 | break; 1588 | } 1589 | } 1590 | break; 1591 | } 1592 | } 1593 | } 1594 | 1595 | /*! get dst daylight saving start, end, shift 1596 | * 1597 | */ 1598 | void CliPlugin::get_dst(time_t *time_dst_start, time_t *time_dst_end, int *time_dst_shift) { 1599 | get_dst_start_end(1, time_dst_start, time_dst_end, time_dst_shift); 1600 | get_dst_start_end(0, time_dst_start, time_dst_end, time_dst_shift); 1601 | } 1602 | 1603 | /*! epoch time since 1. Jan 2000 1604 | * 1605 | */ 1606 | long CliPlugin::get_epoch_since_2000(time_t tinput) { 1607 | time_t time_utc_on_2000 = 946684800; // 1. Jan 2000 UTC seconds since epoch 1608 | return (long) difftime(tinput, time_utc_on_2000); 1609 | } 1610 | 1611 | /*! get gmt offset to local timezone 1612 | * 1613 | */ 1614 | int CliPlugin::get_gmtdiff() { 1615 | struct tm tm1, tm2; 1616 | time_t t1, t2; 1617 | time_t tnow = time(NULL); 1618 | tm1 = *gmtime(&tnow); 1619 | tm2 = *localtime(&tnow); 1620 | t1 = mktime(&tm1); 1621 | t2 = mktime(&tm2); 1622 | return (int) difftime(t2, t1); 1623 | } 1624 | 1625 | /*! print help 1626 | * 1627 | */ 1628 | void CliPlugin::printhelp() { 1629 | char data[MAX_STR]; 1630 | int x = 0; 1631 | x += snprintf(&data[x], MAX_STR - x, "r \tread attributes\n"); 1632 | x += snprintf(&data[x], MAX_STR - x, "b \tread basic attributes\n"); 1633 | x += snprintf(&data[x], MAX_STR - x, "b \tread basic attributes on cluster\n"); 1634 | x += snprintf(&data[x], MAX_STR - x, "m \tsend match descriptor request (discover cluster)\n"); 1635 | x += snprintf(&data[x], MAX_STR - x, "p \tpermit Joining on device (coordinator = 0)\n"); 1636 | x += snprintf(&data[x], MAX_STR - x, "zclattr \tsend ZCL attribute request\n"); 1637 | x += snprintf(&data[x], MAX_STR - x, "zclcmd \tsend ZCL command request\n"); 1638 | x += snprintf(&data[x], MAX_STR - x, "zclcmdgrp \tsend ZCL command request to group\n"); 1639 | x += snprintf(&data[x], MAX_STR - x, "zdpcmd \tsend ZDP command request\n"); 1640 | x += snprintf(&data[x], MAX_STR - x, "zclattrmanu \tsend ZCL attribute request manufacturer specific\n"); 1641 | x += snprintf(&data[x], MAX_STR - x, "zclcmdmanu \tsend ZCL command request manufacturer specific\n"); 1642 | 1643 | writeToConnectedClients(data); 1644 | } 1645 | #if QT_VERSION < 0x050000 1646 | Q_EXPORT_PLUGIN2(pilight_plugin, PilightApsPlugin) 1647 | #endif 1648 | --------------------------------------------------------------------------------