Hola a todos! Hace unos meses me surgio la idea de empezar a correr en simrace nuevamente, en pandemia me compré el volante y lo usé un rato pero perdi la motivación cuando me di cuenta de lo dificil que es manejar deportivamente y mucho más dificil es mejorar sin tener un experto que te de indicaciones claras sobre que estás haciendo mal, por eso durante un tiempo investigue bastante y vi que si queria mejorar necesitaba si o si un coach, pero como en cloudhesive tengo una cuenta de aws para poder investigar me puse a la tarea de crear un sistema que me ayude a mejorar mis tiempos de vuelta y me puse a armar un pit wall digital para sim racing y mostrar el resultado en el AWS Community Day Argentina, el 12 de septiembre de 2026.
⭐ Todo el material de mis charlas — slides, código, diagramas — vive en github.com/alvarongg/charlas-pub. Si le tirás una estrella al repo te va a llegar una notificación cada vez que suba contenido nuevo. No spam, solo técnica.
La idea
La idea es simple — quiero mejorar como piloto, y para eso necesito ver mi telemetría en vivo, tener un historial de mis vueltas, y que un experto me diga en qué me estoy equivocando (y en donde). Para esta primera etapa elegi Assetto Corsa (el simulador) ya que tiene una funcionalidad que se llama "Shared Memory" la genera toda esa información en tiempo real, pero se queda ahí, en la memoria compartida de mi PC. Nadie más la ve, y cuando cierro el juego, se pierde.
Luego de un trabajo de investigación de un par de dias, ya tenia mi primera fase completada, logré leer la memoria del simulador de forma correcta, sin romper el juego y sin problemas de offset o padding. (Gracias a la comunidad de asseto por toda la ingenieria inversa que hicieron sobre el juego ya que la documentación es basatnte pobre en este sentido.
Ahora ya tenia como capturar los datos, necesitaba una forma de enviarlos a aws para poder procesarlso, Así que terminé usando esto como excusa para meterme de lleno en AWS IoT Core. Y cuanto más avanzaba, más me daba cuenta de que el problema que estaba resolviendo no tiene nada que ver con autos: es el mismo problema que tiene cualquiera que necesite sacar datos de un dispositivo que no vive dentro de tu infraestructura — un sensor en una planta, un camión de una flota, un wearable médico, una máquina en una línea de producción.
En todos esos casos el desafío es idéntico: cómo hacés que ese dispositivo te mande datos de forma segura, sin que tenga que confiar ciegamente en tu backend, y cómo procesás eso en tiempo real sin que se te caiga todo con el primer pico de tráfico.
Este post es sobre eso. Voy a usar el pit wall como caso de estudio porque es el que tengo andando, pero el hilo conductor es entender el patrón: capturar información de un dispositivo fuera de AWS, y procesarla con AWS IoT.
El problema, en términos generales
Antes de meterme en el proyecto, pensemos el problema sin autos de por medio. Tenés N dispositivos, en cualquier lado, generando datos todo el tiempo. Necesitás:
- Que cada dispositivo pueda mandarte datos sin que vos tengas que confiar en él más de lo necesario — si se compromete uno, no querés que ese dispositivo pueda hacerse pasar por otro, ni leer los datos de los demás.
- Que la conexión no dependa de que tu backend esté "escuchando" activamente — el dispositivo tiene que poder mandar datos aunque tu Lambda esté fría, aunque tu API esté de mantenimiento, aunque haya 10.000 dispositivos mandando al mismo tiempo.
- Procesar esos datos apenas llegan, sin que cada dispositivo tenga que saber nada de tu arquitectura interna — el dispositivo publica, y listo. Lo que pase después (guardar en una base, avisar a alguien, calcular algo) es responsabilidad tuya, no suya.
Ese es, casi textual, el problema que resuelve AWS IoT Core.
Por qué no "le pego un POST a un API Gateway"
La primera pregunta que me hice fue esa: ¿por qué no simplemente expongo un endpoint HTTP y que cada dispositivo me mande un POST? Funcionaría para pocos dispositivos, pero se rompe rápido en varios frentes:
- Autenticación por certificado, no por API key. Cada dispositivo se identifica con un certificado X.509 propio. Si alguien saca la PC de un simulador del evento y le copia el certificado, ese certificado solo sirve para ese simulador — nunca para publicar en nombre de otro. Con una API key compartida, perdés esa contención.
- MQTT, no HTTP. El protocolo mantiene una conexión persistente y liviana, pensada para dispositivos con recursos limitados y redes poco confiables — reconecta solo, con backoff, y no perdés el mensaje si la conexión se corta a mitad de camino.
- El "Rules Engine" desacopla al dispositivo de tu backend. El dispositivo no sabe que existe una Lambda, ni una tabla, ni nada. Publica en un topic y se desentiende. Vos decidís, del lado de AWS, qué Lambda se invoca, con qué filtro, y podés cambiar esa lógica sin tocar una sola línea del dispositivo.
Ese último punto es el que más me voló la cabeza cuando lo entendí: la lógica de "qué hacer con el dato" vive completamente separada de la lógica de "cómo llega el dato". Podés agregar un procesamiento nuevo sin redeployar nada en los dispositivos.
Identidad de dispositivo: mínimo privilegio, en serio
Acá está la primera parte del código. Cada simulador (pit_wall-sim-01, 02, 03, 04) tiene su propio Thing en IoT Core, con su propia política. La política no es un checkbox de "puede publicar" — define exactamente en qué topics puede publicar y a cuáles se puede suscribir:
def policy_document(sim_id: str, *, topic_prefix: str = "sim") -> dict:
base = f"{topic_prefix}/{sim_id}"
return {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Connect",
"Effect": "Allow",
"Action": "iot:Connect",
"Resource": [f"arn:aws:iot:...:client/{sim_id}"],
},
{
"Sid": "PublishOutbound",
"Effect": "Allow",
"Action": "iot:Publish",
"Resource": [
f"arn:aws:iot:...:topic/{base}/{topic}"
for topic in ("telemetry", "event", "lap", "session", "status", "log")
],
},
{
"Sid": "SubscribeCommandAsAgent",
"Effect": "Allow",
"Action": ["iot:Subscribe", "iot:Receive"],
"Resource": [f"arn:aws:iot:...:topic/{base}/command"],
},
],
}
Fijate lo que no puede hacer este certificado: no puede publicar en sim/pit-wall-sim-02/telemetry (el topic de otro simulador), y no puede suscribirse a nada que no sea su propio topic de comandos. Si mañana alguien clona el certificado de un simulador, el radio de daño queda contenido a ese único dispositivo — nunca escala al resto de la flota.
Esto es literalmente lo mismo que harías con un sensor de humedad en una planta agrícola, o con la tablet de un camión de reparto: un certificado por dispositivo, un permiso mínimo por dispositivo, sin excepciones.
IoT Rules: el corazón del procesamiento
Una vez que el dato entra por MQTT, ¿quién lo agarra? Ahí entran las IoT Rules: consultas SQL que corren sobre cada mensaje que pasa por un topic, y que disparan una o más acciones.
DATA_TOPICS = ("telemetry", "event", "lap", "session", "status")
def _crear_regla(self, topic: str):
regla = iot.CfnTopicRule(
self, f"Rule{topic}",
topic_rule_payload=iot.CfnTopicRule.TopicRulePayloadProperty(
sql=f"SELECT * FROM '{self.topic_prefix}/+/{topic}'",
actions=[
iot.CfnTopicRule.ActionProperty(
lambda_=iot.CfnTopicRule.LambdaActionProperty(
function_arn=self.ingest_function.function_arn
)
),
iot.CfnTopicRule.ActionProperty(
lambda_=iot.CfnTopicRule.LambdaActionProperty(
function_arn=self.broadcast_function.function_arn
)
),
],
),
)
Lo interesante acá no es el SQL (SELECT * FROM 'sim/+/telemetry', con el + como wildcard de un nivel — cualquier simulador, ese topic puntual). Lo interesante es que una sola regla dispara dos acciones en paralelo, sobre el mismo mensaje:
- Una Lambda de ingesta, que persiste el dato (más sobre esto abajo).
- Una Lambda de broadcast, que reenvía el mismo mensaje por WebSocket a quien esté mirando ese simulador en vivo.
No hay una segunda suscripción al mismo topic, ni una cola intermedia, ni el ingesta reenviándole el dato al broadcast. Es la misma regla, la misma invocación de IoT Core, dos acciones independientes. Si mañana el broadcast se cae, la ingesta sigue funcionando — y viceversa.
Este es el patrón que más me sirvió entender de todo el proyecto: una IoT Rule no es "un trigger", es un punto de fan-out. Podés colgar tantas acciones como necesites (otra Lambda, un tópico SNS, un stream de Kinesis, otra regla) sin tocar nada del lado del dispositivo ni de las reglas existentes.
El desafío real: reconstruir algo coherente a partir de pedacitos
Acá viene la parte menos obvia. MQTT tiene un límite de tamaño de payload, y la telemetría de un simulador genera muestras varias veces por segundo — no entra todo en un solo mensaje. El agente en la PC del simulador arma lotes (batches) de muestras y los publica de a poco, con un número de secuencia cada uno.
Del lado de AWS, eso significa que la Lambda de ingesta nunca recibe "una vuelta completa" — recibe fragmentos, que van a staging en DynamoDB con un TTL (si una sesión nunca cierra una vuelta, ese staging se autodestruye solo). Cuando llega el mensaje que dice "esta vuelta se cerró", ahí sí, la Lambda junta todos los lotes que tiene en staging para esa sesión y reconstruye la traza completa:
class CoberturaIncompletaError(ReconstructionError):
"""La cobertura de lotes en staging no alcanza — todavía.
Las IoT Rules invocan la Lambda una vez por mensaje, sin ninguna
garantía de orden entre invocaciones concurrentes: el mensaje de
"vuelta cerrada" puede llegar antes de que termine de guardarse
su último lote de telemetría. Acá se reintenta unos segundos
dentro de la misma invocación, en vez de descartar la vuelta.
"""
Este tipo de problema — datos que llegan en desorden, sin garantía de que el último pedacito ya esté cuando lo necesitás — es el problema clásico de cualquier pipeline de IoT en tiempo real. No es específico de telemetría de autos: es lo mismo que te pasa reconstruyendo el recorrido de un camión a partir de puntos GPS sueltos, o el estado de una máquina a partir de eventos de sensores que no llegan en orden.
Guardado: una tabla, un bucket, y ya
Del lado del guardado no hay demasiada magia, pero vale la pena nombrarlo porque es donde termina el dato una vez procesado:
-
DynamoDB, en una tabla única (single-table design): sesiones, vueltas, recomendaciones, todo con
PK/SKpensados para que cada consulta sea unaQuerybarata sobre un prefijo, nunca unScande toda la tabla. - S3, para lo pesado: la telemetría reconstruida, los archivos de audio, cualquier blob que no tenga sentido meter en DynamoDB.
Nada de esto es exclusivo de IoT — es el mismo par de servicios que usarías para cualquier backend serverless. Lo importante es que para cuando el dato llega acá, ya pasó por todo el trabajo pesado de identidad, transporte y reconstrucción, así que esta capa queda simple.
De IoT a "en vivo": el otro lado del fan-out
Contaba arriba que la misma IoT Rule dispara dos Lambdas en paralelo. La segunda (broadcast) es la que arma la parte "en vivo" del dashboard: mantiene un índice chico de qué conexión de WebSocket está mirando qué simulador, y cuando llega un mensaje nuevo, lo reenvía tal cual a esas conexiones.
Fijate que este Lambda no procesa nada — no interpreta, no clasifica, no llama a ningún otro servicio. Solo reenvía. Esa separación entre "el camino que persiste" y "el camino que muestra en vivo" es la que permite que uno pueda evolucionar sin romper al otro: si un día quiero agregar un análisis con IA arriba de la ingesta (spoiler: lo hice, es tema de otro post), el WebSocket en vivo ni se entera.
Generalizando el patrón
Volvamos a la idea del principio: esto no es sobre autos. La misma arquitectura, con los mismos cuatro bloques (identidad por dispositivo → IoT Rules → procesamiento → guardado/consumo), sirve para:
| Industria | El "dispositivo" | El dato | Qué reemplaza al pit wall |
|---|---|---|---|
| Agro | Sensor de humedad/temperatura en el campo | Lecturas periódicas | Un dashboard de riego automatizado |
| Logística | Tablet o GPS de un camión de reparto | Posición, velocidad, paradas | Seguimiento de flota en vivo |
| Industria | PLC o sensor de una línea de producción | Vibración, temperatura, ciclos | Mantenimiento predictivo |
| Salud | Wearable de un paciente | Frecuencia cardíaca, pasos | Alertas en tiempo real al equipo médico |
En todos los casos cambia el dominio, pero no cambia la pregunta que tenés que responder: ¿cómo identifico a cada dispositivo sin que uno pueda hacerse pasar por otro, y cómo proceso lo que manda sin acoplar mi lógica de negocio a los detalles del transporte? Esa pregunta la responde AWS IoT Core, la resuelvas con autos, camiones o sensores de humedad.
Próximos pasos
Terminé este proyecto sabiendo mucho más de IoT que de lo que sabía de simracing cuando arranqué, que ya es decir bastante. Todo lo de acá es la base: una vez que tenés el dato capturado, identificado y procesado, podés poner arriba lo que quieras. En mi caso fue un ingeniero de carrera que te habla por voz mientras manejás, generado con Amazon Bedrock y Amazon Polly — de eso hablo en el próximo post.
Una última cosa antes de cerrar:
Todo el material de esta charla — slides, código, diagramas — está en github.com/alvarongg/charlas-pub. Voy a estar subiendo contenido nuevo seguido: más posts como este, ejemplos de código ejecutable y arquitecturas de referencia.
Si le tirás una ⭐ al repo GitHub te avisa automáticamente cuando haya novedades. Es la forma más fácil de no perderte nada — sin newsletter, sin formularios, sin nada raro.
Abrazo grande. Hasta la próxima 🚀
Cualquier pregunta sobre IoT Core, certificados, o el diseño de la tabla, la dejo abierta en los comentarios. ¡Nos vemos en la pista! 🏎️


Top comments (0)