HomeKit Accessory Protocol (HAP) over CoAP/UDP (was: Nanoleaf Essentials bulb via Thread/CoAP)

I’ve renamed this slightly as HAP over UDP/CoAP/Thread seems much more likely to be implemented as it has a much broader appeal. Also, the Nanoleaf LTPDU API isn’t nearly as rich.

I implemented a fake bulb broadcasting the UDP HAP mDNS descriptor and had the iOS Home app pair to it. I’ve got a good idea of the post-pairing communications now as well as the new opcode to enable push notifications!

I’ve pushed a HAP/CoAP control script: nanoleaf-ltpdu/hapcoap.py at 53d4167d07868cbab5de6819a56ff1fe66c94c70 · roysjosh/nanoleaf-ltpdu · GitHub. Devices can only be paired to one controller so you may have to factory reset the bulb if you have it connected to your Apple Home.

The script can perform initial pairing to retrieve long-term secrets, auth via previously-obtained long-term secrets, and reads/writes of arbitrary characteristics. You need to look up the hexadecimal service type and characteristic type, e.g. the Light service’s short form UUID is 0x43 and it contains a required On characteristic which has UUID 0x25.

Reading various Light characteristics and then flipping it off and on:

$ python3 hapcoap.py --devices 58L6 auth=REDACTED read=43,23 read=43,25 read=43,CE read=43,08 read=43,13 read=43,2F write=43,25,00 pause=3 write=43,25,01
WARNING:zeroconf:Error with socket 7 (('::1', 5353, 0, 0))): [Errno 101] Network is unreachable
Traceback (most recent call last):
  File "/usr/lib64/python3.9/asyncio/selector_events.py", line 1056, in sendto
    self._sock.sendto(data, addr)
OSError: [Errno 101] Network is unreachable
M1 ->
<- M2
M3 ->
<- M4
SUCCESS: pair verify
PDU response, TID 70, Success, Len 1108
PDU response, TID 70, Success, Len 15
Body: 4e616e6f6c6561662042756c62
PDU response, TID 70, Success, Len 3
Body: 01
PDU response, TID 70, Success, Len 6
Body: 72010000
PDU response, TID 70, Success, Len 6
Body: 64000000
PDU response, TID 70, Success, Len 6
Body: 0000e041
PDU response, TID 70, Success, Len 6
Body: 00008442
PDU response, TID 70, Success, Len 0
PDU response, TID 70, Success, Len 0
1 Like

Added a “readall” command to read and decode all readable characteristics. I’ve annotated the characteristics below with info from the HAP-NodeJS project.

Service(3e, Accessory Information)
  Characteristic(20, Manufacturer)=(str) Nanoleaf
  Characteristic(21, Model)=(str) NL45
  Characteristic(23, Name)=(str) Nanoleaf A19 58L6
  Characteristic(30, Serial Number)=(str) <REDACTED>
  Characteristic(52, Firmware Revision)=(str) 1.6.8
  Characteristic(53, Hardware Revision)=(str) 1.0.4
  Characteristic(34ab8811ac7f4340bac3fd6a85f9943b)=(str) 5.0;dfeceb3a
  Characteristic(220, Product Data)=(data) <REDACTED>
Service(a2, Protocol Information)
  Characteristic(a5, Service Signature)=(data)
  Characteristic(37, Version)=(str) 1.2.0
Service(55, Pairing)
  Characteristic(50, List Pairings)=(data)
Service(43, Lightbulb)
  Characteristic(a5, Service Signature)=(data)
  Characteristic(23, Name)=(str) Nanoleaf Bulb
  Characteristic(25, On)=(bool) True
  Characteristic(ce, Color Temperature)=(uint32) 370
  Characteristic(8, Brightness %)=(int32) 100
  Characteristic(144, Supported Value Transition)=(data) 010901013402040100000000000109010137020402000000
  Characteristic(143, Value Transition Control)=(data)
  Characteristic(24b, Value Active Transition Count)=(uint8) 0
  Characteristic(13, Hue)=(float) 28.000000
  Characteristic(2f, Saturation)=(float) 66.000000
Service(701, Thread Transport)
  Characteristic(a5, Service Signature)=(data)
  Characteristic(706, OpenThread Version)=(str) OPENTHREAD/7f3013cb0-2020-06-12-ebe5e3ab0-2021-04-26; EFR32; May 25 2021 19:08:05
  Characteristic(702, Thread Node Capabilities)=(uint16) 12
  Characteristic(703, Thread Status)=(uint16) 16
  Characteristic(22b, Current Transport)=(bool) True
  Characteristic(704, Thread Control Point)=(data)
Service(239, Accessory Runtime Information)
  Characteristic(a5, Service Signature)=(data)
  Characteristic(23a, Sleep Interval)=(uint32) 0
  Characteristic(23c, Ping)=(data)
  Characteristic(24a, Heartbeat)=(uint32) 1

Okay, maybe I can figure this out… WIP.


Coming right along!

1 Like


1 Like

Very nice work !

So what HW requirement is there and can this be added as a custom_component to try this out ?

I’m extending aiohomekit and homekit_controller for HAP over UDP/CoAP. It probably won’t be merged in time for 2022.2.0. I’m hopeful that any HomeKit product using Thread (and therefore HAP over CoAP/UDP) will work but I only have the Nanoleaf Essentials A19 bulb to test with…

homekit_controller and aiohomekit maintainer here o/

Super excited to discover this work, and looking forward to reviewing it. It’s a large pull request to aiohomekit, so i think @lambdafunction is correct about it not making it to 2022.2.0.

If I don’t have an existing thread network, can you advise on some test hardware? I already have some Eve devices that support Thread, but nothing to act as a border router. (I’ll need to see it work and see how stable it is before I can merge it really).

@lambdafunction - given the size of this, would you be able to help support it going foward? (E.g. if i tag you in COAP related tickets).

Hey, nice to meet you @Jc2k. I picked up some Nanoleaf products after they announced Thread support. I’m not sure if there are others, I haven’t really looked. A number of the Nanoleaf devices (Lines & Elements?) can be Thread border routers. The Essentials bulbs & light strips connect over BLE or Thread.

Sure, I’d be able to help. Expect a PR in a few days, I’ve got to add write & event support yet.

Reads, write, events oh my!

WIP PR for aiohomekit HAP over CoAP by roysjosh · Pull Request #52 · Jc2k/aiohomekit · GitHub and branch for a future PR to HA GitHub - roysjosh/core at hap-over-coap

I cannot describe how satisfying it was to mess with the bulb in the Android app and see changes propagate to HA real-time.

I’ve currently soft-linked aiohomekit in my venv to my local git checkout, because I didn’t know a better way to pick up my aiohomekit changes.

1 Like

Tried to install your core fork as custom components and added the aiohomekit fork + tlv8 with custom deps addon. All my A19 bulbs were discovered, but for some reason they come through the Nanoleaf component so the configuration results in error. I also have the following error in logger:

Logger: homeassistant
Source: custom_components/homekit_controller/connection.py:260
Integration: HomeKit Controller
First occurred: 11:34:41 AM (1 occurrences)
Last logged: 11:34:41 AM

Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/config/custom_components/homekit_controller/connection.py", line 284, in async_process_entity_map
  File "/config/custom_components/homekit_controller/connection.py", line 260, in async_create_devices
    device = device_registry.async_get_or_create(
TypeError: async_get_or_create() got an unexpected keyword argument 'hw_version'

My border router is NL52 (elements), fw 6.2.3.
The bulbs are NL45, fw 1.6.35, hw 1.2.4.

Any ideas for troubleshooting?
Btw, did you connect your bulbs initially through the native homekit app? I have no Apple devices so I gotta work with homebridge.

1 Like

Oh nice. Make sure you apply this change to the Nanoleaf integration. It allows integrations to take over for HomeKit in case they have better support. One day, the LTPDU API may be the preferred option & we’ll put that line back.

I used the Android app to set up the Elements & then the bulbs. That should get them on the network via Thread eventually. Though obviously since you are seeing the bulbs you’ve gotten to that point. Using the iOS Home app consumes the single “admin” pairing slot which means that HA won’t be able to pair. I think there’s a way to use the iOS Nanoleaf app w/o connecting to Apple’s Home…

Anyway, I haven’t tried the custom component route. It looks like you’re getting an error when the HK component is attempting to register entities. Ahh… hw_version was added Dec 6. Since there was no January release… you don’t have that commit. You could try removing the hw_version line from the HK component.

Oh, also, if you got past pairing but the bulb failed to register (which looks like the boat you’re in, maybe?) there’s a 99% chance you’ll have to factory reset the bulbs. Basically if the single admin pairing slot was consumed but HA failed to save that info… it is lost to the void.

Start with the bulb off. Turn it on, wait 2 seconds, turn it off, wait 5 seconds. Repeat five times. After five on/off sequences, turn it on one more time & after a few seconds the bulb will flash red three times. I’ve had to do this procedure a ton during development!

@Rambooo I added a temporary hack to hopefully allow custom component installs to succeed. You’ll still need to override the Nanoleaf component.

Thanks for the effort, much appreciated! There’s no error anymore, but I’m still unable to discover the bulbs through homekit controller. I think I managed to slightly narrow down the source of problem, but not sure how to debug further. Here’s what I did:

  1. Reset all the bulbs + elements, pair with android, enable Thread
  2. Cloned nanoleaf, homekit_controller and zeroconf components from core@hap-over-coap into my custom_components
  3. Installed aiohomekit@hap-over-coap and tlv8 with custom deps addon

I tried this a couple of times with slight variations and tests between the steps to narrow down the problem. Some observations:

  • The bulbs show up when I clone zeroconf, even if it’s the only custom component. So in the worst case, that’s the only thing I got working :joy:
  • Normally I can find & pair Elements with homekit controller if I press ‘add integration’.
  • After cloning homekit_controller (+ install deps), it won’t find Elements even though it’s unpaired. It does still find and pair with my homebridge so it’s partly functional
  • If I clone homekit_controller without installing the deps, it won’t find / pair with any device so I assume the dependencies are installed correctly

Ah, yes, there were zeroconf changes. HomeKit over Thread has a new mDNS type _hap._udp. Also, there was a single line that silently ignored IPv6 addresses in the zeroconf code. Maybe you can edit the generated zeroconf file by hand if the custom component stuff can’t override it?

@Rambooo tracked down the issue with pairing IP HomeKit devices, thanks for the report. Edit: pushed aiohomekit & homekit_controller fixes.

It works!! Hahaha can’t believe how happy this makes me :joy: connected

Manually editing the generated zeroconf was the fix.

1 Like

Nice, Can you put the step by step of what you did?