devicescript-arquitectura-roles-servicios

Arquitectura de DeviceScript: servicios y roles

  • 5 min

La arquitectura de DeviceScript permite acceder al hardware mediante servicios en lugar de depender directamente de cada implementación.

Si vienes del mundo de Arduino, quizá te preguntes por qué necesitamos esta capa para encender un LED cuando digitalWrite(5, HIGH) resulta tan directo.

Esa es una excelente pregunta. Y la respuesta es la diferencia entre programar un chip y programar una solución.

Para entender esta separación necesitamos distinguir servicios, clientes, roles y drivers, además del protocolo Jacdac que los comunica.

El problema del acoplamiento al hardware

Pensemos en un programa para controlar la temperatura de un invernadero.

  • Leemos un sensor DHT11 en el pin 4.
  • Activamos un relé para el calefactor en el pin 12.

El código queda lleno de referencias a dht.read() y digitalWrite(12, ...).

Un año después, queremos mejorar el sistema.

  1. Cambiamos el sensor DHT11 por un BME280 conectado mediante I2C.
  2. Cambiamos el Arduino Uno por un ESP32 para añadir WiFi.

El resultado es que tenemos que modificar una parte considerable del programa porque la lógica de control está mezclada con los detalles del hardware.

DeviceScript soluciona esto separando el QUÉ del CÓMO.

Qué es un servicio

En DeviceScript, todo hardware se define por lo que hace, no por lo que es.

Un servicio es un contrato estándar que define registros, comandos y eventos. Por ejemplo, el servicio Button expone, entre otros elementos:

  • Tiene un estado pressure (cuánto se aprieta).
  • Tiene un evento down (cuando se pulsa).

Al código principal le da igual si ese botón es:

  • Un pulsador mecánico en el GPIO 5.
  • Un botón táctil capacitivo.
  • Un botón virtual en una pantalla web.

Todos ellos implementan el servicio Button.

Pensad en los Servicios como los Drivers de vuestro PC. Cuando imprimís un documento, a Word le da igual si la impresora es HP, Epson o Laser. Word habla con el “Servicio de Impresora”.

Qué es un rol

Un rol es el cliente que nuestro programa declara para consumir un servicio. DeviceScript utiliza el nombre de la variable como nombre del rol y de la instancia.

Pensemos en un robot con dos motores.

  • Ambos son del tipo (Servicio) Motor.
  • Pero uno tiene el Rol de “ruedaIzquierda” y el otro “ruedaDerecha”.

Cuando en DeviceScript hacemos esto:

const luzCocina = startLightBulb({ pin: pins.GPIO2 })
const luzJardin = startLightBulb({ pin: pins.GPIO4 })
Copied!

Estamos definiendo dos Roles.

  1. Cada llamada monta un servidor LightBulb sobre un pin.
  2. La función devuelve el cliente asociado, que usamos mediante luzCocina o luzJardin.

A partir de ahí, nuestro programa solo habla con luzCocina. Si mañana cambiamos el pin de la luz de la cocina, solo cambiamos la definición del rol al inicio. Toda la lógica del programa permanece intacta.

Simular gracias a la abstracción

¿Por qué es esto tan importante? Porque nos permite el concepto de Gemelo Digital (Digital Twin).

Cuando lanzamos el simulador en VS Code, lo que estamos haciendo es arrancar pequeños programas de software que “fingen” ser hardware.

  • El simulador arranca un “Servidor de Botón”.
  • Tu código busca un “Rol de Botón”.
  • DeviceScript los conecta.

Tu código no sabe que está corriendo en un simulador. No tiene forma de saberlo. Para él, hay un servicio de botón y responde a él.

Esto significa que podemos:

Desarrollar toda la lógica en el tren o en el sofá, sin hardware.

Probar casos límite (¿qué pasa si la temperatura sube a 500ºC?) moviendo un slider, sin tener que prender fuego al sensor real.

Servidores locales y roles sin asignar

En DeviceScript podemos trabajar de dos formas:

Montar un servidor local

Es la forma “Maker”. Definimos el hardware en el propio código main.ts.

// Atamos el rol 'miLed' al pin físico GPIO2
const miLed = startLightBulb({ pin: pins.GPIO2 })
Copied!

Declarar un rol

En proyectos grandes, el código no debería saber nada de pines. Solo decimos: “Necesito una luz llamada ‘status’”.

// main.ts
import { LightBulb } from "@devicescript/core"

const statusLight = new LightBulb()

setInterval(async () => {
    await statusLight.toggle()
}, 500)
Copied!

El rol se enlaza con un servidor compatible desde las herramientas de DeviceScript. El programa consume el servicio sin conocer el pin ni el driver que hay detrás.

Esto permite que el mismo archivo main.ts se compile para una placa ESP32 y para una Raspberry Pi Pico, y en cada una use los pines adecuados, sin tocar una sola línea de código.

Capas de la arquitectura

La arquitectura completa queda así:

Hardware Físico: El pin, el voltaje, el sensor de silicio.

Driver (Server): El código que lee el pin y lo traduce al estándar Jacdac (ej: startButton).

Servicio: El estándar que define qué mensajes se pueden enviar y recibir (ej: “Servicio de Botón”).

Rol: El nombre que le damos a esa capacidad en nuestro programa (ej: “BotonDeTimbre”).

Cliente: Nuestro código en main.ts que consume el Rol.

Qué aporta esta separación

  • Portabilidad: Tu código sobrevive a los cambios de hardware.
  • Simulación: Puedes probar sin hardware.
  • Claridad: El código habla de “puertas” y “luces”, no de “pines” y “voltajes”.