Project: Soft Rains Entry 02

Sixty-two devices and no way in

HomeKit will happily run my house. It will not let a script talk to it.

The house came together faster than I expected on the hardware side. Bulbs, a bridge, a handful of sensors, a couple of hubs, spread across two homes in the Apple Home app because of how the accounts were set up. Most of it I had collected over the years without ever once adding it up, and the total genuinely surprised me: sixty-two, by the count my scripts hand back. The honest number is nearer fifty. Some of the sensors and switches present as several inputs each, so I am double counting a few of them, and I have not bothered to work out exactly how many. Every one of them worked. Not one of them would talk to a script.

The two homes thing is my own fault and I am not going to fix it, because fixing it means re-pairing everything and I have already decided I would rather write code around it than spend a Saturday doing that. Ask me again in six months. Also worth saying, since I was worried about it before moving in: the walls here are a lot more solid than the ones in the condo, and I had half expected the radio side of this to be a constant fight. It has not been. The one place it does struggle is the far end of the upstairs landing, which is also the one place I cannot easily get a mains socket, which feels about right.

This is the thing nobody mentions about HomeKit. It is a genuinely good system for running a house from a phone, and it is a closed door if you want to run one from a program. There is no local HTTP API. There is no CLI. Apple gives you the Home app, and Shortcuts, and past that the only supported way in is to write a native application that links against the HomeKit framework and holds the user permission itself.

So that is what I did, in the loosest possible sense of did. Somebody had already solved most of it in a project called homemcpbridge, so I cloned that and hacked it into the shape I needed rather than starting from an empty file. What came out the other end is a small Swift app that holds the HomeKit permission and exposes the accessories over a socket on my own network. Nothing clever, and not much of it originally mine. It is a translation layer, maybe a few hundred lines that matter, and it is the foundation every other thing in this project stands on.

It was also, at this stage, running on my laptop. Not on anything permanent, not on a machine that stays in the house, on the laptop that goes in my bag every morning. Which meant the whole automation setup went down the moment I walked out of the door with it and came back up when I got home, and I knew that perfectly well the entire time I was building on top of it. It is the sort of thing you file under a problem for another day. It stayed filed there for a good while.

I did not plan to be writing Swift on a weekend. I sat down intending to wire up two lights and a motion sensor, and instead spent the evening reading framework documentation and relearning what a delegate is. That is roughly the shape this whole thing has taken so far: the job I thought I was doing turns out to sit on top of a job nobody mentioned. I have stopped being annoyed about it. It is most of the fun.

Two things that cost me an afternoon each

A new accessory needed a full reboot to appear. I added a Matter device, saw it in the Home app on my phone within seconds, and could not see it from the Mac at all. Not a caching bug I could clear, not something a relaunch fixed. The machine had been up for a few weeks and the local Home database had simply not caught up. Rebooting it fixed it immediately, which is an unsatisfying answer that I have now had to use twice.

My own writes are invisible to me. When somebody flips a switch on the wall, or changes something in the Home app, HomeKit sends out a notification and my bridge hears it. When my bridge is the one making the change, it gets nothing back. Which means any daemon that tracks state in memory will slowly drift away from reality, always in the direction of believing a light is on when it is off. That single quirk has caused more bugs in this project than anything else, and the fix in the end was boring: re-read the real state on a timer and believe the house over my own notes.

And one that was entirely my fault

I had a Hue remote named "Living room switch" and a wall dimmer named "Living Room Switch". Name lookups were case-insensitive, so one of them permanently shadowed the other and half my commands went to a battery-powered remote with nothing attached to it. Everything I know about naming things I apparently had to learn again in my own living room.