Integración Homekit - Nuevas funciones

Hola a todos,

tras haber realizado la integración externa para mis calefactores Neomitis, he seguido utilizando mi cuenta pro Claude :slight_smile:

Me he puesto a portar todo lo posible de Homekit a Gladys.

Hay X PR hechas. Todavía no he podido probarlas, así que los probadores son bienvenidos :wink:

Todo ha sido hecho completamente por Claude. Si he hecho mal algunas cosas, estoy abierto a sus críticas y ayuda para corregir el tiro (la programación no es mi profesión).

A continuación, encontrarán un estado de lo que se ha hecho y queda por hacer (por Claude) :

## Dónde estamos

El puente HomeKit de Gladys exponía 12 tipos de dispositivos. El resto — detectores de humo, cerraduras, termostatos, baterías, botones — seguía siendo invisible desde el iPhone, **sin mensaje de error** : los dispositivos simplemente no aparecían.

**Merged :** [#2781](feat(homekit): expose light, CO, CO2 and air quality sensors by Dreamthy · Pull Request #2781 · GladysAssistant/Gladys · GitHub) — sensores de luminosidad, CO, CO2 y calidad del aire.

**En espera de revisión :**

- [#2792](feat(homekit): expose sirens as a Switch by Dreamthy · Pull Request #2792 · GladysAssistant/Gladys · GitHub) — **Sirena** (`Switch`)

- [#2793](feat(homekit): expose smoke sensors by Dreamthy · Pull Request #2793 · GladysAssistant/Gladys · GitHub) — **Detector de humo** (`SmokeSensor`)

- [#2794](https://github.com/GladysAssistant/Gladys/pull/2794) — **Cerradura** (`LockMechanism`)

- [#2795](feat(homekit): expose buttons as stateless programmable switches by Dreamthy · Pull Request #2795 · GladysAssistant/Gladys · GitHub) — **Botón, control remoto** (`StatelessProgrammableSwitch`)

- [#2796](feat(homekit): expose fans as a Fanv2 by Dreamthy · Pull Request #2796 · GladysAssistant/Gladys · GitHub) — **Ventilador** (`Fanv2`)

- [#2797](feat(homekit): expose device batteries by Dreamthy · Pull Request #2797 · GladysAssistant/Gladys · GitHub) — **Nivel de batería** (`Battery`)

- [#2798](feat(homekit): expose PM2.5 and PM10 densities on the air quality sensor by Dreamthy · Pull Request #2798 · GladysAssistant/Gladys · GitHub) — **Partículas PM2.5 y PM10** (`AirQualitySensor`)

- [#2799](feat(homekit): expose thermostats and air conditioners by Dreamthy · Pull Request #2799 · GladysAssistant/Gladys · GitHub) — **Termostato y aire acondicionado** (`Thermostat`)

En cuanto a las integraciones, afecta principalmente a **Zigbee2mqtt**, **Z-Wave**, **Matter** y **Xiaomi**, más **Nuki** para las cerraduras, **Netatmo** y **MELCloud** para el termostato, y **Bluetooth** para las baterías.

Una novena PR está llegando: la **selección de dispositivos expuestos**. Hoy en día, el puente expone todo lo que sabe exponer, lo que inunda la app Casa en una gran instalación. Añade una opción para exponer solo una selección.

Cada PR cubre una categoría, es legible por sí sola, y tiene una cobertura de pruebas del 100 % en las líneas añadidas.

—

## Cobertura real

`hap-nodejs`, la biblioteca utilizada por Gladys y por Homebridge, expone 73 servicios. Pero no todos son tipos de dispositivos: 38 son tuberías del protocolo (emparejamiento, transporte, firmware), piezas que solo existen dentro de otros servicios, o duplicados obsoletos.

Por lo tanto, quedan **35 servicios realmente expuestos**. El puente cubría **12** antes de este proyecto, cubrirá **15** con las PR en curso — es decir, **43 %**, más características añadidas a servicios existentes.

### ¿De dónde sale este número de 73 — y por qué la documentación de Apple no es la referencia correcta

Trampa clásica al principio: la documentación de HomeKit en el sitio de desarrolladores de Apple describe el **framework** HomeKit, el que se utiliza para escribir una app iOS que *controla* accesorios. Un puente, en cambio, se basa en **HAP**, el HomeKit Accessory Protocol.

Apple publica una *HomeKit Accessory Protocol Specification (Non-Commercial Version)*, cuya versión R2 es la última versión pública. La versión comercial está bajo NDA, reservada para el programa MFi.

Detalle interesante encontrado en el código de `hap-nodejs` : no transcribe el PDF de la especificación, **genera** sus definiciones a partir de recursos de Apple presentes en macOS — `HomeKitDaemon.framework` y las metadatos del HomeKit Accessory Simulator.

Consecuencia práctica: sigue lo que iOS implementa realmente, no lo que la R2 de 2019 describía. Se encuentran servicios anotados « desde iOS 15 », ausentes de la especificación pública. Y esto da un referencial verificable localmente, con las características, sus límites y sus unidades.

—

## Cinco trampas, para quien quiera contribuir

### 1. Una categoría no es suficiente: es el par categoría + tipo

El más vicioso de los cinco. Una característica de Gladys tiene una **categoría** y un **tipo**. El puente verifica que el par exista en su tabla de correspondencia. Si el tipo no está allí, la característica se ignora — **sin error ni registro**.

Sin embargo, una integración puede reasignar la categoría de una característica *sin tocar su tipo*. Dos casos reales, ambos encontrados en el camino :

- **Z-Wave** reclasifica los sensores binarios en `co2-sensor`, dejándoles `type: binary`. Había mapeado `co2-sensor` para los tipos decimal y entero, no binario → todos los detectores de CO2 Z-Wave seguían siendo invisibles. Encontrado en la revisión de la primera PR.

- **Nuki** sube su nivel de batería en `lock:integer`, donde los demás usan `sensor:integer`. Mapear los tipos esperados solos habría silenciosamente descartado todas las cerraduras Nuki.

El método que los evita: listar los pares **realmente producidos** por las integraciones, y nunca suponer los tipos « evidentes » de una categoría.

### 2. Las unidades no se adivinan

HomeKit espera las densidades de partículas en **µg/m³**. Gladys deja que una integración las declare en miligramos, microgramos **o** nanogramos por metro cúbico. Sin conversión, un sensor en mg/m³ se muestra mil veces demasiado bajo, y nada lo señala.

Mismo tema para los COV, pero con un resultado diferente: Gladys los almacena en **ppb**, HomeKit quiere µg/m³. La conversión requiere la masa molar del compuesto, que una característica « COV » genérica no lleva. Ningún factor puede ser elegido honestamente, por lo que los COV quedan fuera del puente, intencionalmente.

### 3. El puente temporiza 5 segundos por defecto

Útil para no inundar el iPhone con cambios de estado. Redhibitorio para un **botón** : una pulsación es un evento, no un estado. Con 5 segundos de retraso, HomeKit reacciona demasiado tarde, o traga la pulsación si una segunda sigue. Hay que poner este retraso a cero explícitamente.

### 4. Gladys es a veces más rica que HomeKit

`BUTTON_STATUS` cuenta **más de cien valores** : clic, doble clic, pulsación larga, pero también rotación, sacudida, flechas direccionales, gestos de luminosidad. HomeKit solo conoce **tres**.

La elección hecha: subir solo los tres que tienen un equivalente exacto, e **ignorar los demás** en lugar de reducirlos a uno de los tres. Desencadenar la automatización equivocada en alguien es peor que no desencadenar ninguna.

### 5. Un dispositivo, un servicio, no tres

HomeKit modela en un solo servicio lo que Gladys divide en varias categorías :

- El servicio `Thermostat` ← consigna caliente, consigna fría, modo, sensor de temperatura

- El servicio `AirQualitySensor` ← índice de calidad, PM2.5, PM10

- El servicio `Battery` ← nivel, alerta de batería baja

Sin agrupación, la app Casa muestra tres baldosas para un solo dispositivo. Esto ocurrió con un ventilador Matter: aparecía como **tres ventiladores distintos**.

—

## Lo que está bloqueado, y por qué

Son límites del **núcleo de Gladys**, no del puente.

**`GarageDoorOpener`, `Outlet`, `Door`, `Window`** — no existe una `device_class` en el modelo Gladys que permita decir « este cierre es una puerta de garaje » o « este enchufe es un enchufe, no un interruptor ». Este es el bloqueo más estructural: cierra cuatro servicios de una vez. El levantamiento supera con creces el puente HomeKit y merecería su propia discusión.

**`SecuritySystem`** — la alarma doméstica no es un dispositivo: vive en `t_house.alarm_mode`, pero el puente itera sobre los dispositivos.

**`Valve`** — los siete tipos `water-valve` son *todos de solo lectura* : flujo, volumen de riego, estado de funcionamiento. No hay ningún comando de apertura. Una baldosa de válvula inoperable sería peor que nada — y estas válvulas ya son controlables, su comando pasa por una característica `switch:binary` que el puente ya expone.

**`OccupancySensor`** — HomeKit espera un booleano estable, mientras que Gladys sube la presencia en `sensor:push`, un evento sin estado persistente. Sería necesario sintetizar un estado con un temporizador de apagado elegido arbitrariamente: es lógica de negocio, no mapeo.

**`HeaterCooler`** — la categoría `heater` solo tiene un tipo, `pilot-wire-mode`. El cable piloto es una especificidad francesa sin equivalente en HomeKit.

**`Television` y `CameraRTPStreamManagement`** — fuera del alcance voluntariamente: son proyectos completos en sí mismos, no son añadidos de correspondencia.

—

## ¿Y por qué no una integración externa?

No es **posible hoy**, por tres razones estructurales:

  1. El puente debe anunciarse en **mDNS en la red local** para que el iPhone lo descubra. Una integración externa gira en una red aislada donde no pasa el multicast.

  2. Debe **escuchar en un puerto fijo**, accesible desde el LAN.

  3. Debe ver **todos los dispositivos** de todas las integraciones, mientras que el modelo externo aísla cada integración en su propio espacio de nombres.

Atención a una confusión frecuente: existen discusiones sobre una integración **HomeKit Controller**, que va en el *otro* sentido — controlar accesorios HomeKit desde Gladys. Esa sí sería portable en externo. El puente, no.

—

## Dónde ayudar

**Probar en hardware real.** Esta es la necesidad número uno. Todo está cubierto por pruebas automatizadas, pero el emparejamiento real no ha sido validado en todos los tipos de dispositivos. Si tienes una cerradura, un termostato, un ventilador o un detector de humo y un iPhone, tu retroalimentación vale más que cualquier prueba unitaria.

**Revisar las PR**, en particular las decisiones de correspondencia: umbrales de detección de gas, bandas de calidad del aire, estados de cerradura. Todas están documentadas y justificadas en las descripciones.

**Resta factible, no hecho:** `FilterMaintenance`, para el seguimiento de filtro HEPA. Un solo productor hoy, por lo que bajo valor — pero es una contribución simple para quien quiera empezar.

¡Gracias por tus PRs @jeromeme !! :smiley:

¡He lanzado las revisiones automáticas de Cursor en tus PRs!

Gracias @pierre-gilles por las revisiones, esto permitió corregir ciertos puntos.

El estado de la situación actualizado:

Corregido — 25 defectos

Primera revisión (15)

  1. Un retraso de 0 s cambiado a 5 s por un || — la corrección del detector de humo era código muerto
  2. Dos clics simples seguidos: el segundo era ignorado
  3. Un ventilador escribía en una característica de solo lectura
  4. Apagado sin lectura previa → encendido a máxima velocidad
  5. La cerradura olvidaba el comando después del primer sondeo
  6. Una cerradura de solo lectura aún aceptaba comandos
  7. Batería muda anunciada como baja (null <= 20 es true en JS)
  8. Modos de climatización no contiguos: «caliente» propuesto en un climatizador solo frío
  9. Consigna de temperatura vinculada en un dispositivo que no la tiene → fallo en el primer sondeo
  10. Lista de modos vacía, rechazada por HAP
  11. Sin límite en el camino de notificación: un sensor que fallaba hacía caer el puente
  12. 5 segundos de retraso en un detector de humo (reportado por Pierre-Gilles)
  13. Ruta /device no probada
  14. Orden de escritura: un fallo parcial dejaba todos los dispositivos expuestos
  15. El fallo de /device eliminaba el código QR de emparejamiento

Segunda revisión (8)

  1. Dos alarmas en el mismo instante: la primera perdida
  2. Un comando de cerradura sobrescribía el estado real — Nuki anunciada como cerrada mientras gira
  3. Control remoto multi-botones: todos los presionamientos en el botón 1
  4. Botones Matter totalmente mudos
  5. La velocidad bruta del ventilador sobrescribía el porcentaje mostrado
  6. Paso por debajo del 20 % de batería nunca notificado, solo en el sondeo siguiente
  7. Termostato nunca «en reposo» entre sus dos consignas
  8. Modo auto escrito en un climatizador que no lo declara

Esta mañana (2)

  1. Presionamientos largos Xiaomi nunca transmitidos
  2. Cuatro pruebas ya fusionadas eliminadas por un rebase — la CI seguía verde

Estado

  • 3 PR fusionadas: #2781, #2792, #2798
  • 7 PR abiertas, todas verdes, sin conflicto
  • 5 aprobadas por Cursor: #2793, #2796, #2797, #2799, #2800
  • 2 en espera de su nueva revisión: #2794, #2795
  • 46 hilos de revisión respondidos
  • 2 issues de seguimiento abiertas: #2806, #2812

Lo que queda por hacer en el puente HomeKit

Probar

  • Emparejar un iPhone real. Nada ha sido probado en hardware real:
    todo está validado por pruebas automatizadas. Se necesita una instalación Gladys
    nativa — no Docker, el multicast mDNS no atraviesa la VM.
    Prioridad a cerraduras, termostatos y controles remotos.
  • Verificar los nombres de los accesorios: HAP rechaza emojis y puntuación, y
    solo emite una advertencia. El accesorio nunca aparece, sin error.

Revisar

Siete PR abiertas, todas verdes, todas revisables por separado: #2793 humo,
#2794 cerradura, #2795 botón, #2796 ventilador, #2797 batería, #2799 termostato,
#2800 selección de dispositivos.

Dos proyectos abiertos para quien quiera

  • #2806 — mapear thermostat:mode y thermostat:operating-state. Los tipos
    existen en el núcleo desde #2752, pero ninguna integración los produce
    aún. Hacerlo al mismo tiempo que la primera que los emita (#2730 lado
    Z-Wave).
  • #2812 — indexar los servicios HomeKit por característica en lugar de por tipo. Una
    sola corrección desbloquearía los controles remotos multi-botones, las persianas
    múltiples y los termostatos con múltiples sondas.

Bloqueado por el núcleo de Gladys

Estos servicios HomeKit no son alcanzables sin una evolución del modelo
de dispositivo. El primero es el más estructurante: desbloquearía cuatro de una vez.

  • GarageDoorOpener, Outlet, Door, Window — falta una noción de
    device_class para distinguir un enchufe de un interruptor, o una puerta de
    garaje de una persiana.
  • SecuritySystem — la alarma vive en t_house.alarm_mode, no en un
    dispositivo.
  • Valve — los siete tipos water-valve son todos de solo lectura. Estas válvulas
    siguen controlables vía switch:binary.
  • OccupancySensor — Gladys sube la presencia sin estado persistente.
  • HeaterCooler — la categoría heater solo tiene el fil piloto, especificidad
    francesa sin equivalente HomeKit.

Hacible, no hecho

  • FilterMaintenance (filtro HEPA) — un solo productor, bajo valor.

¡Muchas gracias por todas estas PRs :slight_smile: ¡Es realmente genial!!

Todo está bien para mí, ¡se ha fusionado en master!

Quizás nuestras funcionalidades no sean lo suficientemente precisas :slight_smile:

No dudes en crear una solicitud de funcionalidad para cada tipo de característica que te gustaría tener en Gladys en Demande de fonctionnalités, ¡puede añadirse totalmente!

En sí, no veo el problema, la integración podría mapear el modo de la alarma en Homekit?

¿Qué quieres decir?

No hay problema, esperando que todo funcione bien :slight_smile:

para la alarma, haré que cree un dispositivo en Homekit con el siguiente mapeo:

  • Gladys / desarmado = Homekit / DESARMADO
  • Gladys / armado = Homekit / ARMADO_AUSENTE
  • Gladys / parcialmente armado = Homekit / ARMADO_EN_CASA
  • Gladys / pánico = Homekit / ALARMA_DISPARADA
    Solo el estado Homekit / ARMADO_NOCHE no se integraría.

para la device_class, lo estudiaré con Claude y, si es necesario, haré una solicitud :wink:

para el OccupancySensor, es un error reportado por Claude:

Cita
Había descartado `presence-sensor` creyendo que no había estado que leer, porque su tipo es `SENSOR.PUSH`.
Al leer `lan-manager.scanPresence.js`, es falso: envía 1 cuando el dispositivo es detectado y 0 cuando desaparece. Es un booleano real, y se mapea directamente en `OccupancySensor`. Igual lado Bluetooth. Haré la PR.