Skip to content

Commit

Permalink
Use extended address in WS API calls
Browse files Browse the repository at this point in the history
This follows the HA Core change and passes the extended address to the
OTBR WS API calls where necessary. It also follows the new OTBR info
format which potentially includes multiple OTBRs.

This allows to support multiple OTBR managed by a single system.

Note: There is one corner case when none of the OTBR is found via
discovery. In this case we offer to reset the OTBR. Currently we simply
offer this for the primary or first one found.
  • Loading branch information
agners committed Jun 26, 2024
1 parent 7d28f3f commit 9a63030
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 33 deletions.
17 changes: 15 additions & 2 deletions src/data/otbr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,46 @@ export interface OTBRInfo {
border_agent_id: string;
channel: number;
extended_address: string;
extended_pan_id: string;
url: string;
}

export const getOTBRInfo = (hass: HomeAssistant): Promise<OTBRInfo> =>
export type OTBRInfoDict = {
[key: string]: OTBRInfo;
};

export const getOTBRInfo = (hass: HomeAssistant): Promise<OTBRInfoDict> =>
hass.callWS({
type: "otbr/info",
});

export const OTBRCreateNetwork = (hass: HomeAssistant): Promise<void> =>
export const OTBRCreateNetwork = (
hass: HomeAssistant,
extended_address: string
): Promise<void> =>
hass.callWS({
type: "otbr/create_network",
extended_address,
});

export const OTBRSetNetwork = (
hass: HomeAssistant,
extended_address: string,
dataset_id: string
): Promise<void> =>
hass.callWS({
type: "otbr/set_network",
extended_address,
dataset_id,
});

export const OTBRSetChannel = (
hass: HomeAssistant,
extended_address: string,
channel: number
): Promise<{ delay: number }> =>
hass.callWS({
type: "otbr/set_channel",
extended_address,
channel,
});
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { getConfigEntryDiagnosticsDownloadUrl } from "../../../../../data/diagno
import {
OTBRCreateNetwork,
OTBRInfo,
OTBRInfoDict,
OTBRSetChannel,
OTBRSetNetwork,
getOTBRInfo,
Expand Down Expand Up @@ -75,7 +76,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {

@state() private _datasets: ThreadDataSet[] = [];

@state() private _otbrInfo?: OTBRInfo;
@state() private _otbrInfo?: OTBRInfoDict;

protected render(): TemplateResult {
const networks = this._groupRoutersByNetwork(this._routers, this._datasets);
Expand Down Expand Up @@ -160,19 +161,24 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
}

private _renderNetwork(network: ThreadNetwork) {
const otbrForNetwork =
this._otbrInfo &&
network.dataset &&
((network.dataset.preferred_extended_address &&
this._otbrInfo[network.dataset.preferred_extended_address]) ||
Object.values(this._otbrInfo).find(
(otbr) => otbr.extended_pan_id === network.dataset.extended_pan_id

Check failure on line 170 in src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts

View workflow job for this annotation

GitHub Actions / Lint and check format

'network.dataset' is possibly 'undefined'.
));
const canImportKeychain =
this.hass.auth.external?.config.canTransferThreadCredentialsToKeychain &&
network.dataset?.extended_pan_id &&
this._otbrInfo &&
this._otbrInfo?.active_dataset_tlvs?.includes(
network.dataset.extended_pan_id
);
otbrForNetwork;

return html`<ha-card>
<div class="card-header">
${network.name}${network.dataset
? html`<div>
<ha-icon-button
.otbr=${otbrForNetwork}
.network=${network}
.path=${mdiInformationOutline}
@click=${this._showDatasetInfo}
Expand All @@ -196,9 +202,10 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
</h4>
</div>
${network.routers.map((router) => {
const otbr =
this._otbrInfo && this._otbrInfo[router.extended_address];
const showOverflow =
("dataset" in network && router.border_agent_id) ||
router.extended_address === this._otbrInfo?.extended_address;
("dataset" in network && router.border_agent_id) || otbr;
return html`<ha-list-item
class="router"
twoline
Expand Down Expand Up @@ -238,6 +245,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
slot="meta"
.network=${network}
.router=${router}
.otbr=${otbr}
@action=${this._handleRouterAction}
>
<ha-icon-button
Expand All @@ -262,8 +270,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
)}
</ha-list-item>`
: ""}
${router.extended_address ===
this._otbrInfo?.extended_address
${otbr
? html`<ha-list-item>
${this.hass.localize(
"ui.panel.config.thread.reset_border_router"
Expand All @@ -288,14 +295,13 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
})}`
: html`<div class="card-content no-routers">
<ha-svg-icon .path=${mdiDevices}></ha-svg-icon>
${network.dataset?.extended_pan_id &&
this._otbrInfo?.active_dataset_tlvs?.includes(
network.dataset.extended_pan_id
)
${otbrForNetwork
? html`${this.hass.localize(
"ui.panel.config.thread.no_routers_otbr_network"
)}
<mwc-button @click=${this._resetBorderRouter}
<mwc-button
.otbr=${otbrForNetwork}
@click=${this._resetBorderRouterEvent}
>${this.hass.localize(
"ui.panel.config.thread.reset_border_router"
)}</mwc-button
Expand All @@ -313,31 +319,33 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
: ""}
${canImportKeychain
? html`<div class="card-actions">
<mwc-button @click=${this._sendCredentials}
<mwc-button .otbr=${otbrForNetwork} @click=${this._sendCredentials}
>Send credentials to phone</mwc-button
>
</div>`
: ""}
</ha-card>`;
}

private _sendCredentials() {
private _sendCredentials(ev) {
const otbr = (ev.currentTarget as any).otbr as OTBRInfo;
if (!this._otbrInfo) {
return;
}
this.hass.auth.external!.fireMessage({
type: "thread/store_in_platform_keychain",
payload: {
mac_extended_address: this._otbrInfo.extended_address,
border_agent_id: this._otbrInfo.border_agent_id ?? "",
active_operational_dataset: this._otbrInfo.active_dataset_tlvs ?? "",
mac_extended_address: otbr.extended_address,
border_agent_id: otbr.border_agent_id ?? "",
active_operational_dataset: otbr.active_dataset_tlvs ?? "",
},
});
}

private async _showDatasetInfo(ev: Event) {
const network = (ev.currentTarget as any).network as ThreadNetwork;
showThreadDatasetDialog(this, { network, otbrInfo: this._otbrInfo });
const otbr = (ev.currentTarget as any).otbr as OTBRInfo;
showThreadDatasetDialog(this, { network, otbrInfo: otbr });
}

private _importExternalThreadCredentials() {
Expand Down Expand Up @@ -454,6 +462,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
private _handleRouterAction(ev: CustomEvent<ActionDetail>) {
const network = (ev.currentTarget as any).network as ThreadNetwork;
const router = (ev.currentTarget as any).router as ThreadRouter;
const otbr = (ev.currentTarget as any).otbr as OTBRInfo;
const index =
network.dataset && router.border_agent_id
? Number(ev.detail.index)
Expand All @@ -463,18 +472,23 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
this._setPreferredBorderAgent(network.dataset!, router);
break;
case 1:
this._resetBorderRouter();
this._resetBorderRouter(otbr);
break;
case 2:
this._changeChannel();
this._changeChannel(otbr);
break;
case 3:
this._setDataset();
this._setDataset(otbr);
break;
}
}

private async _resetBorderRouter() {
private _resetBorderRouterEvent(ev) {
const otbr = (ev.currentTarget as any).otbr as OTBRInfo;
this._resetBorderRouter(otbr);
}

private async _resetBorderRouter(otbr: OTBRInfo) {
const confirm = await showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.thread.confirm_reset_border_router"
Expand All @@ -487,7 +501,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
return;
}
try {
await OTBRCreateNetwork(this.hass);
await OTBRCreateNetwork(this.hass, otbr.extended_address);
} catch (err: any) {
showAlertDialog(this, {
title: this.hass.localize("ui.panel.config.thread.otbr_config_failed"),
Expand All @@ -497,7 +511,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
this._refresh();
}

private async _setDataset() {
private async _setDataset(otbr: OTBRInfo) {
const networks = this._groupRoutersByNetwork(this._routers, this._datasets);
const preferedDatasetId = networks.preferred?.dataset?.dataset_id;
if (!preferedDatasetId) {
Expand All @@ -515,7 +529,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
return;
}
try {
await OTBRSetNetwork(this.hass, preferedDatasetId);
await OTBRSetNetwork(this.hass, otbr.extended_address, preferedDatasetId);
} catch (err: any) {
showAlertDialog(this, {
title: this.hass.localize("ui.panel.config.thread.otbr_config_failed"),
Expand Down Expand Up @@ -595,8 +609,8 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
this._refresh();
}

private async _changeChannel() {
const currentChannel = this._otbrInfo?.channel;
private async _changeChannel(otbr: OTBRInfo) {
const currentChannel = otbr.channel;
const channelStr = await showPromptDialog(this, {
title: this.hass.localize("ui.panel.config.thread.change_channel"),
text: this.hass.localize("ui.panel.config.thread.change_channel_text"),
Expand All @@ -623,7 +637,11 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
return;
}
try {
const result = await OTBRSetChannel(this.hass, channel);
const result = await OTBRSetChannel(
this.hass,
otbr.extended_address,
channel
);
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.thread.change_channel_initiated_title"
Expand Down

0 comments on commit 9a63030

Please sign in to comment.