diff --git a/Printers.cpp b/Printers.cpp new file mode 100644 index 0000000..983642a --- /dev/null +++ b/Printers.cpp @@ -0,0 +1,356 @@ +#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++; + } +} + +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 new file mode 100644 index 0000000..20880d9 --- /dev/null +++ b/Printers.h @@ -0,0 +1,127 @@ +/** + * 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()); +} + +// 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 diff --git a/XBee.cpp b/XBee.cpp index 20e60c7..f10cb27 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() { @@ -320,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; } } @@ -417,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 @@ -438,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; } } @@ -1139,6 +1176,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 @@ -1473,3 +1597,249 @@ void XBee::sendByte(uint8_t b, bool escape) { } } + +void XBeeWithCallbacks::loop() { + if (loopTop()) + loopBottom(); +} + +bool XBeeWithCallbacks::loopTop() { + readPacket(); + if (getResponse().isAvailable()) { + _onResponse.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()); +} + +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::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 + // 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 0; + 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 0; + 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 0; + 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 0; + 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 0; + 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 0; + 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 0; + 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 0; + 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 0; + 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 0; + 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 0; + 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 0; + break; + } + } + } + // Call regular callbacks + loopBottom(); + } + } while (millis() - start < timeout); + return XBEE_WAIT_TIMEOUT; +} + +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 ccbfc23..12c449e 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 @@ -116,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 @@ -216,6 +225,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 @@ -370,6 +384,8 @@ class ZBTxStatusResponse : public FrameIdResponse { uint8_t getDeliveryStatus(); uint8_t getDiscoveryStatus(); bool isSuccess(); + + static const uint8_t API_ID = ZB_TX_STATUS_RESPONSE; }; /** @@ -384,10 +400,33 @@ 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; }; +/** + * 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(); + + static const uint8_t API_ID = ZB_EXPLICIT_RX_RESPONSE; +}; + /** * Represents a Series 2 RX I/O Sample packet */ @@ -417,6 +456,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 @@ -430,6 +471,8 @@ class TxStatusResponse : public FrameIdResponse { TxStatusResponse(); uint8_t getStatus(); bool isSuccess(); + + static const uint8_t API_ID = TX_STATUS_RESPONSE; }; /** @@ -456,6 +499,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; }; @@ -468,6 +513,8 @@ class Rx64Response : public RxResponse { Rx64Response(); uint8_t getRssiOffset(); XBeeAddress64& getRemoteAddress64(); + + static const uint8_t API_ID = RX_64_RESPONSE; private: XBeeAddress64 _remoteAddress; }; @@ -503,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: }; @@ -512,6 +564,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 { @@ -519,6 +572,8 @@ class Rx64IoSampleResponse : public RxIoSampleBaseResponse { Rx64IoSampleResponse(); XBeeAddress64& getRemoteAddress64(); uint8_t getRssiOffset(); + + static const uint8_t API_ID = RX_64_IO_RESPONSE; private: XBeeAddress64 _remoteAddress; }; @@ -532,6 +587,8 @@ class ModemStatusResponse : public XBeeResponse { public: ModemStatusResponse(); uint8_t getStatus(); + + static const uint8_t API_ID = MODEM_STATUS_RESPONSE; }; /** @@ -562,6 +619,8 @@ class AtCommandResponse : public FrameIdResponse { * Returns true if status equals AT_OK */ bool isOk(); + + static const uint8_t API_ID = AT_COMMAND_RESPONSE; }; /** @@ -600,6 +659,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; }; @@ -743,6 +804,239 @@ 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). + * + * 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: + + /** + * 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 (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, 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. + */ + 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 0. If the function returns false, waiting + * continues. After the given timeout passes, this method + * 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 + * (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 + 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); + } + + /** + * 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, + * 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! + */ + 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 + * 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 + * 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; + 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; + 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; +}; + /** * All TX packets that support payloads extend this class */ @@ -757,6 +1051,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. */ @@ -879,13 +1182,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 /** 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(); +} + 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