Android, Rx e Kotlin, ou como fazer uma garra de Lego encolher. Parte 1

Olá, amantes da Habr! Por uma coincidência de sorte, em agosto de 2018, tive a sorte de começar a trabalhar com meu amigo ( kirillskiy ) em um projeto que era incrível em seu interesse. E assim, durante o dia éramos programadores comuns, e à noite estávamos novamente programadores que lutam com questões de reconhecimento de movimentos para pessoas com funcionalidade limitada de seus membros, pessoas naturalmente saudáveis ​​poderiam usar isso, usando tecnologia semelhante de várias maneiras.

Neste artigo , Cyril, em termos gerais, fala sobre o projeto, mas vou contar com mais detalhes e tocar no tópico do andróide nele.
Primeiro, vou falar sobre todo o projeto, o que criamos e como queremos implementar isso:

1) eletromiografia (eletromiografia - registrando a atividade elétrica dos músculos) foi escolhida como uma maneira de obter dados (ah, sim, haverá muitos dados). Pela primeira vez, esse método foi aplicado em 1907, por isso caminhamos ao longo da trilha batida.

2) Encontramos um sensor EMG de 8 canais que funciona com bluetooth (mesmo tendo sua própria API, que no fim acabou sendo absolutamente inútil, porque eu mesmo tinha que me conectar como dispositivo BT. Obrigado pelo menos, escrevemos uma especificação)

3) Decidimos que tudo funcionaria assim:

  • modo de treinamento. Vestimos o sensor no antebraço, selecionamos o tipo de movimento que vamos treinar. Por exemplo ... "dobrando o pincel". e inicie o treinamento (repita o movimento 12 vezes). Vamos salvar os dados recebidos no momento e enviá-los para o servidor, onde treinaremos a rede neural (calmamente, também falarei sobre isso)
  • Modo de reconhecimento direto de movimento. Os dados obtidos durante o movimento são comparados com o modelo obtido como resultado do treinamento da rede neural. Com base nos resultados, já obteremos uma “ESCOVA DE DOBRA”, por exemplo.
  • modo de condução. De acordo com um certo tipo de movimento, algo deve ser feito para se mover. Por exemplo, um manipulador montado na cozinha a partir de um designer (PPC, quão caro) de um famoso fabricante dinamarquês.

4) item do Android. Sou desenvolvedor de Android - e foi um pecado não usá-lo. O Android faz aqui conosco:

  • localiza todos os dispositivos BT disponíveis
  • conecta ao sensor
  • desenha um gráfico com base nos dados obtidos dos sensores (8 canais, frequência 200Hz). 8 curvas bonitas e coloridas.
  • implementa o modo de treinamento (seleção do tipo de movimento treinado, botão de início de treinamento, botão de envio de dados)
  • implementa interação cliente-servidor. É necessário enviar dados ao servidor para que a rede neural seja treinada
  • implementa conexão e interação com o Raspberry PI 3B, ao qual os motores são soldados, que movem o manipulador em movimento.

5) PI 3B de framboesa. foi na framboesa que colocamos o Android Things e, em seguida, criamos o servidor BT, que recebe mensagens de um dispositivo Android e move os motores correspondentes, o que acionou uma super garra da LEGO.

6) servidor. É implantado pelo Docker localmente em um computador. Ele recebe dados enviados pelo seu dispositivo, ensina uma rede neural e retorna um modelo.

Número da peça 1. Android. Desta vez, consideraremos o capô do projeto em relação ao Android até que os dados sejam enviados ao servidor.

É chamado NUKLEOS (https://github.com/cyber-punk-me/nukleos)
Pilha:

- Kotlin
- MVP
- Dagger2
- Retrofit2
- RxKotlin, RxAndroid

para framboesa:

-Android Coisas

No trabalho, eles não me deixam brincar com arquitetura, mas finalmente tive a oportunidade de brincar com um brinquedo antigo chamado MVP.

O aplicativo consiste em um estilo de atividade de navegação inferior e 4 fragmentos:
O primeiro é "Lista de todos os dispositivos BT disponíveis"

Escolhemos um sensor BT de 8 canais, que tinha sua própria API para trabalhar com a BT. Infelizmente, a API acabou sendo absolutamente inútil, porque sugeriu imediatamente a definição de um dos 6 tipos de movimento, mas a precisão do reconhecimento foi de 80% - e isso não foi bom. Bem, precisávamos de dados reais. O valor das alterações nos potenciais bioelétricos que ocorrem nos músculos humanos após a excitação de uma fibra muscular. E para isso foi necessário trabalhar diretamente com este sensor. Os criadores deixaram uma descrição do protocolo para trabalhar com ele, por isso tiveram que procurar por não muito tempo. Posso descrever um exemplo de trabalho com dispositivos BT nus em um artigo separado, se for interessante, mas, em poucas palavras, será assim:

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()) } } } 

Envolva cuidadosamente o serviço BT padrão no RX e obtenha menos dor.

Em seguida, execute a verificação e, graças à rx na assinatura, criamos uma lista de todos os dispositivos colocando-os no 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() } }) 

Selecionando um dos dispositivos, selecione-o e vá para a próxima tela:
"Configurações do sensor"

Nós nos conectamos a ele e começamos a transmitir os dados do sensor usando os comandos que preparamos com antecedência. Felizmente, o protocolo para trabalhar com este dispositivo pelos criadores do sensor é descrito:

 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) } ..... 

Trabalhar com o dispositivo também é cuidadosamente embrulhado em rx para funcionar sem dor.

Os sensores retornaram Byte Arrays naturalmente e foi necessário cortar o conversor, a frequência dos sensores foi de 200 Hz ... se for interessante, posso descrevê-lo em detalhes (bem, ou olhar o código), mas no final trabalhamos com uma quantidade de dados suficientemente grande dessa maneira:

1 - Precisamos desenhar as curvas de cada um dos sensores. Obviamente, não faz sentido renderizar ABSOLUTAMENTE todos os dados, porque no dispositivo móvel não faz sentido para os olhos examinar 200 alterações por segundo em cada sensor. Portanto, não vamos levar tudo.

2 - Precisamos trabalhar com toda a quantidade de dados, se for um processo de aprendizado ou reconhecimento.

para essas necessidades, o RX é ideal para todos os seus filtros.

Gráficos tiveram que ser feitos. Quem se importa - veja PowerfullChartsView na pasta Views.

E agora alguns vídeos:


No vídeo, você verá como o Cyril trabalha com o sistema como um todo. O vídeo está trabalhando com o modelo. Mas o modelo está no servidor. No futuro, é claro, ele estará no dispositivo, o que acelerará significativamente a resposta)

Escreva quais aspectos são interessantes e quais devem ser detalhados. Naturalmente, estamos trabalhando em um projeto e estamos abertos a suas sugestões.

Todo o projeto do github está aqui

Source: https://habr.com/ru/post/pt438034/


All Articles