- PVSM.RU - https://www.pvsm.ru -
Привет, любители Habr! По счастливой случайности в августе 2018 года мне посчастливилось вместе с моим товарищем(kirillskiy [1]) начать работать над потрясающим по своей интересности проектом. И вот, днем мы были обычными программистами, а ночью с̶у̶п̶е̶р̶г̶е̶р̶о̶я̶м̶и̶ снова программистами, которые бьются над вопросами распознавания движений для людей имеющих ограничения функциональности своих конечностей, естественно этим могли бы пользоваться и здоровые люди, используя подобную технологию самыми разными способами.
В этой статье [2], Кирилл в общих чертах рассказывает о проекте, я же расскажу подробнее и затрону тему андроида в нем.
Расскажу вначале о проекте целиком, что мы напридумывали и как захотели реализовать это:
1) Была выбрана ЭМГ [3]( Электромиография — регистрация электрической активности мышц) как способ получения данных( о, да, данных будет много). Впервые данный способ был применен в 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 устройствами могу описать в отдельной статье, если будет интересно, но вкратце выглядит это так:
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]
Автор: garastard
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/android-development/306957
Ссылки в тексте:
[1] kirillskiy: https://habr.com/ru/users/kirillskiy/
[2] этой статье: https://habr.com/ru/post/437888/
[3] ЭМГ: https://ru.wikipedia.org/wiki/%D0%AD%D0%BB%D0%B5%D0%BA%D1%82%D1%80%D0%BE%D0%BC%D0%B8%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%8F
[4] тут: https://github.com/cyber-punk-me
[5] Источник: https://habr.com/ru/post/438034/?utm_campaign=438034
Нажмите здесь для печати.