Статьи

Связь с Bluetooth Низкоэнергетические устройства в Кордове

В этом руководстве вы создадите приложение для посещаемости с использованием Cordova и периферийного устройства Bluetooth с низким энергопотреблением (BLE). Вы создадите свою собственную периферию BLE с помощью Node.js и будете общаться с ней с помощью приложения Cordova.

Я предполагаю, что вы уже знаете Cordova и Ionic Framework. Если нет, ознакомьтесь с этим руководством по созданию простого приложения с использованием Ionic, Advanced App Framework .

Вы можете найти окончательный проект на Github .

Bluetooth Low Energy Concepts

Во-первых, я хотел бы убрать некоторые концепции с пути. Есть два возможных устройства, которые вы можете подключить с помощью BLE:

  • Центральная : также называется «мастер». Это устройство, которое инициирует соединение с ведомым или периферийным устройством. Обычно это смартфон, планшет или компьютер. В этом уроке центральное устройство — это смартфон, на котором запущено приложение, которое вы будете создавать.
  • Периферийный : также называется «раб». Он отвечает за рекламу и ожидает подключения к ней центральных устройств. Примеры таких устройств включают в себя фитнес-трекеры (например, фитбит) или маяки. В этом уроке периферийное устройство, которое я собираюсь использовать, — это Raspberry Pi 3. Если у вас его нет, это также может быть ваш смартфон или компьютер. Он должен работать до тех пор, пока используемое устройство Bluetooth поддерживает Bluetooth 4.0.

Обмен данными осуществляется с помощью Generic Attribute Profile (GATT). ГАТТ определяет способ передачи данных двумя устройствами. Он состоит из услуг и характеристик . Сервисы — это наборы характеристик, которые определяют поведение устройства. Например, есть Служба кровяного давления, которая предоставляет данные о кровяном давлении из монитора кровяного давления. Характеристики — это различные типы данных, доступные в услуге. Для службы артериального давления есть характеристика измерения артериального давления, промежуточного давления в манжете и функции артериального давления. Вы будете создавать свои собственные услуги и их характеристики позже.

Сборка приложения

Приложение для посещаемости состоит из двух компонентов: периферийного устройства BLE и приложения, которое будет с ним разговаривать. Периферийное устройство BLE объявляет сервис, который позволяет подключенным устройствам записывать данные в него. И приложение записывает данные в этот сервис через его характеристику. В частности, вы попросите имя и фамилию участника. Пользователь будет нажимать на кнопку сканирования , которая выведет список всех периферийных устройств BLE рядом с устройством пользователя. Затем пользователь подключается к одному из этих периферийных устройств. После подключения приложение попросит пользователя ввести свое имя и фамилию. Наконец пользователь нажимает на кнопку « присутствовать» , чтобы передать свое имя периферийному устройству BLE.

Вот пара скриншотов, которые показывают окончательный результат:

сканирующие устройства

форма посещаемости

Теперь пришло время создать приложение. Начните с создания нового приложения Ionic:

ionic start ionicBLEAttendance blank 

Добавьте центральный плагин Bluetooth Low Energy для Cordova. Это плагин, который вы будете использовать для общения с периферийным устройством BLE, которое вы создадите позже.

 cordova plugin add cordova-plugin-ble-central 

Перейдите в каталог www , это будет базовый каталог, в котором вы будете работать. Откройте index.html и замените существующий контент следующим:

 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width"> <title></title> <link href="lib/ionic/css/ionic.css" rel="stylesheet"> <link href="css/style.css" rel="stylesheet"> <script src="lib/ionic/js/ionic.bundle.js"></script> <script src="cordova.js"></script> <script src="js/app.js"></script> <script src="js/factories/DeviceFactory.js"></script> <script src="js/controllers/HomeController.js"></script> <script src="js/controllers/DeviceController.js"></script> </head> <body ng-app="starter"> <ion-nav-view></ion-nav-view> </body> </html> 

В этом коде вы связываете три новых файла в дополнение к файлам по умолчанию. Создайте эти файлы.

Откройте app.js и добавьте следующее в функцию обратного вызова для события $ionicPlatform.ready . Это проверяет, включен ли Bluetooth в устройстве. Если он не включен, он пытается включить его, предлагая пользователю включить его.

 ble.isEnabled( function(){ // Bluetooth is enabled }, function(){ // Bluetooth not yet enabled so we try to enable it ble.enable( function(){ // bluetooth now enabled }, function(err){ alert('Cannot enable bluetooth'); } ); } ); 

После функции run добавьте конфигурацию маршрута:

 .config(function($stateProvider, $urlRouterProvider) { $stateProvider .state('home', { url: '/home', templateUrl: 'templates/home.html' }) .state('device', { url: '/device/:id', templateUrl: 'templates/device.html' }); $urlRouterProvider.otherwise('/home'); }); 

Фабрика устройств

Фабрика устройств служит хранилищем данных для устройств, возвращаемых при периферийном сканировании. У него есть методы для добавления устройства, получения всех устройств, получения определенного устройства и сброса хранилища данных. Откройте файл js / factories / DeviceFactory.js и добавьте следующий код.

 (function(){ angular.module('starter') .factory('DeviceFactory', [DeviceFactory]); function DeviceFactory(){ var devices = []; return { addDevice: function(device){ devices.push(device); }, getDevices: function(){ return devices; }, getDevice: function(id){ var device_found = devices.filter(function(device){ return device.id == id; }); return device_found[0]; }, reset: function(){ devices = []; } }; } })(); 

Домашняя страница

Домашняя страница — это страница по умолчанию, которую пользователь видит при открытии приложения. Эта страница позволяет пользователю сканировать близлежащие периферийные устройства BLE и подключаться к ним.

Создайте templates / home.html и добавьте следующее:

 <ion-view title="Ionic BLE Attendance" ng-controller="HomeController as home_ctrl"> <header class="bar bar-header bar-stable"> <h1 class="title">Ionic Bluetooth Attendance</h1> </header> <ion-content class="has-header padding"> <button class="button button-positive button-full" ng-click="scan()"> Scan </button> <div class="card" ng-if="devices.length"> <div class="item item-divider"> Devices Found </div> <div class="item"> <ul class="list"> <li class="item item-button-right" ng-repeat="device in devices"> {{device.name}} <button class="button button-balanced" ng-click="connect(device.id)"> connect </button> </li> </ul> </div> </div> </ion-content> </ion-view> 

Код за домашней страницей — домашний контроллер. Откройте js / controllers / HomeController.js и добавьте следующий код:

 (function(){ angular.module('starter') .controller('HomeController', ['$scope', '$state', 'DeviceFactory', HomeController]); function HomeController($scope, $state, DeviceFactory){ $scope.devices = []; // the devices listed in the page $scope.scan = function(){ DeviceFactory.reset(); ble.startScan( [], function(device){ if(device.name){ DeviceFactory.addDevice({ 'id': device.id, 'name': device.name }); } }, function(err){ alert('Scanning failed. Please try again.'); } ); setTimeout( ble.stopScan, 1500, function(){ $scope.$apply(function(){ $scope.devices = DeviceFactory.getDevices(); }); }, function(){ // Stopping scan failed } ); } $scope.connect = function(device_id){ ble.connect( device_id, function(res){ $state.go('device', { 'id': device_id }); }, function(err){ alert('Something went wrong while trying to connect. Please try again'); } ); } } })(); 

В приведенном выше коде метод scan сначала очищает массив периферийных устройств, хранящихся на фабрике устройств. Затем он сканирует соседние периферийные устройства. Метод startScan принимает массив служб для обнаружения в качестве первого аргумента. В этом случае вы передаете пустой массив, который будет обнаруживать любые службы, рекламируемые ближайшими периферийными устройствами. Второй аргумент — это функция обратного вызова, выполняемая каждый раз, когда он обнаруживает новое устройство, добавляя обнаруженное устройство к фабрике устройств, используя его метод addDevice . Метод addDevice принимает объект, содержащий идентификатор устройства и имя, данное ему. Идентификатор устройства может быть UUID или MAC-адресом.

 $scope.scan = function(){ DeviceFactory.reset(); ble.startScan( [], function(device){ if(device.name){ DeviceFactory.addDevice({ 'id': device.id, 'name': device.name }); } }, function(err){ alert('Scanning failed. Please try again.'); } ); } 

Метод ble.startScan выполняется бесконечно, поэтому для остановки сканирования необходимо вызвать метод ble.stopScan . Вы делаете это через 1,5 секунды (1500 миллисекунд), затем $scope обновляется со всеми устройствами, хранящимися на фабрике устройств, которая, в свою очередь, обновляет интерфейс, чтобы вывести список всех обнаруженных устройств.

 setTimeout( ble.stopScan, 1500, function(){ $scope.$apply(function(){ $scope.devices = DeviceFactory.getDevices(); }); }, function(){ // Stopping scan failed } ); 

Вот пример ответа, который вы получаете от вызова ble.startScan :

 [ { "advertising":{ }, "id":"B8:XX:XX:XX:XX:XX", "rssi":-57, "name":"AttendanceApp" }, { "advertising":{ }, "id":"E7:YY:YY:YY:YY:YY", "rssi":-67, "name":"Flex" } ] 

advertising свойство является ArrayBuffer , поэтому оно пустое. Вы можете преобразовать это в строку, если вы хотите получить доступ к рекламным данным. rssi — это индикация силы rssi сигнала, которая указывает на силу сигнала.

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

 $scope.connect = function(device_id){ ble.connect( device_id, function(res){ $state.go('device', { 'id': device_id }); }, function(err){ alert('Something went wrong while trying to connect. Please try again'); } ); } 

Вот пример ответа, когда вы вызываете ble.connect :

 { "characteristics":[ { "characteristic":"2a00", "service":"1800", "properties":[ "Read" ] }, { "characteristic":"2a01", "service":"1800", "properties":[ "Read" ] }, { "descriptors":[ { "uuid":"2902" } ], "characteristic":"2a05", "service":"1801", "properties":[ "Indicate" ] }, { "descriptors":[ { "uuid":"2902" } ], "characteristic":"34cd", "service":"12ab", "properties":[ "Write" ] } ], "advertising":{ }, "id":"B8:XX:XX:XX:XX:XX", "services":[ "1800", "1801", "12ab" ], "rssi":-55, "name":"AttendanceApp" } 

Он имеет множество characteristics , но интересует вас четвертый элемент, который вы создадите позже:

 { "descriptors":[ { "uuid":"2902" } ], "characteristic":"34cd", "service":"12ab", "properties":[ "Write" ] } 

Страница устройства

Страница устройства — это страница, на которую пользователь переходит при подключении к периферийному устройству. На этой странице пользователь может ввести свое имя и фамилию и нажать на кнопку присутствия , чтобы записать свое посещение.

Создайте templates / device.html и добавьте следующее:

 <ion-view title="Ionic BLE Attendance" ng-controller="DeviceController as device_ctrl" ng-init="init()"> <header class="bar bar-header bar-stable"> <button class="button icon ion-chevron-left" ng-click="backToHome()"></button> <h1 class="title">Ionic Bluetooth Attendance</h1> </header> <header class="bar bar-subheader"> <h2 class="title">{{device.name}}</h2> </header> <ion-content class="has-header has-subheader padding"> <div class="list"> <label class="item item-input"> <input type="text" ng-model="device_ctrl.attendee.firstname" placeholder="First Name"> </label> <label class="item item-input"> <input type="text" ng-model="device_ctrl.attendee.lastname" placeholder="Last Name"> </label> <button class="item button button-positive button-full" ng-click="attend()"> Attend </button> </div> </ion-content> </ion-view> 

Код за страницей устройства — это контроллер устройства. Откройте js / controllers / DeviceController.js и добавьте следующий код:

 (function(){ angular.module('starter') .controller('DeviceController', ['$scope', '$state', '$stateParams', 'DeviceFactory', DeviceController]); function DeviceController($scope, $state, $stateParams, DeviceFactory){ var me = this; var service_id = '12ab'; var characteristic_id = '34cd'; me.attendee = { firstname: '', lastname: '' } $scope.init = function(){ $scope.device = DeviceFactory.getDevice($stateParams.id); } $scope.attend = function(){ ble.write( $stateParams.id, service_id, characteristic_id, btoa(JSON.stringify(me.attendee)), function(response){ if(response == 'OK'){ alert("Your attendance is recorded!"); ble.disconnect($stateParams.id); $state.go('home'); } }, function(err){ alert("Error occured while trying to record your attendance. Please try again."); } ); } $scope.backToHome = function(){ $state.go('home'); ble.disconnect($stateParams.id); } } })(); 

Внутри контроллера находится сервис и характерный идентификатор. Эти значения доступны как свойство в объекте устройства, возвращаемом при подключении к периферийному устройству. Но вы работаете с предположением, что приложение уже знает эти значения заранее, поскольку создатель приложения и периферийное устройство BLE — это одно и то же лицо. Вот почему они здесь жестко запрограммированы, а не извлекаются из возвращаемого значения метода ble.connect .

 var service_id = '12ab'; var characteristic_id = '34cd'; 

Когда страница устройства инициализируется, она вызывает функцию init . Это инициализирует значение объекта device путем вызова метода getDevice в фабрике устройств. Этот метод возвращает конкретное устройство с указанным идентификатором устройства.

 $scope.init = function(){ $scope.device = DeviceFactory.getDevice($stateParams.id); } 

Когда пользователь нажимает кнопку присутствия , этот метод выполняется. Он вызывает метод ble.write который отвечает за запись данных в характеристику. Это принимает следующие аргументы:

  • идентификатор устройства : UUID или MAC-адрес устройства.
  • UUID службы : уникальный идентификатор, назначенный службе.
  • UUID признака : уникальный идентификатор, присвоенный признаку.
  • данные : данные, которые вы хотите отправить. В этом случае объект, содержащий имя и фамилию участника. Он преобразуется в строку с использованием JSON.stringify а полученная строка преобразуется в строку в кодировке base64 с использованием btoa . Это потому, что вы не можете отправлять простые строки через BLE.

Четвертый и пятый аргументы — это функции обратного вызова и функции обратного вызова. Если запрос на запись выполнен успешно, отсоединитесь от устройства с ble.disconnect метода ble.disconnect . $stateParams.id передаваемый этому методу, является идентификатором устройства, который вы передали ранее в качестве параметра состояния от домашнего контроллера. Метод disconnect вызывается, потому что периферийное устройство может подключаться только к одному центральному устройству одновременно. Это означает, что при подключении определенного центрального устройства реклама прекращается. И когда он прекращает рекламу, он не показывает во время сканирования.

 $scope.attend = function(){ ble.write( $stateParams.id, service_id, characteristic_id, btoa(JSON.stringify(me.attendee)), function(response){ if(response == 'OK'){ alert("Your attendance is recorded!"); ble.disconnect($stateParams.id); $state.go('home'); // go back to home page } }, function(err){ alert("Error occurred while trying to record your attendance. Please try again."); } ); } 

Также можно вручную вернуться на домашнюю страницу. Здесь ble.disconnect метод ble.disconnect .

 $scope.backToHome = function(){ $state.go('home'); ble.disconnect($stateParams.id); } 

BLE Периферийный

Теперь пришло время добавить код для периферийного устройства BLE. Прежде чем продолжить, Intsall Bleno, поскольку вы будете использовать этот модуль Node.js для реализации периферийного устройства BLE.

Теперь, когда вы вернулись, создайте новую папку для эмулируемого периферийного устройства, создайте файл package.json и добавьте следующее:

 { "name": "ble-server", "version": "1.0.0", "description": "", "main": "attendance.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "bleno": "^0.4.0" } } 

Откройте новое окно терминала и установите зависимости, выполнив npm install . Это устанавливает Bleno .

Затем создайте файл Participance.js и добавьте следующий код:

 var bleno = require('bleno'); var attendees = []; var settings = { service_id: '12ab', characteristic_id: '34cd' }; bleno.on('stateChange', function(state){ if(state === 'poweredOn'){ bleno.startAdvertising('AttendanceApp', ['12ab']); }else{ bleno.stopAdvertising(); } }); bleno.on('advertisingStart', function(error){ if(error){ // error on advertise start }else{ console.log('started..'); bleno.setServices([ new bleno.PrimaryService({ uuid : settings.service_id, characteristics : [ new bleno.Characteristic({ value : null, uuid : settings.characteristic_id, properties : ['read', 'write'], onWriteRequest : function(data, offset, withoutResponse, callback){ var attendee = JSON.parse(data.toString()); attendee.time_entered = Date.now(); attendees.push(attendee); console.log(attendees); callback(this.RESULT_SUCCESS); } }) ] }) ]); } }); 

Разбивая код выше, сначала включите модуль bleno:

 var bleno = require('bleno'); 

Далее идет массив attendees который служит хранилищем для списка участников. А под ним находится объект, содержащий идентификатор услуги и идентификатор характеристики, используемые в качестве UUID для услуги и характеристики в дальнейшем. Это то же самое в контроллере устройства ранее.

 var attendees = []; // storage for the attendees var settings = { service_id: '12ab', characteristic_id: '34cd' }; 

Слушайте, когда состояние устройства меняется. Вы хотите начать рекламу, как только устройство уже включено, и остановить, если что-нибудь еще. Проверьте список событий, если вы хотите выполнить любую другую операцию в зависимости от состояния устройства. Метод startAdvertising принимает два аргумента — имя, которое вы хотите дать устройству, и массив UUID сервисов для рекламы.

 bleno.on('stateChange', function(state){ if(state === 'poweredOn'){ bleno.startAdvertising('AttendanceApp', [settings.service_id]); }else{ bleno.stopAdvertising(); } }); 

Послушайте, когда начинается реклама:

 bleno.on('advertisingStart', function(error){ if(error){ // error on advertise start }else{ ... } }); 

Если ошибок нет, установите основные службы, доступные на периферии:

 bleno.setServices([ ... ]); 

Внутри массива создайте новый основной сервис. Это принимает UUID, который вы хотите назначить службе, а также массив характеристик:

 new bleno.PrimaryService({ uuid : settings.service_id, characteristics : [ ... ] }); 

Внутри массива для хранения характеристик создайте новую характеристику. Он принимает объект, содержащий значение по умолчанию для характеристики, UUID, который вы хотите назначить, массив свойств и метод реализации для каждого свойства.

 new bleno.Characteristic({ value : null, uuid : settings.characteristic_id, properties : ['write'], onWriteRequest : function(data, offset, withoutResponse, callback){ var attendee = JSON.parse(data.toString()); attendee.time_entered = Date.now(); attendees.push(attendee); console.log(attendees); callback(this.RESULT_SUCCESS); } }) 

Свойства можно read , write , write writeWithoutResponse , notify или indicate . Вы можете использовать любую комбинацию каждого из них в зависимости от ваших потребностей. Для этого приложения посещаемости вам нужна только write , поэтому соответствующий метод реализации — onWriteRequest . Следующие аргументы передаются в этот метод каждый раз, когда приложение выполняет запрос на запись:

  • data : данные, отправленные из приложения, получены в виде Buffer , поэтому вам нужно вызвать метод toString чтобы преобразовать его обратно в строку JSON.
  • offset : если данные, которые вы пытаетесь отправить, представляют собой последовательность байтов. И вы хотите передать только несколько байтов для каждого запроса на запись, используйте смещение для извлечения нужного значения. Думайте об этом как о реализации нумерации страниц в BLE. Смещение не используется в этом уроке, но это полезно знать.
  • withoutResponse : логическое значение, представляющее, хочет ли метод записи получить ответ.
  • callback : функция обратного вызова, отвечающая за отправку ответа обратно в приложение. Вызов callback(this.RESULT_SUCCESS) эквивалентен return "OK" . Это значение, переданное в качестве аргумента функции обратного вызова ble.write метода ble.write в приложении ранее.

Запустите периферийное устройство, выполнив node attendance.js на вашем терминале. Вы должны увидеть следующее на своем терминале после начала рекламы:

 started.. 

Запуск приложения

Запустите приложение, сначала добавив платформу:

 cordova platform add android 

Затем запустите его на своем устройстве или эмуляторе:

 cordova run android 

Следующие шаги

Это оно! Из этого руководства вы узнали, как создать приложение Cordova, которое взаимодействует с периферийным устройством BLE. Это только начало. Существует множество возможностей использования периферийных устройств BLE и Bluetooth в целом. Итак, вот пара рекомендаций, чтобы взять то, что вы узнали, еще дальше:

  • Используйте сокеты, чтобы каждый раз, когда кто-то регистрировался в комнате, все другие люди, которые зарегистрировались ранее, получали уведомление. Вы можете использовать Socket.io для реализации этого.
  • Вместо использования такого устройства, как Raspberry Pi, в качестве периферийного устройства, вы можете использовать приложение в качестве периферийного устройства, а Raspberry Pi — в качестве центрального модуля. Это позволяет центральному модулю бесконечно сканировать близлежащие устройства и определять, кто посещал, в какое конкретное время они находились в комнате и когда они уходили. Вы можете использовать noble и плагин bluetooth LE cordova для реализации этого.
  • Проверьте другие статьи о Bluetooth на Sitepoint .

Я надеюсь, что это руководство предоставило вам достаточно знаний, чтобы начать создавать приложения, использующие технологию Bluetooth Low Energy. Если у вас есть другие идеи проекта, комментарии или вопросы, пожалуйста, дайте мне знать в комментариях.