Getting a DSC PowerSeries Neo Alarm Panel into Apple Home


My house has a hardwired DSC PowerSeries Neo HS2032 alarm panel. Two dozen wired door, window and motion zones, keypads by the doors, and professional monitoring through a local alarm company. Pretty much everything else in the house is in Apple Home, except the alarm. I wanted to fix that. And I didn’t just want door contacts showing up in Home, I wanted a real HomeKit Security System that I could arm, disarm, and see when it was in alarm, with every zone showing up as its own sensor.

Sounds simple enough. But there were a few constraints that made it interesting. First, I wanted to keep everything that already works: the panel, the keypads, the monitoring and every wired sensor stay exactly as they are. Second, no new hardware and no subscription. Third, it had to be local, no cloud sitting between the panel and my phone. And finally, the installer code belongs to the alarm company. They wouldn’t give it to me over the phone, but they agreed to one visit. So anything that needed panel programming had to be right the first time. One shot.

It took four days, and it works. So let’s look at how we got there.

Why the usual answers don’t work

If you search for “DSC alarm HomeKit” almost every answer assumes you have a classic PowerSeries panel (PC1832, PC1864 and so on). Those panels talk to their keypads over the Keybus, which is an unencrypted serial bus that people have been reverse-engineering for over a decade. You put an Envisalink EVL-4 on the Keybus, it exposes an interface over TCP, and homebridge-envisalink turns that into HomeKit accessories. If you want to do it yourself there’s an ESP32 version, dscKeybusInterface.

Here’s the problem. The Neo doesn’t have a Keybus. It talks to its keypads and modules over Corbus, DSC’s newer bus, and that traffic is encrypted. None of the Keybus integrations can see it. Period. Both the openHAB DSC binding and a very long Home Assistant thread confirm this.

Every option I looked at

I started with an AI-generated list of four options. It turned out most of it was wrong in ways that mattered, so I ended up going through everything myself. Here’s the full list, with street prices from late October 2026 (take them as rough numbers):

OptionVerdictWhy
Envisalink EVL-4 (~$110)Doesn’t workReads the classic Keybus only
ESP32 Keybus decoderDoesn’t workSame reason
Self-hosted “local Alarm.com server”Not possibleSee below
Alarm.com cloudRuled outPaid dealer subscription, no native HomeKit, only an unofficial Home Assistant add-on that breaks whenever Alarm.com changes its login or 2FA
Konnected board wired in parallel to zones (~$230–290 per 12 zones)Possible, weakerSees zones only. Armed/alarm state needs extra wiring from the panel’s PGM outputs, and the keyswitch zone needs installer programming anyway
Swap to a PC1832 board + EVL-4DowngradeDiscontinued panel, new PK5500 keypads (~$170–290 each, since Neo keypads don’t work on PowerSeries), full reprogramming, loses PowerG wireless
Qolsys IQ Panel 4 + two IQ Hardwire 16 modulesPossible, costly~$420–480 plus $105–195 per Hardwire 16. HomeKit via a Home Assistant integration that impersonates an IQ Remote, then HA’s HomeKit Bridge, so a second hub. Pairing needs the installer code
Konnected Alarm Panel Pro replacing the panelFallbackLoses DSC keypads and monitored-alarm certification. Its Matter firmware is experimental and reportedly unstable beyond ~6 zones
Ring Retrofit Alarm KitFallback, side effects~$128 per 8 zones (three kits) plus a base station, ~$20/month for monitoring, contacts only, cloud-based
Control4Out of scopeDSC’s official partner for Neo, but a professional install that isn’t HomeKit
DIY ESP32 + ESPHomeLearning projectVoltage-divide each loop to 3.3 V and use ESPHome’s alarm panel. Line supervision is on you

Two of these are worth talking about a bit more, because they ended up shaping the answer.

Why not run Alarm.com locally?

This was my first instinct. If the panel can report to Alarm.com, why not run my own stand-in for Alarm.com on the local network and call it a day? It doesn’t work. The panel needs an Alarm.com communicator module, which is typically cellular on a carrier’s private APN, so the traffic never even touches the home router. On top of that the protocol is proprietary, encrypted and tied to dealer accounts, and Alarm.com deactivates the module when the subscription ends. Dead end. But here’s the thing, asking how Neo panels talk to IP networks at all is what led to the answer.

What about the Ring Retrofit kit?

This one was tempting. I already run homebridge-ring, which exposes Ring Alarm as a Security System. But think for a second about what it actually takes. Three kits for 24 zones, plus a base station, plus a monthly fee for monitoring. It doesn’t support wired smoke/CO detectors, keypads or sirens, and it doesn’t provide power for wired motion detectors. You’d also be swapping resistors: the kit supports up to 5,200 Ω per zone and DSC Neo typically uses 5.6 kΩ end-of-line resistors, so you’d be replacing them with 4.7 kΩ. It only tells you which zone tripped, not which sensor in it. And homebridge-ring goes through Ring’s cloud, so it’s not local either. The side effects just add up.

The answer: ITv2 over the TL280E

The panel already had a TL280E Ethernet communicator installed for central-station reporting. What I didn’t know is that the TL280E supports up to four local integration sessions over DSC’s ITv2 protocol. This is the same mechanism Control4 uses. The panel opens an outbound TCP connection to an integration server that you specify, negotiates an encrypted session, and from then on streams partition and zone state and accepts commands. The central-station reporting path is completely separate, so programming an integration slot doesn’t touch monitoring.

On the software side, NeoHub is a community project (C#/.NET) that has worked out the ITv2 handshake and encryption, and serves panel state over a WebSocket. What was missing was a bridge from NeoHub to HomeKit. So I wrote one.

Here’s what the full chain looks like:

Wired door, window and motion sensors (existing EOL-resistor loops)
        │
DSC PowerSeries Neo HS2032   ← unchanged, keypads still work
        │
TL280E communicator          ← integration slot programmed, monitoring untouched
        │  ITv2 over TCP, Type 2 encryption, non-default key
        ▼
NeoHub (Docker, host networking) on a Raspberry Pi 5
        │  WebSocket API
        ▼
homebridge-dsc-neohub plugin in Homebridge 2.4
        │  HAP
        ▼
Apple Home: 1 Security System (Home / Away / Night / Off, alarm) + 24 sensors

Notice the direction. The panel connects out to the Pi. Nothing on the network connects into the panel.

Day 1: NeoHub, the plugin and a mock panel

I ran NeoHub first under OrbStack on my Mac to get a feel for it, and then moved it to the Pi. Docker wasn’t installed there, so I used Debian’s packages, set the container to restart automatically, and then actually rebooted to make sure it came back. NeoHub’s own compose file mapped the wrong internal port, so I wrote my own. There’s also a just neohub-update recipe that pulls new images.

Read the source when the README stops

NeoHub’s README covers panel programming but says nothing about the WebSocket API. So I read its C# message models and handler instead. That gave me the exact message types and their snake_case field naming, and the eight partition statuses the panel can report. It also turned up two things that shaped the plugin. Zone type is always reported as "door", regardless of what the sensor actually is. And the WebSocket endpoint has no authentication (more on that later).

Mapping a real panel onto HomeKit

HomeKit’s SecuritySystem service has a CurrentState and a TargetState. The interesting part is mapping a real alarm panel’s richer state machine onto those two values. Each partition becomes one Security System. Stay maps to Home, Away to Away, Night to Night, Disarmed to Off, and an alarm becomes ALARM_TRIGGERED. That’s the easy part.

What about delays? Exit delay shows up as “Arming…” in the Home app: the target is the mode you asked for, while the current state stays Disarmed until the delay runs out. Entry delay stays armed. NeoHub doesn’t report entry delay separately (issue #50), and the conservative choice is to keep showing armed until the panel says otherwise.

And what happens when the panel says no? If it refuses to arm (a zone is open, a wrong code), the plugin snaps HomeKit’s TargetState back to the real state instead of leaving the Home app spinning. If NeoHub or the panel connection drops, HomeKit shows No Response instead of stale state. Each zone becomes its own sensor in Home.

Testing without a panel

Here’s the catch. I didn’t have panel access yet, and wouldn’t until the installer came out. So I wrote a mock NeoHub, a small WebSocket server that speaks the same protocol and simulates exit delays, user-code validation, bypassed zones and dropped connections. That gave me 15 automated tests. On top of that I did end-to-end runs in a real Homebridge 2.4 instance, driven through its HAP API in insecure mode (handy for testing, never for production): arming, disarming, a triggered alarm, bypass, disconnects, a wrong code, and starting Homebridge while NeoHub is down.

Docker networks leak into other apps

Deploying it produced a surprise. Installing Docker created the docker0 bridge plus Compose bridge networks, and Homebridge immediately started logging hundreds of “unable to determine which interface” warnings. It was seeing Docker’s 172.17.0.1 bridge address and getting confused. Restricting Homebridge to eth0 didn’t help, so I reverted it. The fix was on the Docker side. Run NeoHub with host networking, so it doesn’t need a bridge at all, and turn off Docker’s default bridge in daemon.json:

{
  "bridge": "none"
}

That left the Pi with only lo and eth0, and zero warnings.

Publishing the plugin

The plugin lives at splusq/homebridge-dsc-neohub (MIT) and on npm. I set up CI on Node 20, 22 and 24, and a tag-driven release workflow.

I wanted to use npm’s trusted publishing, which is OIDC from GitHub Actions with provenance, so there’s no long-lived token sitting anywhere. But it has a chicken-and-egg problem: you can only configure it once the package already exists. So 0.1.1 went out by hand. Then npm view returned a 404 for a few minutes after a successful publish, and npm briefly listed a 0.0.0-stage placeholder while it processed the upload. Configuring trusted publishing on the website didn’t work, but the CLI did (npm trust github …). After that, 0.1.2 published from GitHub Actions with provenance, and the Homebridge UI showed “v0.1.1 → v0.1.2” with release notes. Nice.

One more CI wrinkle. The tests run .ts files directly using Node’s built-in type stripping, which needs Node 22.18 or newer. So CI runs the full suite on 22 and 24, and only checks that the built plugin loads on 20.

Day 2: Preparing for the one installer visit

With a single shot at the installer, I wrote a one-page technician sheet. It has which integration slot to use, the exact keypad programming steps and values, the integration server address and port, the encryption settings, an access-code generator, and a record form for the technician to fill in.

Just as important was what not to do. While researching I found in NeoHub’s issue tracker (#43, #48) that TL280E firmware 5.63 changes the ITv2 handshake and currently breaks every community integration. So the sheet says: don’t upgrade the communicator firmware, don’t touch other slots or the central-station receiver settings, and stay until the connection is confirmed.

I also put the Pi on a schedule. A nightly systemd timer runs a just recipe that upgrades Homebridge’s bundled Node, the NeoHub image and the plugin, and appends to an upgrade history log.

Day 3: Automating the plugin’s upkeep

I didn’t want to babysit the plugin, so it now maintains itself. Dependabot opens dependency PRs, and patch and minor updates auto-merge once the single required test check passes. A scheduled workflow cuts a release every other day, but only if shipped files actually changed. Releases publish to npm through trusted publishing, and Homebridge picks them up in its UI. 0.1.3 shipped TypeScript 7 and the release automation.

Day 4: The panel connects

The technician programmed the integration slot with Type 2 encryption and a non-default key. Partition 1 and all 24 zones showed up in Apple Home. That’s it. Because the plugin had already been tested against the mock, it just worked.

One gotcha worth knowing about: changing the key in NeoHub’s UI doesn’t affect the panel’s existing session. The panel keeps its old session until it reconnects, so you need a docker restart neohub after a key change.

Then some polish. 0.1.4 moved repeated partition-status messages to debug logging. The Pi got tuned too: headless, Wi-Fi and Bluetooth turned off in the firmware config, the performance CPU governor, Docker logs capped at 10 MB × 3 files, and a camera stream switched from re-encoding to copying.

Risks and trade-offs

I want to be clear that this isn’t a vendor-supported setup, so it’s worth looking at what can go wrong.

RiskWhat happensMitigation
Communicator firmware upgraded to 5.63Handshake changes and NeoHub stops connectingAsk the alarm company not to push firmware updates. NeoHub’s logs will show the new encryption type if it happens
A NeoHub updateA future build targeting 5.63 could break older firmwarePin the NeoHub image to a known-good version rather than always pulling latest
CVE-2026-27871Firmware before 5.63 uses a weak ITv2 key exchange (advisory)The ITv2 session stays on the LAN, and use a non-default key. Weigh this against losing integration on 5.63
No authentication on NeoHub’s WebSocketAnything on the LAN can send commandsDisarming still needs a valid user code. Keep the network trusted, or segment it
Pi IP changesPanel can’t find NeoHub, and only the installer can change the targetDHCP reservation
Pi goes downHomeKit shows No Response; panel and monitoring are unaffectedUPS; the panel has its own battery
Plugin shares Homebridge’s main bridgeAnother plugin crashing takes the alarm with itMove it to its own child bridge

The most important property here is that the failure mode is benign. If the Pi, NeoHub or the plugin dies, the alarm keeps working exactly the way it did before. The keypads arm and disarm, sensors trip, and the monitoring company gets the signal. HomeKit just goes dark.

What I learned

Question the first list of options. Half of the initial suggestions were wrong in ways that mattered, an “official” integration that wasn’t, a zone-only solution sold as a full integration. The actual answer came out of a dead end, asking how Neo panels reach the network at all.

Read the source when the docs stop. Everything the plugin needed to know about NeoHub’s API came from its C# models, not its README.

Build a mock before you have the real thing. With panel access gated on a single visit, the mock NeoHub meant the plugin was tested and deployed before the panel was ever connected. On the day, it just worked.

Docker’s networking affects everything else on the host. Bridge interfaces confuse mDNS-heavy apps like Homebridge. Host networking plus "bridge": "none" is a clean fix on a single-purpose box.

Keep secrets out of tool output. When an AI agent is driving the shell, anything that gets printed ends up in the transcript. Feed passwords to sudo -k -S from a file, and never print raw config diffs that might contain tokens.

When you get one shot, write it down. A one-page sheet with the exact values, what not to touch, and a “wait until it connects” step turned a risky visit into a routine one.

All in all, it cost nothing in new hardware, one installer visit, and four evenings. If you have a DSC Neo panel with a TL280E, the plugin is on npm and in the Homebridge plugin search. Just check your communicator firmware first.

Comments

Came here from LinkedIn or X? Join the conversation below — all discussion lives here.