diff --git a/src/data/matter.ts b/src/data/matter.ts new file mode 100644 index 0000000000..f270ead3ad --- /dev/null +++ b/src/data/matter.ts @@ -0,0 +1,39 @@ +import { HomeAssistant } from "../types"; + +export const commissionMatterDevice = ( + hass: HomeAssistant, + code: string +): Promise => + hass.callWS({ + type: "matter/commission", + code, + }); + +export const acceptSharedMatterDevice = ( + hass: HomeAssistant, + pin: number +): Promise => + hass.callWS({ + type: "matter/commission_on_network", + pin, + }); + +export const matterSetWifi = ( + hass: HomeAssistant, + network_name: string, + password: string +): Promise => + hass.callWS({ + type: "matter/set_wifi_credentials", + network_name, + password, + }); + +export const matterSetThread = ( + hass: HomeAssistant, + thread_operation_dataset: string +): Promise => + hass.callWS({ + type: "matter/set_thread", + thread_operation_dataset, + }); diff --git a/src/external_app/external_messaging.ts b/src/external_app/external_messaging.ts index 2bf8fa34fb..87663cc9d5 100644 --- a/src/external_app/external_messaging.ts +++ b/src/external_app/external_messaging.ts @@ -42,12 +42,6 @@ type EMOutgoingMessageWithAnswer = { request: EMOutgoingMessageConfigGet; response: ExternalConfig; }; - "matter/commission": { - request: EMOutgoingMessageMatterCommission; - response: { - code: string; - }; - }; }; interface EMOutgoingMessageExoplayerPlayHLS extends EMMessage { @@ -112,7 +106,8 @@ type EMOutgoingMessageWithoutAnswer = | EMOutgoingMessageExoplayerStop | EMOutgoingMessageThemeUpdate | EMMessageResultSuccess - | EMMessageResultError; + | EMMessageResultError + | EMOutgoingMessageMatterCommission; interface EMIncomingMessageRestart { id: number; diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index f1d4f62a60..71c452a02c 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -478,6 +478,13 @@ class HaPanelConfig extends HassRouterPage { "./integrations/integration-panels/zwave_js/zwave_js-config-router" ), }, + matter: { + tag: "matter-config-panel", + load: () => + import( + "./integrations/integration-panels/matter/matter-config-panel" + ), + }, application_credentials: { tag: "ha-config-application-credentials", load: () => diff --git a/src/panels/config/integrations/ha-integration-card.ts b/src/panels/config/integrations/ha-integration-card.ts index e7eeba7ea3..d6fa797b2e 100644 --- a/src/panels/config/integrations/ha-integration-card.ts +++ b/src/panels/config/integrations/ha-integration-card.ts @@ -70,6 +70,7 @@ const integrationsWithPanel = { mqtt: "/config/mqtt", zha: "/config/zha/dashboard", zwave_js: "/config/zwave_js/dashboard", + matter: "/config/matter", }; @customElement("ha-integration-card") diff --git a/src/panels/config/integrations/integration-panels/matter/matter-config-panel.ts b/src/panels/config/integrations/integration-panels/matter/matter-config-panel.ts new file mode 100644 index 0000000000..852df1ebe1 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/matter/matter-config-panel.ts @@ -0,0 +1,172 @@ +import "@material/mwc-button"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import "../../../../../components/ha-card"; +import { + acceptSharedMatterDevice, + commissionMatterDevice, + matterSetThread, + matterSetWifi, +} from "../../../../../data/matter"; +import "../../../../../layouts/hass-subpage"; +import { haStyle } from "../../../../../resources/styles"; +import { HomeAssistant } from "../../../../../types"; +import "../../../../../components/ha-alert"; +import { showPromptDialog } from "../../../../../dialogs/generic/show-dialog-box"; + +@customElement("matter-config-panel") +export class MatterConfigPanel extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean }) public narrow!: boolean; + + @state() private _error?: string; + + private get _canCommissionMatter() { + return this.hass.auth.external?.config.canCommissionMatter; + } + + protected render(): TemplateResult { + return html` + +
+ +
+ ${this._error + ? html`${this._error}` + : ""} + Matter is still in the early phase of development, it is not + meant to be used in production. This panel is for development + only. + + You can add Matter devices by commissing them if they are not + setup yet, or share them from another controller and enter the + share code. +
+
+ ${this._canCommissionMatter + ? html`Commission device with mobile app` + : ""} + Set WiFi Credentials + Set Thread + Commission device + Add shared device +
+
+
+
+ `; + } + + private _startMobileCommissioning() { + this.hass.auth.external!.fireMessage({ + type: "matter/commission", + }); + } + + private async _setWifi(): Promise { + this._error = undefined; + const networkName = await showPromptDialog(this, { + title: "Network name", + inputLabel: "Network name", + inputType: "string", + confirmText: "Continue", + }); + if (!networkName) { + return; + } + const psk = await showPromptDialog(this, { + title: "Passcode", + inputLabel: "Code", + inputType: "password", + confirmText: "Set Wifi", + }); + if (!psk) { + return; + } + try { + await matterSetWifi(this.hass, networkName, psk); + } catch (err: any) { + this._error = err.message; + } + } + + private async _commission(): Promise { + const code = await showPromptDialog(this, { + title: "Commission device", + inputLabel: "Code", + inputType: "string", + confirmText: "Commission", + }); + if (!code) { + return; + } + this._error = undefined; + try { + await commissionMatterDevice(this.hass, code); + } catch (err: any) { + this._error = err.message; + } + } + + private async _acceptSharedDevice(): Promise { + const code = await showPromptDialog(this, { + title: "Add shared device", + inputLabel: "Pin", + inputType: "number", + confirmText: "Accept device", + }); + if (!code) { + return; + } + this._error = undefined; + try { + await acceptSharedMatterDevice(this.hass, Number(code)); + } catch (err: any) { + this._error = err.message; + } + } + + private async _setThread(): Promise { + const code = await showPromptDialog(this, { + title: "Set Thread operation", + inputLabel: "Dataset", + inputType: "string", + confirmText: "Set Thread", + }); + if (!code) { + return; + } + this._error = undefined; + try { + await matterSetThread(this.hass, code); + } catch (err: any) { + this._error = err.message; + } + } + + static styles = [ + haStyle, + css` + .content { + padding: 24px 0 32px; + max-width: 600px; + margin: 0 auto; + direction: ltr; + } + ha-card:first-child { + margin-bottom: 16px; + } + `, + ]; +} diff --git a/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts b/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts index e0f8f99319..1d81f69fe4 100644 --- a/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts +++ b/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts @@ -1,5 +1,4 @@ import "@material/mwc-button"; -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import "../../../../../components/ha-card"; @@ -45,16 +44,16 @@ class HaPanelDevMqtt extends LitElement {
- + @change=${this._handleTopic} + >

${this.hass.localize("ui.panel.config.mqtt.payload")}