Привет, любители Habr! По счастливой случайности в августе 2018 года мне посчастливилось вместе с моим товарищем(kirillskiy) начать работать над потрясающим по своей интересности проектом. И вот, днем мы были обычными программистами, а ночью с?у?п?е?р?г?е?р?о?я?м?и? снова программистами, которые бьются над вопросами распознавания движений для людей имеющих ограничения функциональности своих конечностей, естественно этим могли бы пользоваться и здоровые люди, используя подобную технологию самыми разными способами.
В этой статье, Кирилл в общих чертах рассказывает о проекте, я же расскажу подробнее и затрону тему андроида в нем.
Расскажу вначале о проекте целиком, что мы напридумывали и как захотели реализовать это:
1) Была выбрана ЭМГ( Электромиография — регистрация электрической активности мышц) как способ получения данных( о, да, данных будет много). Впервые данный способ был применен в 1907 году, поэтому мы шли проторенной дорогой.
2) Нашли 8-ми канальный ЭМГ датчик работающий по bluetooth(даже имеющий свой API, которое в итоге, оказалось абсолютно бесполезным, ибо пришлось самостоятельно коннектиться как BT устройству. Спасибо хоть спецификацию написали)
3) Мы решили, что работать все будет так:
4) Пункт Android. Я Android developer — и грех было этим не воспользоваться. Андроид делает у нас вот что:
5) Raspberry PI 3B. именно на малину мы поставили Android Things, а потом на ней подняли BT server, который принимает сообщения от Android устройства и двигает соответствующие моторы, приводящие в движение супер-клешню из LEGO.
6) Сервер. Разворачивается Докером локально на компе. Принимает высылаемые вашим устройством данные, обучает нейросеть, возвращает модель.
Часть номер 1. Android. В этот раз рассматрим подкапотное пространство проекта, касающееся Android до момента отправки данных на сервер.
Он называется NUKLEOS (https://github.com/cyber-punk-me/nukleos)
Стэк:
— Kotlin
— MVP
— Dagger2
— Retrofit2
— RxKotlin, RxAndroid
для Raspberry:
-Android Things
На работе мне не дают играться с архитектурой, а тут наконец-то появилась возможность поиграться со старой игрушкой под названием MVP.
Приложение состоит из одной БоттомНавигейшн стайл активити и 4х фрагментов:
Первый из них — «Список всех доступных BT устройств»
Мы выбрали 8ми канальный BT датчик, у которого был свой API для работы с BT. К сожалению api оказался абсолютно бесполезным, ибо он сразу предлагал определить один из 6(вроде) типов движения, но точность распознавания была 80% — а это никуда не годится. Ну и нам были нужны фактические данные. Значение изменения биоэлектрических потенциалов, возникающих в мышцах человека при возбуждении мышечного волокона. А для этого надо было работать с этим датчиком напрямую. Создатели оставили описание протокола работы с ним, поэтому ковыряться пришлось не так долго. пример работы с голыми BT устройствами могу описать в отдельной статье, если будет интересно, но вкратце выглядит это так:
Аккуратно оборачиваем стандартный BT service в RX и получаем меньше боли.
Далее, запускаем сканирование, и благодаря rx на подписке формируем список всех устройств, напихивая их в RecyclerView:
Выбрав одно из устройств, выбираем его, и переходим на следующий экран:
«Настройки датчика»
Подключаемся к нему и запускаем стриминг данных датчика, пользуясь заранее подготовленными нами командами. Благо, что протокол работы с этим устройством создателями датчика описан:
Работа с устройством также заботливо обернута в rx, чтобы работать без боли.
Датчики возвращают БайтАррэи естесственно, и надо было запилить конвертор, частота работы датчиков 200ГЦ… если будет интересно, могу описать подробно(ну или код посмотрите), но в итоге мы работаем с достаточно большим кол-вом данных таким образом:
1 — Нам надо отрисовывать кривые каждого из датчиков. Разумеется, что отрисовывать АБСОЛЮТНО все данные нет смысла, ибо на мобильном устройстве глазу нет смысла рассматривать 200 изменений в секунду на каждом датчике. Поэтому мы будем брать не все.
2 — Нам надо работать со всем объемом данных, если это процесс обучения или распознавания.
для этих нужд RX — идеально подходит со всеми своими фильтрами.
Графики пришлось сделать свои. Кому интересно — посмотрите PowerfullChartsView в папке views.
А теперь немного видео:
На видео вы увидите, как Кирилл работает с системой в целом. На видео ведется работа с моделью. Но модель находится на сервере. В дальнейшем она конечно же будет находиться на девайсе, что существенно ускорит отклик)
Пишите, какие аспекты интересны, какие рассказать подробнее. Естественно, мы работаем над проектом и открыты к вашим предложениям.
Весь проект на github тут
В этой статье, Кирилл в общих чертах рассказывает о проекте, я же расскажу подробнее и затрону тему андроида в нем.
Расскажу вначале о проекте целиком, что мы напридумывали и как захотели реализовать это:
1) Была выбрана ЭМГ( Электромиография — регистрация электрической активности мышц) как способ получения данных( о, да, данных будет много). Впервые данный способ был применен в 1907 году, поэтому мы шли проторенной дорогой.
2) Нашли 8-ми канальный ЭМГ датчик работающий по bluetooth(даже имеющий свой API, которое в итоге, оказалось абсолютно бесполезным, ибо пришлось самостоятельно коннектиться как BT устройству. Спасибо хоть спецификацию написали)
3) Мы решили, что работать все будет так:
- режим тренировки. Одеваем датчик на предплечье, выбираем тип движения, который будем тренировать. Например… «сгибание кисти». и начинаем тернировку(раз 12 повторяем движение). Полученные в этот момент данные, мы сохраним и отправим потом на сервер, где обучим нейросеточку(спокойно, про это тоже расскажу)
- режим распознавания непосредственно движения. Данные снятые в процессе движения сопоставляются уже с моделью, поолученной в результате обучения нейросетки. По результатам мы уже получем «СГИБАНИЕ КИСТИ», например.
- режим движения. Надо согласно определенному типу движения, что-то заставить перемещаться. Например манипулятор собранный на кухне из конструктора(ппц, какого дорогого) известного датского производителя.
4) Пункт Android. Я Android developer — и грех было этим не воспользоваться. Андроид делает у нас вот что:
- находит все доступные BT устройства
- подключается к датчику
- отрисовывает график, основываясь на данных снятых с датчиков(8 каналов, частота 200гц). 8 красивейших, разноцветных кривых.
- реализует режим тренировки (выбор типа обучаемого движения, кнопка старта обучения, кнопка отправки данных)
- реализует клиент-серверное взаимодействие. Надобно отправить данные на сервер, чтоб обучалась нейросетка
- реализует соединение и взаимодействие с Raspberry PI 3B, к которой припаяны моторы, приподящие в движение манипулятор.
5) Raspberry PI 3B. именно на малину мы поставили Android Things, а потом на ней подняли BT server, который принимает сообщения от Android устройства и двигает соответствующие моторы, приводящие в движение супер-клешню из LEGO.
6) Сервер. Разворачивается Докером локально на компе. Принимает высылаемые вашим устройством данные, обучает нейросеть, возвращает модель.
Часть номер 1. Android. В этот раз рассматрим подкапотное пространство проекта, касающееся Android до момента отправки данных на сервер.
Он называется NUKLEOS (https://github.com/cyber-punk-me/nukleos)
Стэк:
— Kotlin
— MVP
— Dagger2
— Retrofit2
— RxKotlin, RxAndroid
для Raspberry:
-Android Things
На работе мне не дают играться с архитектурой, а тут наконец-то появилась возможность поиграться со старой игрушкой под названием MVP.
Приложение состоит из одной БоттомНавигейшн стайл активити и 4х фрагментов:
Первый из них — «Список всех доступных BT устройств»
Мы выбрали 8ми канальный BT датчик, у которого был свой API для работы с BT. К сожалению api оказался абсолютно бесполезным, ибо он сразу предлагал определить один из 6(вроде) типов движения, но точность распознавания была 80% — а это никуда не годится. Ну и нам были нужны фактические данные. Значение изменения биоэлектрических потенциалов, возникающих в мышцах человека при возбуждении мышечного волокона. А для этого надо было работать с этим датчиком напрямую. Создатели оставили описание протокола работы с ним, поэтому ковыряться пришлось не так долго. пример работы с голыми BT устройствами могу описать в отдельной статье, если будет интересно, но вкратце выглядит это так:
class BluetoothConnector(val context: Context) {
private val mBTLowEnergyScanner by lazy {
(context.getSystemService(Activity.BLUETOOTH_SERVICE) as BluetoothManager)
.adapter.bluetoothLeScanner
}
private var mBluetoothScanCallback: BluetoothScanCallback? = null
// scan.
fun startBluetoothScan(serviceUUID: UUID?) = Flowable.create<BluetoothDevice>({
mBluetoothScanCallback = BluetoothScanCallback(it)
if (serviceUUID == null) {
mBTLowEnergyScanner.startScan(mBluetoothScanCallback)
} else {
mBTLowEnergyScanner.startScan(
arrayListOf(ScanFilter.Builder().setServiceUuid(ParcelUuid(serviceUUID)).build()),
ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build(),
mBluetoothScanCallback)
}
}, BackpressureStrategy.BUFFER).apply {
doOnCancel { mBTLowEnergyScanner.stopScan(mBluetoothScanCallback) }
}
// scan with timeout
fun startBluetoothScan(interval: Long, timeUnit: TimeUnit, serviceUUID: UUID? = null) = startBluetoothScan(serviceUUID).takeUntil(Flowable.timer(interval, timeUnit))
inner class BluetoothScanCallback(private val emitter: FlowableEmitter<BluetoothDevice>) : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult?) {
super.onScanResult(callbackType, result)
result?.let {
it.device.apply { emitter.onNext(this) }
}
}
override fun onScanFailed(errorCode: Int) {
super.onScanFailed(errorCode)
emitter.onError(RuntimeException())
}
}
}
Аккуратно оборачиваем стандартный BT service в RX и получаем меньше боли.
Далее, запускаем сканирование, и благодаря rx на подписке формируем список всех устройств, напихивая их в RecyclerView:
mFindSubscription = mFindFlowable
?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe({
if (it !in mBluetoothStuffManager.foundBTDevicesList) {
addSensorToList(SensorStuff(it.name, it.address))
mBluetoothStuffManager.foundBTDevicesList.add(it)
}
}, {
hideFindLoader()
showFindError()
if (mBluetoothStuffManager.foundBTDevicesList.isEmpty()) {
showEmptyListText()
}
}, {
hideFindLoader()
showFindSuccess()
if (mBluetoothStuffManager.foundBTDevicesList.isEmpty()) {
showEmptyListText()
}
})
Выбрав одно из устройств, выбираем его, и переходим на следующий экран:
«Настройки датчика»
Подключаемся к нему и запускаем стриминг данных датчика, пользуясь заранее подготовленными нами командами. Благо, что протокол работы с этим устройством создателями датчика описан:
object CommandList {
//Stop the streaming
fun stopStreaming(): Command {
val command_data = 0x01.toByte()
val payload_data = 3.toByte()
val emg_mode = 0x00.toByte()
val imu_mode = 0x00.toByte()
val class_mode = 0x00.toByte()
return byteArrayOf(command_data, payload_data, emg_mode, imu_mode, class_mode)
}
// Start streaming (with filter)
fun emgFilteredOnly(): Command {
val command_data = 0x01.toByte()
val payload_data = 3.toByte()
val emg_mode = 0x02.toByte()
val imu_mode = 0x00.toByte()
val class_mode = 0x00.toByte()
return byteArrayOf(command_data, payload_data, emg_mode, imu_mode, class_mode)
}
.....
Работа с устройством также заботливо обернута в rx, чтобы работать без боли.
Датчики возвращают БайтАррэи естесственно, и надо было запилить конвертор, частота работы датчиков 200ГЦ… если будет интересно, могу описать подробно(ну или код посмотрите), но в итоге мы работаем с достаточно большим кол-вом данных таким образом:
1 — Нам надо отрисовывать кривые каждого из датчиков. Разумеется, что отрисовывать АБСОЛЮТНО все данные нет смысла, ибо на мобильном устройстве глазу нет смысла рассматривать 200 изменений в секунду на каждом датчике. Поэтому мы будем брать не все.
2 — Нам надо работать со всем объемом данных, если это процесс обучения или распознавания.
для этих нужд RX — идеально подходит со всеми своими фильтрами.
Графики пришлось сделать свои. Кому интересно — посмотрите PowerfullChartsView в папке views.
А теперь немного видео:
На видео вы увидите, как Кирилл работает с системой в целом. На видео ведется работа с моделью. Но модель находится на сервере. В дальнейшем она конечно же будет находиться на девайсе, что существенно ускорит отклик)
Пишите, какие аспекты интересны, какие рассказать подробнее. Естественно, мы работаем над проектом и открыты к вашим предложениям.
Весь проект на github тут
Комментарии (4)
Javian
29.01.2019 22:17off
голыми BT устройствами
Вообще бы было интересно. Попали в руки китайские часы с BT — шагометр, отображение уведомлений. Было бы интересно попытаться разобраться как оно общается с родной программой и применить часы с какой-то пользой.garastard Автор
29.01.2019 23:02+2Это был небольшой адок. Я ранее никогда не работал с BT устройствами напрямую и пришлось много покурить этот вопрос в интернетиках. Я обязательно напишу статью на эту тему. Мне ее не хватало в тот момент.
dedyshka
ROS не рассматривали в качестве ОС (многими считается стандартом в данной области)?
garastard Автор
Есть полноценная робо-рука с LEGO Mindstorms, в которой есть даже микроконтроллер есть и он, да, поддерживает ROS.
Но мы к тому моменту выбора манипулятора уже запилили приложение на мобилки, потом приложение шивелящее моторчики, используя Android Things. Потом купили 2 набора эксковаторов — лесапильщиков LEGO, и понеслась…