Arduino:Примеры/ZdpScan

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

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


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

См.также

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