Nós bombeamos a esteira

Recentemente, eu decidi fazer uma compra muito estranha para mim. Sim, eu comprei uma esteira.



E logo percebi que não havia estatísticas detalhadas suficientes ao andar de bicicleta. No caso de uma bicicleta, o aplicativo no telefone grava minha velocidade, frequência cardíaca, cadência e elevação. É muito curioso controlar todos esses parâmetros durante o treinamento, para poder visualizar gráficos e comparar seus resultados periodicamente.

Decidi fazer algo semelhante com a esteira: conecte-a a um smartphone ou tablet para coletar e exibir estatísticas.

Como sempre, minha história está na forma de um artigo de texto tradicional e por meio de vídeo. Como você gosta mais.

Vídeo



Artigo


Projeto


Mesmo no momento em que eu estava coletando a esteira, notei que o controle remoto e a própria esteira conectavam apenas quatro fios. Aparentemente, alguns deles são usados ​​para alimentar o console, porque a tela em si está conectada à rede de 220 volts e os fios restantes são necessários para transmitir sinais de controle na direção oposta - do console para a tela, eles controlam a velocidade e o ângulo da pista.

Liguei o osciloscópio paralelo a esses fios, tentando diferentes combinações.

Como resultado, descobri que tudo era o mesmo que eu esperava. Um dos fios é aterrado e o outro é de 12 volts. O restante transmite dados digitais.

Em um deles, o sinal muda ao alternar velocidade e ângulo. É exatamente disso que eu preciso! A amplitude do sinal é de cerca de quatro volts. Mas o protocolo não parece algo padrão, e o sinal é muito barulhento, quando a faixa está ligada, você precisa filtrá-lo de alguma forma.



O último fio é apenas pulsos com uma frequência constante. Aparentemente, para o console ver a conexão com o cinto de corrida. Se você desconectar esse fio, o controle remoto emitirá um erro imediatamente.

As indicações do sensor de pulso nesses fios não são claramente transmitidas, mas não são necessárias. É melhor conectar um sensor torácico separado, que uso há muito tempo ao andar de bicicleta. Além disso, descobriu-se que o sensor de frequência cardíaca na própria esteira está mentindo bastante, subestimando as leituras.

Montagem do dispositivo


Portanto, a próxima tarefa é montar uma placa que se conecte paralelamente a esses fios, leia a velocidade e o ângulo atuais e, de alguma forma, os transfira sem fio para um tablet ou smartphone.

Mais uma vez, decidi usar o computador de bordo único Onion Omega2. Ele deve fazer um excelente trabalho. Só é necessário baixar a tensão de alimentação para 3,3 volts e filtrar os dados contra interferências.

Para reduzir a tensão, agora uso essas placas prontas com um conversor DC-DC. Eles custam um centavo, podem suportar até dois amperes e a tensão de saída é ajustada com um toque.



Ao mesmo tempo, este painel tem conclusões para soldar diretamente para outro painel, é muito conveniente. O principal é não torcer a torção de tensão após a instalação no circuito.

Para filtrar o ruído na linha de dados, criei um filtro RC comum: um resistor de 2,2 kg-ohm e um capacitor de 22 picofarad. Isso deve filtrar o ruído de alta frequência, deixando um sinal de baixa frequência.

Acabou um cachecol bem pequeno.



Conectei-o aos fios da esteira para ver quão bem o sinal é filtrado quando é ligado e, aparentemente, a forma de onda se tornou quase perfeita.



Módulo Kernel


No entanto, não é tão fácil verificar o desempenho do ferro. Como vimos
anteriormente no osciloscópio, os sinais passam muito rapidamente e não usamos um microcontrolador, mas um computador Omega2 de placa única com Linux a bordo. No Linux, não conseguiremos processar sinais do espaço do usuário tão rapidamente. Mas a partir do núcleo, podemos! Portanto, é hora de escrever um módulo do kernel Linux!

Para fazer isso, você precisa fazer o download das fontes do kernel Linux, no nosso caso, este é um assembly OpenWRT para Omega2 e criar um diretório com o código-fonte do nosso módulo.

Escrever código de módulo é muito parecido com programar um microcontrolador. Também escrevemos em C, também tudo é de baixo nível, também trabalhamos com interrupções e também chegamos às conclusões do GPIO. Somente aqui, além de todos os itens acima, ainda estamos interagindo com o espaço do usuário por meio de um pseudo-arquivo. Assim, nosso módulo do kernel se torna um tipo de adaptador entre o hardware e os aplicativos comuns. Na verdade, isso é chamado de driver.

No começo, eu não sabia como decodificar os sinais, então deduzi sua duração.



Logo ficou claro que os sinais eram codificados com duração de alto nível. Tem 600 microssegundos de comprimento ou 1200 microssegundos de comprimento. O nível baixo é sempre de 600 microssegundos, exceto para a sequência inicial.

Um total de 17 desses cai para cima e para baixo. Aparentemente, são 16 bits de dados mais a sequência inicial. Fiz a decodificação deles, tendo como base que diferenças longas e altas são um zero lógico, e pequenas são uma unidade lógica e entendi o que aconteceu. Vi imediatamente os dados que precisava!



16 bits são, como você sabe, dois bytes. O primeiro byte indica o tipo de dados que está sendo transmitido: o ângulo de inclinação ou velocidade e o segundo byte, os dados em si. O motorista é extremamente simples.

O único parâmetro do driver é o número da porta.

/* Module parameters */
static u8 receive_pin = 11;
module_param(receive_pin, byte, S_IRUGO);
MODULE_PARM_DESC(receive_pin,"Treadmill receiver pin number (default 11)");

Ao inicializar, configure-o para entrada e defina a interrupção, que será acionada toda vez que o nível for alterado.

/* Allocate and init the timer */
data_recv_timer = kzalloc(sizeof(struct hrtimer), GFP_KERNEL);
if (!data_recv_timer) {
    pr_err("treadmill: can't allocate memory for timer\n");
    treadmill_free();
    return -1;
}
hrtimer_init(data_recv_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
data_recv_timer->function = recv_timer_callback;

Nesta interrupção, primeiro olhamos para a hora atual. Em seguida, usamos esse valor para calcular quanto tempo se passou desde a última interrupção acionada e colocá-lo em uma matriz. Obviamente, lembramos da hora atual para o cálculo da próxima vez. Além disso, você deve reiniciar o cronômetro especial.

/* IRQ fired every rising/falling edge of receiver pin */
static irq_handler_t treadmill_irq_handler(unsigned int irq,
    void *dev_id, struct pt_regs *regs)
{
    u64 now = ktime_to_us(ktime_get_boottime());
    u8 value = gpio_get_value(receive_pin);
    u64 time_passed;
    reset_recv_timer();

    if ((timings_pos & 1) == value)
    {
        time_passed = now - last_time;
        if (timings_pos < TIMINGS_BUFFER_SIZE)
        {
            timings[timings_pos] = time_passed;
            timings_pos++;
        }
        last_time = now;
    }

    /* Announce that the IRQ has been handled correctly */
    return (irq_handler_t) IRQ_HANDLED;
}

O truque é que, se o cronômetro ainda funcionar, isso significa que não houve queda de nível no pino por muito tempo e, portanto, é hora de processar as informações coletadas. Na função que o timer chama, verifica-se que houve exatamente 34 quedas, após as quais examinamos quanto tempo cada intervalo durou. Se houver 600 microssegundos, depois 1200 microssegundos, levaremos 900 para o exterior. Se o intervalo for menor, escreveremos um no resultado, deslocando-o um pouco. Após o processamento de cada intervalo, enviamos o resultado para abrir pseudo-arquivos, transferindo dados para o espaço do usuário.

/* Timer */
static enum hrtimer_restart recv_timer_callback(struct hrtimer *timer)
{
    int i, p;
    u16 data;

    if (timings_pos != 34) {
        pr_debug("treadmill: invalid edges count: %d", timings_pos);
        timings_pos = 0; 
        return HRTIMER_NORESTART;
    }

    data = 0;   
    for (i = 2; i < timings_pos; i += 2)
    {
        data >>= 1;
        if (timings[i] < 900) // 600us = 1, 1200 us = 0
            data |= 0x8000;
    }
    
    for (p = 0; p < 2; p++) {
        for (i = 0; i < treadmill_number_opens; i++) {
            if (!(opened_files[i]->f_mode & FMODE_READ)) continue;
            ((struct cfile_t*)opened_files[i]->private_data)->receiver_buffer[
                ((struct cfile_t*)opened_files[i]->private_data)->receiver_write_pos++
                % RECEIVER_BUFFER_SIZE] = (data >> (8 * p)) & 0xFF;
        }
    };
    wake_up_interruptible(&wq_data);

    timings_pos = 0; 
   
    return HRTIMER_NORESTART;
}

Servidor Python e detecção de velocidade


Resta então escrever um script Python que os leia do pseudo-arquivo e os envie pela rede como seqüências JSON. Parece que tudo é bastante direto. No entanto, se tudo é simples com o ângulo de inclinação e o valor no segundo byte corresponde exatamente ao ângulo de inclinação como porcentagem, então com a velocidade tudo se tornou muito mais confuso.

Um valor de 9 corresponde a um quilômetro por hora e um valor de 160 corresponde a 18 quilômetros por hora. Ou seja, a dependência dos dados na velocidade real não é de todo óbvia. Escrevi todos os valores manualmente, os levei ao Excel, plotados e obtive uma curva muito desigual.



E há velocidades quando as leituras no controle remoto são diferentes, mas os dados e a velocidade da pista permanecem os mesmos! Por exemplo, 5,2 km / he 5,3 km / h são realmente as mesmas velocidades. Em todos os lugares trapaceando. Gostaria de saber que velocidade realmente existe? Meça de alguma forma, mas deixe para depois.

Além dessa transferência de papagaios para quilômetros por hora, o script acabou sendo extremamente simples. Lemos os dados do pseudo-arquivo do Linux, decodificamos, aceitamos conexões de rede e transferimos os dados para os clientes conectados pela rede como uma string JSON.

class TreadmillServer:
    def __init__(self, device = "/dev/treadmill", port = 11010, interface = '0.0.0.0'):
        self._device = device
        self._port = port
        self._interface = interface
        self._working = False
        self._clients = []
        self._server_sock = None
        self.incline = 0
        self.speed = 0

    def start(self):
        self._working = True
        self._server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._server_sock.bind((self._interface, self._port))
        self._server_sock.listen(10)
        print("Listening port", self._port)
        Thread(target=self._port_listener, name="Treadmill port listener", daemon=True).start()
        Thread(target=self._device_listener, name="Treadmill device listener", daemon=True).start()

    def stop(self):
        self._working = False
        if self._server_sock != None:
            try:
                self._server_sock.close()
            except:
                pass
            self._server_sock = None

    def __del__(self):
        self.stop()

    def _port_listener(self):
        while self._working and self._server_sock:
            try:
                conn, addr = self._server_sock.accept()
                print('Connected: {0}'.format(addr))
                TreadmillClientConnection(self, conn, addr)
            except Exception as e:
                print("Error:", e)

Eu acho que nenhuma autorização e segurança são necessárias aqui. O estado da esteira não é o tipo de dados que eu gostaria de proteger dos hackers.

Colocamos esse script na inicialização e removemos a placa dentro da esteira. Infelizmente, ele se encaixa apenas em um tubo de metal que conecta o console ao tapete rolante.



Como você sabe, o metal protege o sinal de rádio, então tirei a antena Wi-Fi do cano, mas por baixo de uma caixa de plástico que oculta os fios.



Nesta esteira diretamente "inteligente" está pronta. Ela já sabe como distribuir estatísticas pela rede. Resta apenas escrever um cliente para ela!

Cliente Android


O que, na minha opinião, deveria ser esse cliente. Este é um aplicativo Android que executarei em um tablet ou smartphone e o colocarei no topo da tela da esteira, respectivamente, deve exibir todas as informações sobre os exercícios na tela, substituindo a tela da própria esteira. O aplicativo deve poder trabalhar em segundo plano para que eu possa assistir ao vídeo enquanto corro sem problemas. Além disso, ele deve manter estatísticas detalhadas das corridas, sincronizando tudo com a nuvem e desenhando gráficos da dependência do pulso na velocidade e no ângulo de inclinação.

O coração de tal aplicativo deve ser um serviço que é executado em segundo plano, se conecta à esteira em um loop sem fim, recebe dados e os decodifica. Não há dificuldades particulares nisso.

Sensor de frequência cardíaca


A coisa mais difícil foi trabalhar de repente com um sensor de frequência cardíaca. Muitas armadilhas foram descobertas. Eu tenho um monitor cardíaco no peito aqui:



uso-o há muito tempo quando ando de bicicleta. É bastante padrão, funciona no BLE à Bluetooth Low Enegy, pode ser emparelhado com um telefone e um navegador Garmin sem problemas. Eu não conseguia nem pensar que trabalhar com ele a partir do meu aplicativo seria tão óbvio. Esses sensores possuem GUIDs padrão para diferentes leituras.

Para começar a receber uma frequência cardíaca, você deve primeiro configurar seu monitor de freqüência cardíaca para enviar periodicamente leituras. Eu poderia fazer isso apenas estudando exemplos não úteis e digitando.
Como resultado, escrevi uma aula para trabalhar com um sensor de frequência cardíaca, que automaticamente tenta se conectar a ele e relata periodicamente a freqüência cardíaca atual.

Samsung Health SDK


Quanto a estatísticas e gráficos. Decidi não reinventar o volante, mas usar o que já uso ao andar de bicicleta, ou seja, de alguma forma fazer amizade com o maravilhoso aplicativo Samsung Health.

Agora provavelmente parecerá que estou anunciando a Samsung novamente. Mas em uma bicicleta, esse aplicativo realmente se provou muito bem. Para minha surpresa, ele se conecta a todos os sensores sem problemas, mostra a cadência e a velocidade das rodas e dita as estatísticas nos fones de ouvido, mostra as mesmas estatísticas nos gráficos e fornece realizações e armazena tudo na nuvem.

A pesquisa mostrou que o Samsung Health possui seu próprio SDK, que, embora não seja totalmente inteligível, ainda está documentado: img-developer.samsung.com/onlinedocs/health/android/data/index.html

Trabalhar com ele é essencialmente trabalhar com um banco de dados que armazena uma variedade de leituras, desde as etapas realizadas e as medições da freqüência cardíaca até as fases do açúcar no sangue e do sono. Mas agora estamos interessados ​​em registros de exercícios, que incluem valores escalares, como tipo de exercício, tempo, distância, duração, calorias queimadas e matrizes de dados ao vivo, como histórico de freqüência cardíaca, velocidade e coordenadas.

Todos esses dados devem ser armazenados e preparados corretamente. Alguns precisam ser calculados.

Cálculo da altura


Por exemplo, altura de elevação. Na esteira, sabemos o ângulo de subida em cada ponto no tempo, medido em porcentagem. A porcentagem do ângulo de elevação é a razão da distância percorrida até a subida. Acontece que a velocidade vertical é igual à velocidade usual multiplicada pela inclinação em porcentagem e dividida por cem. Conhecendo a velocidade vertical, podemos calcular a altura atual a cada momento no tempo. Como resultado, ele deve ser inserido nas coordenadas atuais, apesar do fato de que durante o exercício elas não mudam e não são levadas em consideração.
Em resposta a esses dados, o aplicativo Samsung Health mostrará o quanto supostamente subi, bem como a velocidade vertical em cada momento do treinamento.

Contagem de calorias


Mas como contar calorias? Além disso, a contagem de calorias é uma obrigação para a Samsung Health. Ao mesmo tempo, as calorias queimadas são um indicador muito impreciso, que depende de muitos fatores diferentes. Não tenho certeza se faz sentido contá-los.

Eu não criei algo próprio e apenas pesquisei na calculadora (https://42.195km.net/e/treadsim/) e copiei o algoritmo do meu javascript (https://42.195km.net/e/treadsim/treadsim107 .js). Na entrada, ele mede a distância percorrida, o ângulo de elevação e ... o peso.

Eu poderia definir meu peso manualmente, mas, como estamos trabalhando com a Samsung Health, posso tirar meu peso atual de lá. Afinal, eu uso escalas inteligentes da Xiaomi, que são sincronizadas com o Google Fit no meu telefone, o Google FIt através de um aplicativo separado é sincronizado com o Samsung Health, o Samsung Health através da nuvem é sincronizado consigo mesmo no tablet, onde meu aplicativo já o está recebendo.

Aparência do aplicativo


Visualmente, a tarefa do aplicativo é exibir em larga escala as principais indicações: velocidade, ângulo, frequência cardíaca, distância, calorias. É melhor fazer isso em branco sobre fundo preto, para que o consumo da bateria ao usar a tela AMOLED seja mínimo, porque é claro que indicamos que, ao exibir nossa atividade, a tela deve ser ligada constantemente.



Os botões são ocultados automaticamente quando a esteira está ativa. Você pode iniciar e parar o treinamento apenas na velocidade zero.

E, claro, você precisa dar suporte ao modo "imagem na imagem". Isso é feito em apenas algumas linhas. Você só precisa indicar no manifesto que a atividade suporta esse modo e, no código, entrar nele ao minimizar o aplicativo. Como resultado, você pode assistir, por exemplo, ao YouTube e ver as leituras da esteira em um canto da tela. Descobriu-se muito conveniente.



Mas, nessa fase, finalmente fui surpreendido pela dor do desenvolvedor do Android, porque já tenho quatro tamanhos de tela diferentes: o telefone e o tablet no modo normal e eles também estão no modo "imagem na imagem". E aconteceu que, se eu selecionar o tamanho da fonte normal para um tamanho de tela, em outros casos tudo será muito pequeno e muito grande.

Ao desenvolver para Android, existem várias categorias de telas e você pode fazer configurações diferentes se aplicarem automaticamente a elas, mas no meu caso isso não foi suficiente.
Como resultado, tive que calcular e definir os tamanhos das fontes no código, o que acho muito errado. No entanto, funciona perfeitamente como resultado.

Resultado


E aqui está o resultado. Abrimos o aplicativo, aguardamos a conexão com a esteira e o sensor de frequência cardíaca, iniciamos o treinamento e usamos a esteira como de costume.
No final do treino, paramos a esteira. Ao atingir a velocidade zero, o botão "concluir treinamento" será exibido. Clique nele e as estatísticas são enviadas para o Samsung Health. Abra-o e veja todos os dados.







Você pode ver o gráfico do pulso, velocidade e aumento, comparar seu progresso em diferentes intervalos de tempo, tudo isso é armazenado na nuvem e acessível a partir de todos os dispositivos.

Você pode sincronizá-lo com o Google Fit. A beleza. Estou satisfeito com o resultado. Agora, o principal não é dar aulas. Você pode adicionar à funcionalidade do aplicativo para que ele se assemelhe ao treinamento, se eu for preguiçoso por um longo tempo. Mas já estou com preguiça de fazer essa função.

All Articles