Skip to main content

Inicia sesión en CleanKata

Sigue tu progreso, gana XP y desbloquea todas las lecciones.

Al iniciar sesión aceptas nuestros Términos de uso y Política de privacidad.

Arquitectura Limpia70 XP7 min

Arquitectura Embebida Limpia: Evitando el Firmware

El software no se desgasta, pero el hardware queda obsoleto — una Capa de Abstracción de Hardware evita que la lógica de negocio se convierta en firmware atrapado en un procesador específico.

Por qué importa

Robert Martin traza una distinción clara entre firmware y software. El firmware es código tan profundamente acoplado al hardware que no puede ejecutarse en ningún otro lugar. El software puede portarse. La tendencia preocupante es que desarrolladores que no escriben sistemas embebidos están escribiendo firmware de todas formas — acoplan su lógica de negocio a una base de datos, framework o runtime específico hasta que nunca puede extraerse.

La Capa de Abstracción de Hardware (HAL) se sitúa entre la lógica de negocio y el código específico del hardware. La lógica de negocio importa solo la interfaz abstracta de la HAL; la implementación concreta de la HAL maneja las llamadas reales al hardware. La OSAL (Capa de Abstracción del Sistema Operativo) hace lo mismo para las llamadas al RTOS.

La prueba clave: ¿puede tu lógica de negocio ejecutarse en tu laptop de desarrollo sin hardware especial? Si la respuesta es no, tienes firmware. El patrón HAL rompe la dependencia para que tus reglas de negocio puedan probarse con una HAL simulada en cualquier máquina, y el adaptador específico de hardware es reemplazable cuando cambia la plataforma de hardware.

✗El problema

Business logic directly calls hardware drivers. The motor control logic is welded to a specific board. Impossible to unit test without real hardware. Impossible to reuse on a different platform.

Bad

import RPi.GPIO as GPIO  # hardware-specific import
import time

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)

class ConveyorBelt:
    def start(self, duration_seconds: float) -> None:
        GPIO.output(18, GPIO.HIGH)   # RPi-specific hardware call
        time.sleep(duration_seconds)
        GPIO.output(18, GPIO.LOW)    # RPi-specific hardware call

    def emergency_stop(self) -> None:
        GPIO.output(18, GPIO.LOW)    # RPi-specific hardware call

# To run tests: need a physical Raspberry Pi connected to a motor.
# To port to STM32 or Arduino: rewrite ConveyorBelt from scratch.
# The business logic has become firmware.
import { gpio } from "rpi-gpio"; // hardware-specific library

export class ConveyorBelt {
  private readonly PIN = 18;

  async start(durationMs: number): Promise {
    await gpio.setup(this.PIN, gpio.DIR_OUT);
    await gpio.write(this.PIN, true);   // hardware-specific call
    await new Promise(r => setTimeout(r, durationMs));
    await gpio.write(this.PIN, false);  // hardware-specific call
  }

  async emergencyStop(): Promise {
    await gpio.write(this.PIN, false);  // hardware-specific call
  }
}

// To run tests: need a physical device with GPIO pins.
// To port to a different board: rewrite ConveyorBelt from scratch.
// The business logic has become firmware.

✓La solución

A MotorController HAL interface separates the business logic from hardware calls. MockMotorController enables unit tests on any laptop. Switching boards means writing a new adapter — business logic is untouched.

Good

from abc import ABC, abstractmethod
import time

# hal/motor_controller.py  ← Hardware Abstraction Layer (stable interface)
class MotorController(ABC):
    @abstractmethod
    def turn_on(self) -> None: ...

    @abstractmethod
    def turn_off(self) -> None: ...

# hal/rpi_motor_controller.py  ← RPi adapter (volatile, hardware-specific)
import RPi.GPIO as GPIO

class RaspberryPiMotorController(MotorController):
    PIN = 18
    def __init__(self):
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.PIN, GPIO.OUT)
    def turn_on(self)  -> None: GPIO.output(self.PIN, GPIO.HIGH)
    def turn_off(self) -> None: GPIO.output(self.PIN, GPIO.LOW)

# hal/mock_motor_controller.py  ← test double (runs on any machine)
class MockMotorController(MotorController):
    def __init__(self): self.is_on = False
    def turn_on(self)  -> None: self.is_on = True
    def turn_off(self) -> None: self.is_on = False

# domain/conveyor_belt.py  ← pure business logic, no hardware import
class ConveyorBelt:
    def __init__(self, motor: MotorController):
        self._motor = motor

    def start(self, duration_seconds: float) -> None:
        self._motor.turn_on()
        time.sleep(duration_seconds)
        self._motor.turn_off()

    def emergency_stop(self) -> None:
        self._motor.turn_off()

# Tests run on any laptop — no hardware needed.
# Port to STM32: write STM32MotorController. ConveyorBelt unchanged.
// hal/MotorController.ts  ← HAL interface (stable)
export interface MotorController {
  turnOn(): Promise;
  turnOff(): Promise;
}

// hal/RpiMotorController.ts  ← RPi adapter (volatile, hardware-specific)
import { gpio } from "rpi-gpio";
import { MotorController } from "./MotorController";

export class RpiMotorController implements MotorController {
  private readonly PIN = 18;
  async turnOn():  Promise {
    await gpio.setup(this.PIN, gpio.DIR_OUT);
    await gpio.write(this.PIN, true);
  }
  async turnOff(): Promise { await gpio.write(this.PIN, false); }
}

// hal/MockMotorController.ts  ← test double (runs on any machine)
import { MotorController } from "./MotorController";

export class MockMotorController implements MotorController {
  public isOn = false;
  async turnOn():  Promise { this.isOn = true;  }
  async turnOff(): Promise { this.isOn = false; }
}

// domain/ConveyorBelt.ts  ← pure business logic, no hardware import
import { MotorController } from "../hal/MotorController";

export class ConveyorBelt {
  constructor(private readonly motor: MotorController) {}

  async start(durationMs: number): Promise {
    await this.motor.turnOn();
    await new Promise(r => setTimeout(r, durationMs));
    await this.motor.turnOff();
  }

  async emergencyStop(): Promise { await this.motor.turnOff(); }
}

// Tests run on any laptop — no hardware needed.
// Port to new board: write new adapter. ConveyorBelt unchanged.

💡Conclusión clave

Si tu lógica de negocio no puede ejecutarse en tu laptop de desarrollo sin hardware especial, se ha convertido en firmware — no en software. La Capa de Abstracción de Hardware es el mismo patrón que Puertos y Adaptadores aplicado a dispositivos físicos: define una interfaz abstracta en el dominio, impleméntala una vez por objetivo de hardware, y prueba la lógica de negocio con una implementación simulada que funcione en cualquier lugar.

🔧 Algunos ejercicios pueden tener errores. Si algo parece incorrecto, usa el botón Feedback (abajo a la derecha) para reportarlo — nos ayuda a corregirlo rápido.

Pista: Si tu lógica de negocio no puede ejecutarse en tu laptop de desarrollo sin hardware especial, se ha convertido en firmware — no en software.

✗ Tu versión