HomeKit integration - New features

Hello everyone,

After completing the external integration for my Neomitis heatings, I continued using my pro account Claude :slight_smile:

I tackled porting everything possible from Homekit to Gladys.

There are X PRs made. I haven’t been able to test them yet, so testers are welcome :wink:

Everything has been entirely done by Claude. If I made some mistakes, I’m open to your criticism and help to correct them (programming is not my job).

Below you will find a status of what has been done and what remains to be done (by Claude):

## Where We Are

Gladys’s HomeKit bridge exposed 12 types of devices. The rest — smoke detectors, locks, thermostats, batteries, buttons — remained invisible on the iPhone, **without error message** : the devices simply did not appear.

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

**Pending review:**

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

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

- [#2794](Feat/homekit lock by Dreamthy · Pull Request #2794 · GladysAssistant/Gladys · GitHub) — **Lock** (`LockMechanism`)

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

- [#2796](Feat/homekit fan by Dreamthy · Pull Request #2796 · GladysAssistant/Gladys · GitHub) — **Fan** (`Fanv2`)

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

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

- [#2799](Feat/homekit thermostat by Dreamthy · Pull Request #2799 · GladysAssistant/Gladys · GitHub) — **Thermostat and air conditioning** (`Thermostat`)

On the integration side, it mainly affects **Zigbee2mqtt**, **Z-Wave**, **Matter**, and **Xiaomi**, plus **Nuki** for locks, **Netatmo** and **MELCloud** for the thermostat, and **Bluetooth** for batteries.

A ninth PR is coming: the **choice of exposed devices**. Today, the bridge exposes everything it knows how to expose, which floods the Home app on a large installation. It adds an option to expose only a selection.

Each PR covers a category, is readable alone, and has 100% test coverage on the added lines.

## Actual Coverage

`hap-nodejs`, the library used by Gladys as well as Homebridge, exposes 73 services. But not all of them are device types: 38 are plumbing of the protocol (pairing, transport, firmware), pieces that only exist inside other services, or obsolete duplicates.

So there are **35 services actually exposable**. The bridge covered **12** before this project, it will cover **15** with the ongoing PRs — that is **43 %**, plus features added to existing services.

### Where does the number 73 come from — and why Apple’s documentation is not the right reference

Classic pitfall at the start: the HomeKit documentation on Apple’s developer site describes the **HomeKit framework**, which is used to write an iOS app that *controls* HomeKit accessories. A bridge, on the other hand, falls under **HAP**, the HomeKit Accessory Protocol.

Apple publishes a *HomeKit Accessory Protocol Specification (Non-Commercial Version)*, whose Release R2 is the latest public version. The commercial version is under NDA, reserved for the MFi program.

Interesting detail found in the `hap-nodejs` code: it does not transcribe the PDF of the spec, it **generates** its definitions from Apple resources present on macOS — `HomeKitDaemon.framework` and the metadata of the HomeKit Accessory Simulator.

Practical consequence: it follows what iOS actually implements, not what the R2 of 2019 described. You can find services annotated « since iOS 15 », absent from the public spec. And it gives a verifiable local reference, with the characteristics, their limits, and their units.

## Five Pitfalls for Anyone Who Wants to Contribute

### 1. A category is not enough: it’s the category + type pair

The trickiest of the five. A Gladys feature has a **category** and a **type**. The bridge checks that the pair exists in its mapping table. If the type is not there, the feature is ignored — **without error or log**.

Yet an integration can reassign the category of a feature *without touching its type*. Two real cases, both found along the way:

- **Z-Wave** reclassifies binary sensors as `co2-sensor`, leaving them `type: binary`. I had mapped `co2-sensor` for decimal and integer types, not binary → all Z-Wave CO2 detectors remained invisible. Found during review of the first PR.

- **Nuki** reports its battery level as `lock:integer`, where others use `sensor:integer`. Mapping the expected types alone would have silently excluded all Nuki locks.

The method to avoid this: list the **actually produced** category + type pairs by the integrations, and never assume the « obvious » types of a category.

### 2. Units are not guessable

HomeKit expects particle densities in **µg/m³**. Gladys lets an integration declare them in milligrams, micrograms **or** nanograms per cubic meter. Without conversion, a sensor in mg/m³ displays a thousand times too low, and nothing signals it.

Same issue for VOCs, but with a different outcome: Gladys stores them in **ppb**, HomeKit wants µg/m³. The conversion requires the molar mass of the compound, which a generic « VOC » feature does not carry. No factor can be honestly chosen, so VOCs remain out of the bridge, deliberately.

### 3. The bridge times out after 5 seconds by default

Useful to not flood the iPhone with state changes. Fatal for a **button** : a press is an event, not a state. With a 5-second delay, HomeKit reacts too late, or swallows the press if a second follows. This delay must be explicitly set to zero.

### 4. Gladys is sometimes richer than HomeKit

`BUTTON_STATUS` counts **over a hundred values** : click, double-click, long press, but also rotation, shake, directional arrows, brightness gestures. HomeKit only knows **three**.

The choice made: only report the three that have an exact equivalent, and **ignore the others** rather than mapping them to one of the three. Triggering the wrong automation for someone is worse than triggering none.

### 5. One device, one service, not three

HomeKit models in a single service what Gladys splits into several categories:

- The service `Thermostat` ← hot setpoint, cold setpoint, mode, temperature sensor

- The service `AirQualitySensor` ← air quality index, PM2.5, PM10

- The service `Battery` ← level, low battery alert

Without grouping, the Home app displays three tiles for a single device. This happened with a Matter fan: it appeared as **three distinct fans**.

## What is Blocked, and Why

These are limitations of **Gladys’s core**, not of the bridge.

**`GarageDoorOpener`, `Outlet`, `Door`, `Window`** — there is no `device_class` in the Gladys model to say « this shutter is a garage door » or « this outlet is an outlet, not a switch ». This is the most structural blockage: it closes four services at once. The lift goes far beyond the HomeKit bridge and deserves its own discussion.

**`SecuritySystem`** — the home alarm is not a device: it lives in `t_house.alarm_mode`, but the bridge iterates over the devices.

**`Valve`** — the seven `water-valve` types are *all read-only* : flow rate, watering volume, operating status. No opening command. An unactionable valve tile would be worse than nothing — and these valves are already controllable, their command passing through a `switch:binary` feature that the bridge already exposes.

**`OccupancySensor`** — HomeKit expects a stable boolean, while Gladys reports presence in `sensor:push`, an event without persistent state. A state would need to be synthesized with an arbitrarily chosen timeout: this is business logic, not mapping.

**`HeaterCooler`** — the `heater` category has only one type, `pilot-wire-mode`. The pilot wire is a French specificity without HomeKit equivalent.

**`Television` and `CameraRTPStreamManagement`** — intentionally out of scope: these are separate projects, not mapping additions.

## Why not an external integration?

This is **not possible today**, for three structural reasons:

  1. The bridge must announce itself in **mDNS on the local network** for the iPhone to discover it. An external integration runs in an isolated network where multicast does not pass.

  2. It must **listen on a fixed port**, reachable from the LAN.

  3. It must see **all devices** from all integrations, while the external model isolates each integration in its own namespace.

Beware of a common confusion: there are discussions about a **HomeKit Controller**** integration, which goes in the *other* direction — controlling HomeKit accessories from Gladys. That one could be portable externally. The bridge, no.

## Where to help

**Test on real hardware.** This is the number one need. Everything is covered by automated tests, but real pairing has not been validated on all types of devices. If you have a lock, a thermostat, a fan, or a smoke detector and an iPhone, your feedback is worth more than any unit test.

**Review the PRs**, especially the mapping decisions: gas detection thresholds, air quality bands, lock states. They are all documented and justified in the descriptions.

**Remaining feasible, not done: ** `FilterMaintenance`, for HEPA filter tracking. Only one producer today, so low value — but it’s a simple contribution for anyone who wants to get started.

Thanks for your PRs @jeromeme !! :smiley:

I’ve started automatic Cursor reviews on your PRs!