Cómo añadir un nuevo producto basado en idryer-core¶
Usa esta guía cuando crees un producto nuevo sobre idryer-core: un secador de filamento, un bloque calefactor, una iluminación, un sensor u otro módulo. Muestra qué hace el core por ti y qué debe añadir el código del producto.
Un ejemplo completo que compila es el armario de almacenamiento calefactado de la documentación Build-Your-Own-iDryer (example/09-cabinet): recorre todo lo que hay en esta página.
Sobre qué se construye un producto¶
Un producto habla con el core a través de un solo objeto: la fachada iDryer::Link (<iDryer.h>). Dentro de s_link.begin() y s_link.loop() el core:
- obtiene la red Wi-Fi desde la aplicación iDryer por el aire (ESPTouch) o desde el instalador web por USB (Improv) y mantiene la conexión;
- vincula el dispositivo a una cuenta: espera un token de vinculación de un solo uso —desde la aplicación por la red local o por el puerto serie (
PAIR_TOKEN:<token>)— y lo canjea en el portal por un secreto permanente; - se conecta a MQTT y publica
telemetryystatuscon los periodos deConfig; - se anuncia en la red local (mDNS
_idryer._tcp) y acepta comandos de la aplicación por WebSocket; - publica el card manifest de la tarjeta del dispositivo.
Las clases de nivel inferior (IdryerRuntime, CloudStateMachine, LocalAccess y otras) son partes internas del core: el token de vinculación llega a la parte de nube solo dentro de iDryer::Link. Construye el producto sobre la fachada.
1. Proyecto¶
idryer-core va en lib/idryer-core/ (una copia o un enlace simbólico); PlatformIO toma las bibliotecas del core de su library.json. platformio.ini mínimo:
[env:my-device]
platform = espressif32
framework = arduino
board = esp32-c3-devkitm-1
; transporte de ESP8266 de las dependencias de espMqttClient: no compila en ESP32
lib_ignore = ESPAsyncTCP
build_flags =
-DIDRYER_API_BASE='"https://portal.idryer.org/api"'
-DMQTT_BROKER='"mqtt.idryer.org"'
-DMQTT_PORT=8883
-DMQTT_USE_TLS=1
Sin lib_ignore = ESPAsyncTCP la compilación falla en ESPAsyncTCP.cpp; sin MQTT_BROKER y MQTT_PORT el core no compila.
2. Firmware mínimo¶
#include <iDryer.h>
static const iDryer::Config CFG = {
.deviceType = iDryer::DeviceType::Unknown, // no es un producto iDryer: la tarjeta sale del manifiesto
.unitsCount = 1,
.hasAirTemp = true,
.hasAirHumidity = true,
.hardwareVersion = "1.0",
.firmwareVersion = "0.1.0",
.model = "My Device",
};
static iDryer::Link s_link(CFG);
void setup() {
s_link.begin();
// El dispositivo se desvinculó en el portal: borrar el secreto y esperar una nueva vinculación.
s_link.onCommand("revoke", [](JsonObjectConst) { s_link.handleRevoke(); });
}
void loop() {
s_link.loop();
s_link.telemetry.airTempC[0] = readTemp(); // tus funciones de sensores
s_link.telemetry.airHumidityPct[0] = readHumidity();
}
s_link.begin() arranca el Wi-Fi, la vinculación, MQTT y el acceso local; s_link.loop() tiene que ejecutarse siempre, sin delay(). El comando revoke llega desde el portal cuando el dispositivo se desvincula de la cuenta: handleRevoke() borra el secreto y el dispositivo espera un nuevo token de vinculación.
3. Wi-Fi y vinculación: nada en el código¶
El firmware no contiene ni la contraseña de la red ni datos de la cuenta. El usuario conecta el dispositivo en la aplicación iDryer: Conectar un dispositivo nuevo → paso Wi-Fi (la aplicación envía la red por ESPTouch) → paso Vinculación (la aplicación encuentra el dispositivo por mDNS, obtiene del portal un token de un solo uso y se lo entrega; el dispositivo activa el token en el portal por sí mismo). Mientras no hay Wi-Fi, el puerto serie está en silencio: el core lo reserva para el instalador web (Improv).
Paso a paso y con el log esperado: Build-Your-Own-iDryer, capítulo «Inicio del firmware en el core».
4. Datos: telemetría y estado¶
- Los flags
has*deConfigdefinen qué campos del vocabulario van a la telemetría y qué celdas aparecen en la tarjeta. - Escribe los valores en
s_link.telemetry(airTempC,airHumidityPct,heaterTempC,heaterPower01,fanOn,servoOpen) ys_link.status(mode,targetTempC,durationS,elapsedS); el core los publica con los periodos deConfigys_link.publishStatusNow()envía el estado al momento. - Un campo propio: con
s_link.onTelemetryPublish(); en la tarjeta, cons_link.card().sensor(); ver Tarjeta del dispositivo.
5. Ajustes: el menú¶
Los ajustes se describen en src/menu/menu.yaml; el generador menu_gen.py produce código C++, el almacenamiento en NVS y el JSON del menú (El menú como protocolo). El código del producto:
- carga el menú antes de
s_link.begin():menu.initDefaults(),menu.loadFromNVS(),menu_sync_state_to_cache(); - lo publica con
menu_buildFullJson()ys_link.devicePublisher()->publishConfigRaw(): al pasar a estar en línea y con el comandoget_config; - aplica el comando
setconmenu_apply_by_bind()(valor, NVS y caché a la vez) y vuelve a publicar el menú.
El código completo: Build-Your-Own-iDryer, capítulo «Menú desde YAML».
6. Tarjeta del dispositivo¶
Lo que muestra la tarjeta y qué operaciones lanza se declara con s_link.card(): Tarjeta del dispositivo: el card manifest.
7. Contrato¶
Cuando añadas topics nuevos o cambies payloads:
- actualiza
contracts/mqtt_contract.yaml; - ejecuta
contracts/regen.shy haz commit de los archivos generados.
Dispositivos de dos chips¶
Para un ESP32 que trabaja con un controlador aparte (por ejemplo, RP2040) por UART, el core tiene el puente UART idryer_uart.h; la referencia que funciona es el firmware idryer-link.