Espruino:Примеры/BLE-коммуникация

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

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


BLE-коммуникация[1]

В других руководствах (вроде вот этого, где объясняется, как выводить на дисплей Pixl.js данные, считываемые температурным датчиком) мы используем для коммуникации рассылку Bluetooth-объявлений. Этот тип коммуникации хорошо подходит для проектов, где одно устройство коммуницирует со множеством других устройств, но часто проекты не такие масштабные и в них требуется, чтобы просто два устройства коммуницировали друг с другом.

В этом руководстве мы расскажем о том, как при помощи Espruino-устройства создать подключение к другому устройству. Кроме того, благодаря NRF.setConnectionInterval(), с помощью которой задаётся интервал подключения, эту коммуникацию можно сделать очень энергоэффективной (по умолчанию в Espruino 2v00 и новее используется энергоэффективный интервал, но когда по этому каналу связи начинается приём/передача данных, автоматически включается самая быстрая скорость).

Примечание

Для отправителя рассылка Bluetooth-объявлений очень энергоэффективна, но вот получатель должен постоянно прослушивать пакеты, что очень требовательно к энергопотреблению. Так что смело используйте этот тип коммуникации, если вам надо, чтобы получатель как можно быстрее израсходовал свою батарею.

В этом примере мы воспользуемся интерфейсной платой MDBT42Q, подключенной к серводвигателю. Коммуницировать можно несколькими разными способами:

  • При помощи отправки команд в виде JavaScript-кода
  • При помощи отправки команд на кастомную BLE-характеристику
  • При помощи передачи потока данных на кастомную BLE-характеристику

Более подробно читайте ниже (для лучшей навигации мы всё разбили по заголовкам). В видео ниже рассказывается об отправке команд, а в разделе «Передача потока данных» ниже рассказывается, соответственно, о том, как передавать данные потоком.

Bluetooth LE on Espruino, Part 1

Отправка команд (в виде JavaScript-кода)

Получение (периферийное Bluetooth-устройство)

Этот код нужен лишь для управления серводвигателем. В нём мы отправляем функцию flag(), содержащую все необходимые нам действия.

var s = require("servo").connect(D14);
s.move(1,3000); // переместить на позицию 0 через 3 секунды

var timeout;
function flag() {
  if (timeout) clearTimeout();
  s.move(0.2,2000);
  timeout = setTimeout(function() {
    timeout = undefined;
    s.move(1,2000);
  },2000);
}

setWatch(flag, BTN, {repeat:true});

Отправка (центральное Bluetooth-устройство)

Чтобы отправить команду так, будто мы печатаем её в REPL, мы просто воспользуемся модулем «ble_simple_uart». Помните, что для выполнения этих команд нужно будет также указать символ новой строки – это будет аналогично нажатию  ↵ Enter  в левой части IDE Espruino.

function flag() {
  NRF.requestDevice({ filters: [{ namePrefix: 'MDBT42Q' }]
                    }).then(function(device) {
    require("ble_simple_uart").write(
          device,
          "flag()\n",
          function() {
            print('Готово!');
          });
  });
}

setWatch(flag, BTN, {repeat:true});
Примечание

По умолчанию Espruino считает, что команды отправляются из REPL, и поэтому шлёт обратно все отправленные ей символы (плюс результат выполнения команды). Как правило, это нежелательно, так как замедляет коммуникацию. О том, как отключить это эхо-отображение, читайте в третьем абзаце этой статьи.

Отправка команд (кастомный Bluetooth-сервис)

Получение (периферийное Bluetooth-устройство)

Мы также можем создать более «нормальное» Bluetooth-устройство, создав собственный Bluetooth-сервис, а также отключить REPL (которую вы, возможно, использовали ранее) при помощи настройки {uart:false} в функции setServices().

Вы всегда должны использовать собственный уникальный UUID – его можно создать при помощи UUID-генератора (который можно найти онлайн) или, если у вас Linux, вписав в терминал date | md5sum. Затем сохраните этот UUID и поменяйте в нём символы с 5-го по 8-ой на «0001», «0002» и так далее в каждом новом сервисе или характеристике, которые вам понадобятся. Для BLE это эффективный подход, потому что так мы отправляем 128-битный UUID всего один раз.

// Тот же код, что и прежде:
var s = require("servo").connect(D14);
s.move(1,3000); // переместить на позицию 0 через 3 секунды

var timeout;
function flag() {
  if (timeout) clearTimeout();
  s.move(0.2,2000);
  timeout = setTimeout(function() {
    timeout = undefined;
    s.move(1,2000);
  },2000);
}

setWatch(flag, BTN, {repeat:true});

// Новый код, где мы задаем сервис и характеристику:
NRF.setServices({
  "3e440001-f5bb-357d-719d-179272e4d4d9": {
    "3e440002-f5bb-357d-719d-179272e4d4d9": {
      value : [0],
      maxLen : 1,
      writable : true,
      onWrite : function(evt) {
        // Записав значение в характеристику, вызываем flag():
        flag();
      }
    }
  }
}, { uart : false });
// Меняем рассылаемое в объявлениях название:
NRF.setAdvertising({}, {name:"Flag"});

Отправка (центральное Bluetooth-устройство)

Код для Puck.js чуть посложнее, чем нужно, потому что также вылавливает ошибки и одновременно управляется сразу с несколькими вызовами.

Но базовую часть, где используются requestDevice(), connect(), getPrimaryService(), getCharacteristic() и writeValue(), можно использовать и в Web Bluetooth.

var busy = false;

function flag() {
  if (busy) {
    digitalPulse(LED1,1,[10,200,10,200,10]);
    return;
  }
  busy = true;
  var gatt;
  NRF.requestDevice({ filters: [{ name: 'Flag' }] })
  .then(function(device) {
    console.log("Нашли");
    digitalPulse(LED2,1,10);
    return device.gatt.connect();
  }).then(function(g) {
    console.log("Подключились");
    digitalPulse(LED3,1,10);
    gatt = g;
    return gatt.getPrimaryService(
        "3e440001-f5bb-357d-719d-179272e4d4d9");
  }).then(function(service) {
    return service.getCharacteristic(
        "3e440002-f5bb-357d-719d-179272e4d4d9");
  }).then(function(characteristic) {
    return characteristic.writeValue(1);
  }).then(function() {
    digitalPulse(LED2,1,[10,200,10,200,10]);
    gatt.disconnect();
    console.log("Готово!");
    busy=false;
  }).catch(function(e) {
    digitalPulse(LED1,1,10);
    console.log("ERROR",e);
    busy=false;
  });
}

setWatch(flag, BTN, {repeat:true});

NRF.setServices({}, { uart : false });
NRF.setAdvertising({}, {showName:false, connectable:false, discoverable:false});
Примечание

О том, как повысить скорость соединения при помощи запоминания характеристик, смотрите в видео.

Передача потока данных

В этой ситуации мы отправляем на Espruino не команду, а поток данных.

Bluetooth LE on Espruino, Part 2

Получение (периферийное Bluetooth-устройство)

В коде получателя мы задаём сервисы (как и в прошлом примере), но также дополнительно используем данные, которые были присланы в событии onWrite().

NRF.setServices({
  "3e440001-f5bb-357d-719d-179272e4d4d9": {
    "3e440002-f5bb-357d-719d-179272e4d4d9": {
      value : [0],
      maxLen : 1,
      writable : true,
      onWrite : function(evt) {
        // Данные пришли в виде байта.
        // Преобразовываем их в число в диапазоне между 0 и 1:
        var n = evt.data[0] / 255;
        // Отправляем данные напрямую серводвигателю как ШИМ:
        analogWrite(D14, (1.2 + n*0.8)/20, {freq:50});
      }
    }
  }
}, { uart : false });

// При отключении останавливаем серводвигатель:
NRF.on('disconnect', function() {
  digitalWrite(D14,0);
});
// Меняем название на «Flag»:
NRF.setAdvertising({}, {name:"Flag"});

Отправка (центральное Bluetooth-устройство)

В коде для отправителя данных всё примерно то же самое, что и раньше, но в этот раз мы не отключаемся, а просто постоянно вызываем writeValue(). Обратите внимание, что в этом коде мы также определяем, продолжается ли передача данных и не было ли потеряно соединение.

var gatt,characteristic;

NRF.requestDevice({ filters: [{ name: 'Flag' }] }).then(function(device) {
  console.log("Нашли");
  return device.gatt.connect();
}).then(function(g) {
  console.log("Подключились");
  gatt = g;
  return gatt.getPrimaryService(
    "3e440001-f5bb-357d-719d-179272e4d4d9");
}).then(function(service) {
  return service.getCharacteristic(
    "3e440002-f5bb-357d-719d-179272e4d4d9");
}).then(function (c) {
  console.log("Прочли характеристику");
  characteristic = c;

  startWriting();
});


function startWriting() {
  var busy = false;
  var i = setInterval(function() {
    if (!gatt.connected) {
      clearInterval(i);
      return;
    }
    if (busy) return;
    busy = true;
    var n = analogRead(D5)*255;
    characteristic.writeValue([n]).then(function() {
      busy = false;
    });
  }, 50);
}

См.также

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