Cat hungry.png
Здравствуйте! Собираем деньги на перевод материалов по электронике(https://www.allaboutcircuits.com/education/). Реквизиты указаны здесь.

Arduino:Примеры/ZdpScan

Материал из Онлайн справочника
Перейти к: навигация, поиск

Перевод: Максим Кузьмин (Cubewriter)
Перевел 2686 статей для сайта.

Контакты:

Проверка/Оформление/Редактирование: Мякишев Е.А.


Поиск узлов в XBee-сети[1]

Этот пример показывает, как при помощи библиотеки XBee настроить XBee-модуль типа Series 2 на поиск других узлов в XBee-сети. Всего может быть найдено не более 10 узлов. Каждый найденный узел можно изучить более подробно, запросив у него информацию о конечных точках, профилях и кластерах, которые он поддерживает.

Код

  1. /**
  2.  * Автор – Мэттхиджса Куиджмана (Matthijs Kooijman) 2015 год,
  3.  * все права защищены.
  4.  *
  5.  * Этот файл – часть библиотеки XBee-Arduino.
  6.  *
  7.  * Разрешение на использование библиотеки предоставляется бесплатно и
  8.  * любому, кто владеет копией этого файла, с правом делать что угодно
  9.  * безо всяких ограничений, включая копирование, модификацию и
  10.  * повторное распространение.
  11.  
  12.  * НИКАКИХ ГАРАНТИЙ НЕ ПРЕДОСТАВЛЯЕТСЯ.
  13.  */
  14.  
  15. #include <XBee.h>
  16. #include <Printers.h>
  17.  
  18. #include "zigbee.h"
  19.  
  20. /*
  21. Этот пример – для XBee-модуля типа Series 2.
  22.  
  23. Он находит до 10 узлов в сети ZigBee, в обратном порядке извлекая из всех роутеров таблицы о соседских роутерах, а затем передает эти данные по последовательной коммуникации и показывает в консоли.
  24.  
  25. Каждый узел можно выбрать и изучить более подробно, а затем сделать запрос (при помощи сообщений от ZigBee Device Profile), чтобы узнать дополнительную информацию о конечных точках, профилях и кластерах, которые этот узел поддерживает.
  26.  
  27. Этот пример рассчитан на Arduino с двумя последовательными портами (вроде Leonardo и Mega). Если нужно, замените Serial и Serial1 ниже на то, что соответствует вашему оборудованию.
  28. */
  29.  
  30. #if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
  31. #error Этот код рассчитан на числа в формате «от младшего к старшему»
  32. #endif
  33.  
  34. XBeeWithCallbacks xbee;
  35.  
  36. /** Вспомогательная функция, генерирующая идентификаторы для Zdo-транзакций
  37.  */
  38. uint8_t getNextTransactionId() {
  39.   static uint8_t id = 0;
  40.   return id++;
  41. }
  42.  
  43. #ifndef lengthof
  44. #define lengthof(x) (sizeof(x)/sizeof(*x))
  45. #endif
  46.  
  47. /**
  48.  * Вспомогательная функция, показывающая название поля, а после него –
  49.  * шестнадцатеричное значение и символ новой строки.
  50.  */
  51. template <typename T>
  52. static void printField(const __FlashStringHelper *prefix, T data);
  53. template <typename T>
  54. static void printField(const __FlashStringHelper *prefix, T data) {
  55.         Serial.print(prefix);
  56.         printHex(Serial, data);
  57.         Serial.println();
  58. }
  59.  
  60. void printActiveEndpoints(const zdo_active_ep_rsp_header_t *rsp) {
  61.   Serial.println(F("Active endpoints response"));  //  "Ответ с конечными точками"
  62.   printField(F("  About: 0x"), rsp->network_addr_le);  //  "О сети: 0x"
  63.   Serial.print(F("  Endpoints found: 0x"));  //  "  Найденные конечные точки: 0x"
  64.   printHex(Serial, rsp->endpoints, rsp->ep_count, F(", 0x"), NULL);
  65.   Serial.println();
  66. }
  67.  
  68. void printClusters(const __FlashStringHelper *prefix, uint16_t* clusters, uint8_t count) {
  69.   Serial.print(prefix);
  70.   for (uint8_t i = 0; i < count; ++i) {
  71.     if (i > 0) Serial.print(F(", "));
  72.     Serial.print(F("0x"));
  73.     printHex(Serial, ((uint16_t*)clusters)[i]);
  74.   }
  75.   if (!count) Serial.print(F("none"));
  76.  
  77.   Serial.println();
  78. }
  79.  
  80. void printSimpleDescriptor(zdo_simple_desc_resp_header_t *rsp) {
  81.   zdo_simple_desc_header_t *desc = (zdo_simple_desc_header_t*)((uint8_t*)rsp + sizeof(zdo_simple_desc_resp_header_t));
  82.   uint8_t *clusters = ((uint8_t*)desc + sizeof(zdo_simple_desc_header_t));
  83.  
  84.   Serial.println(F("Simple descriptor response"));    //  "Ответ с общим описателем"
  85.   printField(F("  About: 0x"), rsp->network_addr_le);  //  "  О сети: 0x"
  86.   printField(F("  Endpoint: 0x"), desc->endpoint);  //  "  Конечная точка: 0x"
  87.   printField(F("  Profile ID: 0x"), desc->profile_id_le);  //  "  ID профиля: 0x"
  88.   printField(F("  Device ID: 0x"), desc->device_id_le);  //  "  ID устройства: 0x"
  89.   printField(F("  Device Version: "), (uint8_t)(desc->device_version & 0xf));  //  "  Версия устройства: 0x"
  90.  
  91.   uint8_t ip_count = *clusters++;
  92.   printClusters(F("  Input clusters: "), (uint16_t*)clusters, ip_count);  //  "  Вводные кластеры: "
  93.   clusters += 2*ip_count;
  94.   uint8_t op_count = *clusters++;
  95.   printClusters(F("  Output clusters: "), (uint16_t*)clusters, op_count);  //  "  Выводные кластеры: "
  96. }
  97.  
  98. void toggle(XBeeAddress64& addr) {
  99.   uint8_t payload[] = {0x01, 0x00, 0x02};
  100.   ZBExplicitTxRequest tx(addr, 0xfffe, 0, 0, payload, sizeof(payload), 0, 9, 9, 0x0006, 0x0104) ;
  101.   tx.setFrameId(xbee.getNextFrameId());
  102.   xbee.send(tx);
  103. }
  104.  
  105. /* Функция, сравнивающая ответы с Zdo-запросами. Ее результат
  106.  * передается функции waitFor(). Передаваемые данные – это
  107.  * идентификатор (ID) Zdo-транзакции, который использовался для
  108.  * запроса и будет использован, чтобы выбрать правильный ответ.
  109.  */
  110. bool matchZdoReply(ZBExplicitRxResponse& rx, uintptr_t data) {
  111.   uint8_t *payload = rx.getFrameData() + rx.getDataOffset();
  112.   uint8_t transactionId = (uint8_t)data;
  113.  
  114.   return rx.getSrcEndpoint() == 0 &&
  115.          rx.getDstEndpoint() == 0 &&
  116.          rx.getProfileId() == WPAN_PROFILE_ZDO &&
  117.          payload[0] == transactionId;
  118. }
  119.  
  120. /**
  121.  * Создаем TX-запрос, чтобы отправить Zdo-запрос.
  122.  */
  123. ZBExplicitTxRequest buildZdoRequest(XBeeAddress64 addr, uint16_t cluster_id, uint8_t *payload, size_t len) {
  124.   ZBExplicitTxRequest tx(addr, payload, len);
  125.   tx.setSrcEndpoint(WPAN_ENDPOINT_ZDO);
  126.   tx.setDstEndpoint(WPAN_ENDPOINT_ZDO);
  127.   tx.setClusterId(cluster_id);
  128.   tx.setProfileId(WPAN_PROFILE_ZDO);
  129.   tx.setFrameId(xbee.getNextFrameId());
  130.   return tx;
  131. }
  132.  
  133. /**
  134.  * Создаем Zdo-запрос, отправляем его и ждем ответа (который будет
  135.  * сохранен в указанный объект). Возвращаем «true», если ответ был
  136.  * получен, или «false», если что-то пошло не так (сообщение об ошибке
  137.  * уже было показано).
  138.  */
  139. bool handleZdoRequest(const __FlashStringHelper *msg, ZBExplicitRxResponse& rx, XBeeAddress64 addr, uint16_t cluster_id, uint8_t *payload, size_t len) {
  140.   ZBExplicitTxRequest tx = buildZdoRequest(addr, cluster_id, (uint8_t*)payload, len);
  141.   xbee.send(tx);
  142.  
  143.   uint8_t transaction_id = payload[0];
  144.   // Здесь ждем 5000 миллисекунд, поскольку TX-таймаут по умолчанию
  145.   // составляет 4,8 секунды (NH = 1,6 секунды, помноженное на три
  146.   // попытки):
  147.   uint8_t status = xbee.waitFor(rx, 5000, matchZdoReply, transaction_id, tx.getFrameId());
  148.   switch(status) {
  149.     case 0: // Успешно
  150.       return true;
  151.     case XBEE_WAIT_TIMEOUT:
  152.       Serial.print(F("No reply received from 0x"));  //  "Не получено ответа от 0x"
  153.       printHex(Serial, addr.getMsb());
  154.       printHex(Serial, addr.getLsb());
  155.       Serial.print(F(" while "));
  156.       Serial.print(msg);
  157.       Serial.println(F("."));
  158.       return false;
  159.     default:
  160.       Serial.print(F("Failed to send to 0x"));  //  "Не удалось отправить на 0x"
  161.       printHex(Serial, addr.getMsb());
  162.       printHex(Serial, addr.getLsb());
  163.       Serial.print(F(" while "));
  164.       Serial.print(msg);
  165.       Serial.print(F(". Status: 0x"));  //  ". Статус: 0x"
  166.       printHex(Serial, status);
  167.       Serial.println();
  168.       return false;
  169.   }
  170. }
  171.  
  172. /**
  173.  * Запрашиваем список активных конечных точек у узла с указанным
  174.  * адресом. Отображаем найденные конечные точки, запрашиваем по ним
  175.  * дополнительную информацию, а затем показываем ее.
  176.  */
  177. void get_active_endpoints(XBeeAddress64& addr, uint16_t addr16) {
  178.   zdo_active_ep_req_t payload = {
  179.     .transaction = getNextTransactionId(),
  180.     .network_addr_le = addr16,
  181.   };
  182.   printField(F("Discovering services on 0x"), addr16);  //  "Поиск сервисов на 0x"
  183.  
  184.   ZBExplicitRxResponse rx;
  185.   if (!handleZdoRequest(F("requesting active endpoints"),  //  "Запрос активных конечных точек"
  186.                         rx, addr, ZDO_ACTIVE_EP_REQ,
  187.                         (uint8_t*)&payload, sizeof(payload)))
  188.     return;
  189.  
  190.   zdo_active_ep_rsp_header_t *rsp = (zdo_active_ep_rsp_header_t*)(rx.getFrameData() + rx.getDataOffset());
  191.  
  192.   if (rsp->status) {
  193.     printField(F("Active endpoints request rejected. Status: 0x"), rsp->status);  //  "Запрос активных конечных точек отклонен. Статус: 0x"
  194.     return;
  195.   }
  196.  
  197.   printActiveEndpoints(rsp);
  198.  
  199.   // Копируем список конечных точек, потому что описатель ниже удалит
  200.   // данные в rx / rsp:
  201.   uint8_t endpoints[rsp->ep_count];
  202.   memcpy(endpoints, rsp->endpoints, sizeof(endpoints));
  203.  
  204.   // Запрашиваем общий описатель для каждой конечной точки:
  205.   for (uint8_t i = 0; i < sizeof(endpoints); ++i)
  206.     get_simple_descriptor(addr, addr16, endpoints[i]);
  207. }
  208.  
  209. void get_simple_descriptor(XBeeAddress64& addr, uint16_t addr16, uint8_t endpoint) {
  210.   zdo_simple_desc_req_t payload = {
  211.     .transaction = getNextTransactionId(),
  212.     .network_addr_le = addr16,
  213.     .endpoint = endpoint,
  214.   };
  215.  
  216.   ZBExplicitRxResponse rx;
  217.   if (!handleZdoRequest(F("requesting simple descriptor"),
  218.                         rx, addr, ZDO_SIMPLE_DESC_REQ,
  219.                         (uint8_t*)&payload, sizeof(payload)))  //  "Запрос общего описателя"
  220.     return;
  221.  
  222.   zdo_simple_desc_resp_header_t *rsp = (zdo_simple_desc_resp_header_t*)(rx.getFrameData() + rx.getDataOffset());
  223.  
  224.   if (rsp->status) {
  225.     printField(F("Failed to fetch simple descriptor. Status: 0x"), rsp->status);  //  "Не удалось извлечь общий описатель. Статус: 0x"
  226.     return;
  227.   }
  228.  
  229.   printSimpleDescriptor(rsp);
  230. }
  231.  
  232. bool getAtValue(uint8_t cmd[2], uint8_t *buf, size_t len, uint16_t timeout = 150) {
  233.   AtCommandRequest req(cmd);
  234.   req.setFrameId(xbee.getNextFrameId());
  235.   uint8_t status = xbee.sendAndWait(req, timeout);
  236.   if (status != 0) {
  237.     Serial.print(F("Failed to read "));  //  "Не удалось прочесть "
  238.     Serial.write(cmd, 2);
  239.     Serial.print(F(" command. Status: 0x"));  //  " команду. Статус: 0x"
  240.     Serial.println(status, HEX);
  241.     return false;
  242.   }
  243.  
  244.   AtCommandResponse response;
  245.   xbee.getResponse().getAtCommandResponse(response);
  246.   if (response.getValueLength() != len) {
  247.     Serial.print(F("Unexpected response length in "));  //  "Непредвиденная длина ответа в"
  248.     Serial.write(cmd, 2);
  249.     Serial.println(F(" response"));   //  " ответе"
  250.     return false;
  251.   }
  252.  
  253.   memcpy(buf, response.getValue(), len);
  254.   return true;
  255. }
  256.  
  257. // Инвертируем формат (имеется в виду порядок байтов) указанного буфера.
  258. void invertEndian(uint8_t *buf, size_t len) {
  259.   for (uint8_t i = 0, j = len - 1; i < len/2; ++i, j--) {
  260.     uint8_t tmp = buf[i];
  261.     buf[i] = buf[j];
  262.     buf[j] = tmp;
  263.   }
  264. }
  265.  
  266. /**
  267.  * Создаем struct, чтобы сохранить информацию о найденных узлах.
  268.  */
  269. struct node_info {
  270.   XBeeAddress64 addr64;
  271.   uint16_t addr16;
  272.   uint8_t type: 2;
  273.   uint8_t visited: 1;
  274. };
  275.  
  276. /**
  277.  * Список найденных узлов.
  278.  */
  279. node_info nodes[10];
  280. uint8_t nodes_found = 0;
  281.  
  282. /**
  283.  * Сканируем сеть и находим все остальные узлы при помощи извлечения
  284.  * этих данных из таблиц о соседских узлах. Найденные узлы сохраняем в
  285.  * массиве для узлов.
  286.  */
  287. void scan_network() {
  288.   Serial.println();
  289.   Serial.println("Discovering devices");  //  "Поиск устройств"
  290.   // Извлекаем ID действующей персональной сети (PAN ID), чтобы
  291.   // отфильтровать результаты из таблиц о соседских узлах.
  292.   uint8_t pan_id[8];
  293.   getAtValue((uint8_t*)"OP", pan_id, sizeof(pan_id));
  294.   // XBee-модуль отправляет данные в формате «от старшего байта
  295.   // к младшему», а ZDO-запросы используют формат «от младшего
  296.   // к старшему». Чтобы было проще сравнивать, конвертируем
  297.   // в «от младшему к старшему».
  298.   invertEndian(pan_id, sizeof(pan_id));
  299.  
  300.   // Извлекаем адрес локального узла:
  301.   XBeeAddress64 local;
  302.   uint8_t shbuf[4], slbuf[4], mybuf[2];
  303.   if (!getAtValue((uint8_t*)"SH", shbuf, sizeof(shbuf)) ||
  304.       !getAtValue((uint8_t*)"SL", slbuf, sizeof(slbuf)) ||
  305.       !getAtValue((uint8_t*)"MY", mybuf, sizeof(mybuf)))
  306.     return;
  307.  
  308.   nodes[0].addr64.setMsb((uint32_t)shbuf[0] << 24 | (uint32_t)shbuf[1] << 16 | (uint32_t)shbuf[2] << 8 | shbuf[3]);
  309.   nodes[0].addr64.setLsb((uint32_t)slbuf[0] << 24 | (uint32_t)slbuf[1] << 16 | (uint32_t)slbuf[2] << 8 | slbuf[3]);
  310.   nodes[0].addr16 = (uint16_t)mybuf[0] << 8 | mybuf[1];
  311.   nodes[0].type = ZDO_MGMT_LQI_REQ_TYPE_UNKNOWN;
  312.   nodes[0].visited = false;
  313.   nodes_found = 1;
  314.  
  315.   Serial.print(F("0) 0x"));
  316.   printHex(Serial, nodes[0].addr64);
  317.   Serial.print(F(" (0x"));
  318.   printHex(Serial, nodes[0].addr16);
  319.   Serial.println(F(", Self)"));
  320.  
  321.   // В nodes[0] теперь содержится наш собственный адрес, а остальное
  322.   //  – неверно. Мы изучаем сеть, запрашивая таблицы с данными о
  323.   // соседских узлах. Сначала притворяемся, будто отправляем пакет
  324.   // самим себе – прошивка XBee обрабатывает его, притворяясь, что
  325.   // ответ было получен (с одной оговоркой: по всей видимости, ответ
  326.   // приходит раньше TX-статуса).
  327.   uint8_t next = 0;
  328.   do {
  329.     // Запрашиваем у первого узла таблицу с данными о соседних узлах:
  330.     zdo_mgmt_lqi_req_t payload = {
  331.       .transaction = getNextTransactionId(),
  332.       .start_index = 0,
  333.     };
  334.  
  335.     do {
  336.       ZBExplicitRxResponse rx;
  337.       if (!handleZdoRequest(F("requesting LQI/neighbour table"),  //  "Запрос таблицы с данными о соседних узлах"
  338.                             rx, nodes[next].addr64, ZDO_MGMT_LQI_REQ,
  339.                             (uint8_t*)&payload, sizeof(payload)))
  340.         break;
  341.  
  342.       zdo_mgmt_lqi_rsp_t *rsp = (zdo_mgmt_lqi_rsp_t*)(rx.getFrameData() + rx.getDataOffset());
  343.       if (rsp->status != 0) {
  344.         if (rsp->status != ZDO_STATUS_NOT_SUPPORTED) {
  345.           Serial.print(F("LQI query rejected by 0x"));  //  "Запрос таблицы с данными о соседних узлах отклонен со стороны 0x"
  346.           printHex(Serial, nodes[next].addr16);
  347.           Serial.print(F(". Status: 0x"));  //  ". Статус: 0x"
  348.           printHex(Serial, rsp->status);
  349.           Serial.println();
  350.         }
  351.         break;
  352.       }
  353.  
  354.       if (rsp->start_index != payload.start_index) {
  355.         Serial.println(F("Unexpected start_index, skipping this node"));  //  "Непредвиденный start_index, пропускаем этот узел"
  356.         break;
  357.       }
  358.  
  359.       for (uint8_t i = 0; i < rsp->list_count; ++i) {
  360.         zdo_mgmt_lqi_entry_t *e = &rsp->entries[i];
  361.         node_info *n = &nodes[nodes_found];
  362.  
  363.         if (memcmp(&e->extended_pan_id_le, &pan_id, sizeof(pan_id)) != 0) {
  364.           Serial.println(F("Ignoring node in other PAN"));  //  "Игнорируем узел в другой PAN"
  365.           continue;
  366.         }
  367.  
  368.         // Если мы уже знаем об этом узле, пропускаем его:
  369.         uint8_t dup;
  370.         for (dup = 0; dup < nodes_found; ++dup) {
  371.           if (nodes[dup].addr16 == e->nwk_addr_le)
  372.             break;
  373.         }
  374.         if (dup != nodes_found)
  375.           continue;
  376.  
  377.         n->addr64.setMsb(e->extended_addr_le >> 32);
  378.         n->addr64.setLsb(e->extended_addr_le);
  379.         n->addr16 = e->nwk_addr_le;
  380.         n->type = e->flags0 & 0x3;
  381.  
  382.         Serial.print(nodes_found);
  383.         Serial.print(F(") 0x"));
  384.         printHex(Serial, n->addr64);
  385.         Serial.print(F(" (0x"));
  386.         printHex(Serial, n->addr16);
  387.         switch (n->type) {
  388.           case ZDO_MGMT_LQI_REQ_TYPE_COORDINATOR:
  389.             Serial.println(F(", Coordinator)"));  //  ", Координатор"
  390.             break;
  391.           case ZDO_MGMT_LQI_REQ_TYPE_ROUTER:
  392.             Serial.println(F(", Router)"));  //  ", Роутер"
  393.             break;
  394.           case ZDO_MGMT_LQI_REQ_TYPE_ENDDEVICE:
  395.             Serial.println(F(", End device)"));  //  ", Конечное устройство"
  396.             break;
  397.           case ZDO_MGMT_LQI_REQ_TYPE_UNKNOWN:
  398.             Serial.println(F(", Unknown)"));  //  ", Неизвестно"
  399.             break;
  400.         }
  401.         nodes_found++;
  402.  
  403.         if (nodes_found == lengthof(nodes)) {
  404.           Serial.println(F("Device table full, terminating network scan"));  //  "Таблица устройств заполнена, завершаем сканирование сети"
  405.           return;
  406.         }
  407.       }
  408.  
  409.       // Получили данные обо всех доступных «соседях»? Заканчиваем.
  410.       if (rsp->start_index + rsp->list_count >= rsp->table_entries)
  411.         break;
  412.       // Еще остались? Запускаем еще один цикл поиска.
  413.       payload.start_index += rsp->list_count;
  414.       payload.transaction = getNextTransactionId();
  415.     } while (true);
  416.  
  417.     // Заканчиваем с этим узлом, переходим к следующему:
  418.     nodes[next].visited = true;
  419.     ++next;
  420.   } while (next < nodes_found);
  421.   Serial.println(F("Finished scanning"));  //  "Завершение сканирования"
  422.   Serial.println(F("Press a number to scan that node, or press r to rescan the network"));  //  "Введите число, чтобы просканировать узел
  423.                         //  или нажмите «r», чтобы начать новое
  424.                         //  сканирование сети"
  425. }
  426.  
  427. void setup() {
  428.   Serial.begin(9600);
  429.  
  430.   Serial1.begin(9600);
  431.   xbee.setSerial(Serial1);
  432.  
  433.   xbee.onPacketError(printErrorCb, (uintptr_t)(Print*)&Serial);
  434.  
  435.   // Задаем AO=1, чтобы получать явные RX-фреймы. Поскольку значение
  436.   // AO не записывается командой «WR» на flash-память, то после
  437.   // перезагрузке должно остаться в сохранности.
  438.   uint8_t value = 1;
  439.   AtCommandRequest req((uint8_t*)"AO", &value, sizeof(value));
  440.   req.setFrameId(xbee.getNextFrameId());
  441.   uint8_t status = xbee.sendAndWait(req, 150);
  442.   if (status == 0)
  443.     Serial.println(F("Set AO=1"));  //  "Выставляем AO=1"
  444.   else
  445.     Serial.println(F("Failed to set AO, expect problems"));  //  "Не удалось выставить AO=1, могут возникнуть проблемы"
  446.  
  447.   scan_network();
  448. }
  449.  
  450.  
  451. void loop() {
  452.   // Считываем информацию из последовательной коммуникации, чтобы
  453.   // узнать, выбран ли узел. Если выбран, запускаем сканирование этого
  454.   // узла, а затем новое сканирование сети.
  455.   if (Serial.available()) {
  456.     uint8_t c = Serial.read();
  457.     if (c >= '0' && c <= '9') {
  458.       int n = c - '0';
  459.       if (n < nodes_found) {
  460.         get_active_endpoints(nodes[n].addr64, nodes[n].addr16);
  461.         scan_network();
  462.       }
  463.     } else if (c == 'r') {
  464.       scan_network();
  465.     }
  466.   }
  467.  
  468.   xbee.loop();
  469. }

См.также

Внешние ссылки

  1. github.com - ZdpScan.ino