Code Monkey home page Code Monkey logo

blerpc-android's People

Contributors

aseeveidev avatar monnoroch avatar nikolas-lfdesigns avatar pffan91 avatar st-small avatar vadim-aura avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

blerpc-android's Issues

Make protobuf messages backwards-compatible

Currently incoming messages parser are forward-compatible, thus supporting messages with more information than expected. A parser should be backward-compatible too: e.g. support older versions of the same message while proto declaration is set to have more information, than received.
All fields that are not found in the raw value should be set to default values.

Обработать ошибку при соединении с девайсом

Иногда bluetoothDevice.connectGatt(...) может вернуть null, если в процессе соединения возникает ошибка:

public BluetoothGatt connectGatt(Context context, boolean autoConnect,
                                 BluetoothGattCallback callback, int transport, int phy,
                                 Handler handler) {
  if (callback == null)
    throw new NullPointerException("callback is null");

  // TODO(Bluetooth) check whether platform support BLE
  //     Do the check here or in GattServer?
  BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
  IBluetoothManager managerService = adapter.getBluetoothManager();
  try {
    IBluetoothGatt iGatt = managerService.getBluetoothGatt();
    if (iGatt == null) {
      // BLE is not supported
      return null;
    }
    BluetoothGatt gatt = new BluetoothGatt(iGatt, this, transport, phy);
    gatt.connect(autoConnect, callback, handler);
    return gatt;
  } catch (RemoteException e) {Log.e(TAG, "", e);}
  return null;
}

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

bluetoothGatt = Optional.of(bluetoothDevice.connectGatt(context, true, gattCallback));

Эту строку нужно изменить так, чтобы если вернулся null - завершить все текущие запросы с ошибкой, вызвав failAllAndReset() метод.

Make init(queue:) private and peripheral non optional type.

Сейчас для поддержки создания мок объекта bleServiceDriver добавлен метод init(queue:) и peripheral сделан опциональным типом. Необходимо убрать добавленный метод и вернуть peripheral как константу, при этом сохранив работоспособность тестов.

Добавить возможность выбора WriteType по дефолту

У BluetoothGattCharacteristic есть метод public void setWriteType(int writeType), в который можно передать: WRITE_TYPE_DEFAULT, WRITE_TYPE_NO_RESPONSE и WRITE_TYPE_SIGNED.

Если установлен тип WRITE_TYPE_DEFAULT, то при Write запросе всегда возвращается в ответе то, что мы послали. Как понятно по названию - именно этот тип поставлен по дефолту и наш девайс тратит, хоть и незначительно, больше энергии на то, чтобы послать обратно те же самы данные.

Это нам не нужно, поэтому стоит добавить возможность выбора типа запроса на запись.

Задача состоит из двух частей:

  • добавить возможность выбора типа для всех запросов по умолчанию
  • добавить MethodOption в blerpc.proto для возможности изменения типа запроса для отдельного сообщения

Переоформить по Google Java Style Guide

Как минимум, это отступы в 2 пробела. Возможно мы еще что-то кастомное используем у себя. Надо, чтоб было полностью стандартно.

Улучшить тесты

Блок кода, игнорирующий обновление после отписки протестирован тестом testValueChangedAfterReset. Это не правильно:

  1. Reset тут вообще ни при чем.
  2. Там два if и нужно два теста. Конкретных теста на именно эти ситуации, а не reset.

Нужно уведомлять об успешной подписке на BLE характеристику

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

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

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

Варианты решений:

  1. В onDescriptorWrite() мы получаем коллбэк о том, что подписка прошла успешно, а в onCharacteristicChanged() мы получаем уже значения по этой подписке и посылаем их подписчику.
    Нам нужно при успешной подписке, о чем мы узнаем в onDescriptorWrite(), сразу же отправить подписчику пустой Message, чтобы его уведомить о том, что все ок. Для этого в начало метода BleRpcChannel#185 handleSubscribe(), который вызывается при успешной подписке, нужно добавить следующий код
private void handleSubscribe(BluetoothGattCharacteristic characteristic, byte[] value) {
    RpcCall call = calls.getFirst();
    call.done.run(call.responsePrototype.getDefaultInstanceForType());
    ...
}

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

  1. Создать SubscribtionBleRpcController extends BleRpcController, в который добавить + 1 абстрактный метод onSubscribeSuccess(). И если мы используем именно SubscribtionBleRpcController при создании запроса, то BleRpcChannel вызовет этот коллбэк при успешной подписке. В начало метода BleRpcChannel#185 handleSubscribe(), который вызывается при успешной подписке нужно добавить следующий код
private void handleSubscribe(BluetoothGattCharacteristic characteristic, byte[] value) {
    BleRpcController controller = calls.getFirst().controller;
    if (controller instanceOf SubscriptionBleRpcController) {
        ((SubscriptionBleRpcController) controller).onSubscribeSuccess();
    }
    ...
}

Преимущество еще и в том, что подписчик (приложение) может сам решить нужен ему коллбэк при успешной подписке или нет.

Нет системного вызова для отписки от характеристики

Сейчас BleRpc вызывает только setCharacteristicNotification с параметром true, но с параметром false при отписке никто не вызывает, что в зависимости от имплементации стека у производителя чипа, может нагружать систему ненужной работой и/или привести к сбою.
Нужно добавить такой вызов при уничтожении подписки.

Добавить возможность запроса RSSI и MTU

Раз уж мы создаем библиотеку для всех и не предполагаем, что пользователям придется что-то доделывать под себя, то нужно предоставить максимальное количество действий которое может пригодиться.

Одно из главных действий BLE - RSSI (received signal strength indicator), это запрос, который возвращает уровень сигнала, по которому мы можем с хорошей точностью определить, как далеко находится девайс.

Некоторые варианты применения:

  • для нахождения девайса. Есть приложение для наушников Apple, которое показывает как далеко находится девайс (не показывает в какой стороне, но это легко определить пользователю)
  • для навигации в помещении. https://habrahabr.ru/post/245325/

Чтобы получить RSSI нужно вызвать соответствующий запрос:

boolean isRequestSuccessful = bluetoothGatt.readRemoteRssi();

И обработать ответ в BluetoothGattCallback, который используется при создании BluetoothGatt:

@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
    workHandler.post(() -> {
        if (status != BluetoothGatt.GATT_SUCCESS) {
            handleRssiError("Read rssi failed, status=%d.", status);
        } else {
            handleRssiResult(rssi);
        }
    });
}

Также может понадобиться изменение размера MTU (Maximum Transmission Unit) - максимальный размер передаваемого пакета.

Чтобы изменить MTU нужно вызвать соответствующий запрос:

boolean isRequestSuccessful = bluetoothGatt.requestMtu(mtuSize);

И обработать ответ в BluetoothGattCallback, который используется при создании BluetoothGatt:

@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
    workHandler.post(() -> {
        if (status != BluetoothGatt.GATT_SUCCESS) {
            handleMtuError("Change mtu failed, status=%d.", status);
        } else {
            handleMtuResult(mtu);
        }
    });
}

Решение:

  1. Учитывая, что запросы для получения RSSI и изменения MTU вызываются иным образом по сравнению с другими запросами, то нужно создать дополнительные типы запросов:
enum MethodType {
    UNKNOWN = 0;
    WRITE = 1;
    READ = 2;
    SUBSCRIBE = 3;
    RSSI = 4;
    MTU = 5;
}

И в BleRpcChannel обработать запросы аналогично READ, WRITE.

Добавить поддержку массивов конвертером

Для массивов недостаточно иметь from и to аннотации. Нужно еще иметь возможность указать длину каждого элемента в массиве в байтах. Предлагаю добавить cell или cell_length аннотацию. А конвертер будет проверять:

  • чтобы она стояла только для массивов
  • чтобы она была кратна длине поля
  • чтобы она подходила для типа, который содержит массив

Невозможно отправить команду сразу после отписки от любой характеристики

Сейчас невозможно отправить команду на чтение/запись, если последней командой была отписка от характеристики.
Порядок воспроизведения:
-подписаться на характеристику
-отписаться от характеристики [подождав пока не придет значение]
-попробовать послать любую команду

ОР:
команда отправлена
ФР:
команда была проигнорирована, ответ "завис"

Отписываться от BLE характеристика при вызове startCancel

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

Пример 1: у нас много mAh и на 1% мы можем работать 1 час. Мы подписались на батарею, через 5 минут вызвали startCancel() и забыли. А подписка будет все еще активна целый час, пока процент батареи не изменится.

Пример 2: мы подписались на получение номеров блоков прошивки, после чего посылаем метаданные прошивки. И тут метаданные не подходят и мы отменяем перепрошивку и отписываемся от перепрошивки, что вызовет startCancel(). А номер блока никогда не придет и подписка будет активна до тех пор, пока мы будем подключены.

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

Убрать валидацию длины сообщения

Как обнаружилось, мы можем ожидать, что ответ придет пустой (он нам не нужен и не зачем его обрабатывать), а на самом деле вернуться какие-либо данные.

Например по дефолту, если response пустой, то вернется копия request. Или API обновится и девайс будет посылать дополнительные данные, а предыдущие версии приложения не должны при этом ломаться. Таким образом если длина реального response в байтах больше ожидаемого - мы не кидаем ошибку.

Если реальная длина меньше ожидаемой, то я предлагаю оставить ошибку, так как такого быть не должно, при обновлении API старые поля меняться не должны.

Добавить поддержку Windows для Rx плагина

Protoс - простая программа, которая парсит proto файлы и передает обработанную информацию плагинам, которые уже генерируют нужные классы на нужном языке. Java плагин -генерирует основные java классы и т.д. Плагин - это самая обычная программа (бинарник), которая берет 'in' поток и создает файлы содержащие код.

Мы написали на java свой плагин, который генерирует Rx обертку, но при сборке jar программу мы просто оборачиваем в бинарник для Linux и MacOs. Нужно добавить еще создание пакета для Windows.

Сделать скомпилированный jar файл Rx плагина исполняемым и добавить classsifier

Для того, чтобы protobuf-gradle-plugin мог использовать в качестве плагина jar файл, нужно сделать следующее:

  • добавить артефакту classifier, так как protobuf-gradle-plugin не поддерживает jar без него, если его не передать, то будет добавлен стандартный classifier и наш jar файл будет просто не найден в репозитории. Убедиться в подставлении стандартного classifier можно здесь.
  • сделать jar файл полностью исполняемым. Из множества испробованных библиотек, полностью исполняемым файл может делать только spring-boot. Подробнее тут. Вот что написано в доке, именно это нам и нужно:
    Spring Boot provides support for fully executable archives. An archive is made fully executable by prepending a shell script that knows how to launch the application. On Unix-like platforms, this launch script allows the archive to be run directly like any other executable or to be installed as a service.

Итог: залил в Buntray, подключил в приложении, все заработало

Двусторонний стриминг

Можно сделать SUBSCRIBE + WRITE характеристику с помощью которой можно реализовать двусторонний стриминг: ты пишешь в характеристику значения по-одному, а она присылает ответы, когда хочет.

При наличии этого инструмента можно сильно упростить прошивку, например.

Сделать валидацию аннотацию для автоматического конвертера в protobuf plugin

На данный момент написан protobuf plugin, который генерирует перед компиляцией приложения RxJava обертку. Задача заключается в том, чтобы встроить в этот плагин или написать отдельный плагин для валидации аннотаций автоматического конвертера, которая сейчас проводится в Runtime.

Преимущества данного подхода очевидны:

  • мы получаем ошибки на этапе компиляции, соответственно избегаем того, что можно пропустить какую-нибудь ошибку
  • в Runtime меньше работы, значит приложение будет работать быстрее, хоть и незначительно

Но нужно продумать:

  • насколько это будет удобно
  • читаемо
  • сколько займет времени
  • стоит ли это делать сейчас?

Наиболее хорошим вариантом кажется написание отдельного плагина для валидации аннотаций, так как:

  • мы можем не использовать RxJava обертки, но хотеть валидацию
  • мы можем использовать RxJava, но не хотеть валидацию

Поддержать multipart-сообщения

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

Поддержать ограничения на размер.

Например, бывают боля, которые должны принимать диапазон от 10 до 50. Они будут упихнуты в один байт. Хорошо бы иметь аннотации для обозначения реального диапазона и код для их проверки.

Автоматический конвертер для BleRpc

Сейчас мы конфигурируем BleRpc своим инстансам interface MessageConverter для конвертации байты <-> протобуфы, в которой мы просто достаем нужные байты и интерпритируем их как нужные типы данных. При этом, в документации ко всем полям мы пишем сколько байт они занимают. Моя идея:

Заменить комментарии на аннотаци:

// Response with last steps.
//
// Encoded as 9 bytes.
message GetStepsResponse {
    // Last steps count measured by the pedometer. (4 byte).
    int32 steps = 1;
    // Walk type determined by the pedometer (1 byte).
    int32 walk_type = 2;
    // Number of seconds  since previous step count measurement in seconds (4 byte).
    int32 seconds_since_last_measurement = 3;
}

превратится в

// Response with last steps.
message GetStepsResponse {
    option (blerpc.message) = {
        proto_bytes: 9
    };

    // Last steps count measured by the pedometer.
    int32 steps = 1 [(blerpc.field) = {
        from_byte: 0
        to_byte: 4
    }];

    // Walk type determined by the pedometer.
    int32 walk_type = 2 [(blerpc.field) = {
        from_byte: 4
        to_byte: 5
    }];

    // Number of seconds  since previous step count measurement in seconds.
    int32 seconds_since_last_measurement = 3 [(blerpc.field) = {
        from_byte: 5
        to_byte: 9
    }];
}

Написать конвертер

blerpc.AnnotationMessageConverter, который будет читать эти аннотации и самостоятельно конвертировать обьекты в байты и назад. Если пользователю нужны хитрые кейсы, когда в одном байте несколько bool переменных, например, то тут ему придется написать свой конвертер для этого кейса, но в 90% случаев по умолчанию можно будет использовать этот. Наше API пока что покрывается им на 100%.

Контроль версий модулей

В данный момент команда bintrayUpload запусткает по очереди аплоад каждого из наших модулей по очереди. Если загрузка одного из модулей не пройдет, то другие модули загружены точно не будут, потому что цепочка прервется.

В какой ситуации может быть ошибка - когда модуль с такой же версией уже загружен в JCenter(). Таким образом если мы сделали изменения и изменили версию только у reactive-blerpc, то он загружен в JCenter() не будет, так как версия blerpc не изменилась и она остановит всю цепочку.

Первой мыслью было использовать флаг override, который появился буквально 2 недели назад в новом обновлении либы для Upload. Но это плохо, вдруг мы забудем поменять версию и тогда у пользователей просто обновится модуль, хотя они версию не меняли.

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

after_success:
  - if [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST_BRANCH" == "" ]; then
          gradle blerpc:bintrayUpload -PbintrayUser=monnoroch -PbintrayKey="$BINTRAY_API_KEY" -PdryRun=false;
          gradle blerpcproto:bintrayUpload -PbintrayUser=monnoroch -PbintrayKey="$BINTRAY_API_KEY" -PdryRun=false;
          gradle reactive-blerpc:bintrayUpload -PbintrayUser=monnoroch -PbintrayKey="$BINTRAY_API_KEY" -PdryRun=false;
    fi

Добавить логирование в конвертер

Нужно добавить логирование следующим образом:

Level.INFO:

  • посылаемые и приходящие байты и protobuf message
  • подключение к девайсу, отключение, запрос, подписка и т.д.
  • фабрика создана, сервис создан

Level.WARNING

  • ответ от девайса больше, чем заявлено в options
  • ошибка при запросе
  • остальные некритические ошибки

Исправить ошибку при заливке в Bintray

При повышении версии библиотеки она перестала заливаться в Bintray. На это есть несколько причин:

  • давно не заливали нашу либо, из-за чего Bintray протух. Bintray - это связующее звено между jcenter и нами. У него есть свой локальный репозиторий куда все вначале заливается, а потом копируется в jcenter. Вот локальный репозиторий протух, нужно было туда зайти и его восстановить
  • повысить версию библиотеки, со старой не работает
  • в Gradle файле изменить параметр userOrg - это вообще магия, сложно понять, почему это нужно было, тяжело было это вычислить
  • не кидать ошибку в задаче doclint, которая появилась с повышением версии Gradle и не воспринимает ссылки на Java классы в Javadoc

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.