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

EDIT August 3 2022
Huge thanks to @Jc2k and @bdraco for helping me to merge the HAP/CoAP work and to them for the HAP/BLE. They also did a lot of work to common code that made my addition easier. I’ve tested 2022.8 with my Nanoleaf Essentials bulbs and both BLE and CoAP work beautifully. We’re still waiting on PRs to merge for aiocoap so some of you might see splats with >1 HAP/CoAP device. Also of note: we’ll shortly have Thread provisioning in the HA frontend!

EDIT July 30th 2022
HAP over BLE and HAP over Thread will be available behind environment variable flags in 2022.8. See this post for more details.

EDIT June 6th 2022
TL;DR summary: we are waiting on a few PRs to aiocoap to be merged, then a handful of commits to the homekit_controller component would enable HAP over BLE and Thread. Check post #93 for steps to be an early adopter and tester.

If you have an appropriate Thread Border router somewhere on your WiFi network, Essentials bulbs are translated from Thread to IPv6/CoAP and discoverable over mDNS. I’ve done some documentation of nanoleaf’s protocol here: https://github.com/roysjosh/nanoleaf-ltpdu

Awesome work! I’m trying to use your documentation to communicate with my Essential light strip from Node-RED. So far no luck, but hopefully will get it working at some point.

Thanks! Let me know if there’s anything that isn’t clear or if you find something that is different between the bulb and the light strip. It was fairly straightforward to hack something together with Python’s aiocoap, cryptography.hazmat.primitives, and zeroconf libraries.

Could you provide some code samples? I managed to make the lightstrip blink by sending the lb/0/id command, but the crypto stuff might be out of my scope.

I added an example in Python 3 for the key exchange. After that, sending & receiving payloads are just wrapped in aesCtx.update(payload). Let me know how it goes!

Hi, have you been successful with controlling your essentials with home assistant? I would like to do that too =D

I’ve added a control script: https://github.com/roysjosh/nanoleaf-ltpdu/blob/ab8d3bf40bd7ed8c48224ff7f2f34d0f054803de/nlctl.py

1 Like

I got an Nanoleaf Essentials lightstrip hooked up via thread through an updated Nanoleaf Shapes as border router. When checking via avahi-browse and wireshark, I don’t see it announcing any _ltpdu._udp service, only

mymlan% avahi-browse -a |grep Shape                                                                                   ~
+   eth0 IPv6 Shapes 785E                                   _nanoleafapi._tcp    local
+   eth0 IPv4 Shapes 785E                                   _nanoleafapi._tcp    local
+   eth0 IPv6 Shapes 785E                                   _nanoleafms._tcp     local
+   eth0 IPv4 Shapes 785E                                   _nanoleafms._tcp     local
+   eth0 IPv6 Shapes 785E                                   _hap._tcp            local
+   eth0 IPv4 Shapes 785E                                   _hap._tcp            local

Hmm I am using Elements as the border router and here is what I see:

$ avahi-browse -a | grep -i -e nanoleaf -e shapes -e essentials -e _hap -e _ltpdu
+ enp6s0f0 IPv6 Nanoleaf A19 12FA                             _ltpdu._udp          local
+ enp6s0f0 IPv6 Nanoleaf A19 3TH2                             _ltpdu._udp          local
+ enp6s0f0 IPv4 Nanoleaf A19 12FA                             _ltpdu._udp          local
+ enp6s0f0 IPv4 Nanoleaf A19 3TH2                             _ltpdu._udp          local
+ enp6s0f0 IPv6 Nanoleaf A19 12FA                             _hap._udp            local
+ enp6s0f0 IPv6 Nanoleaf A19 3TH2                             _hap._udp            local
+ enp6s0f0 IPv4 Nanoleaf A19 12FA                             _hap._udp            local
+ enp6s0f0 IPv4 Nanoleaf A19 3TH2                             _hap._udp            local
+ enp6s0f0 IPv6 Elements 4749                                 _nanoleafapi._tcp    local
+ enp6s0f0 IPv4 Elements 4749                                 _nanoleafapi._tcp    local
+ enp6s0f0 IPv6 Elements 4749                                 _nanoleafms._tcp     local
+ enp6s0f0 IPv4 Elements 4749                                 _nanoleafms._tcp     local
+ enp6s0f0 IPv6 Elements 4749                                 _hap._tcp            local
+ enp6s0f0 IPv4 Elements 4749                                 _hap._tcp            local

Do you see a _hap._udp service?

I think the problem could be that my machine is not configured properly for IPv6. It has a link-local IPv6 address, but when I try to run your script it says it can’t reach the network.

lazy question: what should I do on a ubuntu 20.04 system to make it more ipv6-friendly? I don’t have IPv6 from my ISP.

I get an error also but it continues:

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
KEX response CoAP(code:2.04 Changed) header: 01010020
Auth/Token response CoAP(code:2.04 Changed) header: 576059e4 payload: d4
Auth/Token plaintext: (01f1, 1, 00)
Execute LightOnOff response CoAP(code:2.04 Changed) header: 37bbada4 payload: 30a5909aa067b1e291013bb5
Execute LightOnOff plaintext: 000100076c622f302f6f6f0003000100
Disconnect response CoAP(code:2.04 Changed) header: f4bc85a1 payload: 33
Disconnect plaintext: 01f1000100

I don’t get IPv6 from my ISP either. From what I can tell, the Thread border router advertises itself as a router for the /64 the Thread network is translated to:

15:20:16.608233 00:55:da:5f:47:49 > 33:33:00:00:00:01, ethertype IPv6 (0x86dd), length 86: (flowlabel 0x82a6c, hlim 255, next-header ICMPv6 (58) payload length: 32) fe80::255:daff:fe5f:4749 > ff02::1: [icmp6 sum ok] ICMP6, router advertisement, length 32
	hop limit 0, Flags [none], pref medium, router lifetime 0s, reachable time 0ms, retrans timer 0ms
	  route info option (24), length 16 (2):  fd0a:4080:4937::/64, pref=medium, lifetime=1800s

Compare the advertised /64 with the Essentials addresses:

$ avahi-browse -r -d local _ltpdu._udp
+ enp6s0f0 IPv6 Nanoleaf A19 12FA                             _ltpdu._udp          local
+ enp6s0f0 IPv6 Nanoleaf A19 3TH2                             _ltpdu._udp          local
= enp6s0f0 IPv4 Nanoleaf A19 3TH2                             _ltpdu._udp          local
   hostname = [Nanoleaf-A19-3TH2.local]
   address = [fd0a:4080:4937:0:REDACTED]
   port = [5683]
   txt = ["eui64=REDACTED" "md=NL45" "srcvers=1.6.8" "id=REDACTED"]
= enp6s0f0 IPv4 Nanoleaf A19 12FA                             _ltpdu._udp          local
   hostname = [Nanoleaf-A19-12FA.local]
   address = [fd0a:4080:4937:0:REDACTED]
   port = [5683]
   txt = ["eui64=REDACTED" "md=NL45" "srcvers=1.6.8" "id=REDACTED"]

All that to say so long as you have IPv6 enabled and a Unique Local Address (ULA) on your interface (starting with fcXX or fdXX) you should be okay.

Yes, I see the router advertisement as well. And I see IPv6 addresses in mdns, but no _ltpdu._udp.

I just realized that I haven’t actually managed to add my bulbs and lightstrip to the thread network created by the shapes controller. I have only been using crappy bluetooth to control them so far. Something seems to be broken in Nanoleaf’s setup flow.

Yeah, last time I set up a bulb it took a while before it joined the Thread network. I have no idea what triggered the join. I’m not sure if the bulb scans or if the app triggers the join via BLE. I would guess the latter based on the little I know of Thread network provisioning.

How long was “a while”? It’s now been several days.

I think I’ll start over from scratch now that all devices have updated firmware, but I’m not holding my breath.

Starting over with the updated firmware seems to have helped. I now have a thread network with devices in it. Now I just need to debug you python code :slight_smile:

Excited to see some progress on this. I had a handful of A19 Essentials bulbs that I used w/ HomeKit before switching back over to HA, love to use them again vs buying more Hue.

If anyone’s curious I was able to make the nlctl.py script work with an Essentials bulb w/ a homepod mini border router.

5 Likes

Thanks for this work, it’s been quite hard to find information on how to interact with any thread network devices.

@lambdafunction - This is great work. I’m able to control my essentials strips with this over a homepod mini. Have you managed to determine if there’s a way to subscribe to updates (eg, with a CoAP observe or Server Sent Events) so that it would be possible to push current state to HA?

Following up here as well for the HA community. The iOS app is the only one that gets events pushed to it. The iOS app hits different CoAP endpoints than the Android and desktop apps. There are /, /0, /1, and /2 CoAP endpoints that seem to do HAP over CoAP. Technical discussion will be here: Event Support · Issue #1 · roysjosh/nanoleaf-ltpdu · GitHub