Arduino:Примеры/ZdpScan
Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску
Содержание | Знакомство с Arduino | Продукты | Основы | Справочник языка Arduino | Примеры | Библиотеки | Хакинг | Изменения | Сравнение языков Arduino и Processing |
Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.
Поиск узлов в XBee-сети[1]
Этот пример показывает, как при помощи библиотеки XBee настроить XBee-модуль типа Series 2 на поиск других узлов в XBee-сети. Всего может быть найдено не более 10 узлов. Каждый найденный узел можно изучить более подробно, запросив у него информацию о конечных точках, профилях и кластерах, которые он поддерживает.
Код
/**
* Автор – Мэттхиджса Куиджмана (Matthijs Kooijman) 2015 год,
* все права защищены.
*
* Этот файл – часть библиотеки XBee-Arduino.
*
* Разрешение на использование библиотеки предоставляется бесплатно и
* любому, кто владеет копией этого файла, с правом делать что угодно
* безо всяких ограничений, включая копирование, модификацию и
* повторное распространение.
* НИКАКИХ ГАРАНТИЙ НЕ ПРЕДОСТАВЛЯЕТСЯ.
*/
#include <XBee.h>
#include <Printers.h>
#include "zigbee.h"
/*
Этот пример – для XBee-модуля типа Series 2.
Он находит до 10 узлов в сети ZigBee, в обратном порядке извлекая из всех роутеров таблицы о соседских роутерах, а затем передает эти данные по последовательной коммуникации и показывает в консоли.
Каждый узел можно выбрать и изучить более подробно, а затем сделать запрос (при помощи сообщений от ZigBee Device Profile), чтобы узнать дополнительную информацию о конечных точках, профилях и кластерах, которые этот узел поддерживает.
Этот пример рассчитан на Arduino с двумя последовательными портами (вроде Leonardo и Mega). Если нужно, замените Serial и Serial1 ниже на то, что соответствует вашему оборудованию.
*/
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
#error Этот код рассчитан на числа в формате «от младшего к старшему»
#endif
XBeeWithCallbacks xbee;
/** Вспомогательная функция, генерирующая идентификаторы для Zdo-транзакций
*/
uint8_t getNextTransactionId() {
static uint8_t id = 0;
return id++;
}
#ifndef lengthof
#define lengthof(x) (sizeof(x)/sizeof(*x))
#endif
/**
* Вспомогательная функция, показывающая название поля, а после него –
* шестнадцатеричное значение и символ новой строки.
*/
template <typename T>
static void printField(const __FlashStringHelper *prefix, T data);
template <typename T>
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); // "О сети: 0x"
Serial.print(F(" Endpoints found: 0x")); // " Найденные конечные точки: 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); // " О сети: 0x"
printField(F(" Endpoint: 0x"), desc->endpoint); // " Конечная точка: 0x"
printField(F(" Profile ID: 0x"), desc->profile_id_le); // " ID профиля: 0x"
printField(F(" Device ID: 0x"), desc->device_id_le); // " ID устройства: 0x"
printField(F(" Device Version: "), (uint8_t)(desc->device_version & 0xf)); // " Версия устройства: 0x"
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);
}
/* Функция, сравнивающая ответы с Zdo-запросами. Ее результат
* передается функции waitFor(). Передаваемые данные – это
* идентификатор (ID) Zdo-транзакции, который использовался для
* запроса и будет использован, чтобы выбрать правильный ответ.
*/
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;
}
/**
* Создаем TX-запрос, чтобы отправить Zdo-запрос.
*/
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;
}
/**
* Создаем Zdo-запрос, отправляем его и ждем ответа (который будет
* сохранен в указанный объект). Возвращаем «true», если ответ был
* получен, или «false», если что-то пошло не так (сообщение об ошибке
* уже было показано).
*/
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];
// Здесь ждем 5000 миллисекунд, поскольку TX-таймаут по умолчанию
// составляет 4,8 секунды (NH = 1,6 секунды, помноженное на три
// попытки):
uint8_t status = xbee.waitFor(rx, 5000, matchZdoReply, transaction_id, tx.getFrameId());
switch(status) {
case 0: // Успешно
return true;
case XBEE_WAIT_TIMEOUT:
Serial.print(F("No reply received from 0x")); // "Не получено ответа от 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")); // "Не удалось отправить на 0x"
printHex(Serial, addr.getMsb());
printHex(Serial, addr.getLsb());
Serial.print(F(" while "));
Serial.print(msg);
Serial.print(F(". Status: 0x")); // ". Статус: 0x"
printHex(Serial, status);
Serial.println();
return false;
}
}
/**
* Запрашиваем список активных конечных точек у узла с указанным
* адресом. Отображаем найденные конечные точки, запрашиваем по ним
* дополнительную информацию, а затем показываем ее.
*/
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); // "Поиск сервисов на 0x"
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); // "Запрос активных конечных точек отклонен. Статус: 0x"
return;
}
printActiveEndpoints(rsp);
// Копируем список конечных точек, потому что описатель ниже удалит
// данные в rx / rsp:
uint8_t endpoints[rsp->ep_count];
memcpy(endpoints, rsp->endpoints, sizeof(endpoints));
// Запрашиваем общий описатель для каждой конечной точки:
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); // "Не удалось извлечь общий описатель. Статус: 0x"
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")); // " команду. Статус: 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;
}
// Инвертируем формат (имеется в виду порядок байтов) указанного буфера.
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, чтобы сохранить информацию о найденных узлах.
*/
struct node_info {
XBeeAddress64 addr64;
uint16_t addr16;
uint8_t type: 2;
uint8_t visited: 1;
};
/**
* Список найденных узлов.
*/
node_info nodes[10];
uint8_t nodes_found = 0;
/**
* Сканируем сеть и находим все остальные узлы при помощи извлечения
* этих данных из таблиц о соседских узлах. Найденные узлы сохраняем в
* массиве для узлов.
*/
void scan_network() {
Serial.println();
Serial.println("Discovering devices"); // "Поиск устройств"
// Извлекаем ID действующей персональной сети (PAN ID), чтобы
// отфильтровать результаты из таблиц о соседских узлах.
uint8_t pan_id[8];
getAtValue((uint8_t*)"OP", pan_id, sizeof(pan_id));
// XBee-модуль отправляет данные в формате «от старшего байта
// к младшему», а ZDO-запросы используют формат «от младшего
// к старшему». Чтобы было проще сравнивать, конвертируем
// в «от младшему к старшему».
invertEndian(pan_id, sizeof(pan_id));
// Извлекаем адрес локального узла:
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] теперь содержится наш собственный адрес, а остальное
// – неверно. Мы изучаем сеть, запрашивая таблицы с данными о
// соседских узлах. Сначала притворяемся, будто отправляем пакет
// самим себе – прошивка XBee обрабатывает его, притворяясь, что
// ответ было получен (с одной оговоркой: по всей видимости, ответ
// приходит раньше TX-статуса).
uint8_t next = 0;
do {
// Запрашиваем у первого узла таблицу с данными о соседних узлах:
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")); // "Запрос таблицы с данными о соседних узлах отклонен со стороны 0x"
printHex(Serial, nodes[next].addr16);
Serial.print(F(". Status: 0x")); // ". Статус: 0x"
printHex(Serial, rsp->status);
Serial.println();
}
break;
}
if (rsp->start_index != payload.start_index) {
Serial.println(F("Unexpected start_index, skipping this node")); // "Непредвиденный start_index, пропускаем этот узел"
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")); // "Игнорируем узел в другой PAN"
continue;
}
// Если мы уже знаем об этом узле, пропускаем его:
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;
}
}
// Получили данные обо всех доступных «соседях»? Заканчиваем.
if (rsp->start_index + rsp->list_count >= rsp->table_entries)
break;
// Еще остались? Запускаем еще один цикл поиска.
payload.start_index += rsp->list_count;
payload.transaction = getNextTransactionId();
} while (true);
// Заканчиваем с этим узлом, переходим к следующему:
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")); // "Введите число, чтобы просканировать узел
// или нажмите «r», чтобы начать новое
// сканирование сети"
}
void setup() {
Serial.begin(9600);
Serial1.begin(9600);
xbee.setSerial(Serial1);
xbee.onPacketError(printErrorCb, (uintptr_t)(Print*)&Serial);
// Задаем AO=1, чтобы получать явные RX-фреймы. Поскольку значение
// AO не записывается командой «WR» на flash-память, то после
// перезагрузке должно остаться в сохранности.
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")); // "Выставляем AO=1"
else
Serial.println(F("Failed to set AO, expect problems")); // "Не удалось выставить AO=1, могут возникнуть проблемы"
scan_network();
}
void loop() {
// Считываем информацию из последовательной коммуникации, чтобы
// узнать, выбран ли узел. Если выбран, запускаем сканирование этого
// узла, а затем новое сканирование сети.
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();
}
См.также
Внешние ссылки
Arduino продукты | |
---|---|
Начальный уровень | Arduino Uno • Arduino Leonardo • Arduino 101 • Arduino Robot • Arduino Esplora • Arduino Micro • Arduino Nano • Arduino Mini • Arduino Starter Kit • Arduino Basic Kit • MKR2UNO • TFT-дисплей Arduino |
Продвинутые функции | Arduino Mega 2560 • Arduino Zero • Arduino Due • Arduino Mega ADK • Arduino Pro • Arduino Motor Shield • Arduino USB Host Shield • Arduino Proto Shield • MKR Proto Shield • MKR Proto Large Shield • Arduino ISP • Arduino USB 2 Serial Micro • Arduino Mini USB Serial Adapter |
Интернет вещей | Arduino Yun • Arduino Ethernet • Arduino MKR1000 • Arduino WiFi 101 Shield • Arduino GSM Shield V2 • Arduino WiFi Shield • Arduino Wireless SD Shield • Arduino Wireless Proto Shield • Arduino Ethernet Shield V2 • Arduino Yun Shield • Arduino MKR1000 Bundle |
Носимые устройства | Arduino Gemma • Lilypad Arduino Simple • Lilypad Arduino Main Board • Lilypad Arduino USB • LilyPad Arduino SimpleSnap |
3D-печать | Arduino Materia 101 |
Устаревшие устройства | - |
Примеры Arduino | |
---|---|
Стандартные функции | |
Основы |
|
Цифровой сигнал |
|
Аналоговый сигнал |
|
Связь |
|
Управляющие структуры |
|
Датчики |
|
Дисплей |
Примеры, объясняющие основы управления дисплеем:
|
Строки |
|
USB (для Leonardo, Micro и Due плат) |
В этой секции имеют место примеры, которые демонстрируют использование библиотек, уникальных для плат Leonardo, Micro и Due.
|
Клавиатура |
|
Мышь |
|
Разное |
Категории:
- Страницы, использующие повторяющиеся аргументы в вызовах шаблонов
- Справочник языка Arduino
- Arduino
- Перевод от Сubewriter
- Проверка:myagkij
- Оформление:myagkij
- Редактирование:myagkij
- Страницы, где используется шаблон "Навигационная таблица/Телепорт"
- Страницы с телепортом
- Пример
- Примеры
- Пример программирования Arduino
- Примеры программирования Arduino