Arduino:Примеры/ZdpScan

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

Перевод: Максим Кузьмин (Cubewriter) Перевел 364226 статей для сайта.</br>Контакты:</br>* Skype: cubewriter</br>* E-mail: cubewriter@gmail.com</br>* Максим Кузьмин на freelance.ru
Проверка/Оформление/Редактирование: Мякишев Е.А.


Поиск узлов в 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 }

См.также

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