From 4abe6a145ca2777bad1fc6a865a4911fef42cc2f Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 7 Jul 2015 14:54:23 +0200 Subject: [PATCH 01/17] Add ZBExplicitTxRequest This allows sending "Explicit Addressing ZigBee Command" API frames, that allow specifying the endpoints, profile id and cluster id for a transmitted message, e.g. to interact through standard Zigbee protocols. --- XBee.cpp | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ XBee.h | 61 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 147 insertions(+), 1 deletion(-) diff --git a/XBee.cpp b/XBee.cpp index 20e60c7..3787d5c 100755 --- a/XBee.cpp +++ b/XBee.cpp @@ -1139,6 +1139,93 @@ void ZBTxRequest::setOption(uint8_t option) { _option = option; } + + +ZBExplicitTxRequest::ZBExplicitTxRequest() : ZBTxRequest() { + _srcEndpoint = DEFAULT_ENDPOINT; + _dstEndpoint = DEFAULT_ENDPOINT; + _profileId = DEFAULT_PROFILE_ID; + _clusterId = DEFAULT_CLUSTER_ID; + setApiId(ZB_EXPLICIT_TX_REQUEST); +} + +ZBExplicitTxRequest::ZBExplicitTxRequest(XBeeAddress64 &addr64, uint16_t addr16, uint8_t broadcastRadius, uint8_t option, uint8_t *payload, uint8_t payloadLength, uint8_t frameId, uint8_t srcEndpoint, uint8_t dstEndpoint, uint16_t clusterId, uint16_t profileId) +: ZBTxRequest(addr64, addr16, broadcastRadius, option, payload, payloadLength, frameId) { + _srcEndpoint = srcEndpoint; + _dstEndpoint = dstEndpoint; + _profileId = profileId; + _clusterId = clusterId; + setApiId(ZB_EXPLICIT_TX_REQUEST); +} + +ZBExplicitTxRequest::ZBExplicitTxRequest(XBeeAddress64 &addr64, uint8_t *payload, uint8_t payloadLength) +: ZBTxRequest(addr64, payload, payloadLength) { + _srcEndpoint = DEFAULT_ENDPOINT; + _dstEndpoint = DEFAULT_ENDPOINT; + _profileId = DEFAULT_PROFILE_ID; + _clusterId = DEFAULT_CLUSTER_ID; + setApiId(ZB_EXPLICIT_TX_REQUEST); +} + +uint8_t ZBExplicitTxRequest::getFrameData(uint8_t pos) { + if (pos < 10) { + return ZBTxRequest::getFrameData(pos); + } else if (pos == 10) { + return _srcEndpoint; + } else if (pos == 11) { + return _dstEndpoint; + } else if (pos == 12) { + return (_clusterId >> 8) & 0xff; + } else if (pos == 13) { + return _clusterId & 0xff; + } else if (pos == 14) { + return (_profileId >> 8) & 0xff; + } else if (pos == 15) { + return _profileId & 0xff; + } else if (pos == 16) { + return _broadcastRadius; + } else if (pos == 17) { + return _option; + } else { + return getPayload()[pos - ZB_EXPLICIT_TX_API_LENGTH]; + } +} + +uint8_t ZBExplicitTxRequest::getFrameDataLength() { + return ZB_EXPLICIT_TX_API_LENGTH + getPayloadLength(); +} + +uint8_t ZBExplicitTxRequest::getSrcEndpoint() { + return _srcEndpoint; +} + +uint8_t ZBExplicitTxRequest::getDstEndpoint() { + return _dstEndpoint; +} + +uint16_t ZBExplicitTxRequest::getClusterId() { + return _clusterId; +} + +uint16_t ZBExplicitTxRequest::getProfileId() { + return _profileId; +} + +void ZBExplicitTxRequest::setSrcEndpoint(uint8_t endpoint) { + _srcEndpoint = endpoint; +} + +void ZBExplicitTxRequest::setDstEndpoint(uint8_t endpoint) { + _dstEndpoint = endpoint; +} + +void ZBExplicitTxRequest::setClusterId(uint16_t clusterId) { + _clusterId = clusterId; +} + +void ZBExplicitTxRequest::setProfileId(uint16_t profileId) { + _profileId = profileId; +} #endif #ifdef SERIES_1 diff --git a/XBee.h b/XBee.h index ccbfc23..e1340bc 100755 --- a/XBee.h +++ b/XBee.h @@ -52,6 +52,7 @@ // the non-variable length of the frame data (not including frame id or api id or variable data size (e.g. payload, at command set value) #define ZB_TX_API_LENGTH 12 +#define ZB_EXPLICIT_TX_API_LENGTH 18 #define TX_16_API_LENGTH 3 #define TX_64_API_LENGTH 9 #define AT_COMMAND_API_LENGTH 2 @@ -68,6 +69,12 @@ #define DEFAULT_FRAME_ID 1 #define NO_RESPONSE_FRAME_ID 0 +// These are the parameters used by the XBee ZB modules when you do a +// regular "ZB TX request". +#define DEFAULT_ENDPOINT 232 +#define DEFAULT_CLUSTER_ID 0x0011 +#define DEFAULT_PROFILE_ID 0xc105 + // TODO put in tx16 class #define ACK_OPTION 0 #define DISABLE_ACK_OPTION 1 @@ -879,13 +886,65 @@ class ZBTxRequest : public PayloadRequest { // declare virtual functions uint8_t getFrameData(uint8_t pos); uint8_t getFrameDataLength(); -private: XBeeAddress64 _addr64; uint16_t _addr16; uint8_t _broadcastRadius; uint8_t _option; }; +/** + * Represents a Series 2 TX packet that corresponds to Api Id: ZB_EXPLICIT_TX_REQUEST + * + * See the warning about maximum packet size for ZBTxRequest above, + * which probably also applies here as well. + * + * Note that to distinguish reply packets from non-XBee devices, set + * AO=1 to enable reception of ZBExplicitRxResponse packets. + */ +class ZBExplicitTxRequest : public ZBTxRequest { +public: + /** + * Creates a unicast ZBExplicitTxRequest with the ACK option and + * DEFAULT_FRAME_ID. + * + * It uses the Maxstream profile (0xc105), both endpoints 232 + * and cluster 0x0011, resulting in the same packet as sent by a + * normal ZBTxRequest. + */ + ZBExplicitTxRequest(XBeeAddress64 &addr64, uint8_t *payload, uint8_t payloadLength); + /** + * Create a ZBExplicitTxRequest, specifying all fields. + */ + ZBExplicitTxRequest(XBeeAddress64 &addr64, uint16_t addr16, uint8_t broadcastRadius, uint8_t option, uint8_t *payload, uint8_t payloadLength, uint8_t frameId, uint8_t srcEndpoint, uint8_t dstEndpoint, uint16_t clusterId, uint16_t profileId); + /** + * Creates a default instance of this class. At a minimum you + * must specify a payload, payload length and a destination + * address before sending this request. + * + * Furthermore, it uses the Maxstream profile (0xc105), both + * endpoints 232 and cluster 0x0011, resulting in the same + * packet as sent by a normal ZBExplicitTxRequest. + */ + ZBExplicitTxRequest(); + uint8_t getSrcEndpoint(); + uint8_t getDstEndpoint(); + uint16_t getClusterId(); + uint16_t getProfileId(); + void setSrcEndpoint(uint8_t endpoint); + void setDstEndpoint(uint8_t endpoint); + void setClusterId(uint16_t clusterId); + void setProfileId(uint16_t profileId); +protected: + // declare virtual functions + uint8_t getFrameData(uint8_t pos); + uint8_t getFrameDataLength(); +private: + uint8_t _srcEndpoint; + uint8_t _dstEndpoint; + uint16_t _profileId; + uint16_t _clusterId; +}; + #endif /** From 36ab282fd8a9fd9324953d0f81ec08fb2dc62417 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 7 Jul 2015 14:54:23 +0200 Subject: [PATCH 02/17] Add ZBExplicitRxResponse This allows receiving "ZigBee Explicit Rx Indicator" API frames, that specify the endpoints, profile id and cluster id for a received message, e.g. to interact through standard Zigbee protocols. These API frames are enabled by setting AO=1. --- XBee.cpp | 37 +++++++++++++++++++++++++++++++++++++ XBee.h | 24 ++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/XBee.cpp b/XBee.cpp index 3787d5c..79ef4ae 100755 --- a/XBee.cpp +++ b/XBee.cpp @@ -176,6 +176,43 @@ void XBeeResponse::getZBRxResponse(XBeeResponse &rxResponse) { zb->getRemoteAddress64().setLsb((uint32_t(getFrameData()[4]) << 24) + (uint32_t(getFrameData()[5]) << 16) + (uint16_t(getFrameData()[6]) << 8) + (getFrameData()[7])); } +ZBExplicitRxResponse::ZBExplicitRxResponse(): ZBRxResponse() { +} + +uint8_t ZBExplicitRxResponse::getSrcEndpoint() { + return getFrameData()[10]; +} + +uint8_t ZBExplicitRxResponse::getDstEndpoint() { + return getFrameData()[11]; +} + +uint16_t ZBExplicitRxResponse::getClusterId() { + return (uint16_t)(getFrameData()[12]) << 8 | getFrameData()[13]; +} + +uint16_t ZBExplicitRxResponse::getProfileId() { + return (uint16_t)(getFrameData()[14]) << 8 | getFrameData()[15]; +} + +uint8_t ZBExplicitRxResponse::getOption() { + return getFrameData()[16]; +} + +// markers to read data from packet array. +uint8_t ZBExplicitRxResponse::getDataOffset() { + return 17; +} + +uint8_t ZBExplicitRxResponse::getDataLength() { + return getPacketLength() - getDataOffset() - 1; +} + +void XBeeResponse::getZBExplicitRxResponse(XBeeResponse &rxResponse) { + // Nothing to add to that + getZBRxResponse(rxResponse); +} + ZBRxIoSampleResponse::ZBRxIoSampleResponse() : ZBRxResponse() { diff --git a/XBee.h b/XBee.h index e1340bc..f42d5e0 100755 --- a/XBee.h +++ b/XBee.h @@ -223,6 +223,11 @@ class XBeeResponse { * to populate response */ void getZBRxResponse(XBeeResponse &response); + /** + * Call with instance of ZBExplicitRxResponse class only if getApiId() == ZB_EXPLICIT_RX_RESPONSE + * to populate response + */ + void getZBExplicitRxResponse(XBeeResponse &response); /** * Call with instance of ZBRxIoSampleResponse class only if getApiId() == ZB_IO_SAMPLE_RESPONSE * to populate response @@ -395,6 +400,25 @@ class ZBRxResponse : public RxDataResponse { XBeeAddress64 _remoteAddress64; }; +/** + * Represents a Series 2 Explicit RX packet + * + * Note: The receive these responses, set AO=1. With the default AO=0, + * you will receive ZBRxResponses, not knowing exact details. + */ +class ZBExplicitRxResponse : public ZBRxResponse { +public: + ZBExplicitRxResponse(); + uint8_t getSrcEndpoint(); + uint8_t getDstEndpoint(); + uint16_t getClusterId(); + uint16_t getProfileId(); + uint8_t getOption(); + uint8_t getDataLength(); + // frame position where data starts + uint8_t getDataOffset(); +}; + /** * Represents a Series 2 RX I/O Sample packet */ From 2ec69c5656e037440d9de85e9c687c4739fce5f2 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Mon, 6 Jul 2015 18:05:39 +0200 Subject: [PATCH 03/17] Add XBeeWithCallbacks class This is just a minimal implementation, that supports the onPacketError() callback and onResponse() callback. More callbacks need to be added to make this class truly useful. --- XBee.cpp | 9 +++++++ XBee.h | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/XBee.cpp b/XBee.cpp index 79ef4ae..cb55018 100755 --- a/XBee.cpp +++ b/XBee.cpp @@ -1597,3 +1597,12 @@ void XBee::sendByte(uint8_t b, bool escape) { } } + +void XBeeWithCallbacks::loop() { + readPacket(); + if (getResponse().isAvailable()) { + _onResponse.call(getResponse()); + } else if (getResponse().isError()) { + _onPacketError.call(getResponse().getErrorCode()); + } +} diff --git a/XBee.h b/XBee.h index f42d5e0..fee6d20 100755 --- a/XBee.h +++ b/XBee.h @@ -774,6 +774,80 @@ class XBee { Stream* _serial; }; + +/** + * This class can be used instead of the XBee class and allows + * user-specified callback functions to be called when responses are + * received, simplifying the processing code and reducing boilerplate. + * + * To use it, first register your callback functions using the onXxx + * methods. Each method has a uintptr_t data argument, that can be used to + * pass arbitrary data to the callback (useful when using the same + * function for multiple callbacks, or have a generic function that can + * behave differently in different circumstances). Supplying the data + * parameter is optional, but the callback must always accept it (just + * ignore it if it's unused). The uintptr_t type is an integer type + * guaranteed to be big enough to fit a pointer (it is 16-bit on AVR, + * 32-bit on ARM), so it can also be used to store a pointer to access + * more data if required (using proper casts). + * + * There can be only one callback of each type registered at one time, + * so registering callback overwrites any previously registered one. To + * unregister a callback, pass NULL as the function. + * + * To ensure that the callbacks are actually called, call the loop() + * method regularly (in your loop() function, for example). This takes + * care of calling readPacket() and getResponse() other methods on the + * XBee class, so there is no need to do so directly (though it should + * not mess with this class if you do, it would only mean some callbacks + * aren't called). + */ +class XBeeWithCallbacks : public XBee { +public: + + /** + * Register a packet error callback. It is called whenever an + * error occurs in the packet reading process. Arguments to the + * callback will be the error code and the data parameter. + * while registering the callback. + */ + void onPacketError(void (*func)(uint8_t, uintptr_t), uintptr_t data = 0) { _onPacketError.set(func, data); } + + /** + * Register a response received callback. It is called whenever + * a response was succesfully received. + * + * Arguments to the callback will be the received response and + * the data parameter passed while registering the callback. + */ + void onResponse(void (*func)(XBeeResponse&, uintptr_t), uintptr_t data = 0) { _onResponse.set(func, data); } + + /** + * Regularly call this method, which ensures that the serial + * buffer is processed and the appropriate callbacks are called. + */ + void loop(); +private: + template struct Callback { + void (*func)(Arg, uintptr_t); + uintptr_t data; + void set(void (*func)(Arg, uintptr_t), uintptr_t data) { + this->func = func; + this->data = data; + } + bool call(Arg arg) { + if (this->func) { + this->func(arg, this->data); + return true; + } + return false; + } + }; + + Callback _onPacketError; + Callback _onResponse; +}; + /** * All TX packets that support payloads extend this class */ From a46fbd2584fb0d1e7b98322067226d20143858db Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Mon, 6 Jul 2015 22:16:26 +0200 Subject: [PATCH 04/17] Add response specific callbacks This adds a callback for each type of response that exists. Whenever a respons is received, the corresponding callback is called. When it is not defined, the onOtherResponse callback is called instead. --- XBee.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ XBee.h | 53 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 109 insertions(+), 3 deletions(-) diff --git a/XBee.cpp b/XBee.cpp index cb55018..dc5933c 100755 --- a/XBee.cpp +++ b/XBee.cpp @@ -1602,6 +1602,65 @@ void XBeeWithCallbacks::loop() { readPacket(); if (getResponse().isAvailable()) { _onResponse.call(getResponse()); + + bool called = false; + + uint8_t id = getResponse().getApiId(); + + if (id == ZB_TX_STATUS_RESPONSE) { + ZBTxStatusResponse response; + getResponse().getZBTxStatusResponse(response); + called = _onZBTxStatusResponse.call(response); + } else if (id == ZB_RX_RESPONSE) { + ZBRxResponse response; + getResponse().getZBRxResponse(response); + called = _onZBRxResponse.call(response); + } else if (id == ZB_EXPLICIT_RX_RESPONSE) { + ZBExplicitRxResponse response; + getResponse().getZBExplicitRxResponse(response); + called = _onZBExplicitRxResponse.call(response); + } else if (id == ZB_IO_SAMPLE_RESPONSE) { + ZBRxIoSampleResponse response; + getResponse().getZBRxIoSampleResponse(response); + called = _onZBRxIoSampleResponse.call(response); + } else if (id == TX_STATUS_RESPONSE) { + TxStatusResponse response; + getResponse().getTxStatusResponse(response); + called = _onTxStatusResponse.call(response); + } else if (id == RX_16_RESPONSE) { + Rx16Response response; + getResponse().getRx16Response(response); + called = _onRx16Response.call(response); + } else if (id == RX_64_RESPONSE) { + Rx64Response response; + getResponse().getRx64Response(response); + called = _onRx64Response.call(response); + } else if (id == RX_16_IO_RESPONSE) { + Rx16IoSampleResponse response; + getResponse().getRx16IoSampleResponse(response); + called = _onRx16IoSampleResponse.call(response); + } else if (id == RX_64_IO_RESPONSE) { + Rx64IoSampleResponse response; + getResponse().getRx64IoSampleResponse(response); + called = _onRx64IoSampleResponse.call(response); + } else if (id == MODEM_STATUS_RESPONSE) { + ModemStatusResponse response; + getResponse().getModemStatusResponse(response); + called = _onModemStatusResponse.call(response); + } else if (id == AT_COMMAND_RESPONSE) { + AtCommandResponse response; + getResponse().getAtCommandResponse(response); + called = _onAtCommandResponse.call(response); + } else if (id == REMOTE_AT_COMMAND_RESPONSE) { + RemoteAtCommandResponse response; + getResponse().getRemoteAtCommandResponse(response); + called = _onRemoteAtCommandResponse.call(response); + } + + if (!called) + _onOtherResponse.call(getResponse()); + + } else if (getResponse().isError()) { _onPacketError.call(getResponse().getErrorCode()); } diff --git a/XBee.h b/XBee.h index fee6d20..32fb96c 100755 --- a/XBee.h +++ b/XBee.h @@ -808,20 +808,54 @@ class XBeeWithCallbacks : public XBee { /** * Register a packet error callback. It is called whenever an * error occurs in the packet reading process. Arguments to the - * callback will be the error code and the data parameter. - * while registering the callback. + * callback will be the error code (as returned by + * XBeeResponse::getErrorCode()) and the data parameter. while + * registering the callback. */ void onPacketError(void (*func)(uint8_t, uintptr_t), uintptr_t data = 0) { _onPacketError.set(func, data); } /** * Register a response received callback. It is called whenever - * a response was succesfully received. + * a response was succesfully received, before a response + * specific callback (or onOtherResponse) below is called. * * Arguments to the callback will be the received response and * the data parameter passed while registering the callback. */ void onResponse(void (*func)(XBeeResponse&, uintptr_t), uintptr_t data = 0) { _onResponse.set(func, data); } + /** + * Register an other response received callback. It is called + * whenever a response was succesfully received, but no response + * specific callback was registered using the functions below + * (after the onResponse callback is called). + * + * Arguments to the callback will be the received response and + * the data parameter passed while registering the callback. + */ + void onOtherResponse(void (*func)(XBeeResponse&, uintptr_t), uintptr_t data = 0) { _onOtherResponse.set(func, data); } + + // These functions register a response specific callback. They + // are called whenever a response of the appropriate type was + // succesfully received (after the onResponse callback is + // called). + // + // Arguments to the callback will be the received response + // (already converted to the appropriate type) and the data + // parameter passed while registering the callback. + void onZBTxStatusResponse(void (*func)(ZBTxStatusResponse&, uintptr_t), uintptr_t data = 0) { _onZBTxStatusResponse.set(func, data); } + void onZBRxResponse(void (*func)(ZBRxResponse&, uintptr_t), uintptr_t data = 0) { _onZBRxResponse.set(func, data); } + void onZBExplicitRxResponse(void (*func)(ZBExplicitRxResponse&, uintptr_t), uintptr_t data = 0) { _onZBExplicitRxResponse.set(func, data); } + void onZBRxIoSampleResponse(void (*func)(ZBRxIoSampleResponse&, uintptr_t), uintptr_t data = 0) { _onZBRxIoSampleResponse.set(func, data); } + void onTxStatusResponse(void (*func)(TxStatusResponse&, uintptr_t), uintptr_t data = 0) { _onTxStatusResponse.set(func, data); } + void onRx16Response(void (*func)(Rx16Response&, uintptr_t), uintptr_t data = 0) { _onRx16Response.set(func, data); } + void onRx64Response(void (*func)(Rx64Response&, uintptr_t), uintptr_t data = 0) { _onRx64Response.set(func, data); } + void onRx16IoSampleResponse(void (*func)(Rx16IoSampleResponse&, uintptr_t), uintptr_t data = 0) { _onRx16IoSampleResponse.set(func, data); } + void onRx64IoSampleResponse(void (*func)(Rx64IoSampleResponse&, uintptr_t), uintptr_t data = 0) { _onRx64IoSampleResponse.set(func, data); } + void onModemStatusResponse(void (*func)(ModemStatusResponse&, uintptr_t), uintptr_t data = 0) { _onModemStatusResponse.set(func, data); } + void onAtCommandResponse(void (*func)(AtCommandResponse&, uintptr_t), uintptr_t data = 0) { _onAtCommandResponse.set(func, data); } + void onRemoteAtCommandResponse(void (*func)(RemoteAtCommandResponse&, uintptr_t), uintptr_t data = 0) { _onRemoteAtCommandResponse.set(func, data); } + /** * Regularly call this method, which ensures that the serial * buffer is processed and the appropriate callbacks are called. @@ -846,6 +880,19 @@ class XBeeWithCallbacks : public XBee { Callback _onPacketError; Callback _onResponse; + Callback _onOtherResponse; + Callback _onZBTxStatusResponse; + Callback _onZBRxResponse; + Callback _onZBExplicitRxResponse; + Callback _onZBRxIoSampleResponse; + Callback _onTxStatusResponse; + Callback _onRx16Response; + Callback _onRx64Response; + Callback _onRx16IoSampleResponse; + Callback _onRx64IoSampleResponse; + Callback _onModemStatusResponse; + Callback _onAtCommandResponse; + Callback _onRemoteAtCommandResponse; }; /** From 1d210797cdf4c08865fb27c8244c97bb9e0efa75 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 9 Jul 2015 16:45:27 +0200 Subject: [PATCH 05/17] Add static API_ID constant to response classes All response classes that represent an actual API packet, now have this constant, which should make it easier to use templates to define generic methods that work for all response types. --- XBee.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/XBee.h b/XBee.h index 32fb96c..10bbfba 100755 --- a/XBee.h +++ b/XBee.h @@ -382,6 +382,8 @@ class ZBTxStatusResponse : public FrameIdResponse { uint8_t getDeliveryStatus(); uint8_t getDiscoveryStatus(); bool isSuccess(); + + static const uint8_t API_ID = ZB_TX_STATUS_RESPONSE; }; /** @@ -396,6 +398,8 @@ class ZBRxResponse : public RxDataResponse { uint8_t getDataLength(); // frame position where data starts uint8_t getDataOffset(); + + static const uint8_t API_ID = ZB_RX_RESPONSE; private: XBeeAddress64 _remoteAddress64; }; @@ -417,6 +421,8 @@ class ZBExplicitRxResponse : public ZBRxResponse { uint8_t getDataLength(); // frame position where data starts uint8_t getDataOffset(); + + static const uint8_t API_ID = ZB_EXPLICIT_RX_RESPONSE; }; /** @@ -448,6 +454,8 @@ class ZBRxIoSampleResponse : public ZBRxResponse { uint8_t getDigitalMaskMsb(); uint8_t getDigitalMaskLsb(); uint8_t getAnalogMask(); + + static const uint8_t API_ID = ZB_IO_SAMPLE_RESPONSE; }; #endif @@ -461,6 +469,8 @@ class TxStatusResponse : public FrameIdResponse { TxStatusResponse(); uint8_t getStatus(); bool isSuccess(); + + static const uint8_t API_ID = TX_STATUS_RESPONSE; }; /** @@ -487,6 +497,8 @@ class Rx16Response : public RxResponse { Rx16Response(); uint8_t getRssiOffset(); uint16_t getRemoteAddress16(); + + static const uint8_t API_ID = RX_16_RESPONSE; protected: uint16_t _remoteAddress; }; @@ -499,6 +511,8 @@ class Rx64Response : public RxResponse { Rx64Response(); uint8_t getRssiOffset(); XBeeAddress64& getRemoteAddress64(); + + static const uint8_t API_ID = RX_64_RESPONSE; private: XBeeAddress64 _remoteAddress; }; @@ -543,6 +557,7 @@ class Rx16IoSampleResponse : public RxIoSampleBaseResponse { uint16_t getRemoteAddress16(); uint8_t getRssiOffset(); + static const uint8_t API_ID = RX_16_IO_RESPONSE; }; class Rx64IoSampleResponse : public RxIoSampleBaseResponse { @@ -550,6 +565,8 @@ class Rx64IoSampleResponse : public RxIoSampleBaseResponse { Rx64IoSampleResponse(); XBeeAddress64& getRemoteAddress64(); uint8_t getRssiOffset(); + + static const uint8_t API_ID = RX_64_IO_RESPONSE; private: XBeeAddress64 _remoteAddress; }; @@ -563,6 +580,8 @@ class ModemStatusResponse : public XBeeResponse { public: ModemStatusResponse(); uint8_t getStatus(); + + static const uint8_t API_ID = MODEM_STATUS_RESPONSE; }; /** @@ -593,6 +612,8 @@ class AtCommandResponse : public FrameIdResponse { * Returns true if status equals AT_OK */ bool isOk(); + + static const uint8_t API_ID = AT_COMMAND_RESPONSE; }; /** @@ -631,6 +652,8 @@ class RemoteAtCommandResponse : public AtCommandResponse { * Returns true if command was successful */ bool isOk(); + + static const uint8_t API_ID = REMOTE_AT_COMMAND_RESPONSE; private: XBeeAddress64 _remoteAddress64; }; From 5963bea3e158cb9d25bca94f31c3900798e4f81d Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 9 Jul 2015 20:49:34 +0200 Subject: [PATCH 06/17] Split XBeeWithCallbacks::loop() into top and bottom This allows reusing both parts, while inserting a bit of code in between, in subsequent commits. --- XBee.cpp | 124 +++++++++++++++++++++++++++++-------------------------- XBee.h | 14 +++++++ 2 files changed, 79 insertions(+), 59 deletions(-) diff --git a/XBee.cpp b/XBee.cpp index dc5933c..23192ab 100755 --- a/XBee.cpp +++ b/XBee.cpp @@ -1599,69 +1599,75 @@ void XBee::sendByte(uint8_t b, bool escape) { void XBeeWithCallbacks::loop() { + if (loopTop()) + loopBottom(); +} + +bool XBeeWithCallbacks::loopTop() { readPacket(); if (getResponse().isAvailable()) { _onResponse.call(getResponse()); - - bool called = false; - - uint8_t id = getResponse().getApiId(); - - if (id == ZB_TX_STATUS_RESPONSE) { - ZBTxStatusResponse response; - getResponse().getZBTxStatusResponse(response); - called = _onZBTxStatusResponse.call(response); - } else if (id == ZB_RX_RESPONSE) { - ZBRxResponse response; - getResponse().getZBRxResponse(response); - called = _onZBRxResponse.call(response); - } else if (id == ZB_EXPLICIT_RX_RESPONSE) { - ZBExplicitRxResponse response; - getResponse().getZBExplicitRxResponse(response); - called = _onZBExplicitRxResponse.call(response); - } else if (id == ZB_IO_SAMPLE_RESPONSE) { - ZBRxIoSampleResponse response; - getResponse().getZBRxIoSampleResponse(response); - called = _onZBRxIoSampleResponse.call(response); - } else if (id == TX_STATUS_RESPONSE) { - TxStatusResponse response; - getResponse().getTxStatusResponse(response); - called = _onTxStatusResponse.call(response); - } else if (id == RX_16_RESPONSE) { - Rx16Response response; - getResponse().getRx16Response(response); - called = _onRx16Response.call(response); - } else if (id == RX_64_RESPONSE) { - Rx64Response response; - getResponse().getRx64Response(response); - called = _onRx64Response.call(response); - } else if (id == RX_16_IO_RESPONSE) { - Rx16IoSampleResponse response; - getResponse().getRx16IoSampleResponse(response); - called = _onRx16IoSampleResponse.call(response); - } else if (id == RX_64_IO_RESPONSE) { - Rx64IoSampleResponse response; - getResponse().getRx64IoSampleResponse(response); - called = _onRx64IoSampleResponse.call(response); - } else if (id == MODEM_STATUS_RESPONSE) { - ModemStatusResponse response; - getResponse().getModemStatusResponse(response); - called = _onModemStatusResponse.call(response); - } else if (id == AT_COMMAND_RESPONSE) { - AtCommandResponse response; - getResponse().getAtCommandResponse(response); - called = _onAtCommandResponse.call(response); - } else if (id == REMOTE_AT_COMMAND_RESPONSE) { - RemoteAtCommandResponse response; - getResponse().getRemoteAtCommandResponse(response); - called = _onRemoteAtCommandResponse.call(response); - } - - if (!called) - _onOtherResponse.call(getResponse()); - - + return true; } else if (getResponse().isError()) { _onPacketError.call(getResponse().getErrorCode()); } + return false; +} + +void XBeeWithCallbacks::loopBottom() { + bool called = false; + uint8_t id = getResponse().getApiId(); + + if (id == ZB_TX_STATUS_RESPONSE) { + ZBTxStatusResponse response; + getResponse().getZBTxStatusResponse(response); + called = _onZBTxStatusResponse.call(response); + } else if (id == ZB_RX_RESPONSE) { + ZBRxResponse response; + getResponse().getZBRxResponse(response); + called = _onZBRxResponse.call(response); + } else if (id == ZB_EXPLICIT_RX_RESPONSE) { + ZBExplicitRxResponse response; + getResponse().getZBExplicitRxResponse(response); + called = _onZBExplicitRxResponse.call(response); + } else if (id == ZB_IO_SAMPLE_RESPONSE) { + ZBRxIoSampleResponse response; + getResponse().getZBRxIoSampleResponse(response); + called = _onZBRxIoSampleResponse.call(response); + } else if (id == TX_STATUS_RESPONSE) { + TxStatusResponse response; + getResponse().getTxStatusResponse(response); + called = _onTxStatusResponse.call(response); + } else if (id == RX_16_RESPONSE) { + Rx16Response response; + getResponse().getRx16Response(response); + called = _onRx16Response.call(response); + } else if (id == RX_64_RESPONSE) { + Rx64Response response; + getResponse().getRx64Response(response); + called = _onRx64Response.call(response); + } else if (id == RX_16_IO_RESPONSE) { + Rx16IoSampleResponse response; + getResponse().getRx16IoSampleResponse(response); + called = _onRx16IoSampleResponse.call(response); + } else if (id == RX_64_IO_RESPONSE) { + Rx64IoSampleResponse response; + getResponse().getRx64IoSampleResponse(response); + called = _onRx64IoSampleResponse.call(response); + } else if (id == MODEM_STATUS_RESPONSE) { + ModemStatusResponse response; + getResponse().getModemStatusResponse(response); + called = _onModemStatusResponse.call(response); + } else if (id == AT_COMMAND_RESPONSE) { + AtCommandResponse response; + getResponse().getAtCommandResponse(response); + called = _onAtCommandResponse.call(response); + } else if (id == REMOTE_AT_COMMAND_RESPONSE) { + RemoteAtCommandResponse response; + getResponse().getRemoteAtCommandResponse(response); + called = _onRemoteAtCommandResponse.call(response); + } + + if (!called) + _onOtherResponse.call(getResponse()); } diff --git a/XBee.h b/XBee.h index 10bbfba..d46272a 100755 --- a/XBee.h +++ b/XBee.h @@ -885,6 +885,20 @@ class XBeeWithCallbacks : public XBee { */ void loop(); private: + /** + * Top half of a typical loop(). Calls readPacket(), calls + * onPacketError on error, calls onResponse when a response is + * available. Returns in the true in the latter case, after + * which a caller should typically call loopBottom(). + */ + bool loopTop(); + + /** + * Bottom half of a typical loop. Call only when a valid + * response was read, will call all response-specific callbacks. + */ + void loopBottom(); + template struct Callback { void (*func)(Arg, uintptr_t); uintptr_t data; From 979cc4a7941e9800b1329b638f0419f3d9bb1591 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 9 Jul 2015 20:51:13 +0200 Subject: [PATCH 07/17] Add XBeeWithCallbacks::waitForStatus() This method loops until a status response is received (e.g. a AtCommandResponse, TxResponse, etc.) to greatly simplify getting the result of a command sent. While waiting, any other responses are processed by the callbacks as normal, so no response gets lost while waiting. A matchStatus() helper method is introduced, preparing to reuse it in a future commit. --- XBee.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ XBee.h | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/XBee.cpp b/XBee.cpp index 23192ab..1aa8deb 100755 --- a/XBee.cpp +++ b/XBee.cpp @@ -1671,3 +1671,44 @@ void XBeeWithCallbacks::loopBottom() { if (!called) _onOtherResponse.call(getResponse()); } + +uint8_t XBeeWithCallbacks::matchStatus(uint8_t frameId) { + uint8_t id = getResponse().getApiId(); + uint8_t *data = getResponse().getFrameData(); + uint8_t len = getResponse().getFrameDataLength(); + uint8_t offset = 0; + + // Figure out if this frame has a frameId and if so, where the + // status byte to return is located + if (id == AT_COMMAND_RESPONSE) + offset = 3; + else if (id == REMOTE_AT_COMMAND_RESPONSE) + offset = 13; + else if (id == TX_STATUS_RESPONSE) + offset = 1; + else if (id == ZB_TX_STATUS_RESPONSE) + offset = 4; + + // If this is an API frame that contains a status, the frame is + // long enough to contain it and the frameId matches the one + // given, return the status byte + if (offset && offset < len && data[0] == frameId) + return data[offset]; + return 0xff; +} + +uint8_t XBeeWithCallbacks::waitForStatus(uint8_t frameId, uint16_t timeout) { + unsigned long start = millis(); + do { + if (loopTop()) { + uint8_t status = matchStatus(frameId); + if (status != 0xff) + return status; + + // Call regular callbacks + loopBottom(); + } + } while (millis() - start < timeout); + return XBEE_WAIT_TIMEOUT ; +} + diff --git a/XBee.h b/XBee.h index d46272a..7670100 100755 --- a/XBee.h +++ b/XBee.h @@ -123,6 +123,8 @@ #define ADDRESS_NOT_FOUND 0x24 #define ROUTE_NOT_FOUND 0x25 #define PAYLOAD_TOO_LARGE 0x74 +// Returned by XBeeWithCallbacks::waitForStatus on timeout +#define XBEE_WAIT_TIMEOUT 0xff // modem status #define HARDWARE_RESET 0 @@ -884,7 +886,42 @@ class XBeeWithCallbacks : public XBee { * buffer is processed and the appropriate callbacks are called. */ void loop(); + + /** + * Wait for a status API response with the given frameId and + * return the status from the packet (for ZB_TX_STATUS_RESPONSE, + * this returns just the delivery status, not the routing + * status). If the timeout is reached before reading the + * response, XBEE_WAIT_TIMEOUT is returned instead. + * + * While waiting, any other responses received are passed to the + * relevant callbacks, just as if calling loop() continuously + * (except for the status response sought, that one is only + * passed to the OnResponse handler and no others). + * + * After this method returns, the response itself can still be + * retrieved using getResponse() as normal. + */ + uint8_t waitForStatus(uint8_t frameId, uint16_t timeout); private: + /** + * Internal version of waitFor that does not need to be + * templated (to prevent duplication the implementation for + * every response type you might want to wait for). Instead of + * using templates, this accepts the apiId to wait for and will + * cast the given response object and the argument to the given + * function to the corresponding type. This means that the + * void* given must match the api id! + */ + bool waitForInternal(uint8_t apiId, void *response, uint16_t timeout, void *func, uintptr_t data); + + /** + * Helper that checks if the current response is a status + * response with the given frame id. If so, returns the status + * byte from the response, otherwise returns 0xff. + */ + uint8_t matchStatus(uint8_t frameId); + /** * Top half of a typical loop(). Calls readPacket(), calls * onPacketError on error, calls onResponse when a response is From 67adb105495ae53e1015c34969e5cff214fe353f Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 9 Jul 2015 21:13:28 +0200 Subject: [PATCH 08/17] Add XBeeWithCallbacks::sendAndWait() This just combines send() and waitForStatus() in one convenient call. --- XBee.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/XBee.h b/XBee.h index 7670100..4c98443 100755 --- a/XBee.h +++ b/XBee.h @@ -887,6 +887,18 @@ class XBeeWithCallbacks : public XBee { */ void loop(); + /** + * Sends a XBeeRequest (TX packet) out the serial port, and wait + * for a status response API frame (up until the given timeout). + * Essentially this just calls send() and waitForStatus(). + * See waitForStatus for the meaning of the return value and + * more details. + */ + uint8_t sendAndWait(XBeeRequest &request, uint16_t timeout) { + send(request); + return waitForStatus(request.getFrameId(), timeout); + } + /** * Wait for a status API response with the given frameId and * return the status from the packet (for ZB_TX_STATUS_RESPONSE, From 4bd4bd7595d80718e605995ddbb06cce2dcde984 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 9 Jul 2015 21:29:56 +0200 Subject: [PATCH 09/17] Add XBeeWithCallbacks::waitFor() This allows waiting for an arbitrary API response. Combined with a user-defined "matching" function, this allows e.g. waiting for a reply to a packet previously sent in a convenient way. The main work is done using a waitForInternal() method, so the actual waitFor() can be a template method (to allow passing a response object directly, without having to also pass a matching API id). The implementation could have been put inside waitFor() directly, but then you would end up with a complete copy of the method for each response type you wait for. --- XBee.cpp | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ XBee.h | 25 +++++++++++ 2 files changed, 148 insertions(+) diff --git a/XBee.cpp b/XBee.cpp index 1aa8deb..e2e346d 100755 --- a/XBee.cpp +++ b/XBee.cpp @@ -1697,6 +1697,129 @@ uint8_t XBeeWithCallbacks::matchStatus(uint8_t frameId) { return 0xff; } +bool XBeeWithCallbacks::waitForInternal(uint8_t apiId, void *response, uint16_t timeout, void *func, uintptr_t data) { + unsigned long start = millis(); + do { + // Wait for a packet of the right type + if (loopTop()) { + if (getResponse().getApiId() == apiId) { + // If the type is right, call the right + // conversion function based on the + // ApiId and call the match function. + // Because the match function is + // essentially called in the same way, + // regardless of the subclass used, the + // compiler can reduce most of the below + // mess into a single piece of code + // (though for fully optimizing, the + // separate getXxxResponse() methods + // must be unified as well). + switch(apiId) { + case ZBTxStatusResponse::API_ID: { + ZBTxStatusResponse *r = (ZBTxStatusResponse*)response; + bool(*f)(ZBTxStatusResponse&,uintptr_t) = (bool(*)(ZBTxStatusResponse&,uintptr_t))func; + getResponse().getZBTxStatusResponse(*r); + if(!f || f(*r, data)) + return true; + break; + } + case ZBRxResponse::API_ID: { + ZBRxResponse *r = (ZBRxResponse*)response; + bool(*f)(ZBRxResponse&,uintptr_t) = (bool(*)(ZBRxResponse&,uintptr_t))func; + getResponse().getZBRxResponse(*r); + if(!f || f(*r, data)) + return true; + break; + } + case ZBExplicitRxResponse::API_ID: { + ZBExplicitRxResponse *r = (ZBExplicitRxResponse*)response; + bool(*f)(ZBExplicitRxResponse&,uintptr_t) = (bool(*)(ZBExplicitRxResponse&,uintptr_t))func; + getResponse().getZBExplicitRxResponse(*r); + if(!f || f(*r, data)) + return true; + break; + } + case ZBRxIoSampleResponse::API_ID: { + ZBRxIoSampleResponse *r = (ZBRxIoSampleResponse*)response; + bool(*f)(ZBRxIoSampleResponse&,uintptr_t) = (bool(*)(ZBRxIoSampleResponse&,uintptr_t))func; + getResponse().getZBRxIoSampleResponse(*r); + if(!f || f(*r, data)) + return true; + break; + } + case TxStatusResponse::API_ID: { + TxStatusResponse *r = (TxStatusResponse*)response; + bool(*f)(TxStatusResponse&,uintptr_t) = (bool(*)(TxStatusResponse&,uintptr_t))func; + getResponse().getTxStatusResponse(*r); + if(!f || f(*r, data)) + return true; + break; + } + case Rx16Response::API_ID: { + Rx16Response *r = (Rx16Response*)response; + bool(*f)(Rx16Response&,uintptr_t) = (bool(*)(Rx16Response&,uintptr_t))func; + getResponse().getRx16Response(*r); + if(!f || f(*r, data)) + return true; + break; + } + case Rx64Response::API_ID: { + Rx64Response *r = (Rx64Response*)response; + bool(*f)(Rx64Response&,uintptr_t) = (bool(*)(Rx64Response&,uintptr_t))func; + getResponse().getRx64Response(*r); + if(!f || f(*r, data)) + return true; + break; + } + case Rx16IoSampleResponse::API_ID: { + Rx16IoSampleResponse *r = (Rx16IoSampleResponse*)response; + bool(*f)(Rx16IoSampleResponse&,uintptr_t) = (bool(*)(Rx16IoSampleResponse&,uintptr_t))func; + getResponse().getRx16IoSampleResponse(*r); + if(!f || f(*r, data)) + return true; + break; + } + case Rx64IoSampleResponse::API_ID: { + Rx64IoSampleResponse *r = (Rx64IoSampleResponse*)response; + bool(*f)(Rx64IoSampleResponse&,uintptr_t) = (bool(*)(Rx64IoSampleResponse&,uintptr_t))func; + getResponse().getRx64IoSampleResponse(*r); + if(!f || f(*r, data)) + return true; + break; + } + case ModemStatusResponse::API_ID: { + ModemStatusResponse *r = (ModemStatusResponse*)response; + bool(*f)(ModemStatusResponse&,uintptr_t) = (bool(*)(ModemStatusResponse&,uintptr_t))func; + getResponse().getModemStatusResponse(*r); + if(!f || f(*r, data)) + return true; + break; + } + case AtCommandResponse::API_ID: { + AtCommandResponse *r = (AtCommandResponse*)response; + bool(*f)(AtCommandResponse&,uintptr_t) = (bool(*)(AtCommandResponse&,uintptr_t))func; + getResponse().getAtCommandResponse(*r); + if(!f || f(*r, data)) + return true; + break; + } + case RemoteAtCommandResponse::API_ID: { + RemoteAtCommandResponse *r = (RemoteAtCommandResponse*)response; + bool(*f)(RemoteAtCommandResponse&,uintptr_t) = (bool(*)(RemoteAtCommandResponse&,uintptr_t))func; + getResponse().getRemoteAtCommandResponse(*r); + if(!f || f(*r, data)) + return true; + break; + } + } + } + // Call regular callbacks + loopBottom(); + } + } while (millis() - start < timeout); + return false; +} + uint8_t XBeeWithCallbacks::waitForStatus(uint8_t frameId, uint16_t timeout) { unsigned long start = millis(); do { diff --git a/XBee.h b/XBee.h index 4c98443..21a34e6 100755 --- a/XBee.h +++ b/XBee.h @@ -887,6 +887,31 @@ class XBeeWithCallbacks : public XBee { */ void loop(); + /** + * Wait for a API response of the given type, optionally + * filtered by the given match function. + * + * If a match function is given it is called for every response + * of the right type received, passing the response and the data + * parameter passed to this method. If the function returns true + * (or if no function was passed), waiting stops and this method + * returns true. If the function returns false, waiting + * continues. After the given timeout passes, this method + * returns false. + * + * While waiting, any other responses received are passed to the + * relevant callbacks, just as if calling loop() continuously + * (except for the response sought, that one is only passed to + * the OnResponse handler and no others). + * + * After this method returns, the response itself can still be + * retrieved using getResponse() as normal. + */ + template + bool waitFor(Response& response, uint16_t timeout, bool (*func)(Response&, uintptr_t) = NULL, uintptr_t data = 0) { + return waitForInternal(Response::API_ID, &response, timeout, (void*)func, data); + } + /** * Sends a XBeeRequest (TX packet) out the serial port, and wait * for a status response API frame (up until the given timeout). From c6fb5bf650a98b4bec037a6d0280f9a00caf8792 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 10 Jul 2015 14:04:08 +0200 Subject: [PATCH 10/17] Allow XBeeWithCallbacks::waitFor() to check status responses too Now, if a frameId is passed, waitFor() will check for status response while waiting. If one is received that matches the frameId and has a non-zero status (e.g. an error), waiting stops and the status is returned. When sending a TX packet and subsequently listening for a RX reply, this allows waiting for a reply and the TX status at the same time and stop waiting if the TX turns out to have failed. This is particularly convenient since the TX status can sometimes arrive *before* the RX reply and sometimes *after*, which makes a sequential waitForStatus() and waitFor() approach not work. --- XBee.cpp | 36 ++++++++++++++++++++++-------------- XBee.h | 25 ++++++++++++++++++++----- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/XBee.cpp b/XBee.cpp index e2e346d..c9e4236 100755 --- a/XBee.cpp +++ b/XBee.cpp @@ -1697,11 +1697,19 @@ uint8_t XBeeWithCallbacks::matchStatus(uint8_t frameId) { return 0xff; } -bool XBeeWithCallbacks::waitForInternal(uint8_t apiId, void *response, uint16_t timeout, void *func, uintptr_t data) { +uint8_t XBeeWithCallbacks::waitForInternal(uint8_t apiId, void *response, uint16_t timeout, void *func, uintptr_t data, int16_t frameId) { unsigned long start = millis(); do { // Wait for a packet of the right type if (loopTop()) { + if (frameId >= 0) { + uint8_t status = matchStatus(frameId); + // If a status was found, but it was not + // a zero success status, stop waiting + if (status != 0xff && status != 0) + return status; + } + if (getResponse().getApiId() == apiId) { // If the type is right, call the right // conversion function based on the @@ -1720,7 +1728,7 @@ bool XBeeWithCallbacks::waitForInternal(uint8_t apiId, void *response, uint16_t bool(*f)(ZBTxStatusResponse&,uintptr_t) = (bool(*)(ZBTxStatusResponse&,uintptr_t))func; getResponse().getZBTxStatusResponse(*r); if(!f || f(*r, data)) - return true; + return 0; break; } case ZBRxResponse::API_ID: { @@ -1728,7 +1736,7 @@ bool XBeeWithCallbacks::waitForInternal(uint8_t apiId, void *response, uint16_t bool(*f)(ZBRxResponse&,uintptr_t) = (bool(*)(ZBRxResponse&,uintptr_t))func; getResponse().getZBRxResponse(*r); if(!f || f(*r, data)) - return true; + return 0; break; } case ZBExplicitRxResponse::API_ID: { @@ -1736,7 +1744,7 @@ bool XBeeWithCallbacks::waitForInternal(uint8_t apiId, void *response, uint16_t bool(*f)(ZBExplicitRxResponse&,uintptr_t) = (bool(*)(ZBExplicitRxResponse&,uintptr_t))func; getResponse().getZBExplicitRxResponse(*r); if(!f || f(*r, data)) - return true; + return 0; break; } case ZBRxIoSampleResponse::API_ID: { @@ -1744,7 +1752,7 @@ bool XBeeWithCallbacks::waitForInternal(uint8_t apiId, void *response, uint16_t bool(*f)(ZBRxIoSampleResponse&,uintptr_t) = (bool(*)(ZBRxIoSampleResponse&,uintptr_t))func; getResponse().getZBRxIoSampleResponse(*r); if(!f || f(*r, data)) - return true; + return 0; break; } case TxStatusResponse::API_ID: { @@ -1752,7 +1760,7 @@ bool XBeeWithCallbacks::waitForInternal(uint8_t apiId, void *response, uint16_t bool(*f)(TxStatusResponse&,uintptr_t) = (bool(*)(TxStatusResponse&,uintptr_t))func; getResponse().getTxStatusResponse(*r); if(!f || f(*r, data)) - return true; + return 0; break; } case Rx16Response::API_ID: { @@ -1760,7 +1768,7 @@ bool XBeeWithCallbacks::waitForInternal(uint8_t apiId, void *response, uint16_t bool(*f)(Rx16Response&,uintptr_t) = (bool(*)(Rx16Response&,uintptr_t))func; getResponse().getRx16Response(*r); if(!f || f(*r, data)) - return true; + return 0; break; } case Rx64Response::API_ID: { @@ -1768,7 +1776,7 @@ bool XBeeWithCallbacks::waitForInternal(uint8_t apiId, void *response, uint16_t bool(*f)(Rx64Response&,uintptr_t) = (bool(*)(Rx64Response&,uintptr_t))func; getResponse().getRx64Response(*r); if(!f || f(*r, data)) - return true; + return 0; break; } case Rx16IoSampleResponse::API_ID: { @@ -1776,7 +1784,7 @@ bool XBeeWithCallbacks::waitForInternal(uint8_t apiId, void *response, uint16_t bool(*f)(Rx16IoSampleResponse&,uintptr_t) = (bool(*)(Rx16IoSampleResponse&,uintptr_t))func; getResponse().getRx16IoSampleResponse(*r); if(!f || f(*r, data)) - return true; + return 0; break; } case Rx64IoSampleResponse::API_ID: { @@ -1784,7 +1792,7 @@ bool XBeeWithCallbacks::waitForInternal(uint8_t apiId, void *response, uint16_t bool(*f)(Rx64IoSampleResponse&,uintptr_t) = (bool(*)(Rx64IoSampleResponse&,uintptr_t))func; getResponse().getRx64IoSampleResponse(*r); if(!f || f(*r, data)) - return true; + return 0; break; } case ModemStatusResponse::API_ID: { @@ -1792,7 +1800,7 @@ bool XBeeWithCallbacks::waitForInternal(uint8_t apiId, void *response, uint16_t bool(*f)(ModemStatusResponse&,uintptr_t) = (bool(*)(ModemStatusResponse&,uintptr_t))func; getResponse().getModemStatusResponse(*r); if(!f || f(*r, data)) - return true; + return 0; break; } case AtCommandResponse::API_ID: { @@ -1800,7 +1808,7 @@ bool XBeeWithCallbacks::waitForInternal(uint8_t apiId, void *response, uint16_t bool(*f)(AtCommandResponse&,uintptr_t) = (bool(*)(AtCommandResponse&,uintptr_t))func; getResponse().getAtCommandResponse(*r); if(!f || f(*r, data)) - return true; + return 0; break; } case RemoteAtCommandResponse::API_ID: { @@ -1808,7 +1816,7 @@ bool XBeeWithCallbacks::waitForInternal(uint8_t apiId, void *response, uint16_t bool(*f)(RemoteAtCommandResponse&,uintptr_t) = (bool(*)(RemoteAtCommandResponse&,uintptr_t))func; getResponse().getRemoteAtCommandResponse(*r); if(!f || f(*r, data)) - return true; + return 0; break; } } @@ -1817,7 +1825,7 @@ bool XBeeWithCallbacks::waitForInternal(uint8_t apiId, void *response, uint16_t loopBottom(); } } while (millis() - start < timeout); - return false; + return XBEE_WAIT_TIMEOUT; } uint8_t XBeeWithCallbacks::waitForStatus(uint8_t frameId, uint16_t timeout) { diff --git a/XBee.h b/XBee.h index 21a34e6..2f81791 100755 --- a/XBee.h +++ b/XBee.h @@ -895,9 +895,24 @@ class XBeeWithCallbacks : public XBee { * of the right type received, passing the response and the data * parameter passed to this method. If the function returns true * (or if no function was passed), waiting stops and this method - * returns true. If the function returns false, waiting + * returns 0. If the function returns false, waiting * continues. After the given timeout passes, this method - * returns false. + * returns XBEE_WAIT_TIMEOUT. + * + * If a valid frameId is passed (e.g. 0-255 inclusive) and a + * status API response frame is received while waiting, that has + * a *non-zero* status, waiting stops and that status is + * received. This is intended for when a TX packet was sent and + * you are waiting for an RX reply, which will most likely never + * arrive when TX failed. However, since the status reply is not + * guaranteed to arrive before the RX reply (a remote module can + * send a reply before the ACK), first calling waitForStatus() + * and then waitFor() can sometimes miss the reply RX packet. + * + * Note that when the intended response is received *before* the + * status reply, the latter will not be processed by this + * method and will be subsequently processed by e.g. loop() + * normally. * * While waiting, any other responses received are passed to the * relevant callbacks, just as if calling loop() continuously @@ -908,8 +923,8 @@ class XBeeWithCallbacks : public XBee { * retrieved using getResponse() as normal. */ template - bool waitFor(Response& response, uint16_t timeout, bool (*func)(Response&, uintptr_t) = NULL, uintptr_t data = 0) { - return waitForInternal(Response::API_ID, &response, timeout, (void*)func, data); + uint8_t waitFor(Response& response, uint16_t timeout, bool (*func)(Response&, uintptr_t) = NULL, uintptr_t data = 0, int16_t frameId = -1) { + return waitForInternal(Response::API_ID, &response, timeout, (void*)func, data, frameId); } /** @@ -950,7 +965,7 @@ class XBeeWithCallbacks : public XBee { * function to the corresponding type. This means that the * void* given must match the api id! */ - bool waitForInternal(uint8_t apiId, void *response, uint16_t timeout, void *func, uintptr_t data); + uint8_t waitForInternal(uint8_t apiId, void *response, uint16_t timeout, void *func, uintptr_t data, int16_t frameId); /** * Helper that checks if the current response is a status From 46e0be8b9c04b86f46f68e96fb1ea60710ce5bc5 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Sat, 11 Jul 2015 18:06:44 +0200 Subject: [PATCH 11/17] Add PayloadRequest::setPayload overload to set pointer and length This allows setting both in one call, which is slightly more convenient. --- XBee.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/XBee.h b/XBee.h index 2f81791..6c7126c 100755 --- a/XBee.h +++ b/XBee.h @@ -1035,6 +1035,15 @@ class PayloadRequest : public XBeeRequest { * Sets the payload array */ void setPayload(uint8_t* payloadPtr); + + /* + * Set the payload and its length in one call. + */ + void setPayload(uint8_t* payloadPtr, uint8_t payloadLength) { + setPayload(payloadPtr); + setPayloadLength(payloadLength); + } + /** * Returns the length of the payload array, as specified by the user. */ From 164aa6711ba803f1be705d9b039e52571645b975 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Sun, 12 Jul 2015 11:43:42 +0200 Subject: [PATCH 12/17] Add note about blocking and receiving within callbacks --- XBee.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/XBee.h b/XBee.h index 6c7126c..d41c46f 100755 --- a/XBee.h +++ b/XBee.h @@ -826,6 +826,17 @@ class XBee { * XBee class, so there is no need to do so directly (though it should * not mess with this class if you do, it would only mean some callbacks * aren't called). + * + * Inside callbacks, you should generally not be blocking / waiting. + * Since callbacks can be called from inside waitFor() and friends, a + * callback that doesn't return quickly can mess up the waitFor() + * timeout. + * + * Sending packets is not a problem inside a callback, but avoid + * receiving a packet (e.g. calling readPacket(), loop() or waitFor() + * and friends) inside a callback (since that would overwrite the + * current response, messing up any pending callbacks and waitFor() etc. + * methods already running). */ class XBeeWithCallbacks : public XBee { public: From e0af45e98e945e2b17190e33888a370ea35cda8a Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Sun, 12 Jul 2015 15:53:38 +0200 Subject: [PATCH 13/17] Fix parsing of Rx16/64IoSampleResponses The old code didn't calculate the offsets correctly causing isDigitalEnabled() and isDigitalOn() to only at the first sample data (instead of looking at the mask or subsequent samples). This introduces a getSampleStart() method to allow sharing some code between getAnalog() and isDigitalOn(). --- XBee.cpp | 44 ++++++++++++++++++++++---------------------- XBee.h | 5 +++++ 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/XBee.cpp b/XBee.cpp index c9e4236..f10cb27 100755 --- a/XBee.cpp +++ b/XBee.cpp @@ -357,9 +357,9 @@ bool RxIoSampleBaseResponse::isAnalogEnabled(uint8_t pin) { bool RxIoSampleBaseResponse::isDigitalEnabled(uint8_t pin) { if (pin < 8) { - return ((getFrameData()[getSampleOffset() + 4] >> pin) & 1) == 1; + return ((getFrameData()[getSampleOffset() + 2] >> pin) & 1) == 1; } else { - return (getFrameData()[getSampleOffset() + 3] & 1) == 1; + return (getFrameData()[getSampleOffset() + 1] & 1) == 1; } } @@ -454,20 +454,16 @@ bool RxIoSampleBaseResponse::isDigitalEnabled(uint8_t pin) { // return (this.getProcessedPacketBytes()[startIndex] << 8) + this.getProcessedPacketBytes()[startIndex + 1]; // } -// THIS IS WRONG -uint16_t RxIoSampleBaseResponse::getAnalog(uint8_t pin, uint8_t sample) { - - // analog starts 3 bytes after sample size, if no dio enabled - uint8_t start = 3; +uint8_t RxIoSampleBaseResponse::getSampleStart(uint8_t sample) { + uint8_t spacing = 0; if (containsDigital()) { // make room for digital i/o sample (2 bytes per sample) - start+=2*(sample + 1); + spacing += 2; } - uint8_t spacing = 0; - - // spacing between samples depends on how many are enabled. add one for each analog that's enabled + // spacing between samples depends on how many are enabled. add + // 2 bytes for each analog that's enabled for (int i = 0; i <= 5; i++) { if (isAnalogEnabled(i)) { // each analog is two bytes @@ -475,29 +471,33 @@ uint16_t RxIoSampleBaseResponse::getAnalog(uint8_t pin, uint8_t sample) { } } -// std::cout << "spacing is " << static_cast(spacing) << std::endl; + // Skip 3-byte header and "sample" full samples + return getSampleOffset() + 3 + sample * spacing; +} - // start depends on how many pins before this pin are enabled +uint16_t RxIoSampleBaseResponse::getAnalog(uint8_t pin, uint8_t sample) { + uint8_t start = getSampleStart(sample); + + if (containsDigital()) { + // Skip digital sample info + start += 2; + } + + // Skip any analog samples before this pin for (int i = 0; i < pin; i++) { if (isAnalogEnabled(i)) { start+=2; } } - start+= sample * spacing; - -// std::cout << "start for analog pin ["<< static_cast(pin) << "]/sample " << static_cast(sample) << " is " << static_cast(start) << std::endl; - -// std::cout << "returning index " << static_cast(getSampleOffset() + start) << " and index " << static_cast(getSampleOffset() + start + 1) << ", val is " << static_cast(getFrameData()[getSampleOffset() + start] << 8) << " and " << + static_cast(getFrameData()[getSampleOffset() + start + 1]) << std::endl; - - return (uint16_t)((getFrameData()[getSampleOffset() + start] << 8) + getFrameData()[getSampleOffset() + start + 1]); + return (uint16_t)((getFrameData()[start] << 8) + getFrameData()[start + 1]); } bool RxIoSampleBaseResponse::isDigitalOn(uint8_t pin, uint8_t sample) { if (pin < 8) { - return ((getFrameData()[getSampleOffset() + 4] >> pin) & 1) == 1; + return ((getFrameData()[getSampleStart(sample) + 1] >> pin) & 1) == 1; } else { - return (getFrameData()[getSampleOffset() + 3] & 1) == 1; + return (getFrameData()[getSampleStart(sample)] & 1) == 1; } } diff --git a/XBee.h b/XBee.h index d41c46f..12c449e 100755 --- a/XBee.h +++ b/XBee.h @@ -550,6 +550,11 @@ class RxIoSampleBaseResponse : public RxResponse { */ bool isDigitalOn(uint8_t pin, uint8_t sample); uint8_t getSampleOffset(); + + /** + * Gets the offset of the start of the given sample. + */ + uint8_t getSampleStart(uint8_t sample); private: }; From 0da629f85312f2059b29bbbb94d462102c884ec3 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Mon, 13 Jul 2015 17:13:44 +0200 Subject: [PATCH 14/17] Add a number of printHex helper functions These allow easily printing integers and XBeeAddress64 values with leading zeroes (which Arduino's print doesn't have any easy way to do). Also, a version is available that can print a buffer of bytes with configurable separators. These functions aren't used yet, but will be used in examples that will be added next. --- Printers.cpp | 23 ++++++++++++++ Printers.h | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 Printers.cpp create mode 100644 Printers.h diff --git a/Printers.cpp b/Printers.cpp new file mode 100644 index 0000000..b297213 --- /dev/null +++ b/Printers.cpp @@ -0,0 +1,23 @@ +#include "Printers.h" + +void printHex(Print& p, const uint8_t* buf, size_t len, const __FlashStringHelper* byte_sep, const __FlashStringHelper* group_sep, size_t group_by) { + size_t cur_group = 0; + while (len--) { + // Print the group separator whenever starting a new + // group + if (group_by && group_sep && cur_group == group_by) { + p.print(group_sep); + cur_group = 0; + } + + // Print the byte separator, except when at the start of + // a new group (this also excludes the first byte) + if (cur_group != 0 && byte_sep) + p.print(byte_sep); + + printHex(p, *buf); + + buf++; + cur_group++; + } +} diff --git a/Printers.h b/Printers.h new file mode 100644 index 0000000..063530b --- /dev/null +++ b/Printers.h @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2009 Andrew Rapp. All rights reserved. + * + * This file is part of XBee-Arduino. + * + * XBee-Arduino is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * XBee-Arduino is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBee-Arduino. If not, see . + */ + +#ifndef XBee_Printers_h +#define XBee_Printers_h + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + +#include "XBee.h" + +// Need to define global variables to allow PROGMEM pointers as default +// arguments below. Since these variables are const, there won't be any +// linker conflicts from defining these in a header file. +const char default_byte_sep_arr[] PROGMEM = " "; +const char default_group_sep_arr[] PROGMEM = "\r\n"; +const __FlashStringHelper * const default_byte_sep = (const __FlashStringHelper*)default_byte_sep_arr; +const __FlashStringHelper * const default_group_sep = (const __FlashStringHelper*)default_group_sep_arr; + +/** + * Print a buffer byte-by-byte. Each byte is separated by byte_sep and + * every group_by bytes are separated by group_sep instead. + * + * For example, to print 8 bytes per line, each byte separated by a + * newline, to Serial: + * + * printHex(Serial, buf, len, F(" "), F("\r\n"), 8); + * + * Values shown are also the defaults. + * + * Pass NULL as group_by or byte_sep to not have that separator. + */ +void printHex(Print& p, const uint8_t* buf, size_t len, const __FlashStringHelper* byte_sep = default_byte_sep, const __FlashStringHelper* group_sep = default_group_sep, size_t group_by = 8); + +/** + * Print a single byte, in hex, using a leading zero if needed. + */ +inline void printHex(Print& p, uint8_t v) { + // Add leading zero if needed + if (v < 0x10) + p.write('0'); + p.print(v, HEX); +} + +/** + * Print a 16 bit integer, in hex, using leading zeroes if needed. + */ +inline void printHex(Print& p, uint16_t v) { + printHex(p, (uint8_t)(v >> 8)); + printHex(p, (uint8_t)v); +} + +/** + * Print a 32 bit integer, in hex, using leading zeroes if needed. + */ +inline void printHex(Print& p, uint32_t v) { + printHex(p, (uint16_t)(v >> 16)); + printHex(p, (uint16_t)v); +} + +/** + * Print a 64-bit address, in hex, using leading zeroes if needed. + */ +inline void printHex(Print& p, XBeeAddress64 v) { + printHex(p, v.getMsb()); + printHex(p, v.getLsb()); +} + +#endif // XBee_Printers_h From 470bff4c97e433bbf3581844a17a5083eb89ae72 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Mon, 13 Jul 2015 17:16:31 +0200 Subject: [PATCH 15/17] Add a bunch of printing callback functions These functions can be used as callbacks for XBeeWithCallback and print various packets. printErrorCb() can easily print errors to simplify sketches. printRawResponseCb() and printResponseCb() print complete responses and will be mostly useful for debugging (especially the generic printResponseCb() version is fairly big due to all the strings, it compiles to around 5.5k). --- Printers.cpp | 333 +++++++++++++++++++++++++++++++++++++++++++++++++++ Printers.h | 39 ++++++ 2 files changed, 372 insertions(+) diff --git a/Printers.cpp b/Printers.cpp index b297213..983642a 100644 --- a/Printers.cpp +++ b/Printers.cpp @@ -21,3 +21,336 @@ void printHex(Print& p, const uint8_t* buf, size_t len, const __FlashStringHelpe cur_group++; } } + +void printErrorCb(uint8_t code, uintptr_t data) { + Print *p = (Print*)data; + p->print(F("Error reading API packet. Error code: ")); + p->println(code); +} + +void printErrorCb(ZBTxStatusResponse& r, uintptr_t data) { + Print *p = (Print*)data; + if (!r.isSuccess()) { + p->print(F("Error sending Zigbee packet. Delivery status: ")); + p->println(r.getDeliveryStatus()); + } +} + +void printErrorCb(TxStatusResponse& r, uintptr_t data) { + Print *p = (Print*)data; + if (!r.isSuccess()) { + p->print(F("Error sending packet. Delivery status: ")); + p->println(r.getStatus()); + } +} + +void printErrorCb(AtCommandResponse& r, uintptr_t data) { + Print *p = (Print*)data; + if (!r.isOk()) { + p->print(F("Error sending ")); + p->write(r.getCommand(), 2); + p->print(F(" command. Status: ")); + p->println(r.getStatus()); + } +} + +void printErrorCb(RemoteAtCommandResponse& r, uintptr_t data) { + Print *p = (Print*)data; + if (!r.isOk()) { + p->print(F("Error sending remote ")); + p->write(r.getCommand(), 2); + p->print(F(" command. Status: ")); + p->println(r.getStatus()); + } +} + +void printErrorCb(XBeeResponse& r, uintptr_t data) { + uint8_t id = r.getApiId(); + // Figure out the API type and call the corresonding function + if (id == ZB_TX_STATUS_RESPONSE) { + ZBTxStatusResponse response; + r.getZBTxStatusResponse(response); + printErrorCb(response, data); + } else if (id == TX_STATUS_RESPONSE) { + TxStatusResponse response; + r.getTxStatusResponse(response); + printErrorCb(response, data); + } else if (id == AT_COMMAND_RESPONSE) { + AtCommandResponse response; + r.getAtCommandResponse(response); + printErrorCb(response, data); + } else if (id == REMOTE_AT_COMMAND_RESPONSE) { + RemoteAtCommandResponse response; + r.getRemoteAtCommandResponse(response); + printErrorCb(response, data); + } +} + + + +void printRawResponseCb(XBeeResponse& response, uintptr_t data) { + Print *p = (Print*)data; + p->print("Response received: "); + // Reconstruct the original packet + uint8_t header[] = {START_BYTE, response.getMsbLength(), response.getLsbLength(), response.getApiId()}; + printHex(*p, header, sizeof(header), F(" "), NULL); + p->write(' '); + printHex(*p, response.getFrameData(), response.getFrameDataLength(), F(" "), NULL); + p->println(); +} + + + +/** + * Helper function to print a field name, followed by the hexadecimal + * value and a newline. + */ +template +static void printField(Print* p, const __FlashStringHelper *prefix, T data) { + p->print(prefix); + printHex(*p, data); + p->println(); +} + +void printResponseCb(ZBTxStatusResponse& status, uintptr_t data) { + Print *p = (Print*)data; + p->println(F("ZBTxStatusResponse received:")); + printField(p, F(" FrameId: 0x"), status.getFrameId()); + printField(p, F(" To: 0x"), status.getRemoteAddress()); + printField(p, F(" Delivery status: 0x"), status.getDeliveryStatus()); + printField(p, F(" Discovery status: 0x"), status.getDiscoveryStatus()); +} + +void printResponseCb(ZBRxResponse& rx, uintptr_t data) { + Print *p = (Print*)data; + p->println(F("ZBRxResponse received:")); + printField(p, F(" From: 0x"), rx.getRemoteAddress64()); + printField(p, F(" From: 0x"), rx.getRemoteAddress16()); + printField(p, F(" Receive options: 0x"), rx.getOption()); + if (rx.getDataLength() > 8) + p->print(" Payload:\r\n "); + else + p->print(" Payload: "); + printHex(*p, rx.getFrameData() + rx.getDataOffset(), rx.getDataLength(), F(" "), F("\r\n "), 8); + p->println(); +} + +void printResponseCb(ZBExplicitRxResponse& rx, uintptr_t data) { + Print *p = (Print*)data; + p->println(F("ZBExplicitRxResponse received:")); + printField(p, F(" From: 0x"), rx.getRemoteAddress64()); + printField(p, F(" From: 0x"), rx.getRemoteAddress16()); + printField(p, F(" Receive options: 0x"), rx.getOption()); + printField(p, F(" Src endpoint: 0x"), rx.getSrcEndpoint()); + printField(p, F(" Dst endpoint: 0x"), rx.getDstEndpoint()); + printField(p, F(" Cluster id: 0x"), rx.getClusterId()); + printField(p, F(" Profile id: 0x"), rx.getProfileId()); + if (rx.getDataLength() > 8) + p->print(" Payload:\r\n "); + else + p->print(" Payload: "); + printHex(*p, rx.getFrameData() + rx.getDataOffset(), rx.getDataLength(), F(" "), F("\r\n "), 8); + p->println(); +} + +void printResponseCb(ZBRxIoSampleResponse& rx, uintptr_t data) { + Print *p = (Print*)data; + p->println(F("ZBRxIoSampleResponse received:")); + printField(p, F(" From: 0x"), rx.getRemoteAddress64()); + printField(p, F(" From: 0x"), rx.getRemoteAddress16()); + printField(p, F(" Receive options: 0x"), rx.getOption()); + for (uint8_t i = 0; i < 16; ++i) { + if (rx.isDigitalEnabled(i)) { + p->print(F(" Digital pin ")); + p->print(i); + p->print(F(": ")); + p->print(rx.isDigitalOn(i) ? "HIGH" : "LOW"); + p->println(); + } + } + for (uint8_t i = 0; i < 8; ++i) { + if (rx.isAnalogEnabled(i)) { + p->print(F(" Analog pin ")); + p->print(i); + p->print(F(": 0x")); + printHex(*p, rx.getAnalog(i)); + p->println(); + } + } +} + +void printResponseCb(TxStatusResponse& status, uintptr_t data) { + Print *p = (Print*)data; + p->println(F("TxStatusResponse received:")); + printField(p, F(" FrameId: 0x"), status.getFrameId()); + printField(p, F(" Status: 0x"), status.getStatus()); +} + +void printResponseCb(Rx16Response& rx, uintptr_t data) { + Print *p = (Print*)data; + p->println("Rx16Response received:"); + printField(p, F(" From: 0x"), rx.getRemoteAddress16()); + printField(p, F(" Rssi: 0x"), rx.getRssi()); + printField(p, F(" Receive options: 0x"), rx.getOption()); + if (rx.getDataLength() > 8) + p->print(" Payload:\r\n "); + else + p->print(" Payload: "); + printHex(*p, rx.getFrameData() + rx.getDataOffset(), rx.getDataLength(), F(" "), F("\r\n "), 8); + p->println(); +} + +void printResponseCb(Rx64Response& rx, uintptr_t data) { + Print *p = (Print*)data; + p->println("Rx64Response received:"); + printField(p, F(" From: 0x"), rx.getRemoteAddress64()); + printField(p, F(" Rssi: 0x"), rx.getRssi()); + printField(p, F(" Receive options: 0x"), rx.getOption()); + if (rx.getDataLength() > 8) + p->print(" Payload:\r\n "); + else + p->print(" Payload: "); + printHex(*p, rx.getFrameData() + rx.getDataOffset(), rx.getDataLength(), F(" "), F("\r\n "), 8); + p->println(); +} + +/** + * Helper function to share common functionality between the two sample + * resonses. + */ +static void printSamples(Print* p, RxIoSampleBaseResponse& rx) { + for (uint8_t s = 0; s < rx.getSampleSize(); ++s) { + p->print(F(" Sample ")); + p->print(s); + p->println(F(":")); + + for (uint8_t i = 0; i < 9; ++i) { + if (rx.isDigitalEnabled(i)) { + p->print(F(" Digital pin ")); + p->print(i); + p->print(F(": ")); + p->print(rx.isDigitalOn(i, s) ? "HIGH" : "LOW"); + p->println(); + } + } + for (uint8_t i = 0; i < 7; ++i) { + if (rx.isAnalogEnabled(i)) { + p->print(F(" Analog pin ")); + p->print(i); + p->print(F(": 0x")); + printHex(*p, rx.getAnalog(i, s)); + p->println(); + } + } + } +} + +void printResponseCb(Rx16IoSampleResponse& rx, uintptr_t data) { + Print *p = (Print*)data; + p->println("Rx16IoSampleResponse received:"); + printField(p, F(" From: 0x"), rx.getRemoteAddress16()); + printField(p, F(" Rssi: 0x"), rx.getRssi()); + printField(p, F(" Receive options: 0x"), rx.getOption()); + printField(p, F(" Number of samples: 0x"), rx.getSampleSize()); + printSamples(p, rx); +} + +void printResponseCb(Rx64IoSampleResponse& rx, uintptr_t data) { + Print *p = (Print*)data; + p->println("Rx64IoSampleResponse received:"); + printField(p, F(" From: 0x"), rx.getRemoteAddress64()); + printField(p, F(" Rssi: 0x"), rx.getRssi()); + printField(p, F(" Receive options: 0x"), rx.getOption()); + printField(p, F(" Number of samples: 0x"), rx.getSampleSize()); + printSamples(p, rx); +} + +void printResponseCb(ModemStatusResponse& status, uintptr_t data) { + Print *p = (Print*)data; + p->println("ModemStatusResponse received:"); + printField(p, F(" Status: 0x"), status.getStatus()); +} + +void printResponseCb(AtCommandResponse& at, uintptr_t data) { + Print *p = (Print*)data; + p->println("AtCommandResponse received:"); + p->print(F(" Command: ")); + p->write(at.getCommand(), 2); + p->println(); + printField(p, F(" Status: 0x"), at.getStatus()); + if (at.getValueLength()) { + p->print(F(" Value: ")); + printHex(*p, at.getValue(), at.getValueLength(), F(" "), NULL); + p->println(); + } +} + +void printResponseCb(RemoteAtCommandResponse& at, uintptr_t data) { + Print *p = (Print*)data; + p->println("AtRemoteCommandResponse received:"); + printField(p, F(" To: 0x"), at.getRemoteAddress64()); + printField(p, F(" To: 0x"), at.getRemoteAddress16()); + p->print(F(" Command: ")); + p->write(at.getCommand(), 2); + p->println(); + printField(p, F(" Status: 0x"), at.getStatus()); + if (at.getValueLength()) { + p->print(F(" Value: ")); + printHex(*p, at.getValue(), at.getValueLength(), F(" "), NULL); + p->println(); + } +} + +void printResponseCb(XBeeResponse& r, uintptr_t data) { + uint8_t id = r.getApiId(); + // Figure out the API type and call the corresonding function + if (id == ZB_TX_STATUS_RESPONSE) { + ZBTxStatusResponse response; + r.getZBTxStatusResponse(response); + printResponseCb(response, data); + } else if (id == ZB_RX_RESPONSE) { + ZBRxResponse response; + r.getZBRxResponse(response); + printResponseCb(response, data); + } else if (id == ZB_EXPLICIT_RX_RESPONSE) { + ZBExplicitRxResponse response; + r.getZBExplicitRxResponse(response); + printResponseCb(response, data); + } else if (id == ZB_IO_SAMPLE_RESPONSE) { + ZBRxIoSampleResponse response; + r.getZBRxIoSampleResponse(response); + printResponseCb(response, data); + } else if (id == TX_STATUS_RESPONSE) { + TxStatusResponse response; + r.getTxStatusResponse(response); + printResponseCb(response, data); + } else if (id == RX_16_RESPONSE) { + Rx16Response response; + r.getRx16Response(response); + printResponseCb(response, data); + } else if (id == RX_64_RESPONSE) { + Rx64Response response; + r.getRx64Response(response); + printResponseCb(response, data); + } else if (id == RX_16_IO_RESPONSE) { + Rx16IoSampleResponse response; + r.getRx16IoSampleResponse(response); + printResponseCb(response, data); + } else if (id == RX_64_IO_RESPONSE) { + Rx64IoSampleResponse response; + r.getRx64IoSampleResponse(response); + printResponseCb(response, data); + } else if (id == MODEM_STATUS_RESPONSE) { + ModemStatusResponse response; + r.getModemStatusResponse(response); + printResponseCb(response, data); + } else if (id == AT_COMMAND_RESPONSE) { + AtCommandResponse response; + r.getAtCommandResponse(response); + printResponseCb(response, data); + } else if (id == REMOTE_AT_COMMAND_RESPONSE) { + RemoteAtCommandResponse response; + r.getRemoteAtCommandResponse(response); + printResponseCb(response, data); + } +} diff --git a/Printers.h b/Printers.h index 063530b..20880d9 100644 --- a/Printers.h +++ b/Printers.h @@ -85,4 +85,43 @@ inline void printHex(Print& p, XBeeAddress64 v) { printHex(p, v.getLsb()); } +// The following functions are intended to be used as callbacks, to +// print various information about received responses. All of the +// require a Print* to be passed as the data parameter. For example, to +// print to Serial any TxStatusResponses that contain errors, do: +// +// xbee.onTxStatusResponse(printErrorCb, (uintptr_t)(Print*)&Serial); +// +// Most of these callbacks can either be used as a response-specific +// callback, to only work that specific API response type, or as a +// generic callback (onResponse or onOtherResponse), in which case the +// relevant version of the callback will be called automatically. + +// printErrorCb prints any error messages in status responses. +void printErrorCb(uint8_t code, uintptr_t data); +void printErrorCb(ZBTxStatusResponse& r, uintptr_t data); +void printErrorCb(TxStatusResponse& r, uintptr_t data); +void printErrorCb(AtCommandResponse& r, uintptr_t data); +void printErrorCb(RemoteAtCommandResponse& r, uintptr_t data); +void printErrorCb(XBeeResponse& r, uintptr_t data); + +// printRawResponseCb prints the raw bytes of a response. +void printRawResponseCb(XBeeResponse& response, uintptr_t data); + +// printResponseCb prints a human-readable version of a response, showing +// the values of all fields individually. +void printResponseCb(ZBTxStatusResponse& status, uintptr_t data); +void printResponseCb(ZBRxResponse& rx, uintptr_t data); +void printResponseCb(ZBExplicitRxResponse& rx, uintptr_t data); +void printResponseCb(ZBRxIoSampleResponse& rx, uintptr_t data); +void printResponseCb(TxStatusResponse& status, uintptr_t data); +void printResponseCb(Rx16Response& rx, uintptr_t data); +void printResponseCb(Rx64Response& rx, uintptr_t data); +void printResponseCb(Rx16IoSampleResponse& rx, uintptr_t data); +void printResponseCb(Rx64IoSampleResponse& rx, uintptr_t data); +void printResponseCb(ModemStatusResponse& status, uintptr_t data); +void printResponseCb(AtCommandResponse& at, uintptr_t data); +void printResponseCb(RemoteAtCommandResponse& at, uintptr_t data); +void printResponseCb(XBeeResponse& r, uintptr_t data); + #endif // XBee_Printers_h From 27fcc038f2034cb380fb58c7cea1445a94bdeb4f Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Sun, 12 Jul 2015 21:42:06 +0200 Subject: [PATCH 16/17] Add Echo_Callbacks example This is an example that receives packets and echoes them back to the sender. It uses the new callbacks to show their usage. --- examples/Echo_Callbacks/Echo_Callbacks.ino | 126 +++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 examples/Echo_Callbacks/Echo_Callbacks.ino diff --git a/examples/Echo_Callbacks/Echo_Callbacks.ino b/examples/Echo_Callbacks/Echo_Callbacks.ino new file mode 100644 index 0000000..e824b51 --- /dev/null +++ b/examples/Echo_Callbacks/Echo_Callbacks.ino @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2015 Matthijs Kooijman + * + * This file is part of XBee-Arduino. + * + * XBee-Arduino is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * XBee-Arduino is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBee-Arduino. If not, see . + */ + +#include +#include + +/* + This example is for Series 1 and 2 XBee (no changes needed for either). + + It listens for incoming packets and echoes back any data received back + to the sender. This example shows how to use XBeeWithCallbacks to + concisely express reading of response packets. + + This example assumes an Arduino with two serial ports (like the + Leonardo or Mega). Replace Serial and Serial1 below appropriately for + your hardware. +*/ + +// create the XBee object +XBeeWithCallbacks xbee; + +void zbReceive(ZBRxResponse& rx, uintptr_t) { + // Create a reply packet containing the same data + // This directly reuses the rx data array, which is ok since the tx + // packet is sent before any new response is received + ZBTxRequest tx; + tx.setAddress64(rx.getRemoteAddress64()); + tx.setAddress16(rx.getRemoteAddress16()); + tx.setPayload(rx.getFrameData() + rx.getDataOffset(), rx.getDataLength()); + + // Send the reply, but do not wait for the tx status reply. If an + // error occurs, the global onTxStatusResponse handler will print an + // error message, but no message is printed on succes. + xbee.send(tx); + Serial.println(F("Sending ZBTxRequest")); +} + +void receive16(Rx16Response& rx, uintptr_t) { + // Create a reply packet containing the same data + // This directly reuses the rx data array, which is ok since the tx + // packet is sent before any new response is received + Tx16Request tx; + tx.setAddress16(rx.getRemoteAddress16()); + tx.setPayload(rx.getFrameData() + rx.getDataOffset(), rx.getDataLength()); + + // Send the reply, but do not wait for the tx status reply. If an + // error occurs, the global onTxStatusResponse handler will print an + // error message, but no message is printed on succes. + xbee.send(tx); + Serial.println(F("Sending Tx16Request")); +} + +void receive64(Rx64Response& rx, uintptr_t) { + // Create a reply packet containing the same data + // This directly reuses the rx data array, which is ok since the tx + // packet is sent before any new response is received + Tx64Request tx; + tx.setAddress64(rx.getRemoteAddress64()); + tx.setPayload(rx.getFrameData() + rx.getDataOffset(), rx.getDataLength()); + + // Send the reply, but do not wait for the tx status reply. If an + // error occurs, the global onTxStatusResponse handler will print an + // error message, but no message is printed on succes. + xbee.send(tx); + Serial.println(F("Sending Tx64Request")); +} +void setup() { + Serial.begin(9600); + + Serial1.begin(9600); + xbee.setSerial(Serial1); + + // Make sure that any errors are logged to Serial. The address of + // Serial is first cast to Print*, since that's what the callback + // expects, and then to uintptr_t to fit it inside the data parameter. + xbee.onPacketError(printErrorCb, (uintptr_t)(Print*)&Serial); + xbee.onTxStatusResponse(printErrorCb, (uintptr_t)(Print*)&Serial); + xbee.onZBTxStatusResponse(printErrorCb, (uintptr_t)(Print*)&Serial); + + // These are called when an actual packet received + xbee.onZBRxResponse(zbReceive); + xbee.onRx16Response(receive16); + xbee.onRx64Response(receive64); + + // Print any unhandled response with proper formatting + xbee.onOtherResponse(printResponseCb, (uintptr_t)(Print*)&Serial); + + // Enable this to print the raw bytes for _all_ responses before they + // are handled + //xbee.onResponse(printRawResponseCb, (uintptr_t)(Print*)&Serial); + + // Set AO=0 to make sure we get ZBRxResponses, not + // ZBExplicitRxResponses (only supported on series2). This probably + // isn't needed, but nicely shows how to use sendAndWait(). + uint8_t value = 0; + AtCommandRequest req((uint8_t*)"AO", &value, sizeof(value)); + req.setFrameId(xbee.getNextFrameId()); + // Send the command and wait up to 150ms for a response + uint8_t status = xbee.sendAndWait(req, 150); + if (status == 0) + Serial.println(F("Set AO=0")); + else + Serial.println(F("Failed to set AO (this is expected on series1)")); +} + +void loop() { + // Continuously let xbee read packets and call callbacks. + xbee.loop(); +} + From 6b063ab756febfdebe5ba48f764861807beeb19b Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Mon, 13 Jul 2015 16:48:15 +0200 Subject: [PATCH 17/17] Add ZdpScan example This example allows scanning a network for joined devices, and for endpoints, profiles and clusters supported by those devices. At the same time, this shows some more advanced uses of the callback API, waiting for actual reply packet received from other nodes in addition to waiting for status replies from the local XBee module. --- examples/ZdpScan/ZdpScan.ino | 463 +++++++++++++++++++++++++++++++++++ examples/ZdpScan/zigbee.h | 185 ++++++++++++++ 2 files changed, 648 insertions(+) create mode 100644 examples/ZdpScan/ZdpScan.ino create mode 100644 examples/ZdpScan/zigbee.h diff --git a/examples/ZdpScan/ZdpScan.ino b/examples/ZdpScan/ZdpScan.ino new file mode 100644 index 0000000..afaa074 --- /dev/null +++ b/examples/ZdpScan/ZdpScan.ino @@ -0,0 +1,463 @@ +/** + * Copyright (c) 2015 Matthijs Kooijman. + * + * This file is part of XBee-Arduino. + * + * Permission is hereby granted, free of charge, to anyone obtaining a + * copy of this file, to do whatever they want with them without any + * restriction, including, but not limited to, copying, modification and + * redistribution. + * + * NO WARRANTY OF ANY KIND IS PROVIDED. + */ + +#include +#include + +#include "zigbee.h" + +/* + This example is for Series 2 XBee. + + It discovers up to 10 nodes on the Zigbee network by recursively fetching + neighbour tables from all routers and presents the result on the serial + console. A node can be selected to be further examined, which will then + be queried (using messages from the Zigbee Device Profile) for the + endpoints, profiles and clusters it supports. + + This example assumes an Arduino with two serial ports (like the + Leonardo or Mega). Replace Serial and Serial1 below appropriately for + your hardware. +*/ + +#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ +#error This code relies on little endian integers! +#endif + +XBeeWithCallbacks xbee; + +/** Helper to generate sequential Zdo transaction identifiers */ +uint8_t getNextTransactionId() { + static uint8_t id = 0; + return id++; +} + +#ifndef lengthof +#define lengthof(x) (sizeof(x)/sizeof(*x)) +#endif + +/** + * Helper function to print a field name, followed by the hexadecimal + * value and a newline. + */ +template +static void printField(const __FlashStringHelper *prefix, T data); +template +static void printField(const __FlashStringHelper *prefix, T data) { + Serial.print(prefix); + printHex(Serial, data); + Serial.println(); +} + +void printActiveEndpoints(const zdo_active_ep_rsp_header_t *rsp) { + Serial.println(F("Active endpoints response")); + printField(F(" About: 0x"), rsp->network_addr_le); + Serial.print(F(" Endpoints found: 0x")); + printHex(Serial, rsp->endpoints, rsp->ep_count, F(", 0x"), NULL); + Serial.println(); +} + +void printClusters(const __FlashStringHelper *prefix, uint16_t* clusters, uint8_t count) { + Serial.print(prefix); + for (uint8_t i = 0; i < count; ++i) { + if (i > 0) Serial.print(F(", ")); + Serial.print(F("0x")); + printHex(Serial, ((uint16_t*)clusters)[i]); + } + if (!count) Serial.print(F("none")); + + Serial.println(); +} + +void printSimpleDescriptor(zdo_simple_desc_resp_header_t *rsp) { + zdo_simple_desc_header_t *desc = (zdo_simple_desc_header_t*)((uint8_t*)rsp + sizeof(zdo_simple_desc_resp_header_t)); + uint8_t *clusters = ((uint8_t*)desc + sizeof(zdo_simple_desc_header_t)); + + Serial.println(F("Simple descriptor response")); + printField(F(" About: 0x"), rsp->network_addr_le); + printField(F(" Endpoint: 0x"), desc->endpoint); + printField(F(" Profile ID: 0x"), desc->profile_id_le); + printField(F(" Device ID: 0x"), desc->device_id_le); + printField(F(" Device Version: "), (uint8_t)(desc->device_version & 0xf)); + + uint8_t ip_count = *clusters++; + printClusters(F(" Input clusters: "), (uint16_t*)clusters, ip_count); + clusters += 2*ip_count; + uint8_t op_count = *clusters++; + printClusters(F(" Output clusters: "), (uint16_t*)clusters, op_count); +} + +void toggle(XBeeAddress64& addr) { + uint8_t payload[] = {0x01, 0x00, 0x02}; + ZBExplicitTxRequest tx(addr, 0xfffe, 0, 0, payload, sizeof(payload), 0, 9, 9, 0x0006, 0x0104) ; + tx.setFrameId(xbee.getNextFrameId()); + xbee.send(tx); +} + +/* Matching function that can be passed to waitFor() that matches + * replies to Zdo requests. The data passed along is the Zdo transaction + * id that was used for the request, which will be used to select the + * right reply. + */ +bool matchZdoReply(ZBExplicitRxResponse& rx, uintptr_t data) { + uint8_t *payload = rx.getFrameData() + rx.getDataOffset(); + uint8_t transactionId = (uint8_t)data; + + return rx.getSrcEndpoint() == 0 && + rx.getDstEndpoint() == 0 && + rx.getProfileId() == WPAN_PROFILE_ZDO && + payload[0] == transactionId; +} + +/** + * Create a tx request to send a Zdo request. + */ +ZBExplicitTxRequest buildZdoRequest(XBeeAddress64 addr, uint16_t cluster_id, uint8_t *payload, size_t len) { + ZBExplicitTxRequest tx(addr, payload, len); + tx.setSrcEndpoint(WPAN_ENDPOINT_ZDO); + tx.setDstEndpoint(WPAN_ENDPOINT_ZDO); + tx.setClusterId(cluster_id); + tx.setProfileId(WPAN_PROFILE_ZDO); + tx.setFrameId(xbee.getNextFrameId()); + return tx; +} + +/** + * Create a zdo request, send it and wait for a reply (which will be + * stored in the given response object). + * Returns true when a response was received, returns false if something + * goes wrong (an error message will have been prined already). + */ +bool handleZdoRequest(const __FlashStringHelper *msg, ZBExplicitRxResponse& rx, XBeeAddress64 addr, uint16_t cluster_id, uint8_t *payload, size_t len) { + ZBExplicitTxRequest tx = buildZdoRequest(addr, cluster_id, (uint8_t*)payload, len); + xbee.send(tx); + + uint8_t transaction_id = payload[0]; + // This waits up to 5000 seconds, since the default TX timeout (NH + // value of 1.6s, times three retries) is 4.8s. + uint8_t status = xbee.waitFor(rx, 5000, matchZdoReply, transaction_id, tx.getFrameId()); + switch(status) { + case 0: // Success + return true; + case XBEE_WAIT_TIMEOUT: + Serial.print(F("No reply received from 0x")); + printHex(Serial, addr.getMsb()); + printHex(Serial, addr.getLsb()); + Serial.print(F(" while ")); + Serial.print(msg); + Serial.println(F(".")); + return false; + default: + Serial.print(F("Failed to send to 0x")); + printHex(Serial, addr.getMsb()); + printHex(Serial, addr.getLsb()); + Serial.print(F(" while ")); + Serial.print(msg); + Serial.print(F(". Status: 0x")); + printHex(Serial, status); + Serial.println(); + return false; + } +} + +/** + * Request a list of active endpoints from the node with the given + * address. Print the endpoints discovered and then request more details + * for each of the endpoints and print those too. + */ +void get_active_endpoints(XBeeAddress64& addr, uint16_t addr16) { + zdo_active_ep_req_t payload = { + .transaction = getNextTransactionId(), + .network_addr_le = addr16, + }; + printField(F("Discovering services on 0x"), addr16); + + ZBExplicitRxResponse rx; + if (!handleZdoRequest(F("requesting active endpoints"), + rx, addr, ZDO_ACTIVE_EP_REQ, + (uint8_t*)&payload, sizeof(payload))) + return; + + zdo_active_ep_rsp_header_t *rsp = (zdo_active_ep_rsp_header_t*)(rx.getFrameData() + rx.getDataOffset()); + + if (rsp->status) { + printField(F("Active endpoints request rejected. Status: 0x"), rsp->status); + return; + } + + printActiveEndpoints(rsp); + + // Copy the endpoint list, since requesting a descriptor below will + // invalidate the data in rx / rsp. + uint8_t endpoints[rsp->ep_count]; + memcpy(endpoints, rsp->endpoints, sizeof(endpoints)); + + // Request the simple descriptor for each endpoint + for (uint8_t i = 0; i < sizeof(endpoints); ++i) + get_simple_descriptor(addr, addr16, endpoints[i]); +} + +void get_simple_descriptor(XBeeAddress64& addr, uint16_t addr16, uint8_t endpoint) { + zdo_simple_desc_req_t payload = { + .transaction = getNextTransactionId(), + .network_addr_le = addr16, + .endpoint = endpoint, + }; + + ZBExplicitRxResponse rx; + if (!handleZdoRequest(F("requesting simple descriptor"), + rx, addr, ZDO_SIMPLE_DESC_REQ, + (uint8_t*)&payload, sizeof(payload))) + return; + + zdo_simple_desc_resp_header_t *rsp = (zdo_simple_desc_resp_header_t*)(rx.getFrameData() + rx.getDataOffset()); + + if (rsp->status) { + printField(F("Failed to fetch simple descriptor. Status: 0x"), rsp->status); + return; + } + + printSimpleDescriptor(rsp); +} + +bool getAtValue(uint8_t cmd[2], uint8_t *buf, size_t len, uint16_t timeout = 150) { + AtCommandRequest req(cmd); + req.setFrameId(xbee.getNextFrameId()); + uint8_t status = xbee.sendAndWait(req, timeout); + if (status != 0) { + Serial.print(F("Failed to read ")); + Serial.write(cmd, 2); + Serial.print(F(" command. Status: 0x")); + Serial.println(status, HEX); + return false; + } + + AtCommandResponse response; + xbee.getResponse().getAtCommandResponse(response); + if (response.getValueLength() != len) { + Serial.print(F("Unexpected response length in ")); + Serial.write(cmd, 2); + Serial.println(F(" response")); + return false; + } + + memcpy(buf, response.getValue(), len); + return true; +} + +// Invert the endianness of a given buffer +void invertEndian(uint8_t *buf, size_t len) { + for (uint8_t i = 0, j = len - 1; i < len/2; ++i, j--) { + uint8_t tmp = buf[i]; + buf[i] = buf[j]; + buf[j] = tmp; + } +} + +/** + * Struct to keep info about discovered nodes. + */ +struct node_info { + XBeeAddress64 addr64; + uint16_t addr16; + uint8_t type: 2; + uint8_t visited: 1; +}; + +/** + * List of nodes found. + */ +node_info nodes[10]; +uint8_t nodes_found = 0; + +/** + * Scan the network and discover all other nodes by traversing neighbour + * tables. The discovered nodes are stored in the nodes array. + */ +void scan_network() { + Serial.println(); + Serial.println("Discovering devices"); + // Fetch our operating PAN ID, to filter the LQI results + uint8_t pan_id[8]; + getAtValue((uint8_t*)"OP", pan_id, sizeof(pan_id)); + // XBee sends in big-endian, but ZDO requests use little endian. For + // easy comparsion, convert to little endian + invertEndian(pan_id, sizeof(pan_id)); + + // Fetch the addresses of the local node + XBeeAddress64 local; + uint8_t shbuf[4], slbuf[4], mybuf[2]; + if (!getAtValue((uint8_t*)"SH", shbuf, sizeof(shbuf)) || + !getAtValue((uint8_t*)"SL", slbuf, sizeof(slbuf)) || + !getAtValue((uint8_t*)"MY", mybuf, sizeof(mybuf))) + return; + + nodes[0].addr64.setMsb((uint32_t)shbuf[0] << 24 | (uint32_t)shbuf[1] << 16 | (uint32_t)shbuf[2] << 8 | shbuf[3]); + nodes[0].addr64.setLsb((uint32_t)slbuf[0] << 24 | (uint32_t)slbuf[1] << 16 | (uint32_t)slbuf[2] << 8 | slbuf[3]); + nodes[0].addr16 = (uint16_t)mybuf[0] << 8 | mybuf[1]; + nodes[0].type = ZDO_MGMT_LQI_REQ_TYPE_UNKNOWN; + nodes[0].visited = false; + nodes_found = 1; + + Serial.print(F("0) 0x")); + printHex(Serial, nodes[0].addr64); + Serial.print(F(" (0x")); + printHex(Serial, nodes[0].addr16); + Serial.println(F(", Self)")); + + // nodes[0] now contains our own address, the rest is invalid. We + // explore the network by asking for LQI info (neighbour table). + // Initially, this pretends to send a packet to ourselves, which the + // XBee firmware conveniently handles by pretending that a reply was + // received (with one caveat: it seems the reply arrives _before_ the + // TX status). + uint8_t next = 0; + do { + // Query node i for its LQI table + zdo_mgmt_lqi_req_t payload = { + .transaction = getNextTransactionId(), + .start_index = 0, + }; + + do { + ZBExplicitRxResponse rx; + if (!handleZdoRequest(F("requesting LQI/neighbour table"), + rx, nodes[next].addr64, ZDO_MGMT_LQI_REQ, + (uint8_t*)&payload, sizeof(payload))) + break; + + zdo_mgmt_lqi_rsp_t *rsp = (zdo_mgmt_lqi_rsp_t*)(rx.getFrameData() + rx.getDataOffset()); + if (rsp->status != 0) { + if (rsp->status != ZDO_STATUS_NOT_SUPPORTED) { + Serial.print(F("LQI query rejected by 0x")); + printHex(Serial, nodes[next].addr16); + Serial.print(F(". Status: 0x")); + printHex(Serial, rsp->status); + Serial.println(); + } + break; + } + + if (rsp->start_index != payload.start_index) { + Serial.println(F("Unexpected start_index, skipping this node")); + break; + } + + for (uint8_t i = 0; i < rsp->list_count; ++i) { + zdo_mgmt_lqi_entry_t *e = &rsp->entries[i]; + node_info *n = &nodes[nodes_found]; + + if (memcmp(&e->extended_pan_id_le, &pan_id, sizeof(pan_id)) != 0) { + Serial.println(F("Ignoring node in other PAN")); + continue; + } + + // Skip if we know about this node already + uint8_t dup; + for (dup = 0; dup < nodes_found; ++dup) { + if (nodes[dup].addr16 == e->nwk_addr_le) + break; + } + if (dup != nodes_found) + continue; + + n->addr64.setMsb(e->extended_addr_le >> 32); + n->addr64.setLsb(e->extended_addr_le); + n->addr16 = e->nwk_addr_le; + n->type = e->flags0 & 0x3; + + Serial.print(nodes_found); + Serial.print(F(") 0x")); + printHex(Serial, n->addr64); + Serial.print(F(" (0x")); + printHex(Serial, n->addr16); + switch (n->type) { + case ZDO_MGMT_LQI_REQ_TYPE_COORDINATOR: + Serial.println(F(", Coordinator)")); + break; + case ZDO_MGMT_LQI_REQ_TYPE_ROUTER: + Serial.println(F(", Router)")); + break; + case ZDO_MGMT_LQI_REQ_TYPE_ENDDEVICE: + Serial.println(F(", End device)")); + break; + case ZDO_MGMT_LQI_REQ_TYPE_UNKNOWN: + Serial.println(F(", Unknown)")); + break; + } + nodes_found++; + + if (nodes_found == lengthof(nodes)) { + Serial.println(F("Device table full, terminating network scan")); + return; + } + } + + // Got all neighbours available? Done. + if (rsp->start_index + rsp->list_count >= rsp->table_entries) + break; + // More left? Loop and get more. + payload.start_index += rsp->list_count; + payload.transaction = getNextTransactionId(); + } while (true); + + // Done with this node, on to the next + nodes[next].visited = true; + ++next; + } while (next < nodes_found); + Serial.println(F("Finished scanning")); + Serial.println(F("Press a number to scan that node, or press r to rescan the network")); +} + +void setup() { + Serial.begin(9600); + + Serial1.begin(9600); + xbee.setSerial(Serial1); + + xbee.onPacketError(printErrorCb, (uintptr_t)(Print*)&Serial); + + // Set AO=1 to receive explicit RX frames + // Because this does not write to flash with WR, AO should be reverted + // on reboot. + uint8_t value = 1; + AtCommandRequest req((uint8_t*)"AO", &value, sizeof(value)); + req.setFrameId(xbee.getNextFrameId()); + uint8_t status = xbee.sendAndWait(req, 150); + if (status == 0) + Serial.println(F("Set AO=1")); + else + Serial.println(F("Failed to set AO, expect problems")); + + scan_network(); +} + + +void loop() { + // Read serial to see if a node was chosen. If so, start a closer scan + // of that node and rescan. + if (Serial.available()) { + uint8_t c = Serial.read(); + if (c >= '0' && c <= '9') { + int n = c - '0'; + if (n < nodes_found) { + get_active_endpoints(nodes[n].addr64, nodes[n].addr16); + scan_network(); + } + } else if (c == 'r') { + scan_network(); + } + } + + xbee.loop(); +} diff --git a/examples/ZdpScan/zigbee.h b/examples/ZdpScan/zigbee.h new file mode 100644 index 0000000..3048b2e --- /dev/null +++ b/examples/ZdpScan/zigbee.h @@ -0,0 +1,185 @@ +// This file contains defines and structs, taken from +// https://github.com/digidotcom/xbee_ansic_library/blob/master/include/ +// Response headers were modified to include a transaction field and +// some new content was added for the xbee-arduino library. + +/* + * Copyright (c) 2010-2012 Digi International Inc., + * All rights not expressly granted are reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343 + * ======================================================================= + */ + + +#ifndef __XBEE_ZIGBEE_H +#define __XBEE_ZIGBEE_H + +#define PACKED_STRUCT struct __attribute__((__packed__)) +typedef uint64_t addr64; + +/** + * ZigBee Stack Profile IDs + * 4-bit values used in ZigBee beacons + */ +/// Network Specific +#define WPAN_STACK_PROFILE_PROPRIETARY 0x0 +/// ZigBee (2006) +#define WPAN_STACK_PROFILE_ZIGBEE 0x1 +/// ZigBee PRO (2007) +#define WPAN_STACK_PROFILE_ZIGBEE_PRO 0x2 + +/** + * Profile IDs + */ +/// ZigBee Device Object (aka ZigBee Device Profile) +#define WPAN_PROFILE_ZDO 0x0000 + +/// Smart Energy Profile +#define WPAN_PROFILE_SMART_ENERGY 0x0109 + +/// Digi International, mfg-specific +#define WPAN_PROFILE_DIGI 0xC105 + +/** + * List of fixed endpoints + */ +/// ZigBee Device Object/Profile +#define WPAN_ENDPOINT_ZDO 0x00 +/// Digi Smart Energy +#define WPAN_ENDPOINT_DIGI_SE 0x5E +/// Digi Device Objects +#define WPAN_ENDPOINT_DDO 0xE6 +/// Digi Data +#define WPAN_ENDPOINT_DIGI_DATA 0xE8 +/// Broadcast Endpoint +#define WPAN_ENDPOINT_BROADCAST 0xFF +//@} + +// Status values +#define ZDO_STATUS_SUCCESS 0x00 +// 0x01 to 0x7F are reserved +#define ZDO_STATUS_INV_REQUESTTYPE 0x80 +#define ZDO_STATUS_DEVICE_NOT_FOUND 0x81 +#define ZDO_STATUS_INVALID_EP 0x82 +#define ZDO_STATUS_NOT_ACTIVE 0x83 +#define ZDO_STATUS_NOT_SUPPORTED 0x84 +#define ZDO_STATUS_TIMEOUT 0x85 +#define ZDO_STATUS_NO_MATCH 0x86 +// 0x87 is reserved +#define ZDO_STATUS_NO_ENTRY 0x88 +#define ZDO_STATUS_NO_DESCRIPTOR 0x89 +#define ZDO_STATUS_INSUFFICIENT_SPACE 0x8A +#define ZDO_STATUS_NOT_PERMITTED 0x8B +#define ZDO_STATUS_TABLE_FULL 0x8C +#define ZDO_STATUS_NOT_AUTHORIZED 0x8D + +/// Cluster IDs with the high bit set are responses. +#define ZDO_CLUST_RESPONSE_MASK 0x8000 +#define ZDO_CLUST_IS_RESPONSE(c) (c & ZDO_CLUST_RESPONSE_MASK) + +/********************************************************* + Simple Descriptor +**********************************************************/ +/// cluster ID for ZDO Simple_Desc request +#define ZDO_SIMPLE_DESC_REQ 0x0004 +/// cluster ID for ZDO Simple_Desc response +#define ZDO_SIMPLE_DESC_RSP 0x8004 +/// frame format for ZDO Simple_Desc request +typedef PACKED_STRUCT zdo_simple_desc_req_t { + uint8_t transaction; + uint16_t network_addr_le; + uint8_t endpoint; ///< 0x01 to 0xFE +} zdo_simple_desc_req_t; + +/// header for ZDO Simple_Desc response, followed by a SimpleDescriptor +typedef PACKED_STRUCT zdo_simple_desc_resp_header_t { + uint8_t transaction; + uint8_t status; ///< see ZDO_STATUS_* macros + uint16_t network_addr_le; ///< device's network address (little-endian) + uint8_t length; ///< length of simple descriptor + // variable-length simple descriptor follows +} zdo_simple_desc_resp_header_t; + +/// header for ZDO SimpleDescriptor (part of a Simple_Desc response), followed +/// by uint8_t input cluster count, multiple uint16_t input cluster IDs, +/// uint8_t output cluster count, multiple uint16_t output cluster IDs +typedef PACKED_STRUCT zdo_simple_desc_header_t { + uint8_t endpoint; ///< 0x01 to 0xFE + uint16_t profile_id_le; ///< endpoint's profile ID (little-endian) + uint16_t device_id_le; ///< endpoint's device ID (little-endian) + uint8_t device_version; ///< upper 4 bits are reserved + // variable-length cluster counts and ids follow +} zdo_simple_desc_header_t; + + +/********************************************************* + Active EP Descriptor +**********************************************************/ +/// cluster ID for ZDO Active_EP request +#define ZDO_ACTIVE_EP_REQ 0x0005 +/// cluster ID for ZDO Active_EP response +#define ZDO_ACTIVE_EP_RSP 0x8005 +/// frame format for ZDO Active_EP request +/// @see zdo_send_descriptor_req() +typedef PACKED_STRUCT zdo_active_ep_req_t { + uint8_t transaction; + uint16_t network_addr_le; +} zdo_active_ep_req_t; + +/// header for ZDO Active_EP response, followed by \c .ep_count +/// uint8_t endpoints +typedef PACKED_STRUCT zdo_active_ep_rsp_header_t { + uint8_t transaction; + uint8_t status; ///< see ZDO_STATUS_* macros + uint16_t network_addr_le; + uint8_t ep_count; + uint8_t endpoints[]; +} zdo_active_ep_rsp_header_t; + +/********************************************************* + Management LQI Request +**********************************************************/ + +/// cluster ID for ZDO LQI Request +#define ZDO_MGMT_LQI_REQ 0x0031 +/// cluster ID for ZDO LQI Response +#define ZDO_MGMT_LQI_RSP 0x8031 + +/// frame format for a ZDO LQI Request +typedef PACKED_STRUCT zdo_mgmt_lqi_req_t { + uint8_t transaction; + uint8_t start_index; +} zdo_mgmt_lqi_req_t; + +/// frame format for a ZDO LQI Table Entry +typedef PACKED_STRUCT zdo_mgmt_lqi_entry_t { + uint64_t extended_pan_id_le; + uint64_t extended_addr_le; + uint16_t nwk_addr_le; + uint8_t flags0; + #define ZDO_MGMT_LQI_REQ_TYPE_COORDINATOR 0x0 + #define ZDO_MGMT_LQI_REQ_TYPE_ROUTER 0x1 + #define ZDO_MGMT_LQI_REQ_TYPE_ENDDEVICE 0x2 + #define ZDO_MGMT_LQI_REQ_TYPE_UNKNOWN 0x3 + uint8_t flags1; + uint8_t depth; + uint8_t lqi; +} zdo_mgt_lqi_entry_t; + +/// frame format for a ZDO LQI Response +typedef PACKED_STRUCT zdo_mgmt_lqi_rsp_t { + uint8_t transaction; + uint8_t status; + uint8_t table_entries; + uint8_t start_index; + uint8_t list_count; + zdo_mgmt_lqi_entry_t entries[]; +} zdo_mgmt_lqi_rsp_t; + + +#endif // __XBEE_ZIGBEE_H