Retorno de experiencia: 11 integraciones externas y lo que nos ha costado idas y vueltas (doc, SDK, plantilla, núcleo)
Hola a todos, hola @pierre-gilles,
Desde agosto, he publicado 11 integraciones externas: UniFi, Android TV Remote, IPP, SolarEdge, Plex, Subsonic, Speedtest, ecojoko, Astronomía, Jellyfin & Emby y Dreame. El sistema de integraciones externas es realmente agradable de usar, ¡gracias!
Al revisar el historial de los repositorios y los hilos del foro, una buena parte de las versiones «correctivas» no venían de errores en mi código. Venían de cosas que Gladys espera sin que esté escrito en ninguna parte, o que la documentación dice de otra manera. La mayoría de las veces, el error era silencioso: un dispositivo aceptado pero nunca consultado, un componente de widget que desaparece, una integración ausente de la tienda. Lo agrupo todo aquí, clasificado por costo, con para cada punto lo que propongo del lado de la documentación, SDK o núcleo.
Todo ha sido verificado de nuevo hoy en master (núcleo 03d4696d, SDK 500db06, plantilla 4ea0c7b, tienda a052400), con archivo y línea en los bloques plegables. He descartado lo que ya está corregido o ya solicitado, y lo listo al final. Pienso especialmente en los tickets de @prohand (SDK #36, plantilla #19) y en el tema Dreame (#3153 a #3156).
En resumen: los 5 puntos que habrían evitado más versiones
- Polling:
should_poll: truees obligatorio, pero ninguna especificación ni la documentación pública lo mencionan. La plantilla publicapoll_frequency: 300(en segundos, sinshould_poll), y su propia página de Descubrimiento es rechazada con un 400. Costo para mí: aproximadamente 11 versiones publicadas, en 5 integraciones. min/maxobligatorios en todas las funcionalidades, incluyendotext. El descubrimiento los acepta ausentes, luego «Agregar» falla con un 422. La especificación los califica como «opcionales». 4 integraciones afectadas.- Pares categoría/tipo: se validan en dos listas separadas, por lo que cualquier par pasa. Se descubre en la práctica que un par no tiene etiqueta, no tiene icono, o que se trata como un sensor en lugar de un comando. 5 integraciones afectadas.
- Tienda: un rechazo del indexador no avisa a nadie. La razón solo está en un
rejected.jsondel cual la documentación no da la URL. 7 integraciones afectadas, incluyendo ecojoko, que permaneció introuvable para su probador durante 2 horas. - Estados publicados antes de que el dispositivo sea añadido: el núcleo responde
200, luego los descarta. Una integración que deduplica, como recomienda la documentación, nunca los envía de nuevo, por lo que «no hay valor reciente» justo después de la adición. 3 integraciones afectadas.
1. Polling: should_poll, unidades y frecuencias
Lo que Gladys realmente espera:
- El planificador solo consulta un dispositivo si
should_poll === truey sipoll_frequencyestá definido. should_pollvalefalsepor defecto. Nada se deduce depoll_frequency, y la página de Descubrimiento publica el dispositivo tal cual.- Resultado: un dispositivo publicado con solo
poll_frequencyes aceptado, pero nunca consultado. No hay error ni registro, solo «no hay valor reciente».
Lo que dice la documentación:
- Las especificaciones
host-api-endpoints.md:40,websocket-protocol.md:12ycommand-routing.md:5describen el poll «para un dispositivo con unapoll_frequency». Ningún archivo dedocs/specs/external-integrations/mencionashould_poll. - La documentación pública (
/docs/dev/external-integrations/) solo tiene una línea sobreonPoll. - El README del SDK en
masterha ganado hoy una sección «Polling devices» (gracias @prohand), pero aún no se ha publicado en npm (0.14.0).
La plantilla muestra el ejemplo que no funciona:
src/config.js:19contienepoll_frequency: 300, // segundos, retomado porweatherStation.js:45yplug.js:64, sinshould_poll.- El núcleo rechaza este lote con
400 devices[0].poll_frequency: invalid poll frequency. El falso Gladys del SDK enmasterlo reproduce. - La página de Descubrimiento de una integración creada desde la plantilla está por lo tanto vacía de origen. Este punto de SDK #36 ha sido corregido del lado del SDK, no en la plantilla.
Otras trampas del mismo tema:
- Lista cerrada de 1 a 60 segundos. Nada está previsto para una frecuencia lenta (5 min, 15 min, 1 h), que sin embargo es el caso común de una API en la nube con cuota: SolarEdge, ecojoko, Speedtest. Cada integración reimplementa por lo tanto su temporizador en el contenedor. Si este es el comportamiento deseado, merece una frase en la documentación.
- Un solo campo inválido hace rechazar todo el lote, y el SDK solo registra un error de manejador a nivel de depuración. En SolarEdge 1.0.1, el Descubrimiento estaba vacío sin ningún rastro a nivel de registro por defecto.
poll_frequencyyshould_pollnunca vuelven a pasar en un dispositivo ya creado. No forman parte de la firma destructure_changed, por lo que corregir el polling en una nueva versión no repara los dispositivos existentes (IPP, Subsonic). Y la página Dispositivos de una integración externa solo propone el nombre y la habitación: el usuario no puede corregirlo él mismo.- Detalle del front: el selector de frecuencia genérico (
UpdateDeviceForm.jsx:57-77) no propone 15 segundos, aunque el planificador lo gestiona.
Propuestas:
- Núcleo (lo más simple): para una integración externa, considerar
should_poll = truetan pronto como unapoll_frequencyválida es publicada. De lo contrario, rechazar con un 400 unapoll_frequencysinshould_poll. - Núcleo: incluir
should_pollypoll_frequencyen la firma destructure_changed. - Plantilla:
should_poll: true, un valor deDEVICE_POLL_FREQUENCIESen ms, y un ejemplo de temporizador interno para frecuencias lentas. - Documentación: citar
should_pollen las tres especificaciones y en el sitio, y decir francamente «más allá de 60 segundos, temporizador en el contenedor». - SDK: registrar los errores de los manejadores (
onScanRequest…) a nivelerror, nodebug.
Pruebas (master 03d4696d)
server/lib/device/device.add.js:37:if (device.should_poll === true && device.poll_frequency)server/models/device.js:46-50:should_pollvalefalsepor defectoserver/lib/external-integration/externalIntegration.setDiscoveredDevices.js:55-56: rechaza unapoll_frequencyfuera de la lista, no controlashould_pollfront/src/routes/integration/all/external-integration/discover-page/index.js:136-151: publica el objeto publicado tal cualexternalIntegration.getDiscoveredDevices.js:274-283: la firma solo contiene campos de funcionalidad- Historial: UniFi 1.2.8 → 1.3.2 (5 versiones en un día), SolarEdge 1.0.1 → 1.0.3, Subsonic 1.0.2 → 1.0.3, Speedtest 1.0.1 → 1.0.2, e IPP y Plex antes de la publicación
2. min y max obligatorios, incluso para text
El constatado:
t_device_featuredeclaramin,max,read_onlyyhas_feedbacken NOT NULL sin valor por defecto.- El descubrimiento no controla ninguno de los cuatro. El error solo llega al hacer clic en «Agregar a Gladys»: un 422. Desde #2733 al menos nombra la funcionalidad culpable, lo que ayuda.
- La especificación dice lo contrario:
host-api-endpoints.md:40habla de «every other optional column (unit,min,max)». - Los tipos del SDK también dicen
min?: number; max?: number.
Esto ocurrió con IPP, Plex, Subsonic y Astronomía. En IPP, los límites fueron retirados porque «inútiles para texto», luego puestos de nuevo el mismo día después del 422.
Propuestas:
- O el núcleo pone
0/0por defecto en la publicación, como hace Zigbee2MQTT, o rechaza con un 400 tan pronto comoPOST /discovered_device. En ambos casos, no más fallos tardíos. - Hacer
min/maxobligatorios enindex.d.tsy corregir la frase de la especificación.
3. Pares categoría/tipo: cualquier par pasa
El constatado:
- La detección prueba la categoría en una lista y el tipo en otra (
setDiscoveredDevices.js:69-74). La unidad se controla sin mirar la categoría, yDEVICE_FEATURE_UNITS_BY_CATEGORYno se utiliza en ningún lugar del lado del servidor. - La documentación hace referencia a las constantes, pero los tipos genéricos (
decimal,integer,binary…) se aceptan con cualquier categoría. - Por lo tanto, se descubre el significado de una pareja en la realidad. Lo que hemos pagado:
- UniFi: categoría
sensorinexistente, luegospeed-sensor/integera reemplazar pordatarate/rate, luegohas_feedbackfaltante. Tres versiones. - Android TV:
button/clicktratado como un sensor, por lo que los botones de la aplicación no son cliqueables (1.0.5 → 1.1.0). Fue necesario cambiar a untext/select. - IPP:
level-sensorcon un tipo genérico no tiene icono. - Astronomía:
light-sensor/binaryno tiene etiqueta, por lo que « Dispositivo (undefined) » en Descubrimiento. Reemplazado porinput/binary. - ecojoko: la potencia publicada en
energy-sensor/powerconmin: 0hacía que la aguja del medidor se saliera en exceso solar. Se necesitabagrid-sensor/power, firmado, con límites simétricos. Ni el sentido « consumo » de uno ni el sentido « intercambio firmado » del otro están escritos.
- UniFi: categoría
- Pequeño error de paso:
front/src/utils/consts.jsdeclara dos veces la claveLIGHT_SENSORenDeviceFeatureCategoriesIcon(l. 189 y 271). El segundo sobrescribe al primero, por lo quelight-sensor/integerya no tiene icono.
Propuestas:
- Validar la pareja (y la unidad por categoría) en
setDiscoveredDevices. - Exportar desde el SDK una tabla « categoría → tipos permitidos », con cada uno: sensor o comando,
read_onlyesperado, signo y límites típicos. - Hacer que todo esto sea verificado por el falso Gladys del SDK.
4. Store: rechazos silenciosos
El diagnóstico:
- El validador del store es valioso, pero un depósito que no pasa el indexador no recibe ninguna respuesta.
- La razón solo está en
rejected.json, cuyo README del store ni el sitio web dan la URL real (https://integration-store-storage.gladysassistant.com/rejected.json).DEFAULT_STORE_BASE_URLaún apunta a GitHub Pages. - Lo que hemos pagado así:
- descripción de más de 100 caracteres: ecojoko ausente del store durante 2 horas, y Pat no la encontraba;
placeholderno multilingüe (Speedtest);- portada de más de 150 Ko (Astronomía);
- tipos
text/passworda renombrar astring/secret, ydisplay_ifrechazado (UniFi); - una versión anunciada sin imagen: la integración sale del catálogo (IPP), o la actualización no se propone y el probador debe desinstalar y luego reinstalar (Android TV 1.1.0).
- Cadencia: la documentación anuncia un paso « cada hora » (cron
13 * * * *). En la práctica, GitHub solo ejecuta 3 a 6 pases programados por día desde finales de septiembre, por ejemplo 08:56 y luego 16:08 hoy. Es mejor anunciar « en el día ».
Propuestas:
- Dar la URL real de
rejected.jsonen la documentación. - Notificar al desarrollador: un estado de commit, o un issue abierto automáticamente en su depósito.
- Ejecutar
npx github:GladysAssistant/integration-store --skip-image-checken la CI del template. El comentario deci.yml:15-17, que dice lo contrario, se ha vuelto obsoleto con--skip-image-check.
5. Estados publicados antes de la adición del dispositivo: 200, luego descartados
El diagnóstico:
POST /stateresponde200 { success: true }para una funcionalidad que aún no existe.- El núcleo luego lo descarta con un simple
logger.info(device.newStateEvent.js:16-21), y el cupo de 300 estados por minuto aún se consume. - Sin embargo, la documentación recomienda, con razón, publicar solo los cambios. La integración ya « ha enviado » el valor, y el dispositivo recién agregado se queda en « sin valor reciente » hasta el próximo cambio (Plex, IPP, Subsonic).
- La solución, vaciar la caché y volver a publicar todo en
onDeviceCreated, no está escrita en ningún lado.
Propuestas:
- Decirlo en la documentación y en el template (
onDeviceCreated→ volver a publicar). - Mejor: devolver en la respuesta la lista de
external_iddesconocidos, para que el SDK pueda mantenerlos en espera.
6. Comportamientos silenciosos para documentar, o para registrar
« Actualizar » en la pestaña Descubrimiento (structure_changed).
- Solo compara
external_id,category,type,unit,min,maxystepde cada funcionalidad, más la adición o la eliminación de una funcionalidad. - No se activa en los nombres,
read_only,has_feedback,params,supported_optionsni en la encuesta.supported_optionsyparamsse resincronizan en silencio en cada publicación. - La especificación (
host-api-endpoints.md:52) solo dice « características añadidas/modificadas ». Una lista explícita habría evitado dos eliminaciones y recreaciones de dispositivo en el probador Dreame. - Para confirmar de tu lado (lo leí en el código, sin haberlo visto en la realidad): « Actualizar » parece reescribir el nombre del dispositivo elegido por el usuario con el nombre publicado (
device.create.js:140-143). La habitación, en cambio, se preserva. Esto sería contrario a la especificación, que dice que el nombre y la habitación pertenecen al usuario.
Nombre de una funcionalidad sola de su tipo.
- El tablero de control muestra la etiqueta genérica del tipo en lugar del nombre publicado, excepto para MQTT (
DISPLAY_FEATURE_NAME_FOR_THOSE_SERVICES = { mqtt: true }). Lo había pedido en el tema Dreame, sin respuesta. - El mismo efecto en la pantalla Descubrimiento: cuatro puertos PoE UniFi se mostraban como cuatro « Conmutador » indiscernibles.
- Una integración externa elige sus nombres: propongo agregarla a esta regla.
Idioma del usuario.
setValue,poll,scene.action.runy las acciones de configuración no reciben el idioma. Solo los widgets y el clima lo reciben.- Cada integración que produce texto agrega un campo
languagea su configuración (IPP, Astronomía, Jellyfin, Dreame). - Propongo transmitir
languageen estos payloads, o documentar el límite.
Cupo de 300 estados por minuto.
- La documentación solo menciona el
429. Sería útil precisar tres cosas:- es el lote completo el que se rechaza;
- un lote rechazado en 400 aún consume el cupo;
- cada estado aceptado reevalúa las escenas.
gladys_version y actualizaciones.
- Un núcleo antiguo rechaza cualquier campo de manifiesto desconocido. Declarar un widget fuerza
gladys_version >= 5.1.0, y el índice solo guarda el último manifiesto: los núcleos más antiguos ya no tienen ninguna actualización. - Está documentado y es comprensible. Sin embargo, en un núcleo antiguo:
isUpdateAvailable/getLatestVersionsolo comparan los números, por lo que la insignia « Actualización disponible » se enciende;- al hacer clic, el manifiesto se descarta con un simple
warn(update.js:33-49) y el contenedor se recrea de la misma manera; - la insignia permanece encendida, sin mensaje para el usuario.
- Las especificaciones
core/store.md:23ycontracts/management-api.md:13afirman que el catálogo está « filtrado porgladys_version». Propongo probar la compatibilidad enisUpdateAvailable.
Energía.
- Solo un
energy-sensor/indexacumulado activa el consumo de 30 minutos y el costo. Unenergy-production-sensor/indexno deriva nada: la funcionalidadthirty-minutes-productionno es creada por nadie. host-api-endpoints.md:63cita los controles deslizantes de producción, lo que lleva a creer lo contrario. ecojoko publica un índice de producción para los productores solares, y solo sirve para el historial.
7. Widgets (5.1): lo que desaparece sin previo aviso
La capacidad es excelente, y el validador del SDK ya atrapa muchas cosas. Quedan algunos agujeros:
- El presupuesto de 8 componentes se aplica antes de resolver las referencias (
getWidgetContent.js:62-69).- Una baldosa vinculada a un dispositivo aún no añadido ocupa un espacio y luego se retira. Puede haber desplazado a un componente válido.
- El resultado mutilado permanece en caché hasta el TTL (hasta 1 hora), ya que la adición del dispositivo no invalida la caché.
- Por lo tanto, es necesario pensar en llamar a
requestWidgetRefreshenonDeviceCreated: debe documentarse o, mejor aún, invalidarse en el núcleo.
- Botón cuya clave de acción ya está tomada: se descarta con un
warnen el lado del servidor, y la integración no sabe nada. En Dreame, solo se mostraba un atajo de tres (0.3.0 → 0.4.0). La especificación dice « único », pero no dice que el duplicado se elimina. - Línea
statussinvalue: se descarta sin ningún registro. - Toast de acción: se trunca a 200 caracteres por
slice, sin elipsis.- Un objeto multilingüe sin clave
enno da ningún toast. MAX_WIDGET_MESSAGE_LENGTHexiste en el SDK, pero nada lo usa.
- Un objeto multilingüe sin clave
card-list: ladatereemplaza el subtítulo, en lugar de añadirse. La especificación dice « subtitle or date »; debería especificarse « la fecha prevalece » (Jellyfin, Plex).validateWidgetContentsolo funciona en modo debug. Propongo ejecutarlo en cadaonWidgetGetcon unwarnen el lado de la integración, para que el desarrollador vea lo que el núcleo va a retirar.
8. Plantilla y documentación pública
-
.gitignorey.prettierignore: la regladata/, prevista para el volumen/data, también excluyesrc/data/. La carpeta entonces falta en la imagen construida por la CI (Astronomía, antes de la 1.0.0). Debe anclarse en/data/. -
Campos
numberdecimales: se ha corregido enmaster(#3167,step="any"), pero aún no se ha publicado.- En la 5.1.4, el ejemplo de latitud/longitud de la plantilla (
48.8566) sigue siendo imposible de ingresar. Astronomía perdió una versión por esto. - El esquema del manifiesto aún rechaza
step: no se puede declarar una resolución.
- En la 5.1.4, el ejemplo de latitud/longitud de la plantilla (
-
Documentación pública:
- anuncia el SDK
0.12.0, mientras que npm está en 0.14.0; - describe el antiguo flujo de lanzamiento, sin CHANGELOG ni lanzamiento de GitHub;
- no dice que « Ver el changelog de esta versión » abre el lanzamiento de GitHub de la etiqueta;
- aún no habla del falso Gladys.
- La plantilla ahora hace todo esto (#20, gracias): solo falta la página.
- anuncia el SDK
-
Publicar el SDK 0.15: el falso Gladys y la documentación del polling esperan en
master. Este falso Gladys podría convertirse en la red de seguridad si también verifica:poll_frequencysinshould_poll;- la ausencia de
min/max; - los pares categoría/tipo;
- el cupo de 300 estados por minuto;
- la longitud de los toasts.
Hoy,
{ poll_frequency: 60000 }sinshould_poll, con una funcionalidadlevel-sensor/decimalsin límites, responde{ success: true }.
Ya corregido o ya solicitado: no lo vuelvo a solicitar
- Botón
primaryen modo oscuro (#3153 → #3162),secretydefaulten las acciones (#3154 → #3163, #3155 → #3164), listas del aspirador ysupported_options(#3156 → #3171), camposnumberdecimales (#3167). Todo está enmaster, nada está aún en una versión publicada. - SDK #36 y plantilla #19 de @prohand: unidad de
poll_frequency, falso Gladys, lanzamiento que rompía Prettier, lanzamiento de GitHub y changelog, límite de 100 caracteres,placeholdermultilingüe. - Límite de 200 dispositivos por descubrimiento, elevado en agosto.
¡Gracias por leer o hacer que Claude lo haga!
!