Bombeamos la cinta de correr

Recientemente, me decidí por una compra muy extraña para mí. Sí, compré una cinta de correr.



Y pronto me di cuenta de que no había suficientes estadísticas detalladas, como al andar en bicicleta. En el caso de una bicicleta, la aplicación en el teléfono escribe mi velocidad, frecuencia cardíaca, cadencia y elevación. Es muy curioso controlar todos estos parámetros durante el entrenamiento, para poder ver gráficos y comparar sus resultados de vez en cuando.

Así que decidí hacer algo similar con la cinta de correr: conectarla a un teléfono inteligente o tableta para recopilar y mostrar estadísticas.

Como de costumbre, mi historia tiene la forma de un artículo de texto tradicional y a través de un video. Como más te guste.

Vídeo



Artículo


Diseño


Incluso en el momento en que estaba recogiendo la cinta de correr, noté que el control remoto y la correa para correr solo conectaban cuatro cables. Aparentemente, algunos de ellos se usan para alimentar la consola, porque el lienzo en sí está conectado a la red de 220 voltios, y los cables restantes son necesarios para transmitir señales de control en la dirección opuesta: desde la consola al lienzo, controlan la velocidad y el ángulo de la pista.

Conecté el osciloscopio en paralelo a estos cables, probando diferentes combinaciones.

Como resultado, descubrí que todo era más o menos lo que esperaba. Uno de los cables está conectado a tierra y otro es de 12 voltios. El resto transmite datos digitales.

En uno de ellos, la señal cambia al cambiar la velocidad y el ángulo. ¡Esto es exactamente lo que necesito! La amplitud de la señal es de aproximadamente cuatro voltios. Pero el protocolo no se ve como algo estándar, y la señal es muy ruidosa, cuando la pista está activada, debe filtrarla de alguna manera.



El último cable es solo pulsos con una frecuencia constante. Aparentemente, para que la consola vea la conexión a la cinta de correr. Si desconecta este cable, el control remoto da un error inmediatamente.

Las indicaciones del sensor de pulso en estos cables claramente no se transmiten, pero no es necesario. Es mejor conectar un sensor de cofre separado, que he estado usando durante mucho tiempo al andar en bicicleta. Además, resultó que el sensor de frecuencia cardíaca en la cinta de correr está mintiendo mucho, subestimando las lecturas.

Montaje del dispositivo


Por lo tanto, la siguiente tarea es armar una placa que se conecte en paralelo a estos cables, lea la velocidad y el ángulo actuales, y de alguna manera los transfiera de forma inalámbrica a una tableta o teléfono inteligente.

Una vez más, decidí usar la computadora de placa única Onion Omega2. Debe hacer un excelente trabajo. Solo es necesario bajar el voltaje de suministro a 3.3 voltios y filtrar los datos de interferencia.

Para reducir el voltaje, ahora uso estas placas preparadas con un convertidor DC-DC. Cuestan un centavo, pueden soportar hasta un par de amperios y el voltaje de salida se ajusta con un giro.



Al mismo tiempo, esta placa tiene conclusiones para soldar directamente a otra placa, es muy conveniente. Lo principal es no torcer el giro de voltaje después de la instalación en el circuito.

Para filtrar el ruido en la línea de datos, hice un filtro RC normal: una resistencia de 2.2 kilo-ohmios y un condensador de 22 picofaradios. Esto debería filtrar el ruido de alta frecuencia, dejando una señal de baja frecuencia.

Resultó una bufanda bastante pequeña.



Lo conecté a los cables de la cinta de correr para ver qué tan bien se filtra la señal cuando se enciende y, aparentemente, la forma de onda se ha vuelto casi perfecta.



Módulo de kernel


Sin embargo, no es tan fácil verificar el rendimiento del hierro. Como vimos
anteriormente en el osciloscopio, las señales van muy rápido y no usamos un microcontrolador, sino una computadora Omega2 de placa única con Linux a bordo. Bajo Linux, no podremos procesar señales desde el espacio del usuario tan rápido. ¡Pero desde el núcleo podemos! Por lo tanto, ¡es hora de escribir un módulo de kernel de Linux!

Para hacer esto, debe descargar las fuentes del kernel de Linux, en nuestro caso se trata de un ensamblado OpenWRT para Omega2, y crear un directorio con el código fuente de nuestro módulo en ellas.

Escribir código de módulo es muy parecido a programar un microcontrolador. También escribimos en C, también todo es de bajo nivel, también trabajamos con interrupciones y también recurrimos a las conclusiones de GPIO. Solo aquí, además de todo lo anterior, todavía estamos interactuando con el espacio del usuario a través de un pseudo-archivo. Por lo tanto, nuestro módulo de kernel se convierte en un tipo de adaptador entre el hardware y las aplicaciones comunes. En realidad, esto se llama el controlador.

Al principio, no sabía cómo decodificar las señales, así que simplemente deduje su duración.



Pronto se hizo evidente que las señales estaban codificadas con una duración de alto nivel. Tiene 600 microsegundos de largo o 1200 microsegundos de largo. El nivel bajo siempre tiene una longitud de 600 microsegundos, excepto la secuencia inicial.

Un total de 17 de tales caídas de arriba a abajo. Aparentemente, se trata de 16 bits de datos más la secuencia inicial. Hice su decodificación, tomando como base que las diferencias largas y altas son un cero lógico, y las cortas son una unidad lógica y obtuve lo que sucedió. ¡Inmediatamente vi los datos que necesitaba!



16 bits son, como saben, dos bytes. El primer byte indica el tipo de datos que se transmiten: el ángulo de inclinación o velocidad, y el segundo byte los datos en sí. El controlador es extremadamente simple.

El único parámetro del controlador es el número de puerto.

/* 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)");

Al inicializar, configúrelo para la entrada y configure la interrupción, que se activará cada vez que cambie el nivel.

/* 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;

En esta interrupción, primero miramos la hora actual. A continuación, usamos este valor para calcular cuánto tiempo ha pasado desde que se activó la última interrupción y ponerlo en una matriz. Por supuesto, recordamos la hora actual para el cálculo la próxima vez. Además, debe reiniciar el temporizador 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;
}

El truco es que si el temporizador aún funciona, significa que no hubo caídas de nivel en el pin durante mucho tiempo y, en consecuencia, es hora de procesar la información recopilada. En la función que llama el temporizador, se verifica que hubo exactamente 34 gotas, después de lo cual observamos cuánto duró cada intervalo. Si hay 600 microsegundos, luego 1200 microsegundos, tomamos 900 en el extranjero. Si el intervalo es menor, escribimos uno en el resultado, desplazándolo por un bit. Después de procesar cada intervalo, enviamos el resultado para abrir pseudo-archivos, transfiriendo así los datos al espacio del usuario.

/* 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 y detección de velocidad


Luego queda escribir un script de Python que los leerá del pseudoarchivo y los enviará a través de la red como una cadena JSON. Parece que todo es bastante sencillo. Sin embargo, si todo es simple con el ángulo de inclinación, y el valor en el segundo byte corresponde exactamente al ángulo de inclinación como un porcentaje, entonces con la velocidad todo resultó mucho más confuso.

Un valor de 9 corresponde a un kilómetro por hora, y un valor de 160 corresponde a 18 kilómetros por hora. Es decir, la dependencia de los datos con la velocidad real no es del todo obvia. Escribí todos los valores manualmente, los introduje en Excel, los graficé y obtuve una curva muy desigual.



Y hay velocidades cuando las lecturas en el control remoto son diferentes, ¡pero los datos y la velocidad de la pista en sí siguen siendo los mismos! Por ejemplo, 5.2 km / hy 5.3 km / h son en realidad las mismas velocidades. En todas partes haciendo trampa. Me pregunto qué velocidad hay realmente. Mídalo de alguna manera, pero déjelo para más tarde.

Además de esta transferencia de loros a kilómetros por hora, el guión resultó ser extremadamente simple. Leemos los datos del pseudoarchivo de Linux, lo decodificamos, aceptamos conexiones de red y transferimos los datos a los clientes conectados a través de la red como una cadena 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)

Creo que no se necesita autorización ni seguridad aquí. El estado de la cinta no es el tipo de datos que me gustaría proteger de los piratas informáticos.

Ponemos este script en el inicio y eliminamos el tablero dentro de la cinta de correr. Por desgracia, solo cabe en un tubo de metal que conecta la consola a la cinta de correr.



Como saben, el metal protege la señal de radio, así que saqué la antena Wi-Fi de la tubería, pero debajo de una carcasa de plástico que oculta los cables.



En esta cinta de correr directamente "inteligente" está lista. Ella ya sabe cómo distribuir estadísticas por la red. ¡Solo queda escribir un cliente para ella!

Cliente de Android


Lo que en mi opinión debería ser un cliente así. Esta es una aplicación de Android que ejecutaré en una tableta o teléfono inteligente y la pondré encima de la pantalla de la cinta de correr, respectivamente, debe mostrar toda la información en los ejercicios en la pantalla, reemplazando la pantalla de la cinta de correr. La aplicación debería poder funcionar en segundo plano, de modo que pueda ver el video sin problemas mientras corro. Además, debe mantener estadísticas detalladas sobre las carreras, sincronizando todo con la nube y dibujando gráficos de la dependencia del pulso en la velocidad y el ángulo de inclinación.

El corazón de una aplicación de este tipo debería ser un servicio que se ejecuta en segundo plano, se conecta a la cinta de correr en un bucle sin fin, recibe datos y los decodifica. No hay dificultades particulares en esto.

Sensor de frecuencia cardíaca


Lo más difícil fue trabajar de repente con un sensor de frecuencia cardíaca. Se descubrieron muchas trampas. Aquí



tengo un monitor de ritmo cardíaco para el pecho: lo he estado usando durante mucho tiempo cuando ando en bicicleta. Es bastante estándar, funciona con BLE a la Bluetooth Low Enegy, puede emparejarse con un teléfono y con un navegador Garmin sin ningún problema. Ni siquiera podía pensar que trabajar con él desde mi aplicación sería tan obvio. Dichos sensores tienen GUID estándar para diferentes lecturas.

Para comenzar a recibir una frecuencia cardíaca, primero debe configurar su monitor de frecuencia cardíaca para enviar lecturas periódicamente. Podría hacer esto solo estudiando ejemplos que no funcionen y escribiendo.
Como resultado, escribí una clase para trabajar con un sensor de frecuencia cardíaca, que intenta conectarse automáticamente e informa periódicamente la frecuencia cardíaca actual.

Samsung Health SDK


En cuanto a estadísticas y gráficos. Decidí no reinventar la rueda, sino usar lo que ya uso al andar en bicicleta, es decir, hacerme amigo de alguna manera con la maravillosa aplicación Samsung Health.

Ahora probablemente parecerá que estoy anunciando Samsung nuevamente. Pero en una bicicleta, esta aplicación realmente ha demostrado ser muy buena. Para mi sorpresa, se conecta a todos los sensores sin problemas, muestra tanto la cadencia como la velocidad de las ruedas, y dicta las estadísticas en los auriculares, muestra las mismas estadísticas con gráficos, proporciona logros y almacena todo en la nube.

La búsqueda mostró que Samsung Health tiene su propio SDK, que, aunque no es completamente inteligible, todavía está documentado: img-developer.samsung.com/onlinedocs/health/android/data/index.html

Trabajar con él es esencialmente trabajar con una base de datos que almacena una variedad de lecturas desde los pasos tomados y las mediciones del ritmo cardíaco hasta el azúcar en la sangre y las fases del sueño. Pero ahora estamos interesados ​​en los registros de ejercicios, que incluyen valores escalares como el tipo de ejercicio, el tiempo, la distancia, la duración, las calorías quemadas y los conjuntos de datos en vivo, como el historial de frecuencia cardíaca, velocidad y coordenadas.

Todos estos datos deben almacenarse y prepararse correctamente. Algunos necesitan ser calculados.

Cálculo de altura


Por ejemplo, altura de elevación. Desde la cinta de correr, conocemos el ángulo de ascenso en cada punto en el tiempo, que se mide en porcentaje. El porcentaje de ángulo de elevación es la relación de la distancia recorrida hasta la subida. Resulta que la velocidad vertical es igual a la velocidad habitual multiplicada por la pendiente como un porcentaje y dividida por cien. Conociendo la velocidad vertical, podemos calcular la altura actual en cada momento en el tiempo. Como resultado, debe ingresarse en las coordenadas actuales, a pesar de que durante el ejercicio no cambian y no se tienen en cuenta.
En respuesta a estos datos, la aplicación Samsung Health mostrará cuánto supuestamente subí, así como la velocidad vertical en cada momento de entrenamiento.

Recuento de calorías


¿Pero cómo contar las calorías? Además, el conteo de calorías es imprescindible para Samsung Health. Al mismo tiempo, las calorías quemadas son un indicador muy inexacto, que depende de muchos factores diferentes. No estoy seguro si tiene sentido contarlos.

No se me ocurrió algo propio y solo busqué en Google la calculadora (https://42.195km.net/e/treadsim/) y copié el algoritmo de mi javascript (https://42.195km.net/e/treadsim/treadsim107 .js). En la entrada, toma la distancia recorrida, el ángulo de elevación y ... el peso.

Podría establecer mi peso manualmente, pero como estamos trabajando con Samsung Health, puedo tomar mi peso actual desde allí. Después de todo, uso balanzas inteligentes de Xiaomi, que se sincronizan con Google Fit en mi teléfono, Google FIt a través de una aplicación separada se sincroniza con Samsung Health, Samsung Health a través de una nube se sincroniza conmigo mismo en la tableta, donde mi aplicación ya lo está recibiendo.

Apariencia de la aplicación


Visualmente, la tarea de la aplicación es la visualización a gran escala de las principales indicaciones: velocidad, ángulo, frecuencia cardíaca, distancia, calorías. Es mejor hacerlo en blanco sobre un fondo negro para que el consumo de batería al usar la pantalla AMOLED sea mínimo, porque, por supuesto, indicamos que al mostrar nuestra actividad, la pantalla debe estar encendida constantemente.



Los botones se ocultan automáticamente cuando la cinta está activa. Puede comenzar y detener el entrenamiento solo a velocidad cero.

Y, por supuesto, debe admitir el modo "imagen en imagen". Esto se hace en unas pocas líneas. Solo necesita indicar en el manifiesto que la actividad admite este modo, y en el código entrar al minimizar la aplicación. Como resultado, puede ver, por ejemplo, YouTube y ver las lecturas de la cinta en una esquina de la pantalla. Resultó muy conveniente.



Pero en esta etapa, finalmente me sorprendió el dolor del desarrollador para Android, porque ya obtengo cuatro tamaños de pantalla diferentes: el teléfono y la tableta en modo normal y también están en el modo "imagen en imagen". Y sucedió que si selecciono el tamaño de fuente normal para un tamaño de pantalla, en otros casos todo es demasiado pequeño, entonces demasiado grande.

Al desarrollar para Android, hay varias categorías de pantallas, y puede hacer que se apliquen automáticamente diferentes configuraciones, pero en mi caso esto no fue suficiente.
Como resultado, tuve que calcular y establecer los tamaños de fuente en el código, lo que creo que está muy mal. Sin embargo, funciona perfectamente como resultado.

Resultado


Y aqui esta el resultado. Abra la aplicación, espere la conexión a la cinta de correr y al sensor de frecuencia cardíaca, comience el entrenamiento y use la cinta de correr como de costumbre.
Al final del entrenamiento, detenemos la cinta de correr. Al alcanzar la velocidad cero, aparecerá el botón "finalizar entrenamiento". Haga clic en él y las estadísticas se enviarán a Samsung Health. Ábralo y vea todos los datos.







Puede ver el gráfico del pulso, la velocidad y el aumento, comparar su progreso a diferentes intervalos de tiempo, todo esto se almacena en la nube y es accesible desde todos los dispositivos.

Puedes sincronizarlo con Google Fit. La belleza. Estoy satisfecho con el resultado. Ahora lo principal es no lanzar clases. Puede agregar a la funcionalidad de la aplicación para que se parezca al entrenamiento si soy vago durante mucho tiempo. Pero ya soy demasiado vago para hacer esta función.

All Articles