惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog RSS Feed
Spread Privacy
Spread Privacy
T
Threatpost
C
Cisco Blogs
P
Palo Alto Networks Blog
AI
AI
Cyberwarzone
Cyberwarzone
NISL@THU
NISL@THU
P
Privacy & Cybersecurity Law Blog
G
GRAHAM CLULEY
Simon Willison's Weblog
Simon Willison's Weblog
T
Tor Project blog
Latest news
Latest news
AWS News Blog
AWS News Blog
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
WordPress大学
WordPress大学
Vercel News
Vercel News
S
Securelist
爱范儿
爱范儿
J
Java Code Geeks
Know Your Adversary
Know Your Adversary
S
Schneier on Security
Hugging Face - Blog
Hugging Face - Blog
F
Fortinet All Blogs
Last Week in AI
Last Week in AI
D
DataBreaches.Net
宝玉的分享
宝玉的分享
D
Darknet – Hacking Tools, Hacker News & Cyber Security
MongoDB | Blog
MongoDB | Blog
Engineering at Meta
Engineering at Meta
K
Kaspersky official blog
美团技术团队
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志
量子位
博客园_首页
Attack and Defense Labs
Attack and Defense Labs
S
Secure Thoughts
Google Online Security Blog
Google Online Security Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
腾讯CDC
T
Threat Research - Cisco Blogs
雷峰网
雷峰网
有赞技术团队
有赞技术团队
www.infosecurity-magazine.com
www.infosecurity-magazine.com
P
Privacy International News Feed
S
Security Affairs

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
De Keras a Bare-Metal C++: Construyendo un motor de inferencia dentro de un Arduino Uno (Parte 3)
galp76 · 2026-05-03 · via DEV Community

En la Parte 2 de esta serie, logramos entrenar exitosamente una red neuronal recurrente (LSTM) en Python, capaz de predecir la falla de una turbina aeroespacial con un error de apenas 10 vuelos.

El modelo era un éxito en la nube. Pero en el mundo del mantenimiento industrial y el Edge Computing, no siempre tenemos servidores disponibles. Quería llevar a MAJN (mi red neuronal) al mundo físico. Así que tomé una decisión de hardware deliberadamente extrema para forzar mis habilidades en C++: desplegaría el modelo dentro de un humilde Arduino Uno.

Aquí chocamos de frente con la física del hardware: nuestro modelo en Keras tenía 14,665 parámetros en formato de punto flotante de 32 bits (float32), eso equivale a unos 60 Kilobytes. El cerebro del Arduino Uno (el microcontrolador ATmega328P) tiene 32 KB de memoria Flash (disco duro) y apenas 2 KB de memoria RAM.

En este artículo, documentaré paso a paso cómo logré meter esta red neuronal en el microcontrolador desarrollando un motor de inferencia LSTM en C++ puro desde cero.

La "Dieta" Matemática: Cuantización Lineal a 8-bits (Quantization)

En Python, nuestro modelo operaba con números decimales de 32 bits (float32), pero el microcontrolador del Arduino Uno carece de una Unidad de Punto Flotante (FPU) por hardware. Obligarlo a procesar decimales mediante emulación por software destruiría su rendimiento y saturaría sus 2KB de RAM al instante. La solución técnica fue aplicar Cuantización (Quantization): una transformación matemática donde escalamos los más de 14,000 pesos decimales para convertirlos en números enteros de un solo byte (int8_t). Esto reduciría el peso de la red a 14.6 KB, perfecto para la Flash de 32 KB.

Aquí está el script en Python que extrae los pesos de Keras, busca el valor absoluto máximo de toda la red, y calcula un Factor de Escala:

# Extracción y Cuantización en Python
pesos_keras = modelo.get_weights()
todos_los_pesos = np.concatenate([p.flatten() for p in pesos_keras])
max_abs = np.max(np.abs(todos_los_pesos))

# Buscamos encajar el peso más grande en el límite de un int8_t (127)
factor_escala = 126.0 / max_abs

# Multiplicamos la matriz, redondeamos y convertimos a entero de 1 byte
matriz_cuantizada = np.round(matriz_peso * factor_escala).astype(np.int8)

Enter fullscreen mode Exit fullscreen mode

Con esto ya tenemos las proporciones matemáticas exactas, pero comprimidas en números enteros minúsculos.

Siguiente reto: el muro de la Arquitectura Harvard (PROGMEM)

Las computadoras modernas usan la arquitectura Von Neumann (datos y programas comparten la RAM). El ATmega328P usa Arquitectura Harvard (la Flash y la RAM están separadas físicamente).

Si en C++ estándar declaras un arreglo global enorme (int8_t pesos[14665]), el compilador lo guarda en la Flash, pero al encender la placa, intenta copiarlo todo a la RAM para trabajar. Con 2 KB de RAM, esto provoca un Stack Overflow instantáneo y el Arduino muere.

La solución en Bare-Metal AVR C++ es usar la directiva PROGMEM. Esto "ancla" el arreglo a la Flash de forma permanente.
Pero esto genera un nuevo problema: en C++, un puntero normal no puede leer la memoria Flash. Para extraer nuestros pesos en tiempo de ejecución, tuvimos que usar aritmética de punteros y llamadas a bajo nivel con pgm_read_byte_near():

// El arreglo vive exclusivamente en los 32KB de Flash
const int8_t matriz_pesos_0[2800] PROGMEM = {12, -45, 89, ...};

// Para leerlo usando un índice (offset)
int8_t peso = (int8_t)pgm_read_byte_near(matriz_pesos_0 + indice);

Enter fullscreen mode Exit fullscreen mode

Álgebra lineal sin decimales: evitando el desbordamiento

Nuestro Arduino no tiene Unidad de Punto Flotante (FPU) ni librerías como NumPy. Tuvimos que escribir el producto punto (multiplicación de matrices) usando bucles for anidados.

Aquí el peligro era matemático: Si multiplicas un sensor cuantizado (ej. 100) por un peso cuantizado (ej. 50), el resultado es 5000. ¡Eso no cabe en los 8 bits de un int8_t! La memoria "daría la vuelta" arrojando basura negativa.
Para evitarlo, la acumulación en el código de C++ forzosamente se hizo usando un tipo de dato más grande (int32_t):

int32_t acumulador = 0;
// Multiplicación en 32 bits para evitar overflow de memoria
acumulador += (int32_t)sensor * (int32_t)peso;

Enter fullscreen mode Exit fullscreen mode

Aproximando las funciones de activación (Hard Tanh & Hard Sigmoid)

Una celda LSTM requiere funciones trigonométricas continuas (Sigmoide y Tangente Hiperbólica). Ejecutar la función exp() en un microcontrolador de 8 bits destruye el rendimiento por los ciclos de reloj que requiere.

La solución en el mundo del TinyML es usar "Hard Activations" (Aproximaciones lineales). Reemplazamos la compleja curva sigmoide por funciones de clipping (topes) usando solo sumas y divisiones sobre nuestra variable ESCALA constante:

int32_t hard_sigmoid_8bit(int32_t x) {
  // Aproximación de la Sigmoide adaptada a números enteros escalados
  int32_t sig = (x / 2) + (ESCALA / 2);
  if (sig > ESCALA) return ESCALA;
  if (sig < 0) return 0;
  return sig;
}

Enter fullscreen mode Exit fullscreen mode

El Motor LSTM en C++ y la Integración Final

Con todas las piezas listas, programamos el bucle del LSTM. Creé dos arreglos en la memoria RAM (h_estado y c_estado) para conservar el contexto temporal entre ciclos, y programé las cuatro compuertas (Forget, Input, Cell, Output).

Para que no quede ninguna duda de cómo se ve una Red Neuronal Recurrente escrita "a bajo nivel", aquí está el código completo que corrió dentro del Arduino Uno.

En este bloque pueden observar las aproximaciones a las funciones de activación (hard_tanh y hard_sigmoid), los arreglos globales para mantener la memoria temporal de los ciclos de la turbina (h_estado y c_estado), y el cálculo algebraico de las cuatro compuertas matemáticas del LSTM leyendo los pesos directamente desde la memoria Flash con PROGMEM:

#include "majn_weights.h"

const int PIN_ALARMA = 13;
const int32_t ESCALA = (int32_t)FACTOR_ESCALA;

// --- MEMORIA DEL LSTM (El "Estado" que viaja en el tiempo) ---
int32_t h_estado[50]; // Hidden State (Estado Oculto)
int32_t c_estado[50]; // Cell State (Memoria a largo plazo)

// --- FUNCIONES DE ACTIVACIÓN CUANTIZADAS ---
int32_t hard_tanh_8bit(int32_t x) {
  if (x > ESCALA) return ESCALA;
  if (x < -ESCALA) return -ESCALA;
  return x;
}

int32_t hard_sigmoid_8bit(int32_t x) {
  int32_t sig = (x / 2) + (ESCALA / 2);
  if (sig > ESCALA) return ESCALA;
  if (sig < 0) return 0;
  return sig;
}

// Función que limpia la "mente" de la red para un motor nuevo
void resetear_memoria_turbina() {
  for(int i = 0; i < 50; i++) {
    h_estado[i] = 0;
    c_estado[i] = 0;
  }
}

void setup() {
  Serial.begin(9600);
  pinMode(PIN_ALARMA, OUTPUT);
  digitalWrite(PIN_ALARMA, LOW);

  resetear_memoria_turbina();
  while(Serial.available()) Serial.read(); 
}

void loop() {
  if (Serial.available() > 0) {
    // Protocolo de comunicación con el "Gemelo Digital" en Python
    char comando = Serial.read();

    if (comando == 'R') {
      // Python pide resetear porque empezó un motor nuevo
      resetear_memoria_turbina();
      digitalWrite(PIN_ALARMA, LOW);
      Serial.println("RESET_OK");
    } 
    else if (comando == 'D') {
      // Python envía datos: 14 sensores cuantizados a 8-bits
      while(Serial.available() < 14) { /* Esperamos recepción */ }

      int8_t sensores[14];
      Serial.readBytes((char*)sensores, 14);

      // =======================================================
      // EL CORAZÓN DEL LSTM (Matemática Bare-Metal)
      // =======================================================
      int32_t h_nuevo[50]; 
      int32_t c_nuevo[50];

      for(int u = 0; u < 50; u++) { 
        // 1. Extraemos los Bias (Sesgos) de la Flash
        int32_t comp_i = (int8_t)pgm_read_byte_near(matriz_pesos_2 + u);       
        int32_t comp_f = (int8_t)pgm_read_byte_near(matriz_pesos_2 + 50 + u);  
        int32_t comp_c = (int8_t)pgm_read_byte_near(matriz_pesos_2 + 100 + u); 
        int32_t comp_o = (int8_t)pgm_read_byte_near(matriz_pesos_2 + 150 + u); 

        // 2. Multiplicamos Entradas x Pesos (W * x)
        int32_t sum_in_i = 0, sum_in_f = 0, sum_in_c = 0, sum_in_o = 0;
        for(int x = 0; x < 14; x++) {
          int32_t entrada = sensores[x];
          sum_in_i += entrada * (int8_t)pgm_read_byte_near(matriz_pesos_0 + (x * 200) + u);
          sum_in_f += entrada * (int8_t)pgm_read_byte_near(matriz_pesos_0 + (x * 200) + 50 + u);
          sum_in_c += entrada * (int8_t)pgm_read_byte_near(matriz_pesos_0 + (x * 200) + 100 + u);
          sum_in_o += entrada * (int8_t)pgm_read_byte_near(matriz_pesos_0 + (x * 200) + 150 + u);
        }
        comp_i += sum_in_i / 64;
        comp_f += sum_in_f / 64;
        comp_c += sum_in_c / 64;
        comp_o += sum_in_o / 64;

        // 3. Multiplicamos Estado Anterior x Pesos Recurrentes (U * h)
        int32_t sum_h_i = 0, sum_h_f = 0, sum_h_c = 0, sum_h_o = 0;
        for(int hu = 0; hu < 50; hu++) {
          int32_t h_pasado = h_estado[hu];
          sum_h_i += h_pasado * (int8_t)pgm_read_byte_near(matriz_pesos_1 + (hu * 200) + u);
          sum_h_f += h_pasado * (int8_t)pgm_read_byte_near(matriz_pesos_1 + (hu * 200) + 50 + u);
          sum_h_c += h_pasado * (int8_t)pgm_read_byte_near(matriz_pesos_1 + (hu * 200) + 100 + u);
          sum_h_o += h_pasado * (int8_t)pgm_read_byte_near(matriz_pesos_1 + (hu * 200) + 150 + u);
        }
        comp_i += sum_h_i / ESCALA;
        comp_f += sum_h_f / ESCALA;
        comp_c += sum_h_c / ESCALA;
        comp_o += sum_h_o / ESCALA;

        // 4. Pasamos por las Funciones de Activación
        int32_t act_i = hard_sigmoid_8bit(comp_i);
        int32_t act_f = hard_sigmoid_8bit(comp_f);
        int32_t act_c = hard_tanh_8bit(comp_c);
        int32_t act_o = hard_sigmoid_8bit(comp_o);

        // 5. Ecuaciones finales de actualización de la celda LSTM
        c_nuevo[u] = (act_f * c_estado[u]) / ESCALA + (act_i * act_c) / ESCALA;
        h_nuevo[u] = (act_o * hard_tanh_8bit(c_nuevo[u])) / ESCALA;
      }

      // Guardamos la memoria para el siguiente ciclo
      for(int i = 0; i < 50; i++) {
        h_estado[i] = h_nuevo[i];
        c_estado[i] = c_nuevo[i];
      }

      // =======================================================
      // CAPAS DENSAS Y RUL PREDICTIVO
      // =======================================================
      int32_t capa_densa[32];
      for(int d = 0; d < 32; d++) {
        capa_densa[d] = (int8_t)pgm_read_byte_near(matriz_pesos_4 + d);
        int32_t sum_densa = 0;
        for(int u = 0; u < 50; u++) {
          sum_densa += h_estado[u] * (int8_t)pgm_read_byte_near(matriz_pesos_3 + (u * 32) + d);
        }
        capa_densa[d] += sum_densa / ESCALA;
        if (capa_densa[d] < 0) capa_densa[d] = 0; // Activación ReLU
      }

      int32_t salida_final = (int8_t)pgm_read_byte_near(matriz_pesos_6 + 0);
      int32_t sum_salida = 0;
      for(int d = 0; d < 32; d++) {
        sum_salida += capa_densa[d] * (int8_t)pgm_read_byte_near(matriz_pesos_5 + d);
      }
      salida_final += sum_salida / ESCALA;

      // Descuantización para obtener ciclos reales
      float rul_predicho = (float)salida_final / FACTOR_ESCALA;
      rul_predicho = abs(rul_predicho); 
      if (rul_predicho > 125) rul_predicho = 125; 

      // Lógica de Alarma Física Industrial
      if (rul_predicho < 15.0) {
         digitalWrite(PIN_ALARMA, HIGH); // Peligro Inminente
         Serial.print("CRITICO,");
      } else {
         digitalWrite(PIN_ALARMA, LOW);
         Serial.print("OK,");
      }
      Serial.println(rul_predicho);
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Para que este motor de inferencia en el Arduino pudiera "ver" los datos de la turbina, necesitaba un programa en la computadora que actuara como un Gemelo Digital.

Escribí este script en Python que utiliza la librería pyserial. Su trabajo es cargar los datos históricos de la NASA, normalizarlos usando nuestro escalador guardado en la Parte 1, cuantizarlos a números enteros multiplicándolos por 64, y enviarlos byte a byte a través del cable USB.

El código inyecta vuelo tras vuelo, y se queda esperando a que el Arduino procese la matemática y devuelva el diagnóstico:

import serial
import pandas as pd
import joblib
import time

# --- CONFIGURACIÓN ---
PUERTO = '/dev/ttyACM0'  # Reemplazar por COM3 en Windows
BAUDIOS = 9600

print("1. Cargando datos de la NASA y el Escalador...")
# (Aquí omitimos el código de carga de Pandas para mantenerlo conciso)
df = cargar_y_limpiar_datos('train_FD001.txt')
scaler = joblib.load('escalador_sensores_turbina.pkl')
df[columnas_sensores] = scaler.transform(df[columnas_sensores])

# Seleccionamos el Motor 1 para la prueba
motor_1 = df[df['id_motor'] == 1]

print("2. Iniciando enlace Serial con MAJN (Arduino)...")
conexion = serial.Serial(PUERTO, BAUDIOS, timeout=2)
time.sleep(2) # Pausa obligatoria para que el Arduino despierte

# Le indicamos al Arduino que limpie su memoria RAM (Nuevo Motor)
conexion.write(b'R')
conexion.readline() # Esperamos el "RESET_OK"

print("3. Inyectando datos de vuelo en tiempo real...")

for index, row in motor_1.iterrows():
    ciclo = int(row['ciclo'])
    sensores_float = row[columnas_sensores].values

    # --- LA CUANTIZACIÓN (El puente entre Python y C++) ---
    # Multiplicamos los floats (0.0 a 1.0) por 64 para enviarlos como enteros
    sensores_int = [int(val * 64) for val in sensores_float]

    # Empaquetamos: La letra 'D' (Data) seguida de los 14 bytes de los sensores
    trama_bytes = b'D' + bytes([max(min(s, 127), -128) & 0xFF for s in sensores_int])

    # Inyectamos los datos por el cable USB
    conexion.write(trama_bytes)

    # Leemos la predicción que calculó el Arduino
    respuesta = conexion.readline().decode('utf-8').strip()

    if respuesta:
        estado, rul = respuesta.split(',')

        if estado == "OK":
            print(f"✈️ Vuelo {ciclo:03d} | Estado: OK | Vida útil: {float(rul):05.2f} ciclos")
        else:
            print(f"🔥 Vuelo {ciclo:03d} | Estado: CRÍTICO | Vida útil: {float(rul):05.2f} ciclos")
            print(">>> ALERTA ROJA: EL LED DEL ARDUINO SE ENCENDIÓ. PARADA DE PLANTA <<<")
            break # Detenemos la simulación para salvar el motor

    time.sleep(0.1) # Simulamos el tiempo entre un vuelo y otro

conexion.close()

Enter fullscreen mode Exit fullscreen mode

La prueba de estrés industrial (100 motores):

Hice pasar los 20,631 vuelos de los 100 motores del Test Set por el cable USB a máxima velocidad. Aquí los resultados del Arduino:
0% de Falsos Negativos: El sistema detectó la falla inminente en los 100 motores sin que se le escapara uno solo.
36.3 vuelos de anticipación: En promedio, el Arduino encendió la alarma 36 vuelos antes de la muerte real del motor. El margen logístico perfecto para programar una parada de planta y solicitar repuestos sin caer en urgencias.

Conclusión

Este proyecto, que comenzó como una curiosidad con Google Gemini, me llevó desde la limpieza de datos en Pandas hasta el Bare-Metal en C++. Me demostró que para hacer Mantenimiento Predictivo; con ingeniería, matemáticas y entendiendo los límites del hardware, podemos llevar la Inteligencia Artificial a la base misma de la industria.