ha-frontend/src/panels/config/integrations/integration-panels/zha/zha-add-devices-page.ts

302 lines
8.3 KiB
TypeScript

import "@material/mwc-button";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../../components/ha-circular-progress";
import {
DEVICE_MESSAGE_TYPES,
LOG_OUTPUT,
ZHADevice,
} from "../../../../../data/zha";
import "../../../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../../../resources/styles";
import { HomeAssistant, Route } from "../../../../../types";
import { documentationUrl } from "../../../../../util/documentation-url";
import { zhaTabs } from "./zha-config-dashboard";
import "./zha-device-pairing-status-card";
import "../../../../../components/ha-textarea";
@customElement("zha-add-devices-page")
class ZHAAddDevicesPage extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow = false;
@property({ type: Boolean }) public isWide = false;
@property({ attribute: false }) public route?: Route;
@state() private _error?: string;
@state() private _discoveredDevices: Record<string, ZHADevice> = {};
@state() private _formattedEvents = "";
@state() private _active = false;
@state() private _showHelp = false;
@state() private _showLogs = false;
private _ieeeAddress?: string;
private _addDevicesTimeoutHandle: any = undefined;
private _subscribed?: Promise<() => Promise<void>>;
public connectedCallback(): void {
super.connectedCallback();
if (this.route && this.route.path && this.route.path !== "") {
this._ieeeAddress = this.route.path.substring(1);
} else {
this._ieeeAddress = undefined;
}
this._subscribe();
}
public disconnectedCallback(): void {
super.disconnectedCallback();
this._unsubscribe();
this._error = undefined;
this._discoveredDevices = {};
this._formattedEvents = "";
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (
changedProps.has("hass") &&
!this._active &&
!changedProps.get("hass")
) {
this._subscribe();
}
}
protected render(): TemplateResult {
return html`
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route!}
.tabs=${zhaTabs}
>
<mwc-button slot="toolbar-icon" @click=${this._toggleLogs}
>${this._showLogs ? "Hide logs" : "Show logs"}</mwc-button
>
<div class="searching">
${this._active
? html`
<h1>
${this.hass!.localize(
"ui.panel.config.zha.add_device_page.spinner"
)}
</h1>
<ha-circular-progress
indeterminate
aria-label="Searching"
></ha-circular-progress>
`
: html`
<div>
<mwc-button @click=${this._subscribe} class="search-button">
${this.hass!.localize(
"ui.panel.config.zha.add_device_page.search_again"
)}
</mwc-button>
</div>
`}
</div>
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
<div class="content">
${Object.keys(this._discoveredDevices).length < 1
? html`
<div class="discovery-text">
<h4>
${this.hass.localize(
"ui.panel.config.zha.add_device_page.pairing_mode",
{
documentation_link: html`
<a
target="_blank"
rel="noopener noreferrer"
href=${documentationUrl(
this.hass,
"/integrations/zha#adding-devices"
)}
>
${this.hass.localize(
"ui.panel.config.zha.add_device_page.pairing_mode_link"
)}
</a>
`,
}
)}
</h4>
<h4>
${this.hass!.localize(
this._active
? "ui.panel.config.zha.add_device_page.discovered_text"
: "ui.panel.config.zha.add_device_page.no_devices_found"
)}
</h4>
</div>
`
: html`
${Object.values(this._discoveredDevices).map(
(device) => html`
<zha-device-pairing-status-card
class="card"
.hass=${this.hass}
.device=${device}
.narrow=${this.narrow}
.showHelp=${this._showHelp}
></zha-device-pairing-status-card>
`
)}
`}
</div>
${this._showLogs
? html`<ha-textarea
readonly
class="log"
autogrow
.value=${this._formattedEvents}
>
</ha-textarea>`
: ""}
</hass-tabs-subpage>
`;
}
private _toggleLogs() {
this._showLogs = !this._showLogs;
}
private _handleMessage(message: any): void {
if (message.type === LOG_OUTPUT) {
this._formattedEvents += message.log_entry.message + "\n";
}
if (message.type && DEVICE_MESSAGE_TYPES.includes(message.type)) {
this._discoveredDevices[message.device_info.ieee] = message.device_info;
}
}
private _unsubscribe(): void {
this._active = false;
if (this._addDevicesTimeoutHandle) {
clearTimeout(this._addDevicesTimeoutHandle);
}
if (this._subscribed) {
this._subscribed.then((unsub) => unsub());
this._subscribed = undefined;
}
}
private _deactivate(): void {
this._active = false;
if (this._addDevicesTimeoutHandle) {
clearTimeout(this._addDevicesTimeoutHandle);
}
}
private _subscribe(): void {
if (!this.hass) {
return;
}
this._active = true;
const data: any = { type: "zha/devices/permit", duration: 254 };
if (this._ieeeAddress) {
data.ieee = this._ieeeAddress;
}
this._subscribed = this.hass.connection.subscribeMessage(
(message) => this._handleMessage(message),
data
);
this._addDevicesTimeoutHandle = setTimeout(
() => this._deactivate(),
254000
);
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.discovery-text {
width: 100%;
padding: 16px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.content {
display: flex;
flex-wrap: wrap;
padding: 4px;
justify-content: center;
}
.error {
color: var(--error-color);
}
ha-circular-progress {
margin: 20px;
}
.searching {
margin-top: 20px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.card {
margin: 8px;
}
.log {
padding: 16px;
}
.toggle-help-icon {
position: absolute;
margin-top: 16px;
margin-right: 16px;
margin-inline-end: 16px;
margin-inline-start: initial;
top: -6px;
right: 0;
inset-inline-end: 0;
inset-inline-start: initial;
color: var(--primary-color);
}
.search-button {
margin-top: 16px;
margin-left: 16px;
margin-inline-start: 16px;
margin-inline-end: initial;
}
.help-text {
color: grey;
padding-left: 16px;
padding-inline-start: 16px;
padding-inline-end: initial;
}
ha-textarea {
width: 100%;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-add-devices-page": ZHAAddDevicesPage;
}
}