monnoroch / blerpc-android Goto Github PK
View Code? Open in Web Editor NEWLicense: MIT License
License: MIT License
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()
метод.
Сейчас для поддержки создания мок объекта bleServiceDriver добавлен метод init(queue:) и peripheral сделан опциональным типом. Необходимо убрать добавленный метод и вернуть peripheral как константу, при этом сохранив работоспособность тестов.
Добавить поддержку little endian для декодинга/энкодинга данных
У BluetoothGattCharacteristic
есть метод public void setWriteType(int writeType)
, в который можно передать: WRITE_TYPE_DEFAULT
, WRITE_TYPE_NO_RESPONSE
и WRITE_TYPE_SIGNED
.
Если установлен тип WRITE_TYPE_DEFAULT
, то при Write
запросе всегда возвращается в ответе то, что мы послали. Как понятно по названию - именно этот тип поставлен по дефолту и наш девайс тратит, хоть и незначительно, больше энергии на то, чтобы послать обратно те же самы данные.
Это нам не нужно, поэтому стоит добавить возможность выбора типа запроса на запись.
Задача состоит из двух частей:
MethodOption
в blerpc.proto
для возможности изменения типа запроса для отдельного сообщенияКак минимум, это отступы в 2 пробела. Возможно мы еще что-то кастомное используем у себя. Надо, чтоб было полностью стандартно.
Блок кода, игнорирующий обновление после отписки протестирован тестом testValueChangedAfterReset
. Это не правильно:
В данный момент BleRpcChannel
устроен таким образом, что при подписке на характеристику невозможно узнать, прошла ли подписка успешно, пока не придет первое значение.
Пример: мы подписались на батарею, но девайс пришлет нам значение, только когда изменится процент, что может случиться допустим через 20 секунд. А это значит, что 20 секунд мы не будем знать, подписка на характеристику в процессе или мы уже подписались.
Особенно критично это в тех ситуациях, когда мы должны точно знать, что подписка активна. Примером служит обновление прошивки девайса. Мы должны подписаться на получение номеров блоков файлы прошивки, которые нужно послать на девайс и только после этого посылать метаданные образа прошивки.
Варианты решений:
onDescriptorWrite()
мы получаем коллбэк о том, что подписка прошла успешно, а в onCharacteristicChanged()
мы получаем уже значения по этой подписке и посылаем их подписчику.onDescriptorWrite()
, сразу же отправить подписчику пустой Message
, чтобы его уведомить о том, что все ок. Для этого в начало метода BleRpcChannel#185 handleSubscribe()
, который вызывается при успешной подписке, нужно добавить следующий кодprivate void handleSubscribe(BluetoothGattCharacteristic characteristic, byte[] value) {
RpcCall call = calls.getFirst();
call.done.run(call.responsePrototype.getDefaultInstanceForType());
...
}
А подписчик (приложение) уже сам решит, игнорировать ему первое пустое значение по подписке или же обработать его, чтобы понять, что подписка активна.
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();
}
...
}
Преимущество еще и в том, что подписчик (приложение) может сам решить нужен ему коллбэк при успешной подписке или нет.
Из диалога:
И тут написать документацию, что мы не возвращаем error потому, что на саомом деле она по другому каналу вернется, с указанием имени теста, который это проверяет.
Related: #66 (comment)
Сейчас BleRpc вызывает только setCharacteristicNotification с параметром true, но с параметром false при отписке никто не вызывает, что в зависимости от имплементации стека у производителя чипа, может нагружать систему ненужной работой и/или привести к сбою.
Нужно добавить такой вызов при уничтожении подписки.
Раз уж мы создаем библиотеку для всех и не предполагаем, что пользователям придется что-то доделывать под себя, то нужно предоставить максимальное количество действий которое может пригодиться.
Одно из главных действий BLE - RSSI (received signal strength indicator), это запрос, который возвращает уровень сигнала, по которому мы можем с хорошей точностью определить, как далеко находится девайс.
Некоторые варианты применения:
Чтобы получить 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);
}
});
}
Решение:
enum MethodType {
UNKNOWN = 0;
WRITE = 1;
READ = 2;
SUBSCRIBE = 3;
RSSI = 4;
MTU = 5;
}
И в BleRpcChannel
обработать запросы аналогично READ
, WRITE
.
Для массивов недостаточно иметь from
и to
аннотации. Нужно еще иметь возможность указать длину каждого элемента в массиве в байтах. Предлагаю добавить cell
или cell_length
аннотацию. А конвертер будет проверять:
Сейчас невозможно отправить команду на чтение/запись, если последней командой была отписка от характеристики.
Порядок воспроизведения:
-подписаться на характеристику
-отписаться от характеристики [подождав пока не придет значение]
-попробовать послать любую команду
ОР:
команда отправлена
ФР:
команда была проигнорирована, ответ "завис"
Судя по коду увидел такую вещь, что отписка от BLE характеристики начнется только тогда, когда придет значение по подписке и в этот момент не будет активных подписок или произойдет ошибка.
Пример 1: у нас много mAh и на 1% мы можем работать 1 час. Мы подписались на батарею, через 5 минут вызвали startCancel() и забыли. А подписка будет все еще активна целый час, пока процент батареи не изменится.
Пример 2: мы подписались на получение номеров блоков прошивки, после чего посылаем метаданные прошивки. И тут метаданные не подходят и мы отменяем перепрошивку и отписываемся от перепрошивки, что вызовет startCancel(). А номер блока никогда не придет и подписка будет активна до тех пор, пока мы будем подключены.
Не знаю, влияет ли активная подписка, по которой не приходят значения на батарею и стоит ли думать о закрытии этой фичи или это ни на что не влияет
Currently we explicitly call workHandler.post()
in all callbacks, which can be avoided by using an overload with a handler.
Как обнаружилось, мы можем ожидать, что ответ придет пустой (он нам не нужен и не зачем его обрабатывать), а на самом деле вернуться какие-либо данные.
Например по дефолту, если response
пустой, то вернется копия request
. Или API обновится и девайс будет посылать дополнительные данные, а предыдущие версии приложения не должны при этом ломаться. Таким образом если длина реального response
в байтах больше ожидаемого - мы не кидаем ошибку.
Если реальная длина меньше ожидаемой, то я предлагаю оставить ошибку, так как такого быть не должно, при обновлении API старые поля меняться не должны.
Protoс - простая программа, которая парсит proto
файлы и передает обработанную информацию плагинам, которые уже генерируют нужные классы на нужном языке. Java плагин -генерирует основные java классы и т.д. Плагин - это самая обычная программа (бинарник), которая берет 'in' поток и создает файлы содержащие код.
Мы написали на java свой плагин, который генерирует Rx обертку, но при сборке jar программу мы просто оборачиваем в бинарник для Linux и MacOs. Нужно добавить еще создание пакета для Windows.
Для того, чтобы protobuf-gradle-plugin мог использовать в качестве плагина jar файл, нужно сделать следующее:
classifier
, так как protobuf-gradle-plugin не поддерживает jar без него, если его не передать, то будет добавлен стандартный classifier
и наш jar файл будет просто не найден в репозитории. Убедиться в подставлении стандартного classifier
можно здесь.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 характеристику с помощью которой можно реализовать двусторонний стриминг: ты пишешь в характеристику значения по-одному, а она присылает ответы, когда хочет.
При наличии этого инструмента можно сильно упростить прошивку, например.
Context:
. Now I'm thinking these should not be ignored: the user is free to ignore them if needed, but the library should not hide errors.На данный момент написан protobuf plugin, который генерирует перед компиляцией приложения RxJava обертку. Задача заключается в том, чтобы встроить в этот плагин или написать отдельный плагин для валидации аннотаций автоматического конвертера, которая сейчас проводится в Runtime.
Преимущества данного подхода очевидны:
Но нужно продумать:
Наиболее хорошим вариантом кажется написание отдельного плагина для валидации аннотаций, так как:
Так ка один пакет не может быть более 20 байт, нам приходится разбивать метод на несколько просто чтобы размер пакета был меньше. Это очень неудобно. Было бы здорово сказать, что размер сообщения сколько удобно и разработать протокол, который хотя бы только для подписки, мержит несколько ответов в один большой.
Например, бывают боля, которые должны принимать диапазон от 10 до 50. Они будут упихнуты в один байт. Хорошо бы иметь аннотации для обозначения реального диапазона и код для их проверки.
Сейчас мы конфигурируем 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
А так же то, что метод Subscribe должен иметь в ответе stream
Нужно добавить логирование следующим образом:
Level.INFO:
Level.WARNING
options
Hello,
I've released jprotoc 0.8.1. It has bug fixes for nested protos and proto file names with invalid characters.
https://github.com/salesforce/grpc-java-contrib/releases/tag/v0.8.1
Добавить возможность указывать ByteOrder в аннотациях к message и в аннотациях к отдельным field
При повышении версии библиотеки она перестала заливаться в Bintray. На это есть несколько причин:
userOrg
- это вообще магия, сложно понять, почему это нужно было, тяжело было это вычислитьdoclint
, которая появилась с повышением версии Gradle и не воспринимает ссылки на Java классы в JavadocA declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.