Skip to content

Add Thread credential sharing to OTBR - #177371

Open
Poshy163 wants to merge 12 commits into
home-assistant:devfrom
Poshy163:otbr-thread-credential-sharing
Open

Add Thread credential sharing to OTBR#177371
Poshy163 wants to merge 12 commits into
home-assistant:devfrom
Poshy163:otbr-thread-credential-sharing

Conversation

@Poshy163

@Poshy163 Poshy163 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Proposed change

Add an otbr/create_ephemeral_key websocket command which activates ephemeral key (ePSKc) mode on the border router and returns the Thread Administration Passcode it generated, so another border router can be given temporary access to the Thread network.

ePSKc.mp4

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • I understand the code I am submitting and can explain how it works.
  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.
  • Any generated code has been carefully reviewed for correctness and compliance with project standards.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies a diff between library versions and ideally a link to the changelog/release notes is added to the PR description.

To help with the load of incoming pull requests:

Add an otbr/create_ephemeral_key websocket command which activates
ephemeral key (ePSKc) mode on the border router and returns the Thread
Administration Passcode it generated, so another border router can be
given temporary access to the Thread network.

The REST endpoints are not in python-otbr-api yet, so they are called
directly for now.
Copilot AI balanced review requested due to automatic review settings July 27, 2026 10:51
@home-assistant home-assistant Bot added cla-signed has-tests integration: otbr new-feature Top 100 Integration is ranked within the top 100 by usage Top 200 Integration is ranked within the top 200 by usage labels Jul 27, 2026
@home-assistant

Copy link
Copy Markdown
Contributor

Hey there @home-assistant/core, mind taking a look at this pull request as it has been labeled with an integration (otbr) you are listed as a code owner for? Thanks!

Code owner commands

Code owners of otbr can trigger bot actions by commenting:

  • @home-assistant close Closes the pull request.
  • @home-assistant mark-draft Mark the pull request as draft.
  • @home-assistant ready-for-review Remove the draft status from the pull request.
  • @home-assistant rename Awesome new title Renames the pull request.
  • @home-assistant reopen Reopen the pull request.
  • @home-assistant unassign otbr Removes the current integration label and assignees on the pull request, add the integration domain after the command.
  • @home-assistant update-branch Update the pull request branch with the base branch.
  • @home-assistant add-label needs-more-information Add a label (needs-more-information, problem in dependency, problem in custom component, problem in config, problem in device, feature-request) to the pull request.
  • @home-assistant remove-label needs-more-information Remove a label (needs-more-information, problem in dependency, problem in custom component, problem in config, problem in device, feature-request) on the pull request.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an admin WebSocket command for temporary Thread credential sharing through OTBR.

Changes:

  • Activates and replaces ephemeral keys through OTBR REST endpoints.
  • Returns the passcode, lifetime, and UDP port.
  • Adds success and error-path tests.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
homeassistant/components/otbr/const.py Defines the key lifetime.
homeassistant/components/otbr/util.py Implements REST activation logic.
homeassistant/components/otbr/websocket_api.py Registers and handles the command.
tests/components/otbr/test_websocket_api.py Tests command behavior and failures.

Comment thread tests/components/otbr/test_websocket_api.py Outdated
Comment thread homeassistant/components/otbr/util.py Outdated
Copilot AI review requested due to automatic review settings July 27, 2026 11:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread homeassistant/components/otbr/util.py Outdated
Copilot AI review requested due to automatic review settings August 21, 2026 03:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (2)

homeassistant/components/otbr/util.py:237

  • Return a conflict instead of deleting another caller's active key. With two concurrent requests, both callers can receive success, but the second request deletes the first caller's key before returning its own, leaving the first UI displaying credentials that no longer work; preserving the active session also prevents its dialog lifecycle from interfering with a replacement session.
        response = await activate()
        if response.status == HTTPStatus.CONFLICT:
            # A key is already active, and one can only be started from the
            # stopped state, so drop it and ask for a replacement.
            delete_response = await session.delete(

homeassistant/components/otbr/init.py:54

  • Catch support-probe failures locally so this optional capability cannot block OTBR setup. _handle_otbr_error converts a timeout or connection error here into HomeAssistantError, which the surrounding setup handler turns into ConfigEntryNotReady even though all required OTBR calls already succeeded.
        otbrdata.ephemeral_key_supported = await otbrdata.get_ephemeral_key_supported(
            hass
        )

@Poshy163

Poshy163 commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

Based on what puddly mentioned within python-otbr-api#267, it now probes GET /node/ba-epskc/state once at setup and exposes the result as ephemeral_key_supported in otbr/info, so the frontend can hide the share button for routers without ePSKc support rather than erroring after the click.

Only a 200 counts as supported, so the probe can't break setup for existing routers.

(there may be a more optimal way of doing this, this is just how i thought would work well-ish)

@Poshy163

Poshy163 commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

The integration adds or removes the button based on GET /node/ba-epskc/state, checked on HAOS boot and on every reload (OTBR intergration entry). The same check runs against any other border router added through the OTBR integration, so the button appears wherever the endpoint responds.

ePSKc support No ePSKc support
ba-epskc/state failure ba-epskc/state success

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comment on lines +230 to +233
response = await activate()
if response.status == HTTPStatus.CONFLICT:
# A key is already active, and one can only be started from the
# stopped state, so replace it unless a device is using it right now
Comment thread homeassistant/components/otbr/util.py Outdated
Comment on lines +279 to +281
if ephemeral_key is not None and ephemeral_key != self.active_ephemeral_key:
return
self.active_ephemeral_key = None
connection.send_error(msg["id"], "delete_ephemeral_key_failed", str(exc))
return

_LOGGER.info("Ephemeral key for %s deleted by %s", data.url, connection.user.name)
Copilot AI review requested due to automatic review settings August 22, 2026 12:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

homeassistant/components/otbr/util.py:110

  • Preserve or revoke the active key when the config entry unloads. These fields exist only on OTBRData, which setup recreates; after a reload, the dialog’s keyed delete compares against None and reports success without sending DELETE, leaving the credential active until timeout, while a new create can also replace the still-displayed key.
    active_ephemeral_key: str | None = None
    active_ephemeral_key_expires: datetime | None = None

homeassistant/components/otbr/util.py:195

  • Move these endpoint calls into python-otbr-api before merging this feature. The integration currently duplicates protocol models, status handling, and raw REST calls as a temporary library workaround; upstream PR #267 has merged, but the latest release is still 2.10.0, so this should wait for a release and then consume the client API instead.
    # The ephemeral key endpoints are called directly until a python-otbr-api
    # release includes them (home-assistant-libs/python-otbr-api#267)
    @_handle_otbr_error

],
)
@pytest.mark.usefixtures("otbr_config_entry_multipan")
async def test_create_ephemeral_key_twice(
Copilot AI review requested due to automatic review settings August 22, 2026 12:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (1)

homeassistant/components/otbr/util.py:195

  • Use the released python-otbr-api ePSKc methods instead of implementing its REST protocol in Core. The client implementation has already merged in home-assistant-libs/python-otbr-api#267, and integration code should remain a thin wrapper; please wait for its release/dependency bump and retain only the Home Assistant-specific locking and lifecycle policy here.
    # The ephemeral key endpoints are called directly until a python-otbr-api
    # release includes them (home-assistant-libs/python-otbr-api#267)
    @_handle_otbr_error

Copilot AI review requested due to automatic review settings August 22, 2026 12:58
@Poshy163
Poshy163 marked this pull request as ready for review August 22, 2026 13:02
@Poshy163
Poshy163 requested a review from a team as a code owner August 22, 2026 13:02
@Poshy163

Poshy163 commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

Vallidation/Testing was done with a M5Stack CoreS3 Thread BR running custom software (cores3-epskc-commissioner) to help emulate/debug any issues that

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

homeassistant/components/otbr/util.py:195

  • Use the python-otbr-api ePSKc client methods instead of implementing its REST protocol in Core. Upstream PR #267 already contains the models and status/error handling, so this temporary duplication violates the integration thin-wrapper boundary; wait for a release and dependency bump, then retain only Home Assistant-specific lifecycle policy here.
    # The ephemeral key endpoints are called directly until a python-otbr-api
    # release includes them (home-assistant-libs/python-otbr-api#267)
    @_handle_otbr_error

Comment thread homeassistant/components/otbr/__init__.py Outdated
Comment thread homeassistant/components/otbr/util.py
Copilot AI review requested due to automatic review settings August 22, 2026 13:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Suppressed comments (1)

homeassistant/components/otbr/util.py:290

  • Revoke the key when a successful activation response cannot be parsed. A 200 POST has already activated ePSKc, but this exception leaves the key uncached, so unload cannot revoke it and the router continues accepting the undisclosed credential until expiry; attempt cleanup before returning the failure and cover malformed responses with a DELETE assertion.
        try:
            activation = await response.json()
            ephemeral_key, port = activation["tap"], activation["port"]
        except (ValueError, KeyError, TypeError) as exc:
            raise python_otbr_api.OTBRError("unexpected API response") from exc

Comment thread homeassistant/components/otbr/util.py Outdated
Comment on lines +204 to +206
# Only 200 proves support; any other status hides the optional feature,
# while connection errors fail setup like the other startup calls
return response.status == HTTPStatus.OK
Comment thread homeassistant/components/otbr/util.py Outdated
Comment on lines +101 to +103
if otbrdata.active_ephemeral_key is not None:
try:
await otbrdata.deactivate_ephemeral_key(hass)
Copilot AI review requested due to automatic review settings August 22, 2026 13:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (4)

Previously missed (1) — in code that hasn't changed since the last review.

homeassistant/components/otbr/util.py:288

  • Revoke a key when a successful activation response cannot be parsed. A 200 means the router already activated ePSKc, but invalid or missing tap/port data raises before the key is recorded, so unload cannot clean it up and the administration credential remains active until expiry. Perform a best-effort DELETE before surfacing this failure and cover it in the malformed-response tests.
        try:
            activation = await response.json()
            ephemeral_key, port = activation["tap"], activation["port"]
        except (ValueError, KeyError, TypeError) as exc:
            raise python_otbr_api.OTBRError("unexpected API response") from exc

homeassistant/components/otbr/util.py:201

  • Move the ePSKc HTTP protocol into python-otbr-api before merging. Core currently pins 2.10.0, while upstream PR #267 has merged these methods but they are not released; duplicating routes, states, and status mapping here violates the integration's thin-wrapper boundary and leaves two client implementations to maintain. Wait for a library release/bump and keep only Home Assistant-specific lifecycle policy here.
        session = async_get_clientsession(hass)
        response = await session.get(
            f"{self.url}/node/ba-epskc/state",
            timeout=aiohttp.ClientTimeout(total=10),
        )

homeassistant/components/otbr/util.py:204

  • Keep transient support-probe failures retryable. This maps every non-200 response, including 500/503, to False; because otbr/info probes again only while the value is None, one temporary server error hides the feature until the entry reloads. Return False only for explicit unsupported statuses and raise for other responses.
        # Only 200 proves support; any other status hides the optional feature,
        # while connection errors fail setup like the other startup calls
        return response.status == HTTPStatus.OK

homeassistant/components/otbr/init.py:103

  • Coordinate unload with in-flight key operations and reject activation after unload begins. This unlocked check can see None while a WebSocket create holds the lifecycle lock awaiting the router; unload then completes, the create stores a live key afterward, and no config entry remains to revoke it.
    if otbrdata.active_ephemeral_key is not None:
        try:
            await otbrdata.deactivate_ephemeral_key(hass)

Copilot AI review requested due to automatic review settings August 22, 2026 13:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

homeassistant/components/otbr/util.py:273

  • Only replace a key when the reported state is known to be safe. OTBR can serialize unrecognized/future states as "unknown"; the current denylist treats every such state as idle and sends DELETE, which could disconnect an active commissioner. Permit replacement only for "started" and fail closed for all other unexpected states.
            if state in EPHEMERAL_KEY_IN_USE_STATES:
                raise EphemeralKeyInUse

Comment on lines +289 to +293
try:
activation = await response.json()
ephemeral_key, port = activation["tap"], activation["port"]
except (ValueError, KeyError, TypeError) as exc:
raise python_otbr_api.OTBRError("unexpected API response") from exc
@joostlek
joostlek requested a review from agners August 24, 2026 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed has-tests integration: otbr new-feature Quality Scale: No score Top 100 Integration is ranked within the top 100 by usage Top 200 Integration is ranked within the top 200 by usage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Investigate the current state of Thread credential sharing to deduct potential improvements

2 participants