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.
Aplicaciones de clientes
Aplicaciones internas
Caso · iRowing · TwoConnect
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.
El punto de partida
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
Desafío 01Sin hardware
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
Telemetría en vivo
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ó
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.
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.
Una librería BLE con licencia MIT detrás de ErgConnection, en vez de mantener dos codebases nativas en Swift y Kotlin.
GATT 133, backoff, resuscripción y el caso “PM5 ocupado”. Orden de operaciones obligatorio, o la conexión muere en silencio.
Un write-ahead log de sesión: si la app muere a mitad de un 2k, la remada no se pierde.
Entrada por radio
2 fuera de rango · se registran, no se descartan
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ó.
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.
El próximo puede ser el tuyo
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.
Otros casos
ERP interno · Gestión y facturación
De cero control a todo medido
Cámara de comercio · Plataforma completa
Sin licencia de SaaS
Outdoor living · Configurador 3D + CRM
Del 3D a la propuesta
Emergencias automotrices · App móvil
3 roles, 1 código
Concesionarias · Verificación de identidad
12 capas, un Trust Score
El punto de partida
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
Desafío 01Sin hardware
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
Telemetría en vivo
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ó
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.
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.
Una librería BLE con licencia MIT detrás de ErgConnection, en vez de mantener dos codebases nativas en Swift y Kotlin.
GATT 133, backoff, resuscripción y el caso “PM5 ocupado”. Orden de operaciones obligatorio, o la conexión muere en silencio.
Un write-ahead log de sesión: si la app muere a mitad de un 2k, la remada no se pierde.
Entrada por radio
2 fuera de rango · se registran, no se descartan
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ó.
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.
El próximo puede ser el tuyo
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.