Google's beacon platform — это решение для работы с Bluetooth маячками. Платформа работает с разными маячками от разных производителей, предоставляя разработчикам единый, простой и гибкий инструмент.


Перед прочтением этой статьи я рекомендую ознакомиться с концепцией Physical Web о которой я рассказывал в своей прошлой статье: Концепция Physical web. Bluetooth маячки. Сравнение стандартов iBeacon, AltBeacon и Eddystone.

Google's beacon platform. Часть 1 — Proximity beacon API
Google's beacon platform. Часть 2 — Nearby meassages API

Основным средством, в рамках Google's beacon platform, для работы на клиентской стороне с bluetooth маячками, является Nearby Messages API. В этой статье я расскажу как настроить проекты на а платформах Android и iOS, и добавить в приложение возможность получать и разбирать сообщения от ble-маячков.

Nearby Messages API


Nearby Messages API — это API, которое реализует парадигму publish-subscribe и позволяет разным устройствам публиковать, и подписываться на сообщения, таким образом обмениваться данными. Nearby Messages API является частью Nearby. Для обмена сообщениями устройства не обязательно должны находиться в одной сети, но должны быть подключены к интернету. В нашем случае подключение к интернету должно быть у того смартфона или планшета на котором мы хотим получать сообщения. Маячкам подключение к интернету не нужно! Nearby Messages API позволяет обмениваться сообщениями с помощью Bluetooth, Bluetooth Low Energy, Wi-Fi и даже ультразвука, но мы будем использовать только Bluetooth Low Energy что бы минимизировать потребление энергии.

Nearby Messages API на Android


Nearby Messages API доступен на устройствах с Android в библиотеке Google Play services версии 7.8.0 или выше.
Убедитесть что у вас установлена последняя версия клиентской библиотеки для Google Play на вашем хосте для разработки:

  • Откройте Android SDK Manager.
  • Перейдите в A ppearance & Behavior > System Settings > Android SDK > SDK Tools и убедитесь что установлены следующие пакеты:
    • Google Play services
    • Google Repository

Для использования Nearby Messages API, конечно же понадобится Google Account. Так же необходимо получить API ключ. Ключи для Android, iOS и Proximity Beacon API должны быть созданы в рамках одно проекта Google Developers Console. Очень советую ознакомиться с Best practices for securely using API keys

Осторожно GIF. Пример того как получить API ключ:
image


Настраиваем проект:
Открываем или создаём новый проект, открываем build.gradle файл и добавляем в него Google Play services client library как зависимость.

Содержание файла build.gradle
apply plugin: 'android'
...

dependencies {
    compile 'com.google.android.gms:play-services-nearby:8.4.0'
}


Настраиваем manifest файл в который добавляем сгенерированный ранее API ключ:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.google.sample.app" >
    <application ...>
        <meta-data
            android:name="com.google.android.nearby.messages.API_KEY"
            android:value="API_KEY" />
        <activity>
        ...
        </activity>
    </application>
</manifest>


Создаём в нашем приложении GoogleApiClient и добавляем Nearby Messages API
Пример кода который показывает как добавить Nearby Messages API:

Создаём в приложении GoogleApiClient:
mGoogleApiClient = new GoogleApiClient.Builder(this)
     .addApi(Nearby.MESSAGES_API)
     .addConnectionCallbacks(this)
     .addOnConnectionFailedListener(this)
     .build();


Получение сообщений


Для получения сообщений от маячков нам необходимо сперва на них подписаться, есть два способа как наше приложение может это сделать:

  • В активном режиме приложения, в ответ на действия пользователя или события.
  • В фоновом режиме, т.е. когда приложение неактивно

Nearby Messages API требует разрешения пользователя на запросы publish() и subscribe(). Поэтому приложение должно проверять на что пользователь уже дал свое согласие и если такое отсутствует, то вызывать диалог запроса разрешений.

Что бы реализовать запрос разрешения у пользователя во время исполнения вашего приложения, вы можете:

  • Присоединить result callback к вызовам publish() and subscribe().
  • Использовать Nearby.Messages.getPermissionStatus() что бы проверить статус разрешения непосредственно перед вызовом publish() или subscribe()

Реализация result callback


Для проверки статус кода ошибки в result callback необходимо вызвать status.getStatusCode(). Если получен статус код APP_NOT_OPTED_IN отобразите диалог запроса разрешений вызовом status.startResolutionForResult() и используйте onActivityResult() что бы повторно реинициировать какие-либо невыполненные запросы подписки или публикации.

Следующий пример демонстрирует простой result callback, который проверяет, какие пользователь предоставил разрешения. Если пользователь не предоставил разрешений, вызывается status.startResolutionForResult(), чтобы предложить пользователю разрешить Nearby Messages. В этом примере boolean mResolvingError используется чтобы избежать многократного запуска таких предложений:

Пример реализации result callback:
private void handleUnsuccessfulNearbyResult(Status status) {
    Log.i(TAG, "Processing error, status = " + status);
    if (mResolvingError) {
        // Already attempting to resolve an error.
        return;
    } else if (status.hasResolution()) {
        try {
            mResolvingError = true;
            status.startResolutionForResult(getActivity(),
                    Constants.REQUEST_RESOLVE_ERROR);
        } catch (IntentSender.SendIntentException e) {
            mResolvingError = false;
            Log.i(TAG, "Failed to resolve error status.", e);
        }
    } else {
        if (status.getStatusCode() == CommonStatusCodes.NETWORK_ERROR) {
            Toast.makeText(getActivity().getApplicationContext(),
                    "No connectivity, cannot proceed. Fix in 'Settings' and try again.",
                    Toast.LENGTH_LONG).show();
        } else {
            // To keep things simple, pop a toast for all other error messages.
            Toast.makeText(getActivity().getApplicationContext(), "Unsuccessful: " +
                    status.getStatusMessage(), Toast.LENGTH_LONG).show();
        }
    }
}


Вы также можете использовать этот обработчик(handler) для обработки любых других NearbyMessageStatusCodes полученных от операций или, например, статус кода CommonStatusCodes.NETWORK_ERROR.

Реиницализация невыполненных запросов


Следующий пример показывает реализацию метода onActivityResult(), который вызывается после того как пользователь отреагирует на диалог запроса разрешений. В случае если пользователь ответит согласием, любые ожидающие запросы подписки или публикации будут выполнены. А данном примере вызывается метод executePendingTasks()

Реиницаиалзация невыполненных запросов:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == Constants.REQUEST_RESOLVE_ERROR) {
        // User was presented with the Nearby opt-in dialog and pressed "Allow".
        mResolvingError = false;
        if (resultCode == Activity.RESULT_OK) {
            // Execute the pending subscription and publication tasks here.
            mMainFragment.executePendingTasks();
        } else if (resultCode == Activity.RESULT_CANCELED) {
            // User declined to opt-in. Reset application state here.
        } else {
            Toast.makeText(this, "Failed to resolve error with code " + resultCode,
                    Toast.LENGTH_LONG).show();
        }
    }


Так же, чтобы уменьшить задержку при сканировании маячков, рекомендуется использовать Strategy.BLE_ONLY при вызове Nearby.Messages.subscribe(). Когда эта опция установлена, Nearby Messages API не задействует Wi-Fi сканирование или классические сканирования Bluetooth. Это уменьшает задержку обнаружения маячков, поскольку система не циклирует через все возможные типы сканирования, а так же уменьшает потреблении энергии.

Подписка в активном режиме:


Когда ваше приложение подписывается на сообщения от маячков будучи в активном режиме, сканирование производится непрерывно пока приложение не отпишется. Такую подписку рекомендуется использовать только когда ваше приложение активно, обычно в ответ на какие то действия пользователя.

Приложение может инициировать подписку в активном режиме вызвав метод Nearby.Messages.subscribe(GoogleApiClient, MessageListener, SubscribeOptions) и установив для параметра Strategy значение BLE_ONLY.

Фрагмент кода демонстрирующий инициирование подписки в активном режиме:
// Create a new message listener.
mMessageListener = new MessageListener() {
    @Override
    public void onFound(Message message) {
        // Do something with the message.
        Log.i(TAG, "Found message: " + message);
    }

    // Called when a message is no longer detectable nearby.
    public void onLost(Message message) {
        // Take appropriate action here (update UI, etc.)
    }
}

// Subscribe to receive messages.
Log.i(TAG, "Trying to subscribe.");
// Connect the GoogleApiClient.
if (!mGoogleApiClient.isConnected()) {
    if (!mGoogleApiClient.isConnecting()) {
        mGoogleApiClient.connect();
    }
} else {
    SubscribeOptions options = new SubscribeOptions.Builder()
            .setStrategy(Strategy.BLE_ONLY)
            .setCallback(new SubscribeCallback() {
                @Override
                public void onExpired() {
                    Log.i(TAG, "No longer subscribing.");
                }
            }).build();

    Nearby.Messages.subscribe(mGoogleApiClient, mMessageListener, options)
            .setResultCallback(new ResultCallback<Status>() {
                @Override
                public void onResult(Status status) {
                    if (status.isSuccess()) {
                        Log.i(TAG, "Subscribed successfully.");
                    } else {
                        Log.i(TAG, "Could not subscribe.");
                        // Check whether consent was given;
                        // if not, prompt the user for consent.
                        handleUnsuccessfulNearbyResult(status);
                    }
                }
            });
}


Для уменьшения потребления энергии и соответственно продления срока работы аккумулятора, вызывайте Nearby.Messages.unsubscribe() в методеOnStop()вашего приложения. Когда подписка на сообщения больше не нужна, приложение должно отписаться вызвав методNearby.Messages.unsubscribe(GoogleApiClient, MessageListener)`.

Подписка в фоновом режиме


Когда приложение подписывается на сообщения в фоновом режиме, срабатывает low-power сканирование при событиях включения экрана, даже когда приложение в настоящее время не активно. Вы можете использовать эти фоновые события low-power сканирования, чтобы "разбудить" приложение в ответ на конкретное сообщение. Фоновые подписки потребляют меньше энергии, чем подписка в активном режиме, но имеют более высокую задержку и низкую надежность.

Подписка в фоновом режиме инициируется вызовом метода Nearby.Messages.subscribe(GoogleApiClient, PendingIntent,SubscribeOptions) и заданием для параметра Strategy значения BLE_ONLY.

Фрагмент кода демонстрирующий инициирование подписки в фоновом режиме:
// Subscribe to messages in the background.
private void backgroundSubscribe() {
    // Connect the GoogleApiClient.
    if (!mGoogleApiClient.isConnected()) {
        if (!mGoogleApiClient.isConnecting()) {
            mGoogleApiClient.connect();
        }
    } else {
        Log.i(TAG, "Subscribing for background updates.");
        SubscribeOptions options = new SubscribeOptions.Builder()
                .setStrategy(Strategy.BLE_ONLY)
                .build();
        Nearby.Messages.subscribe(mGoogleApiClient, getPendingIntent(), options)
                .setResultCallback(new ResultCallback<Status>() {
                    @Override
                    public void onResult(Status status) {
                        if (status.isSuccess()) {
                            Log.i(TAG, "Subscribed successfully.");
                        } else {
                            Log.i(TAG, "Could not subscribe.");
                            handleUnsuccessfulNearbyResult(status);
                        }
                    }
                });
    }
}

private PendingIntent getPendingIntent() {
    return PendingIntent.getService(getApplicationContext(), 0,
            getBackgroundSubscribeServiceIntent(), PendingIntent.FLAG_UPDATE_CURRENT);
}

private Intent getBackgroundSubscribeServiceIntent() {
    return new Intent(getApplicationContext(), BackgroundSubscribeIntentService.class);
}


Фрагмент кода демонстрирующий обработку intent'а в классе BackgroundSubscribeIntentService:
protected void onHandleIntent(Intent intent) {
    Nearby.Messages.handleIntent(intent, new MessageListener() {
        @Override
        public void onFound(Message message) {
            Log.i(TAG, "Found message via PendingIntent: " + message);
        }

        @Override
        public void onLost(Message message) {
            Log.i(TAG, "Lost message via PendingIntent: " + message);
        }
    });
}


Ну и конечно же, если подписка нам больше не нужна, мы должны отписаться вызвав Nearby.Messages.unsubscribe(GoogleApiClient, PendingIntent).

Разбор сообщений


Как мы уже знаем из первой части, каждое вложения состоит из следующих частей:
-Namespace: Идентификатор пространства имен.
-Type: Тип данных.
-Data: Значение данных вложения.

Фрагмент кода демонстрирующий использование MessageListener() для разбора сообщений:
mMessageListener = new MessageListener() {
    @Override
    public void onFound(Message message) {
        // Do something with the message here.
        Log.i(TAG, "Message found: " + message);
        Log.i(TAG, "Message string: " + new String(message.getContent()));
        Log.i(TAG, "Message namespaced type: " + message.getNamespace() +
                "/" + message.getType());
    }

    ...
};


Стоит учесть что разбор содержания зависит от формата байт. Этот пример предполагает, что сообщения закодированы в UTF-8 строку, но ваши сообщения от маячков могут быть закодированы в другой формат.
Чтобы узнать какие пространства имен связаны с вашим проектом, можно вызвать namespaces.list.

Nearby Nearby meassages API на iOS


Для создания проекта использующего Nearby Messages API for iOS нам понадобиться Xcode версии 6.3 или старше.

Google Nearby Messages API для iOS доступен как пакет(pod) CocoaPods. CocoaPods — это менеджер зависимостей с открытым исходным кодом для Swift и Objective-C Cocoa проектов. Для получения дополнительной информации советую ознакомиться с CocoaPods Getting Started guide. Если он у вас не установлен, вы можете сделать это выполнив в терминале следующую комманду:

$ sudo gem install cocoapods

Устанавливаем Nearby messages API использую CocoaPods:

  • Открываем проект или создаём новый, следует убедится что опция Use Automatic Reference Counting включена.
  • Создаём файл с именем Podfile в директории проекта. Этот файл будет определять зависимости проекта.
  • Добавляем зависимости в Podfile. Пример простого Podspec содержащего имя пакета которое для установки
    source 'https://github.com/CocoaPods/Specs.git'
    platform :ios, '7.0'
    pod 'NearbyMessages'
  • В терминале переходим в директорю в котрой находится Podfile
  • Для установки вместе с зависимости API указанных в Podfile необходимо выполнить комманду:
    $ pod install

После этого закрываем Xcode и затем двойным кликом по .xcworkspace проекта запускаем Xcode. Начиная с этого момента, вы должны использовать файл .xcworkspace для открытия проекта.

Как и в случае с Android, нам необходимо получить API ключ. Ключи для Android, iOS и Proximity Beacon API должны быть созданы в рамках одно проекта Google Developers Console. Очень советую ознакомиться с Best practices for securely using API keys

Осторожно GIF. Пример получения API ключа:
image


Теперь, когда всё настроено, мы можем создать объект messageManager и использовать API ключ созданный ранее

#import <GNSMessages.h>

GNSMessageManager *messageManager =
    [[GNSMessageManager alloc] initWithAPIKey:@"API_KEY"];

Подписка в iOS


В iOS сканирование маячков происходит только когда приложение активно. Сканирование маячков в фоновом режиме недоступно для iOS. Что бы подписаться только на BLE маячки нужно задать deviceTypesToDiscover в параметрах подписки kGNSDeviceBLEBeacon.

Фрагмент кода который показывает как это сделать:
_beaconSubscription = [_messageManager
    subscriptionWithMessageFoundHandler:myMessageFoundHandler
                     messageLostHandler:myMessageLostHandler
                            paramsBlock:^(GNSSubscriptionParams *params) {
                              params.deviceTypesToDiscover = kGNSDeviceBLEBeacon;
                            }];


Этот пример кода подписывается только на маячки нашего проекта и получает все сообщения от них.

Если мы хотим получать сообщения от маячков зарегистрированных с другим пространством имён, мы можем передать namespace в параметры подписки. Аналогично мы можем сделать и с типом сообщений которые хотим получать передав конкретный тип сообщений для фильтрации. Для это необходимо задействовать сканирование устройств в GNSStrategy и пробросить значения пространства имен и типа подписки в параметры подписки.

Фрагмент кода который показывает как это сделать:
_beaconSubscription = [_messageManager
    subscriptionWithMessageFoundHandler:myMessageFoundHandler
                     messageLostHandler:myMessageLostHandler
                            paramsBlock:^(GNSSubscriptionParams *params) {
                              params.deviceTypesToDiscover = kGNSDeviceBLEBeacon;
                              params.messageNamespace = @"com.mycompany.mybeaconservice";
                              params.type = @"mybeacontype";
                            }];


По умолчанию, при подписке мы сканируем сразу оба типа маячков, Eddystone и iBeacon. Если у нас задействовано сканирование маячков iBeacon, пользователь получит запрос на использование геолокации. Info.plist приложения должен включать ключ NSLocationAlwaysUsageDescription с крткаим обьяcнением того, почему используеься геолокация. Советую ознакомиться с документацией Apple для деталей.

Если мы хотим сканировать только маячки Eddystone, мы можем отключить сканирование маячков iBeacon в GNSBeaconStrategy. В таком случае iOS не будет запрашивать у пользователя разрешения на использование геолокации.

Фрагмент кода, который показывает как отключить сканирование iBeacon маячков для предыдущего примера:
_beaconSubscription = [_messageManager
    subscriptionWithMessageFoundHandler:myMessageFoundHandler
                     messageLostHandler:myMessageLostHandler
                            paramsBlock:^(GNSSubscriptionParams *params) {
                              params.deviceTypesToDiscover = kGNSDeviceBLEBeacon;
                              params.messageNamespace = @"com.mycompany.mybeaconservice";
                              params.type = @"mybeacontype";
                              params.beaconStrategy = [GNSBeaconStrategy strategyWithParamsBlock:^(GNSBeaconStrategyParams *params) {
                                params.includeIBeacons = NO;
                              };
                            }];


При сканировании маячков iBeacon, диалог запроса разрешений на геолокацию предшествует диалогу разрешений Nearby. Мы можем переопределить этот диалог, например, для того что бы обяьснить пользователю для чего запрашивается разрешение на геолокацию. Для этого необходимо задать permissionRequestHandler в отдельном блоке в параметрах подписки.

Фрагмент кода показывающий как это сделать:
_beaconSubscription = [_messageManager
    subscriptionWithMessageFoundHandler:myMessageFoundHandler
                     messageLostHandler:myMessageLostHandler
                            paramsBlock:^(GNSSubscriptionParams *params) {
                              params.deviceTypesToDiscover = kGNSDeviceBLEBeacon;
                              params.messageNamespace = @"com.mycompany.mybeaconservice";
                              params.type = @"mybeacontype";
                              params.beaconStrategy = [GNSBeaconStrategy strategyWithParamsBlock:^(GNSBeaconStrategyParams *params) {
                                params.includeIBeacons = NO;
                              };
                              params.permissionRequestHandler = ^(GNSPermissionHandler permissionHandler) {
                                // Show your custom dialog here, and don't forget to call permissionHandler after it is dismissed
                                permissionHandler(userGavePermission);
                              };
                            }];


Заключение


Я надеюсь что данный материал будет кому то полезен, сэкономит время и силы.

Комментарии (0)