Contanos tu idea

Caso · iRowing · TwoConnect

iRowing

Una app que lee el ergómetro Concept2 PM5 por Bluetooth y muestra ritmo, potencia y pulsaciones en vivo. Se construyó entera sin tener nunca la máquina.

Flutter · iOS + AndroidBLE · Concept2 PM5Supabase · RLSEmulador en Go

El punto de partida

El cliente tenía la máquina en otro país.
Nosotros teníamos que escribir el código que la lee.

ElPM5eselmonitordelosergómetrosConcept2:publicacadaremadaporBluetoothLowEnergyenunformatobinariopropio.Sinunamáquinasobrelamesanohayformadeleerunsolobytereal—ynoíbamosafrenareldesarrollohastaconseguirla.

La decisión que definió el proyecto fue invertir el orden: primero construir la máquina, después la app. Una máquina de software, fiel al byte.

Cómo se hizo

Una tarjeta, cinco vueltas.

01
02
03
04
05

Desafío 01Sin hardware

Construimos el ergómetro en software

pm5emu es un emulador determinista del PM5 escrito en Go: un único motor de física y frames, ergcore, que genera las mismas tramas binarias que emitiría la máquina real.

Determinista quiere decir reloj virtual y semilla fija: la misma sesión produce exactamente los mismos bytes, siempre. Eso convierte un problema de hardware en un test reproducible.

Escenarios

steady_2k · power_drop · pyramid_with_dropout

PM5en otro país
la escribimos
pm5emuGo · ergcore
física
tramas binarias
reloj virtual
semilla fija
pm5emucorriendo local
ws://localhost:8080
1:57/500 m
Bluetooth necesario0· Linux · macOS · Windows

Telemetría en vivo

Cada remada, dibujada mientras ocurre.

La potencia se muestrea escribiendo un byte en la característica 0x0034 — no por CSAFE, como asume casi toda la documentación de terceros. Los contadores llegan en uint24 little-endian y hay que manejar el wrap y el reset a mano.

Bajo el capó

Lo que costó trabajo.

El PM5 no habla CSAFE para todo

La documentación de terceros repite que el sample rate se pide por CSAFE. Es falso: se escribe un byte en la característica del Rowing Service. Descubrirlo costó una tarde de capturas.

write 1 byte → 0x0034
0x0080ritmopotenciacadencia

Exclusión mutua en 0x0080

La característica multiplexada deja de multiplexar si te suscribís a las individuales. La estrategia correcta es suscribirse sólo a 0x0080 y demultiplexar en Dart.

Sin bridge nativo

Una librería BLE con licencia MIT detrás de ErgConnection, en vez de mantener dos codebases nativas en Swift y Kotlin.

1 interfaz · 0 código nativo

Reconexión real

GATT 133, backoff, resuscripción y el caso “PM5 ocupado”. Orden de operaciones obligatorio, o la conexión muere en silencio.

GATT 133 → backoff → resub

Crash-recovery

Un write-ahead log de sesión: si la app muere a mitad de un 2k, la remada no se pierde.

WAL por sesión

Entrada por radio

2 fuera de rango · se registran, no se descartan

El BLE es entrada no confiable

Todo lo que llega por radio se trata como hostil: se validan rangos fisiológicos en el servidor, se versiona el parser y se registran los frames que no encajan en vez de descartarlos en silencio. Un contador que salta hacia atrás es un bug del wrap, no un atleta que retrocedió.

Datos de salud

Clasificación explícita, base legal bajo GDPR Art. 9 y Ley 25.326, borrado de cuenta desde la app y RLS por fila en PostgreSQL.

RLS por fila · borrado en app
0
transportes en un solo emulador: WebSocket y periférico BLE
0
paquetes en el monorepo, con fronteras verificadas por lint
0
horas de hardware necesarias para desarrollar y correr CI
0
sesión con la máquina real — y alcanzó

El próximo puede ser el tuyo

Te hacemos el sistema a vos

Como iRowing, tu proyecto puede arrancar aunque falten piezas. Contanos qué necesitás y en 30 minutos salimos de la llamada con el alcance cerrado y una fecha de entrega.

El punto de partida

El cliente tenía la máquina en otro país.
Nosotros teníamos que escribir el código que la lee.

ElPM5eselmonitordelosergómetrosConcept2:publicacadaremadaporBluetoothLowEnergyenunformatobinariopropio.Sinunamáquinasobrelamesanohayformadeleerunsolobytereal—ynoíbamosafrenareldesarrollohastaconseguirla.

La decisión que definió el proyecto fue invertir el orden: primero construir la máquina, después la app. Una máquina de software, fiel al byte.

Cómo se hizo

Una tarjeta, cinco vueltas.

01
02
03
04
05

Desafío 01Sin hardware

Construimos el ergómetro en software

pm5emu es un emulador determinista del PM5 escrito en Go: un único motor de física y frames, ergcore, que genera las mismas tramas binarias que emitiría la máquina real.

Determinista quiere decir reloj virtual y semilla fija: la misma sesión produce exactamente los mismos bytes, siempre. Eso convierte un problema de hardware en un test reproducible.

Escenarios

steady_2k · power_drop · pyramid_with_dropout

PM5en otro país
la escribimos
pm5emuGo · ergcore
física
tramas binarias
reloj virtual
semilla fija
pm5emucorriendo local
ws://localhost:8080
1:57/500 m
Bluetooth necesario0· Linux · macOS · Windows

Telemetría en vivo

Cada remada, dibujada mientras ocurre.

La potencia se muestrea escribiendo un byte en la característica 0x0034 — no por CSAFE, como asume casi toda la documentación de terceros. Los contadores llegan en uint24 little-endian y hay que manejar el wrap y el reset a mano.

Bajo el capó

Lo que costó trabajo.

El PM5 no habla CSAFE para todo

La documentación de terceros repite que el sample rate se pide por CSAFE. Es falso: se escribe un byte en la característica del Rowing Service. Descubrirlo costó una tarde de capturas.

write 1 byte → 0x0034
0x0080ritmopotenciacadencia

Exclusión mutua en 0x0080

La característica multiplexada deja de multiplexar si te suscribís a las individuales. La estrategia correcta es suscribirse sólo a 0x0080 y demultiplexar en Dart.

Sin bridge nativo

Una librería BLE con licencia MIT detrás de ErgConnection, en vez de mantener dos codebases nativas en Swift y Kotlin.

1 interfaz · 0 código nativo

Reconexión real

GATT 133, backoff, resuscripción y el caso “PM5 ocupado”. Orden de operaciones obligatorio, o la conexión muere en silencio.

GATT 133 → backoff → resub

Crash-recovery

Un write-ahead log de sesión: si la app muere a mitad de un 2k, la remada no se pierde.

WAL por sesión

Entrada por radio

2 fuera de rango · se registran, no se descartan

El BLE es entrada no confiable

Todo lo que llega por radio se trata como hostil: se validan rangos fisiológicos en el servidor, se versiona el parser y se registran los frames que no encajan en vez de descartarlos en silencio. Un contador que salta hacia atrás es un bug del wrap, no un atleta que retrocedió.

Datos de salud

Clasificación explícita, base legal bajo GDPR Art. 9 y Ley 25.326, borrado de cuenta desde la app y RLS por fila en PostgreSQL.

RLS por fila · borrado en app
0
transportes en un solo emulador: WebSocket y periférico BLE
0
paquetes en el monorepo, con fronteras verificadas por lint
0
horas de hardware necesarias para desarrollar y correr CI
0
sesión con la máquina real — y alcanzó

El próximo puede ser el tuyo

Te hacemos el sistema a vos

Como iRowing, tu proyecto puede arrancar aunque falten piezas. Contanos qué necesitás y en 30 minutos salimos de la llamada con el alcance cerrado y una fecha de entrega.

iRowing · TwoConnect · Flutter, Go y Supabase sobre Concept2 PM5 por Bluetooth Low Energy.