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

@Rambooo I’m not sure how custom component works but make sure you update aiohomekit to the most recent commit. There was a socket (fd) leak in my PR for offline accessories that would crash your HA after a few hours.

@TRoager Sure!

  1. Pair the bulbs with Nanoleaf app on android and make sure they join Thread. If they have been previously paired with iOS, a hard reset is needed first.

  2. Clone / download the needed files from repo core@hap-over-coap

  • Go to core/homeassistant/components
  • Copy folders nanoleaf, homekit_controller and zeroconf
  • Paste into config/custom_components on your home assistant instance
  1. Add a version number to manifest.json on each of the 3 components or hass won’t load them
  • The files are located at the roots of the previously cloned folders
  • For example, add ”version”:”9.9.9” to somewhere in the file, validate syntax if needed
  1. Install dependencies with Custom deps deployment from the Add-on store
  • Add aiohomekit and tlv8 under pypi on the Add-on config:
pypi:
  - git+https://github.com/roysjosh/aiohomekit.git@hap-over-coap
  - tlv8
  • Start the Add-on, confirm successful installation from its log
  1. Restart home assistant

  2. If the bulbs are not discovered automatically after reboot, try to add Homekit Controller manually through “Add integration”

  3. Not sure if this is needed after the latest updates, but if there’s a problem with discovery, editing generated zeroconf.py may help

  • Find /generated/zeroconf.py from homeassistant container and replace it with homeassistant/generated/zeroconf.py from the repo
  • On my installation the file was found at multiple locations, but I only needed to edit one of them and reboot

@lambdafunction all right, thanks for the heads up. Didn’t have any issues so far with one connected bulb but when I tried to add more bulbs my Thread network crashed and homekit controller “lost” the pairing. Might be just nanoleaf doing nanoleaf things though so i’ll try again later.

Update:
Network crashing was unrelated. Running into ‘address in use’ error when trying to pair a second bulb

This error originated from a custom integration.

Logger: custom_components.homekit_controller.config_flow
Source: deps/lib/python3.9/site-packages/aiohomekit/controller/coap/connection.py:608
Integration: HomeKit Controller
First occurred: 6:53:38 PM (1 occurrences)
Last logged: 6:53:38 PM

Pairing attempt failed with an unhandled exception
Traceback (most recent call last):
  File "/config/deps/lib/python3.9/site-packages/aiohomekit/controller/coap/connection.py", line 602, in connect
    await self.do_pair_verify(pairing_data)
  File "/config/deps/lib/python3.9/site-packages/aiohomekit/controller/coap/connection.py", line 557, in do_pair_verify
    coapClient = await Context.create_server_context(root, bind=('::',0))
  File "/usr/local/lib/python3.9/site-packages/aiocoap/protocol.py", line 293, in create_server_context
    await self._append_tokenmanaged_transport(
  File "/usr/local/lib/python3.9/site-packages/aiocoap/protocol.py", line 157, in _append_tokenmanaged_transport
    transport = await token_interface_constructor(tman)
  File "/usr/local/lib/python3.9/site-packages/aiocoap/transports/ws.py", line 191, in create_transport
    server = await websockets.serve(
  File "/usr/local/lib/python3.9/site-packages/websockets/legacy/server.py", line 1086, in __await_impl__
    server = await self._create_server()
  File "/usr/local/lib/python3.9/asyncio/base_events.py", line 1494, in create_server
    raise OSError(err.errno, 'error while attempting '
OSError: [Errno 98] error while attempting to bind on address ('::', 3000, 0, 0): address in use

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/config/custom_components/homekit_controller/config_flow.py", line 392, in async_step_pair
    return await self._entry_from_accessory(pairing)
  File "/config/custom_components/homekit_controller/config_flow.py", line 507, in _entry_from_accessory
    accessories = await pairing.list_accessories_and_characteristics()
  File "/config/deps/lib/python3.9/site-packages/aiohomekit/controller/coap/pairing.py", line 64, in list_accessories_and_characteristics
    await self._ensure_connected()
  File "/config/deps/lib/python3.9/site-packages/aiohomekit/controller/coap/pairing.py", line 26, in _ensure_connected
    await self.connection.connect(self.pairing_data)
  File "/config/deps/lib/python3.9/site-packages/aiohomekit/controller/coap/connection.py", line 608, in connect
    raise AccessoryDisconnectedError('Pair verify failed')
aiohomekit.exceptions.AccessoryDisconnectedError: Pair verify failed

@Rambooo bah, that’s because we create a “server” CoAP context in order to receive push events. aiocoap doesn’t let me choose which listeners it starts, it dynamically detects what dependencies are available and starts whatever it can. Apparently one of the listeners ignores the bind parameter. Not sure how you start your HA but a quick fix might be to export AIOCOAP_SERVER_TRANSPORT=udp6 in the startup script.

I’ve opened a ticket with aiocoap to get this sorted.

I’m running supervised installation against the recommendations on expertise. I couldn’t get export AIOCOAP_SERVER_TRANSPORT=udp6 working, but I’m not sure if I applied it right. Your fix on aiocoap did the job though! All three bulbs are connected and working now.

On another note, seems like Ikea Trådfri didn’t like the changes on aiocoap…

Logger: homeassistant.config_entries
Source: components/tradfri/__init__.py:94

Error setting up entry 192.168.1.12 for tradfri
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 327, in async_setup
    result = await component.async_setup_entry(hass, self)
  File "/usr/src/homeassistant/homeassistant/components/tradfri/__init__.py", line 94, in async_setup_entry
    factory = await APIFactory.init(
  File "/usr/local/lib/python3.9/site-packages/pytradfri/api/aiocoap_api.py", line 47, in init
    await instance._update_credentials()
  File "/usr/local/lib/python3.9/site-packages/pytradfri/api/aiocoap_api.py", line 266, in _update_credentials
    protocol = await self._get_protocol()
  File "/usr/local/lib/python3.9/site-packages/pytradfri/api/aiocoap_api.py", line 63, in _get_protocol
    self._protocol = asyncio.create_task(Context.create_client_context())
  File "/usr/local/lib/python3.9/asyncio/tasks.py", line 361, in create_task
    task = loop.create_task(coro)
  File "/usr/local/lib/python3.9/asyncio/base_events.py", line 433, in create_task
    task = tasks.Task(coro, loop=self, name=name)
TypeError: a coroutine was expected, got <aiocoap.util.asyncio.coro_or_contextmanager.AwaitOrAenter object at 0x7fc86d47e0a0>

Hmm. That looks like a change in aiocoap since the latest 0.4.3 release changing how clients/servers should be created. Interesting. I’ll open a separate issue so the aiocoap project is aware they are making a breaking change.

Damn nice thank you for this detailed guide, although i do not have an android phone so that eliminates this usecase for me :frowning:
I did try just to see if i could anyway but no luck.

Home assistant cannot find any devices. Everything did went smooth according to the guide so i must be thinking its related to me using iphone :frowning:

@TRoager very likely that the single admin slot was consumed by iOS. You could bump up the logging in configuration.yaml to confirm.

logger:
  default: info
  logs:
    aiohomekit: debug
    homeassistant.components.homekit_controller: debug

You’ll see lines like:

Discovered device Elements 4749 (NL52 - 49:BA:45:4C:F6:E7)
HomeKit device 49:BA:45:4C:F6:E7 ignored as already paired

You could try unpairing the device from the iOS Home app to see if the bulb stays on the Thread network.

I tried adding the debugger this is the only thing in the logger:

2022-01-24 20:48:30 DEBUG (MainThread) [aiohomekit.zeroconf] candidate data {b'id': b'DB:BB:D1:9F:74:9E', b'md': b'NL42', b'ff': b'1', b'c#': b'3', b's#': b'1', b'ci': b'5', b'pv': b'1.1', b'sh': b'Y4ooSw==', b'sf': b'0'}

2022-01-24 20:48:30 DEBUG (MainThread) [aiohomekit.zeroconf] candidate data {b'c#': b'1', b'ff': b'8', b'id': b'2C:9F:17:4E:20:59', b'md': b'WEBOS', b'pv': b'1.1', b's#': b'1', b'sf': b'0', b'ci': b'31', b'sh': b'9GmOwA=='}

2022-01-24 20:48:30 DEBUG (MainThread) [aiohomekit.zeroconf] candidate data {b'md': b'HASS Bridge', b'pv': b'1.1', b'id': b'E0:E9:A8:DD:14:5D', b'c#': b'9', b's#': b'1', b'ff': b'0', b'ci': b'2', b'sf': b'0', b'sh': b'3EvlXg=='}

2022-01-24 20:48:30 DEBUG (MainThread) [aiohomekit.controller.controller] Discovered IP devices: [IPDiscovery(host=192.168.1.62, port=6517), IPDiscovery(host=192.168.1.23, port=34258), IPDiscovery(host=192.168.1.10, port=21064)]

But this happens when it searches, but it doesnt find anything
Tried unpairing as well but its just gone

@TRoager I did a bit of reading to see what options are out there now. A user on reddit mentions that Google Seamless Setup only works with BLE not Thread (and Google dev docs don’t seem to say anything contrary) otherwise you could use Google Home on iOS (ew) to get the bulbs online. That’s actually surprising to me but maybe a future Google Home app (Android or iOS) will be able to provision IoT devices onto a Thread network… or maybe when Nanoleaf opens their Border Routers to other devices… or maybe both will have to happen.

I have some vague memory that the iOS Nanoleaf app asked me if I wanted to link devices to Apple’s Home when I first installed it. You could try completely nuking your setup but obviously that’s a lot for maybe nothing.

Other than that, you could setup an Android emulator and install the Nanoleaf app on that.

Long-term, it would be great if HA could do Seamless Setup & provision Thread devices but that’s a long way off…

EDIT: to add, iOS-only users are in a tricky position. How can we get Essentials bulbs onto a Thread network without using the iOS Nanoleaf app which consumes the single admin pairing slot?

I actually found a scrapped android phone and got the nanoleaf app on it. I managed to do add the essential light bulbs and the hexagon shapes as the border router.

Now it finds the shape as nanoleaf component but i can also find it as homekit controller.
The light bulb on the other hand isnt found. So sounds like im missing something since it cannot find it.

"
5. Not sure if this is needed after the latest updates, but if there’s a problem with discovery, editing generated zeroconf.py may help

  • Find /generated/zeroconf.py from homeassistant container and replace it with homeassistant/generated/zeroconf.py from the repo
  • On my installation the file was found at multiple locations, but I only needed to edit one of them and reboot

"

This part, where should i placethe zeroconf.py ?
I am using supervised HA

In my case the bulbs could be discovered through Nanoleaf before editing this file; maybe your problem is something different?
The location was ./var/lib/docker/overlay2/43b15.../diff/usr/src/homeassistant/homeassistant/generated/zeroconf.py

Have you tried monitoring your network with mDNS tools? I noticed that sometimes the bulbs were not showing up through HAP.udp despite joining Thread. I think it had something to do with the bulbs assigned as leaders / childs / routers on the network (just a guess).

I have a hunch we’ll be able to provision devices onto thread ourself via HAP, but will probably involve BLE, using the Thread Transport service you found. In particular, the “Thread Control Point” characteristic. I haven’t been able to find much information about it though. Have been meaning to make some mock accesories with those characteristics to see what iOS does with them.

(Eve devices AIUI use the Apple API’s exclusively and have similar characteristics).

@Jc2k beat you to the punch :wink: GitHub - roysjosh/nanoleaf-ltpdu see TLV 0801. Looks like it is custom for now. I fooled the NL app into thinking it was talking over BLE by returning False for the “Thread current transport” characteristic value & captured the provisioning details. Provisioning has to get standardized eventually… maybe when Matter comes out. I hope.

So one of my Eve devices has this:

                            {
                                "characteristics": [
                                    {
                                        "format": "uint16",
                                        "iid": 20,
                                        "maxValue": 31,
                                        "minValue": 0,
                                        "perms": [
                                            "pr"
                                        ],
                                        "type": "702",
                                        "value": 8
                                    },
                                    {
                                        "format": "uint16",
                                        "iid": 21,
                                        "maxValue": 127,
                                        "minValue": 0,
                                        "perms": [
                                            "pr",
                                            "ev"
                                        ],
                                        "type": "703",
                                        "value": 1
                                    },
                                    {
                                        "format": "tlv8",
                                        "iid": 22,
                                        "perms": [
                                            "pr",
                                            "pw"
                                        ],
                                        "type": "704",
                                        "value": ""
                                    },
                                    {
                                        "format": "string",
                                        "iid": 23,
                                        "perms": [
                                            "pr"
                                        ],
                                        "type": "706",
                                        "value": "OPENTHREAD/20170716-02552-geb173c52; NRF52840; Oct 28 2020"
                                    },
                                    {
                                        "format": "bool",
                                        "iid": 25,
                                        "perms": [
                                            "pr"
                                        ],
                                        "type": "22B",
                                        "value": false
                                    }
                                ],
                                "iid": 19,
                                "type": "701"
                            },

Which looks a heck of a lot like this what you posted:

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)

I’d like to get my hands on the .storage/homekit_controller-entity-map of anyone trying this. I have a couple of goals:

  • See if there are any vendor specific characteristics we can work on getting added in parallel to the work @lambdafunction is doing. These might expose features of the Nanoleaf devices that are available in the Nanoleaf app but not in the Apple Home app.
  • Gather examples of the Thread HAP service and its characteristics so we can tell MTD/FTD/border routers, etc apart, figure out the health of the thread network, using the HAP API etc like the Eve app does.

I’d especially like a dump from an Elements to see what a border router looks like to homekit_controller.

@Jc2k I played around with the Eve app and found the meaning of the bit fields for 702 (Thread node capabilities) and 703 (Thread status).

Capabilities:

  • 0x01 Minimal
  • 0x02 Sleepy
  • 0x04 Full
  • 0x08 Router-eligible
  • 0x10 Border Router capable

The Essentials bulbs advertise Full + Router-eligible.

Status:

  • 0x01 Disabled
  • 0x02 Detached
  • 0x04 Joining
  • 0x08 Child
  • 0x10 Router
  • 0x20 Leader
  • 0x40 Border Router

Changing 22B (Thread Current Transport) from True to False made Eve show the transport as BLE.

Cheers. I’ve added const for these to aiohomekit.

In addition to .storage/homekit_controller-entity-map for as many different nanoleaf devics as possible i’d also like anyone who has tried this to tell me about their network setup so I can prepare for supporting this when its in core.

I’m especially interested in whether anyone has this working between VLANs, and if anyone is running this in docker are they running it in host network mode or not.