Делаем iBeacon и Eddystone Beacon «на коленке»

22eff3d91a3f49b6a754f408c9edacb3.png
iBeacon и Eddystone — это сервисы Apple и Google соответственно, использующие BLE (Bluetooth Low Energy) для локального позиционирования внутри помещений. Базовый принцип у обоих сервисов одинаков, отличается лишь формат передаваемых данных. Маяк (передатчик) периодически, с интервалом от долей секунды до нескольких секунд, передаёт пакеты стандарта Bluetooth LE, которые содержат помимо заголовка дополнительную информацию. Технология не предназначена для точного определения положения в помещении, а лишь для фиксирования момента приближения на некоторое близкое расстояние к маяку.
Классический пример использования маяков — музеи. Приходя в музей, вы устанавливаете на смартфон специальное приложение и отправляетесь осматривать экспозицию. Приближаясь к экспонату (маяку) на некоторое расстояние, смартфон это фиксирует и выводит на экран экскурсионную информацию.
В продаже можно найти немало готовых маяков, но сегодня мы соберем прототип собственного маяка (как iBeacon, так и Eddystone) на микроконтроллере SAML21 и BLE модуле BTLC1000 от Atmel.

Железо


Bluetooth будем реализовывать на базе платы расширения ATBTLC1000-XPRO с данным модулем. В качестве хоста используем микроконтроллер ATSAML21J18B, установленный на отладочной плате ATSAML21-XPRO-B
71ebbdaa99524facaaa207275e42680e.jpg

Генерируем пример для iBeacon


Для этого заходим на страницу Atmel | Start — Web конфигуратор кода для микроконтроллеров Atmel. В одной из наших прошлых статей мы уже писали об использовании этого инструмента. На главной странице нажимаем кнопку Browse All Examples и в открывшемся окне выбираем отладочную плату SAM L21 Xplained Pro, а в поиске набираем строку ibeacon. Выбираем пример BLE Simple-BTLC1000 и жмем кнопку Open Selected Project:
f28ac5dd2d98459095bb4b3a81bcfc83.PNG

Получившаяся по умолчанию конфигурация по-идее соответствует железу и менять тут ничего не нужно. На всякий случай, можно сравнить результат со скриншотом и жмем кнопку EXPORT PROJECT в правом верхнем углу экрана:
dad7ad0fe774402f8f2ba500c33f63c0.PNG

Ставим галочку напротив пункта Makefile (standalone), переименовываем проект и жмем кнопку DOWNLOAD PACK:
8537253d1bbd4e16b1d6f4acc0a5996b.PNG

В результате скачивается файл с расширением *.atzip. В принципе это обычный архив, в котором хранится проект для Atmel Studio. Открываем файл (он должен быть ассоциирован со студией) и ждем пока запустится проект. В открывшемся диалоге меняем при желании имя проекта и путь к папке с проектом и жмем ОК:
608951bd1fcb45bf90bddd8975470866.PNG

Ждем пока проект сформируется.

Пробный запуск


В принципе, пример «из коробки» уже работает как iBeacon. Пробуем скомпилировать проект кнопкой F7.
Если компиляция прошла успешно можно залить проект в микроконтроллер для первых тестов. Рекомендую предварительно запустить терминальную программу для мониторинга дебажной информации. Встроенный отладчик EDBG определяется в системе в том числе и как виртуальный COM-порт, к которому подключен один из SERCOM’ов микроконтроллера
0255345a909c4567b25673d5975f3ffc.PNG

Запускаем свою любимую терминалку, ставим скорость 115200 и галочку DTR.

Жмем кнопку F5 для того чтобы залить прошивку. Если заливаете в первый раз, появится сообщение с просьбой выбрать используемый программатор:
41cb3e1ad13d417f9302e999b9d16b49.PNG

В диалоговом окне указываем наш отладчик:

975f567b652f4a85a3668b0b7c2f5147.PNG

Жмем еще раз F5 и ждем пока запрограммируется контроллер и начнет исполняться программа. Если всё сделано правильно, в терминалке должны увидеть следующую информацию:
6dfa75475a0e4d9c983f69557b5d5fd7.PNG

Теперь запускаем на телефоне любое приложение, работающее с маяками (например, iBeacon & Eddystone Scanner для Android) и мы должны увидеть нашу метку.
fc13a9b19aeb4dba8a82c6ccd254bbb3.png

Немного разберемся с форматом передаваемых данных. А он, по старой традиции Apple, очень прост. Маяк из полезной нагрузки передает следующие данные:
UUID. 128-битный уникальный идентификатор группы маяков, определяющий их тип или принадлежность одной организации
Major. 16-битное беззнаковое значение, с помощью которого можно группировать маяки с одинаковым UUID
Minor. 16-битное беззнаковое значение, с помощью которого можно группировать маяки с одинаковым UUID и Major
Measured Power (уровень сигнала в 1 м от передатчика). 8-битное знаковое целое — значение индикации уровня принимаемого сигнала (RSSI), откалиброванное на расстоянии 1 м от приёмника, которое используется для определения близостимаяка к приёмнику (мобильному устройству). Измеряется в dBm.

В основном файле simple_btlc1000.c определен массив adv_data, в который и помещаются эти данные. Помимо этого, есть дополнительная служебная информация, расшифровку которой можно найти в документе Proximity Beacon
Specification
на странице developer.apple.com/ibeacon.

static uint8_t adv_data[] =
{
0x1a, 0xff, 
0x4c, 0x00,   // Company ID
0x02, 0x15,  // Beacon Type
// Proximity UUID
0x21, 0x8A, 0xF6, 0x52, 0x73, 0xE3, 0x40, 0xB3, 0xB4, 0x1C, 0x19, 0x53, 0x24, 0x2C, 0x72, 0xf4, 
0x00, 0xbb,   // Major
0x00, 0x45,   // Minor 
0xc5   // Measured Power
};


Основное волшебство происходит в функции beacon_init (), которая вызывается непосредственно перед главным циклом в main. В ней, используя API-функцию at_ble_adv_data_set мы сообщаем модулю какие именно данные мы хотим передавать (тот самый массив adv_data), а далее используя функцию at_ble_adv_start запускаем сам процесс передачи с указанием частоты их передачи (BEACON_ADV_INTERVAL).

Код функции beacon_init ()
static void beacon_init(void)
{
        static at_ble_handle_t service;

        /* establish peripheral database */
        if (at_ble_primary_service_define(&service_uuid, &service, NULL, 0, chars, 2) !=  AT_BLE_SUCCESS)
                DBG_LOG("Failed to define the primary service");
        
        /* set beacon advertisement data */
        if(at_ble_adv_data_set(adv_data, sizeof(adv_data), scan_rsp_data, sizeof(scan_rsp_data)) != AT_BLE_SUCCESS)
                DBG_LOG("BLE Beacon advertisement data set failed");
        
        /* BLE start advertisement */
        if(at_ble_adv_start(AT_BLE_ADV_TYPE_UNDIRECTED, AT_BLE_ADV_GEN_DISCOVERABLE, NULL, AT_BLE_ADV_FP_ANY, 
           BEACON_ADV_INTERVAL, BEACON_ADV_TIMEOUT, BEACON_ABSOLUTE_INTERVAL_ADV) != AT_BLE_SUCCESS)
        {
                DBG_LOG("BLE Beacon advertisement failed");
                ble_device_disconnected_ind();
        }
        else
        {
                DBG_LOG("Advertisement started");             
                ble_device_connected_ind();
        }       
}


Переделываем проект под маяки Eddystone от Google


Для этого требуется сделать всего одну вещь — изменить передаваемый массив данных в функцию at_ble_adv_data_set. Заведем отдельный массив и заполним его. Про формат пакета можно прочитать тут. В отличии от iBeacon, Eddystone на данный момент может передавать 3 типа пакетов. Идентификатор типа передается в одном из байтов посылки:
aaae1d49fb6042f2a3ddad018a3a40cb.PNG

Мы будем передавать ссылку.
Формат записи самой ссылки можно подсмотреть на этой странице.

Формат передачи ссылки
URL Scheme Prefix
5d91893398604e7885f5bec0bba8548a.PNG
Eddystone-URL HTTP URL encoding
66101a21229d4a47b58e349d3e347000.PNG

Примечание: домена .ru в списке поддерживаемых нет. Мне очень хотелось, чтобы ссылка была живая, поэтому я напишу geektimes.com, т.к. с него есть редирект на geektimes.ru, а вот habrahabr.com аналогичным образом в домен .ru не перенаправляется.

В итоге получаем следующий пакет для Eddystone:

static uint8_t eddystone_data[] =
{
        0x03,
        0x03,
        0xaa, 0xfe,
        0x10,                                           // длина
        0x16,
        0xaa, 0xfe,                                     // 16-bit Eddystone UUID
        0x10,                                           // Frame Type: 0x10 для URL
        0xf0,                                           // TX Power
        0x00,                                           // URL Scheme: http://www.
        'g', 'e', 'e', 'k', 't', 'i', 'm', 'e', 's',
        0x00,                                           // .com/
};      


Не забываем указать в API новый пакет:

at_ble_adv_data_set(eddystone_data, sizeof(eddystone_data), scan_rsp_data, sizeof(scan_rsp_data)) != AT_BLE_SUCCESS)


Компилим, заливаем, смотрим результат.
1abebfdda6344ea4991c332d83740ec2.png

На этом всё, до новых встреч. Теперь уже на просторах хабра.

© Habrahabr.ru