Clean up ZHA configuration UI (#13610)

This commit is contained in:
David F. Mulcahey 2022-09-27 10:02:45 -04:00 committed by GitHub
parent 0848c096b9
commit 3083d5b04c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 903 additions and 1118 deletions

View File

@ -309,7 +309,7 @@ export const fetchCommandsForCluster = (
cluster_type: clusterType, cluster_type: clusterType,
}); });
export const fetchClustersForZhaNode = ( export const fetchClustersForZhaDevice = (
hass: HomeAssistant, hass: HomeAssistant,
ieeeAddress: string ieeeAddress: string
): Promise<Cluster[]> => ): Promise<Cluster[]> =>

View File

@ -1,9 +1,7 @@
import { import {
mdiCogRefresh, mdiCogRefresh,
mdiDelete, mdiDelete,
mdiDrawPen,
mdiFamilyTree, mdiFamilyTree,
mdiFileTree,
mdiGroup, mdiGroup,
mdiPlus, mdiPlus,
} from "@mdi/js"; } from "@mdi/js";
@ -12,9 +10,7 @@ import type { DeviceRegistryEntry } from "../../../../../../data/device_registry
import { fetchZHADevice } from "../../../../../../data/zha"; import { fetchZHADevice } from "../../../../../../data/zha";
import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box"; import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../../../../types"; import type { HomeAssistant } from "../../../../../../types";
import { showZHAClusterDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-cluster"; import { showZHAManageZigbeeDeviceDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-manage-zigbee-device";
import { showZHADeviceChildrenDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-children";
import { showZHADeviceZigbeeInfoDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-zigbee-info";
import { showZHAReconfigureDeviceDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-reconfigure-device"; import { showZHAReconfigureDeviceDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-reconfigure-device";
import type { DeviceAction } from "../../../ha-config-device-page"; import type { DeviceAction } from "../../../ha-config-device-page";
@ -59,13 +55,6 @@ export const getZHADeviceActions = async (
icon: mdiPlus, icon: mdiPlus,
action: () => navigate(`/config/zha/add/${zhaDevice!.ieee}`), action: () => navigate(`/config/zha/add/${zhaDevice!.ieee}`),
}, },
{
label: hass.localize(
"ui.dialogs.zha_device_info.buttons.device_children"
),
icon: mdiFileTree,
action: () => showZHADeviceChildrenDialog(el, { device: zhaDevice! }),
},
] ]
); );
} }
@ -73,16 +62,10 @@ export const getZHADeviceActions = async (
actions.push( actions.push(
...[ ...[
{ {
label: hass.localize( label: hass.localize("ui.dialogs.zha_device_info.buttons.manage"),
"ui.dialogs.zha_device_info.buttons.zigbee_information"
),
icon: mdiDrawPen,
action: () => showZHADeviceZigbeeInfoDialog(el, { device: zhaDevice }),
},
{
label: hass.localize("ui.dialogs.zha_device_info.buttons.clusters"),
icon: mdiGroup, icon: mdiGroup,
action: () => showZHAClusterDialog(el, { device: zhaDevice }), action: () =>
showZHAManageZigbeeDeviceDialog(el, { device: zhaDevice }),
}, },
{ {
label: hass.localize("ui.dialogs.zha_device_info.buttons.view_network"), label: hass.localize("ui.dialogs.zha_device_info.buttons.view_network"),

View File

@ -23,6 +23,7 @@ export class HaDeviceActionsZha extends LitElement {
@state() private _zhaDevice?: ZHADevice; @state() private _zhaDevice?: ZHADevice;
protected updated(changedProperties: PropertyValues) { protected updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (changedProperties.has("device")) { if (changedProperties.has("device")) {
const zigbeeConnection = this.device.connections.find( const zigbeeConnection = this.device.connections.find(
(conn) => conn[0] === "zigbee" (conn) => conn[0] === "zigbee"

View File

@ -1,142 +0,0 @@
import {
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { HASSDomEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-code-editor";
import { createCloseHeading } from "../../../../../components/ha-dialog";
import {
Cluster,
fetchBindableDevices,
fetchGroups,
ZHADevice,
ZHAGroup,
} from "../../../../../data/zha";
import { haStyleDialog } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types";
import { sortZHADevices, sortZHAGroups } from "./functions";
import { ZHADeviceZigbeeInfoDialogParams } from "./show-dialog-zha-device-zigbee-info";
import { ZHAClusterSelectedParams } from "./types";
import "./zha-cluster-attributes";
import "./zha-cluster-commands";
import "./zha-clusters";
import "./zha-device-binding";
import "./zha-group-binding";
@customElement("dialog-zha-cluster")
class DialogZHACluster extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _device?: ZHADevice;
@state() private _selectedCluster?: Cluster;
@state() private _bindableDevices: ZHADevice[] = [];
@state() private _groups: ZHAGroup[] = [];
public async showDialog(
params: ZHADeviceZigbeeInfoDialogParams
): Promise<void> {
this._device = params.device;
}
protected updated(changedProperties: PropertyValues): void {
super.update(changedProperties);
if (changedProperties.has("_device")) {
this._fetchData();
}
}
protected render(): TemplateResult {
if (!this._device) {
return html``;
}
return html`
<ha-dialog
open
hideActions
@closed=${this._close}
.heading=${createCloseHeading(
this.hass,
this.hass.localize("ui.panel.config.zha.clusters.header")
)}
>
<zha-clusters
.hass=${this.hass}
.selectedDevice=${this._device}
@zha-cluster-selected=${this._onClusterSelected}
></zha-clusters>
${this._selectedCluster
? html`
<zha-cluster-attributes
.hass=${this.hass}
.selectedNode=${this._device}
.selectedCluster=${this._selectedCluster}
></zha-cluster-attributes>
<zha-cluster-commands
.hass=${this.hass}
.selectedNode=${this._device}
.selectedCluster=${this._selectedCluster}
></zha-cluster-commands>
`
: ""}
${this._bindableDevices.length > 0
? html`
<zha-device-binding-control
.hass=${this.hass}
.selectedDevice=${this._device}
.bindableDevices=${this._bindableDevices}
></zha-device-binding-control>
`
: ""}
${this._device && this._groups.length > 0
? html`
<zha-group-binding-control
.hass=${this.hass}
.selectedDevice=${this._device}
.groups=${this._groups}
></zha-group-binding-control>
`
: ""}
</ha-dialog>
`;
}
private _onClusterSelected(
selectedClusterEvent: HASSDomEvent<ZHAClusterSelectedParams>
): void {
this._selectedCluster = selectedClusterEvent.detail.cluster;
}
private _close(): void {
this._device = undefined;
}
private async _fetchData(): Promise<void> {
if (this._device && this.hass) {
this._bindableDevices =
this._device && this._device.device_type !== "Coordinator"
? (await fetchBindableDevices(this.hass, this._device.ieee)).sort(
sortZHADevices
)
: [];
this._groups = (await fetchGroups(this.hass!)).sort(sortZHAGroups);
}
}
static get styles(): CSSResultGroup {
return haStyleDialog;
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-zha-cluster": DialogZHACluster;
}
}

View File

@ -1,69 +0,0 @@
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../../components/ha-code-editor";
import { createCloseHeading } from "../../../../../components/ha-dialog";
import { haStyleDialog } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types";
import { ZHADeviceZigbeeInfoDialogParams } from "./show-dialog-zha-device-zigbee-info";
@customElement("dialog-zha-device-zigbee-info")
class DialogZHADeviceZigbeeInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _signature: any;
public async showDialog(
params: ZHADeviceZigbeeInfoDialogParams
): Promise<void> {
this._signature = JSON.stringify(
{
...params.device.signature,
manufacturer: params.device.manufacturer,
model: params.device.model,
class: params.device.quirk_class,
},
null,
2
);
}
protected render(): TemplateResult {
if (!this._signature) {
return html``;
}
return html`
<ha-dialog
open
hideActions
@closed=${this._close}
.heading=${createCloseHeading(
this.hass,
this.hass.localize(`ui.dialogs.zha_device_info.device_signature`)
)}
>
<ha-code-editor
mode="yaml"
readOnly
.value=${this._signature}
dir="ltr"
>
</ha-code-editor>
</ha-dialog>
`;
}
private _close(): void {
this._signature = undefined;
}
static get styles(): CSSResultGroup {
return haStyleDialog;
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-zha-device-zigbee-info": DialogZHADeviceZigbeeInfo;
}
}

View File

@ -0,0 +1,291 @@
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { mdiClose } from "@mdi/js";
import { customElement, property, state } from "lit/decorators";
import { cache } from "lit/directives/cache";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-code-editor";
import { createCloseHeading } from "../../../../../components/ha-dialog";
import {
fetchBindableDevices,
fetchGroups,
ZHADevice,
ZHAGroup,
} from "../../../../../data/zha";
import { haStyleDialog } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types";
import { sortZHADevices, sortZHAGroups } from "./functions";
import "./zha-cluster-attributes";
import "./zha-cluster-commands";
import "./zha-manage-clusters";
import "./zha-device-binding";
import "./zha-group-binding";
import "./zha-device-children";
import "./zha-device-signature";
import {
Tab,
ZHAManageZigbeeDeviceDialogParams,
} from "./show-dialog-zha-manage-zigbee-device";
import "../../../../../components/ha-header-bar";
import "@material/mwc-tab-bar/mwc-tab-bar";
import "@material/mwc-tab/mwc-tab";
@customElement("dialog-zha-manage-zigbee-device")
class DialogZHAManageZigbeeDevice extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean, reflect: true }) public large = false;
@state() private _currTab: Tab = "clusters";
@state() private _device?: ZHADevice;
@state() private _bindableDevices: ZHADevice[] = [];
@state() private _groups: ZHAGroup[] = [];
public async showDialog(
params: ZHAManageZigbeeDeviceDialogParams
): Promise<void> {
this._device = params.device;
if (!this._device) {
this.closeDialog();
return;
}
this._currTab = params.tab || "clusters";
this.large = false;
}
public closeDialog() {
this._device = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this.addEventListener("close-dialog", () => this.closeDialog());
}
protected willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
if (!this._device) {
return;
}
if (changedProps.has("_device")) {
const tabs = this._getTabs(this._device);
if (!tabs.includes(this._currTab)) {
this._currTab = tabs[0];
}
this._fetchData();
}
}
protected render(): TemplateResult {
if (!this._device) {
return html``;
}
const tabs = this._getTabs(this._device);
return html`
<ha-dialog
open
hideActions
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
this.hass.localize("ui.dialogs.zha_manage_device.heading")
)}
>
<div slot="heading" class="heading">
<ha-header-bar>
<ha-icon-button
slot="navigationIcon"
dialogAction="cancel"
.label=${this.hass.localize(
"ui.dialogs.more_info_control.dismiss"
)}
.path=${mdiClose}
></ha-icon-button>
<div
slot="title"
class="main-title"
.title=${this.hass.localize(
"ui.dialogs.zha_manage_device.heading"
)}
@click=${this._enlarge}
>
${this.hass.localize("ui.dialogs.zha_manage_device.heading")}
</div>
</ha-header-bar>
<mwc-tab-bar
.activeIndex=${tabs.indexOf(this._currTab)}
@MDCTabBar:activated=${this._handleTabChanged}
>
${tabs.map(
(tab) => html`
<mwc-tab
.label=${this.hass.localize(
`ui.dialogs.zha_manage_device.tabs.${tab}`
)}
></mwc-tab>
`
)}
</mwc-tab-bar>
</div>
<div class="content" tabindex="-1" dialogInitialFocus>
${cache(
this._currTab === "clusters"
? html`
<zha-manage-clusters
.hass=${this.hass}
.device=${this._device}
></zha-manage-clusters>
`
: this._currTab === "bindings"
? html`
${this._bindableDevices.length > 0
? html`
<zha-device-binding-control
.hass=${this.hass}
.device=${this._device}
.bindableDevices=${this._bindableDevices}
></zha-device-binding-control>
`
: ""}
${this._device && this._groups.length > 0
? html`
<zha-group-binding-control
.hass=${this.hass}
.device=${this._device}
.groups=${this._groups}
></zha-group-binding-control>
`
: ""}
`
: this._currTab === "signature"
? html`
<zha-device-zigbee-info
.hass=${this.hass}
.device=${this._device}
></zha-device-zigbee-info>
`
: html`
<zha-device-children
.hass=${this.hass}
.device=${this._device}
></zha-device-children>
`
)}
</div>
</ha-dialog>
`;
}
private async _fetchData(): Promise<void> {
if (this._device && this.hass) {
this._bindableDevices =
this._device && this._device.device_type !== "Coordinator"
? (await fetchBindableDevices(this.hass, this._device.ieee)).sort(
sortZHADevices
)
: [];
this._groups = (await fetchGroups(this.hass!)).sort(sortZHAGroups);
}
}
private _enlarge() {
this.large = !this.large;
}
private _handleTabChanged(ev: CustomEvent): void {
const newTab = this._getTabs(this._device)[ev.detail.index];
if (newTab === this._currTab) {
return;
}
this._currTab = newTab;
}
private _getTabs = memoizeOne((device: ZHADevice | undefined) => {
const tabs: Tab[] = ["clusters", "bindings", "signature"];
if (
device &&
(device.device_type === "Router" || device.device_type === "Coordinator")
) {
tabs.push("children");
}
return tabs;
});
static get styles(): CSSResultGroup {
return [
haStyleDialog,
css`
ha-dialog {
--dialog-surface-position: static;
--dialog-content-position: static;
--vertial-align-dialog: flex-start;
}
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
display: block;
}
.content {
outline: none;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-header-bar {
--mdc-theme-primary: var(--app-header-background-color);
--mdc-theme-on-primary: var(--app-header-text-color, white);
border-bottom: none;
}
}
.heading {
border-bottom: 1px solid
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
}
@media all and (min-width: 600px) and (min-height: 501px) {
ha-dialog {
--mdc-dialog-min-width: 560px;
--mdc-dialog-max-width: 560px;
--dialog-surface-margin-top: 40px;
--mdc-dialog-max-height: calc(100% - 72px);
}
.main-title {
overflow: hidden;
text-overflow: ellipsis;
cursor: default;
}
:host([large]) ha-dialog,
ha-dialog[data-domain="camera"] {
--mdc-dialog-min-width: 90vw;
--mdc-dialog-max-width: 90vw;
}
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-zha-manage-zigbee-device": DialogZHAManageZigbeeDevice;
}
}

View File

@ -12,7 +12,7 @@ import {
Cluster, Cluster,
ClusterConfigurationEvent, ClusterConfigurationEvent,
ClusterConfigurationStatus, ClusterConfigurationStatus,
fetchClustersForZhaNode, fetchClustersForZhaDevice,
reconfigureNode, reconfigureNode,
ZHA_CHANNEL_CFG_DONE, ZHA_CHANNEL_CFG_DONE,
ZHA_CHANNEL_MSG_BIND, ZHA_CHANNEL_MSG_BIND,
@ -321,16 +321,16 @@ class DialogZHAReconfigureDevice extends LitElement {
return; return;
} }
this._clusterConfigurationStatuses = new Map( this._clusterConfigurationStatuses = new Map(
(await fetchClustersForZhaNode(this.hass, this._params.device.ieee)).map( (
(cluster: Cluster) => [ await fetchClustersForZhaDevice(this.hass, this._params.device.ieee)
cluster.id, ).map((cluster: Cluster) => [
{ cluster.id,
cluster: cluster, {
bindSuccess: undefined, cluster: cluster,
attributes: new Map<number, AttributeConfigurationStatus>(), bindSuccess: undefined,
}, attributes: new Map<number, AttributeConfigurationStatus>(),
] },
) ])
); );
this._subscribe(this._params); this._subscribe(this._params);
this._status = "started"; this._status = "started";

View File

@ -1,19 +0,0 @@
import { fireEvent } from "../../../../../common/dom/fire_event";
import { ZHADevice } from "../../../../../data/zha";
export interface ZHAClusterDialogParams {
device: ZHADevice;
}
export const loadZHAClusterDialog = () => import("./dialog-zha-cluster");
export const showZHAClusterDialog = (
element: HTMLElement,
params: ZHAClusterDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-zha-cluster",
dialogImport: loadZHAClusterDialog,
dialogParams: params,
});
};

View File

@ -1,20 +0,0 @@
import { fireEvent } from "../../../../../common/dom/fire_event";
import { ZHADevice } from "../../../../../data/zha";
export interface ZHADeviceChildrenDialogParams {
device: ZHADevice;
}
export const loadZHADeviceChildrenDialog = () =>
import("./dialog-zha-device-children");
export const showZHADeviceChildrenDialog = (
element: HTMLElement,
zhaDeviceChildrenParams: ZHADeviceChildrenDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-zha-device-children",
dialogImport: loadZHADeviceChildrenDialog,
dialogParams: zhaDeviceChildrenParams,
});
};

View File

@ -1,20 +0,0 @@
import { fireEvent } from "../../../../../common/dom/fire_event";
import { ZHADevice } from "../../../../../data/zha";
export interface ZHADeviceZigbeeInfoDialogParams {
device: ZHADevice;
}
export const loadZHADeviceZigbeeInfoDialog = () =>
import("./dialog-zha-device-zigbee-info");
export const showZHADeviceZigbeeInfoDialog = (
element: HTMLElement,
zhaDeviceZigbeeInfoParams: ZHADeviceZigbeeInfoDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-zha-device-zigbee-info",
dialogImport: loadZHADeviceZigbeeInfoDialog,
dialogParams: zhaDeviceZigbeeInfoParams,
});
};

View File

@ -0,0 +1,23 @@
import { fireEvent } from "../../../../../common/dom/fire_event";
import { ZHADevice } from "../../../../../data/zha";
export type Tab = "clusters" | "bindings" | "signature" | "children";
export interface ZHAManageZigbeeDeviceDialogParams {
device: ZHADevice;
tab?: Tab;
}
export const loadZHAManageZigbeeDeviceDialog = () =>
import("./dialog-zha-manage-zigbee-device");
export const showZHAManageZigbeeDeviceDialog = (
element: HTMLElement,
params: ZHAManageZigbeeDeviceDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-zha-manage-zigbee-device",
dialogImport: loadZHAManageZigbeeDeviceDialog,
dialogParams: params,
});
};

View File

@ -1,5 +1,5 @@
import { HaSelect } from "../../../../../components/ha-select"; import { HaSelect } from "../../../../../components/ha-select";
import { Cluster, ZHADevice } from "../../../../../data/zha"; import { ZHADevice } from "../../../../../data/zha";
export interface ItemSelectedEvent { export interface ItemSelectedEvent {
target?: HaSelect; target?: HaSelect;
@ -41,10 +41,6 @@ export interface ZHADeviceSelectedParams {
node: ZHADevice; node: ZHADevice;
} }
export interface ZHAClusterSelectedParams {
cluster: Cluster;
}
export interface NodeServiceData { export interface NodeServiceData {
ieee_address: string; ieee_address: string;
} }

View File

@ -1,6 +1,4 @@
import "@material/mwc-button";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiHelpCircle } from "@mdi/js";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
css, css,
@ -10,13 +8,12 @@ import {
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit"; } from "lit";
import { property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { stopPropagation } from "../../../../../common/dom/stop_propagation"; import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card"; import "../../../../../components/ha-card";
import "../../../../../components/ha-icon-button";
import "../../../../../components/ha-select"; import "../../../../../components/ha-select";
import "../../../../../components/ha-service-description"; import "../../../../../components/buttons/ha-progress-button";
import { import {
Attribute, Attribute,
Cluster, Cluster,
@ -27,7 +24,7 @@ import {
} from "../../../../../data/zha"; } from "../../../../../data/zha";
import { haStyle } from "../../../../../resources/styles"; import { haStyle } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types"; import { HomeAssistant } from "../../../../../types";
import "../../../ha-config-section"; import { forwardHaptic } from "../../../../../data/haptics";
import { formatAsPaddedHex } from "./functions"; import { formatAsPaddedHex } from "./functions";
import { import {
ChangeEvent, ChangeEvent,
@ -35,18 +32,15 @@ import {
SetAttributeServiceData, SetAttributeServiceData,
} from "./types"; } from "./types";
@customElement("zha-cluster-attributes")
export class ZHAClusterAttributes extends LitElement { export class ZHAClusterAttributes extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) public hass?: HomeAssistant;
@property() public isWide?: boolean; @property() public device?: ZHADevice;
@property() public showHelp = false;
@property() public selectedNode?: ZHADevice;
@property() public selectedCluster?: Cluster; @property() public selectedCluster?: Cluster;
@state() private _attributes: Attribute[] = []; @state() private _attributes: Attribute[] | undefined;
@state() private _selectedAttributeId?: number; @state() private _selectedAttributeId?: number;
@ -54,78 +48,52 @@ export class ZHAClusterAttributes extends LitElement {
@state() private _manufacturerCodeOverride?: string | number; @state() private _manufacturerCodeOverride?: string | number;
@state() private _readingAttribute = false;
@state() @state()
private _setAttributeServiceData?: SetAttributeServiceData; private _setAttributeServiceData?: SetAttributeServiceData;
protected updated(changedProperties: PropertyValues): void { protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("selectedCluster")) { if (changedProperties.has("selectedCluster")) {
this._attributes = []; this._attributes = undefined;
this._selectedAttributeId = undefined; this._selectedAttributeId = undefined;
this._attributeValue = ""; this._attributeValue = "";
this._fetchAttributesForCluster(); this._fetchAttributesForCluster();
} }
super.update(changedProperties); super.updated(changedProperties);
} }
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.device || !this.selectedCluster || !this._attributes) {
return html``;
}
return html` return html`
<ha-config-section .isWide=${this.isWide}> <ha-card class="content">
<div class="header" slot="header"> <div class="attribute-picker">
<span> <ha-select
${this.hass!.localize( .label=${this.hass!.localize(
"ui.panel.config.zha.cluster_attributes.header" "ui.panel.config.zha.cluster_attributes.attributes_of_cluster"
)} )}
</span> class="menu"
<ha-icon-button .value=${String(this._selectedAttributeId)}
class="toggle-help-icon" @selected=${this._selectedAttributeChanged}
@click=${this._onHelpTap} @closed=${stopPropagation}
.path=${mdiHelpCircle} fixedMenuPosition
.label=${this.hass!.localize("ui.common.help")} naturalMenuWidth
> >
</ha-icon-button> ${this._attributes.map(
</div> (entry) => html`
<span slot="introduction"> <mwc-list-item .value=${String(entry.id)}>
${this.hass!.localize( ${`${entry.name} (id: ${formatAsPaddedHex(entry.id)})`}
"ui.panel.config.zha.cluster_attributes.introduction" </mwc-list-item>
)}
</span>
<ha-card class="content">
<div class="attribute-picker">
<ha-select
.label=${this.hass!.localize(
"ui.panel.config.zha.cluster_attributes.attributes_of_cluster"
)}
class="menu"
.value=${String(this._selectedAttributeId)}
@selected=${this._selectedAttributeChanged}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
>
${this._attributes.map(
(entry) => html`
<mwc-list-item .value=${String(entry.id)}>
${entry.name + " (id: " + formatAsPaddedHex(entry.id) + ")"}
</mwc-list-item>
`
)}
</ha-select>
</div>
${this.showHelp
? html`
<div class="help-text">
${this.hass!.localize(
"ui.panel.config.zha.cluster_attributes.help_attribute_dropdown"
)}
</div>
` `
: ""} )}
${this._selectedAttributeId !== undefined </ha-select>
? this._renderAttributeInteractions() </div>
: ""} ${this._selectedAttributeId !== undefined
</ha-card> ? this._renderAttributeInteractions()
</ha-config-section> : ""}
</ha-card>
`; `;
} }
@ -152,20 +120,15 @@ export class ZHAClusterAttributes extends LitElement {
></paper-input> ></paper-input>
</div> </div>
<div class="card-actions"> <div class="card-actions">
<mwc-button @click=${this._onGetZigbeeAttributeClick}> <ha-progress-button
@click=${this._onGetZigbeeAttributeClick}
.progress=${this._readingAttribute}
.disabled=${this._readingAttribute}
>
${this.hass!.localize( ${this.hass!.localize(
"ui.panel.config.zha.cluster_attributes.get_zigbee_attribute" "ui.panel.config.zha.cluster_attributes.read_zigbee_attribute"
)} )}
</mwc-button> </ha-progress-button>
${this.showHelp
? html`
<div class="help-text2">
${this.hass!.localize(
"ui.panel.config.zha.cluster_attributes.help_get_zigbee_attribute"
)}
</div>
`
: ""}
<ha-call-service-button <ha-call-service-button
.hass=${this.hass} .hass=${this.hass}
domain="zha" domain="zha"
@ -173,44 +136,37 @@ export class ZHAClusterAttributes extends LitElement {
.serviceData=${this._setAttributeServiceData} .serviceData=${this._setAttributeServiceData}
> >
${this.hass!.localize( ${this.hass!.localize(
"ui.panel.config.zha.cluster_attributes.set_zigbee_attribute" "ui.panel.config.zha.cluster_attributes.write_zigbee_attribute"
)} )}
</ha-call-service-button> </ha-call-service-button>
${this.showHelp
? html`
<ha-service-description
.hass=${this.hass}
domain="zha"
service="set_zigbee_cluster_attribute"
class="help-text2"
></ha-service-description>
`
: ""}
</div> </div>
`; `;
} }
private async _fetchAttributesForCluster(): Promise<void> { private async _fetchAttributesForCluster(): Promise<void> {
if (this.selectedNode && this.selectedCluster && this.hass) { if (this.device && this.selectedCluster && this.hass) {
this._attributes = await fetchAttributesForCluster( this._attributes = await fetchAttributesForCluster(
this.hass, this.hass,
this.selectedNode!.ieee, this.device!.ieee,
this.selectedCluster!.endpoint_id, this.selectedCluster!.endpoint_id,
this.selectedCluster!.id, this.selectedCluster!.id,
this.selectedCluster!.type this.selectedCluster!.type
); );
this._attributes.sort((a, b) => a.name.localeCompare(b.name)); this._attributes.sort((a, b) => a.name.localeCompare(b.name));
if (this._attributes.length > 0) {
this._selectedAttributeId = this._attributes[0].id;
}
} }
} }
private _computeReadAttributeServiceData(): private _computeReadAttributeServiceData():
| ReadAttributeServiceData | ReadAttributeServiceData
| undefined { | undefined {
if (!this.selectedCluster || !this.selectedNode) { if (!this.selectedCluster || !this.device) {
return undefined; return undefined;
} }
return { return {
ieee: this.selectedNode!.ieee, ieee: this.device!.ieee,
endpoint_id: this.selectedCluster!.endpoint_id, endpoint_id: this.selectedCluster!.endpoint_id,
cluster_id: this.selectedCluster!.id, cluster_id: this.selectedCluster!.id,
cluster_type: this.selectedCluster!.type, cluster_type: this.selectedCluster!.type,
@ -224,11 +180,11 @@ export class ZHAClusterAttributes extends LitElement {
private _computeSetAttributeServiceData(): private _computeSetAttributeServiceData():
| SetAttributeServiceData | SetAttributeServiceData
| undefined { | undefined {
if (!this.selectedCluster || !this.selectedNode) { if (!this.selectedCluster || !this.device) {
return undefined; return undefined;
} }
return { return {
ieee: this.selectedNode!.ieee, ieee: this.device!.ieee,
endpoint_id: this.selectedCluster!.endpoint_id, endpoint_id: this.selectedCluster!.endpoint_id,
cluster_id: this.selectedCluster!.id, cluster_id: this.selectedCluster!.id,
cluster_type: this.selectedCluster!.type, cluster_type: this.selectedCluster!.type,
@ -250,17 +206,24 @@ export class ZHAClusterAttributes extends LitElement {
this._setAttributeServiceData = this._computeSetAttributeServiceData(); this._setAttributeServiceData = this._computeSetAttributeServiceData();
} }
private async _onGetZigbeeAttributeClick(): Promise<void> { private async _onGetZigbeeAttributeClick(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
const data = this._computeReadAttributeServiceData(); const data = this._computeReadAttributeServiceData();
if (data && this.hass) { if (data && this.hass) {
this._attributeValue = await readAttributeValue(this.hass, data); this._readingAttribute = true;
try {
this._attributeValue = await readAttributeValue(this.hass, data);
forwardHaptic("success");
button.actionSuccess();
} catch (err: any) {
forwardHaptic("failure");
button.actionError();
} finally {
this._readingAttribute = false;
}
} }
} }
private _onHelpTap(): void {
this.showHelp = !this.showHelp;
}
private _selectedAttributeChanged(event: ItemSelectedEvent): void { private _selectedAttributeChanged(event: ItemSelectedEvent): void {
this._selectedAttributeId = Number(event.target!.value); this._selectedAttributeId = Number(event.target!.value);
this._attributeValue = ""; this._attributeValue = "";
@ -278,14 +241,6 @@ export class ZHAClusterAttributes extends LitElement {
width: 100%; width: 100%;
} }
.content {
margin-top: 24px;
}
ha-card {
max-width: 680px;
}
.card-actions.warning ha-call-service-button { .card-actions.warning ha-call-service-button {
color: var(--error-color); color: var(--error-color);
} }
@ -306,33 +261,6 @@ export class ZHAClusterAttributes extends LitElement {
.header { .header {
flex-grow: 1; flex-grow: 1;
} }
.toggle-help-icon {
float: right;
top: -6px;
right: 0;
padding-right: 0px;
color: var(--primary-color);
}
ha-service-description {
display: block;
color: grey;
}
[hidden] {
display: none;
}
.help-text {
color: grey;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 16px;
}
.help-text2 {
color: grey;
padding: 16px;
}
`, `,
]; ];
} }
@ -343,5 +271,3 @@ declare global {
"zha-cluster-attributes": ZHAClusterAttributes; "zha-cluster-attributes": ZHAClusterAttributes;
} }
} }
customElements.define("zha-cluster-attributes", ZHAClusterAttributes);

View File

@ -1,5 +1,4 @@
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiHelpCircle } from "@mdi/js";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
css, css,
@ -13,9 +12,7 @@ import { property, state } from "lit/decorators";
import { stopPropagation } from "../../../../../common/dom/stop_propagation"; import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card"; import "../../../../../components/ha-card";
import "../../../../../components/ha-icon-button";
import "../../../../../components/ha-select"; import "../../../../../components/ha-select";
import "../../../../../components/ha-service-description";
import { import {
Cluster, Cluster,
Command, Command,
@ -24,7 +21,6 @@ import {
} from "../../../../../data/zha"; } from "../../../../../data/zha";
import { haStyle } from "../../../../../resources/styles"; import { haStyle } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types"; import { HomeAssistant } from "../../../../../types";
import "../../../ha-config-section";
import { formatAsPaddedHex } from "./functions"; import { formatAsPaddedHex } from "./functions";
import { ChangeEvent, IssueCommandServiceData } from "./types"; import { ChangeEvent, IssueCommandServiceData } from "./types";
@ -33,13 +29,11 @@ export class ZHAClusterCommands extends LitElement {
@property() public isWide?: boolean; @property() public isWide?: boolean;
@property() public selectedNode?: ZHADevice; @property() public device?: ZHADevice;
@property() public selectedCluster?: Cluster; @property() public selectedCluster?: Cluster;
@state() private _showHelp = false; @state() private _commands: Command[] | undefined;
@state() private _commands: Command[] = [];
@state() private _selectedCommandId?: number; @state() private _selectedCommandId?: number;
@ -50,132 +44,97 @@ export class ZHAClusterCommands extends LitElement {
protected updated(changedProperties: PropertyValues): void { protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("selectedCluster")) { if (changedProperties.has("selectedCluster")) {
this._commands = []; this._commands = undefined;
this._selectedCommandId = undefined; this._selectedCommandId = undefined;
this._fetchCommandsForCluster(); this._fetchCommandsForCluster();
} }
super.update(changedProperties); super.updated(changedProperties);
} }
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.device || !this.selectedCluster || !this._commands) {
return html``;
}
return html` return html`
<ha-config-section .isWide=${this.isWide}> <ha-card class="content">
<div class="header" slot="header"> <div class="command-picker">
<span> <ha-select
${this.hass!.localize( .label=${this.hass!.localize(
"ui.panel.config.zha.cluster_commands.header" "ui.panel.config.zha.cluster_commands.commands_of_cluster"
)} )}
</span> class="menu"
<ha-icon-button .value=${String(this._selectedCommandId)}
class="toggle-help-icon" @selected=${this._selectedCommandChanged}
@click=${this._onHelpTap} @closed=${stopPropagation}
.path=${mdiHelpCircle} fixedMenuPosition
.label=${this.hass!.localize("ui.common.help")} naturalMenuWidth
> >
</ha-icon-button> ${this._commands.map(
(entry) => html`
<mwc-list-item .value=${String(entry.id)}>
${entry.name + " (id: " + formatAsPaddedHex(entry.id) + ")"}
</mwc-list-item>
`
)}
</ha-select>
</div> </div>
<span slot="introduction"> ${this._selectedCommandId !== undefined
${this.hass!.localize( ? html`
"ui.panel.config.zha.cluster_commands.introduction" <div class="input-text">
)} <paper-input
</span> label=${this.hass!.localize(
"ui.panel.config.zha.common.manufacturer_code_override"
<ha-card class="content">
<div class="command-picker">
<ha-select
.label=${this.hass!.localize(
"ui.panel.config.zha.cluster_commands.commands_of_cluster"
)}
class="menu"
.value=${String(this._selectedCommandId)}
@selected=${this._selectedCommandChanged}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
>
${this._commands.map(
(entry) => html`
<mwc-list-item .value=${String(entry.id)}>
${entry.name + " (id: " + formatAsPaddedHex(entry.id) + ")"}
</mwc-list-item>
`
)}
</ha-select>
</div>
${this._showHelp
? html`
<div class="help-text">
${this.hass!.localize(
"ui.panel.config.zha.cluster_commands.help_command_dropdown"
)} )}
</div> type="number"
` .value=${this._manufacturerCodeOverride}
: ""} @value-changed=${this._onManufacturerCodeOverrideChanged}
${this._selectedCommandId !== undefined placeholder=${this.hass!.localize(
? html` "ui.panel.config.zha.common.value"
<div class="input-text"> )}
<paper-input ></paper-input>
label=${this.hass!.localize( </div>
"ui.panel.config.zha.common.manufacturer_code_override" <div class="card-actions">
)} <ha-call-service-button
type="number" .hass=${this.hass}
.value=${this._manufacturerCodeOverride} domain="zha"
@value-changed=${this._onManufacturerCodeOverrideChanged} service="issue_zigbee_cluster_command"
placeholder=${this.hass!.localize( .serviceData=${this._issueClusterCommandServiceData}
"ui.panel.config.zha.common.value" >
)} ${this.hass!.localize(
></paper-input> "ui.panel.config.zha.cluster_commands.issue_zigbee_command"
</div> )}
<div class="card-actions"> </ha-call-service-button>
<ha-call-service-button </div>
.hass=${this.hass} `
domain="zha" : ""}
service="issue_zigbee_cluster_command" </ha-card>
.serviceData=${this._issueClusterCommandServiceData}
>
${this.hass!.localize(
"ui.panel.config.zha.cluster_commands.issue_zigbee_command"
)}
</ha-call-service-button>
${this._showHelp
? html`
<ha-service-description
.hass=${this.hass}
domain="zha"
service="issue_zigbee_cluster_command"
class="help-text2"
></ha-service-description>
`
: ""}
</div>
`
: ""}
</ha-card>
</ha-config-section>
`; `;
} }
private async _fetchCommandsForCluster(): Promise<void> { private async _fetchCommandsForCluster(): Promise<void> {
if (this.selectedNode && this.selectedCluster && this.hass) { if (this.device && this.selectedCluster && this.hass) {
this._commands = await fetchCommandsForCluster( this._commands = await fetchCommandsForCluster(
this.hass, this.hass,
this.selectedNode!.ieee, this.device!.ieee,
this.selectedCluster!.endpoint_id, this.selectedCluster!.endpoint_id,
this.selectedCluster!.id, this.selectedCluster!.id,
this.selectedCluster!.type this.selectedCluster!.type
); );
this._commands.sort((a, b) => a.name.localeCompare(b.name)); this._commands.sort((a, b) => a.name.localeCompare(b.name));
if (this._commands.length > 0) {
this._selectedCommandId = this._commands[0].id;
}
} }
} }
private _computeIssueClusterCommandServiceData(): private _computeIssueClusterCommandServiceData():
| IssueCommandServiceData | IssueCommandServiceData
| undefined { | undefined {
if (!this.selectedNode || !this.selectedCluster) { if (!this.device || !this.selectedCluster || !this._commands) {
return undefined; return undefined;
} }
return { return {
ieee: this.selectedNode!.ieee, ieee: this.device!.ieee,
endpoint_id: this.selectedCluster!.endpoint_id, endpoint_id: this.selectedCluster!.endpoint_id,
cluster_id: this.selectedCluster!.id, cluster_id: this.selectedCluster!.id,
cluster_type: this.selectedCluster!.type, cluster_type: this.selectedCluster!.type,
@ -192,10 +151,6 @@ export class ZHAClusterCommands extends LitElement {
this._computeIssueClusterCommandServiceData(); this._computeIssueClusterCommandServiceData();
} }
private _onHelpTap(): void {
this._showHelp = !this._showHelp;
}
private _selectedCommandChanged(event): void { private _selectedCommandChanged(event): void {
this._selectedCommandId = Number(event.target.value); this._selectedCommandId = Number(event.target.value);
this._issueClusterCommandServiceData = this._issueClusterCommandServiceData =
@ -213,14 +168,6 @@ export class ZHAClusterCommands extends LitElement {
width: 100%; width: 100%;
} }
.content {
margin-top: 24px;
}
ha-card {
max-width: 680px;
}
.card-actions.warning ha-call-service-button { .card-actions.warning ha-call-service-button {
color: var(--error-color); color: var(--error-color);
} }
@ -238,18 +185,6 @@ export class ZHAClusterCommands extends LitElement {
padding-bottom: 10px; padding-bottom: 10px;
} }
.help-text {
color: grey;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 16px;
}
.help-text2 {
color: grey;
padding: 16px;
}
.header { .header {
flex-grow: 1; flex-grow: 1;
} }
@ -261,15 +196,6 @@ export class ZHAClusterCommands extends LitElement {
padding-right: 0px; padding-right: 0px;
color: var(--primary-color); color: var(--primary-color);
} }
ha-service-description {
display: block;
color: grey;
}
[hidden] {
display: none;
}
`, `,
]; ];
} }

View File

@ -59,7 +59,7 @@ export class ZHAClustersDataTable extends LitElement {
title: "ID", title: "ID",
template: (id: number) => html` ${formatAsPaddedHex(id)} `, template: (id: number) => html` ${formatAsPaddedHex(id)} `,
sortable: true, sortable: true,
width: "15%", width: "25%",
}, },
endpoint_id: { endpoint_id: {
title: "Endpoint ID", title: "Endpoint ID",

View File

@ -1,195 +0,0 @@
import "@material/mwc-list/mwc-list-item";
import { mdiHelpCircle } from "@mdi/js";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { property, state } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card";
import "../../../../../components/ha-icon-button";
import "../../../../../components/ha-select";
import "../../../../../components/ha-service-description";
import {
Cluster,
fetchClustersForZhaNode,
ZHADevice,
} from "../../../../../data/zha";
import { haStyle } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types";
import "../../../ha-config-section";
import { computeClusterKey } from "./functions";
declare global {
// for fire event
interface HASSDomEvents {
"zha-cluster-selected": {
cluster?: Cluster;
};
}
}
export class ZHAClusters extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property() public isWide?: boolean;
@property() public selectedDevice?: ZHADevice;
@property() public showHelp = false;
@state() private _selectedClusterIndex = -1;
@state() private _clusters: Cluster[] = [];
protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("selectedDevice")) {
this._clusters = [];
this._selectedClusterIndex = -1;
fireEvent(this, "zha-cluster-selected", {
cluster: undefined,
});
this._fetchClustersForZhaNode();
}
super.update(changedProperties);
}
protected render(): TemplateResult {
return html`
<ha-config-section .isWide=${this.isWide}>
<div class="header" slot="header">
<ha-icon-button
class="toggle-help-icon"
@click=${this._onHelpTap}
.path=${mdiHelpCircle}
.label=${this.hass!.localize("ui.common.help")}
>
</ha-icon-button>
</div>
<span slot="introduction">
${this.hass!.localize("ui.panel.config.zha.clusters.introduction")}
</span>
<ha-card class="content">
<div class="node-picker">
<ha-select
.label=${this.hass!.localize(
"ui.panel.config.zha.common.clusters"
)}
class="menu"
.value=${String(this._selectedClusterIndex)}
@selected=${this._selectedClusterChanged}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
>
${this._clusters.map(
(entry, idx) => html`
<mwc-list-item .value=${String(idx)}
>${computeClusterKey(entry)}</mwc-list-item
>
`
)}
</ha-select>
</div>
${this.showHelp
? html`
<div class="help-text">
${this.hass!.localize(
"ui.panel.config.zha.clusters.help_cluster_dropdown"
)}
</div>
`
: ""}
</ha-card>
</ha-config-section>
`;
}
private async _fetchClustersForZhaNode(): Promise<void> {
if (this.hass) {
this._clusters = await fetchClustersForZhaNode(
this.hass,
this.selectedDevice!.ieee
);
this._clusters.sort((a, b) => a.name.localeCompare(b.name));
}
}
private _selectedClusterChanged(event): void {
this._selectedClusterIndex = Number(event.target!.value);
fireEvent(this, "zha-cluster-selected", {
cluster: this._clusters[this._selectedClusterIndex],
});
}
private _onHelpTap(): void {
this.showHelp = !this.showHelp;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
ha-select {
margin-top: 16px;
}
.menu {
width: 100%;
}
.content {
margin-top: 24px;
}
.header {
flex-grow: 1;
}
ha-card {
max-width: 680px;
}
.node-picker {
align-items: center;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.toggle-help-icon {
float: right;
top: -6px;
right: 0;
padding-right: 0px;
color: var(--primary-color);
}
[hidden] {
display: none;
}
.help-text {
color: grey;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 16px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-cluster": ZHAClusters;
}
}
customElements.define("zha-clusters", ZHAClusters);

View File

@ -1,6 +1,4 @@
import "@material/mwc-button/mwc-button";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiHelpCircle } from "@mdi/js";
import { import {
css, css,
CSSResultGroup, CSSResultGroup,
@ -11,26 +9,19 @@ import {
} from "lit"; } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { stopPropagation } from "../../../../../common/dom/stop_propagation"; import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/buttons/ha-progress-button";
import "../../../../../components/ha-card"; import "../../../../../components/ha-card";
import "../../../../../components/ha-icon-button";
import "../../../../../components/ha-select"; import "../../../../../components/ha-select";
import "../../../../../components/ha-service-description";
import { bindDevices, unbindDevices, ZHADevice } from "../../../../../data/zha"; import { bindDevices, unbindDevices, ZHADevice } from "../../../../../data/zha";
import { haStyle } from "../../../../../resources/styles"; import { haStyle } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types"; import { HomeAssistant } from "../../../../../types";
import "../../../ha-config-section";
import { ItemSelectedEvent } from "./types"; import { ItemSelectedEvent } from "./types";
@customElement("zha-device-binding-control") @customElement("zha-device-binding-control")
export class ZHADeviceBindingControl extends LitElement { export class ZHADeviceBindingControl extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) public hass?: HomeAssistant;
@property() public isWide?: boolean; @property() public device?: ZHADevice;
@property() public selectedDevice?: ZHADevice;
@state() private _showHelp = false;
@state() private _bindTargetIndex = -1; @state() private _bindTargetIndex = -1;
@ -38,77 +29,58 @@ export class ZHADeviceBindingControl extends LitElement {
@state() private _deviceToBind?: ZHADevice; @state() private _deviceToBind?: ZHADevice;
@state() private _bindingOperationInProgress = false;
protected updated(changedProperties: PropertyValues): void { protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("selectedDevice")) { if (changedProperties.has("device")) {
this._bindTargetIndex = -1; this._bindTargetIndex = -1;
} }
super.update(changedProperties); super.updated(changedProperties);
} }
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<ha-config-section .isWide=${this.isWide}> <ha-card class="content">
<div class="header" slot="header"> <div class="command-picker">
<span>Device Binding</span> <ha-select
<ha-icon-button label=${this.hass!.localize(
class="toggle-help-icon" "ui.panel.config.zha.device_binding.picker_label"
@click=${this._onHelpTap} )}
.path=${mdiHelpCircle} class="menu"
.label=${this.hass!.localize("ui.common.help")} .value=${String(this._bindTargetIndex)}
@selected=${this._bindTargetIndexChanged}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
> >
</ha-icon-button> ${this.bindableDevices.map(
</div> (device, idx) => html`
<span slot="introduction">Bind and unbind devices.</span> <mwc-list-item .value=${String(idx)}>
${device.user_given_name
<ha-card class="content"> ? device.user_given_name
<div class="command-picker"> : device.name}
<ha-select </mwc-list-item>
label="Bindable Devices"
class="menu"
.value=${String(this._bindTargetIndex)}
@selected=${this._bindTargetIndexChanged}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
>
${this.bindableDevices.map(
(device, idx) => html`
<mwc-list-item .value=${String(idx)}>
${device.user_given_name
? device.user_given_name
: device.name}
</mwc-list-item>
`
)}
</ha-select>
</div>
${this._showHelp
? html`
<div class="helpText">
Select a device to issue a bind command.
</div>
` `
: ""} )}
<div class="card-actions"> </ha-select>
<mwc-button </div>
@click=${this._onBindDevicesClick} <div class="card-actions">
.disabled=${!(this._deviceToBind && this.selectedDevice)} <ha-progress-button
>Bind</mwc-button @click=${this._onBindDevicesClick}
> .disabled=${!(this._deviceToBind && this.device) ||
${this._showHelp this._bindingOperationInProgress}
? html` <div class="helpText">Bind devices.</div> ` >
: ""} ${this.hass!.localize("ui.panel.config.zha.device_binding.bind")}
<mwc-button </ha-progress-button>
@click=${this._onUnbindDevicesClick} <ha-progress-button
.disabled=${!(this._deviceToBind && this.selectedDevice)} @click=${this._onUnbindDevicesClick}
>Unbind</mwc-button .disabled=${!(this._deviceToBind && this.device) ||
> this._bindingOperationInProgress}
${this._showHelp >
? html` <div class="helpText">Unbind devices.</div> ` ${this.hass!.localize("ui.panel.config.zha.device_binding.unbind")}
: ""} </ha-progress-button>
</div> </div>
</ha-card> </ha-card>
</ha-config-section>
`; `;
} }
@ -120,27 +92,41 @@ export class ZHADeviceBindingControl extends LitElement {
: this.bindableDevices[this._bindTargetIndex]; : this.bindableDevices[this._bindTargetIndex];
} }
private _onHelpTap(): void { private async _onBindDevicesClick(ev: CustomEvent): Promise<void> {
this._showHelp = !this._showHelp; const button = ev.currentTarget as any;
} if (this.hass && this._deviceToBind && this.device) {
this._bindingOperationInProgress = true;
private async _onBindDevicesClick(): Promise<void> { button.progress = true;
if (this.hass && this._deviceToBind && this.selectedDevice) { try {
await bindDevices( await bindDevices(this.hass, this.device.ieee, this._deviceToBind.ieee);
this.hass, button.actionSuccess();
this.selectedDevice.ieee, } catch (err: any) {
this._deviceToBind.ieee button.actionError();
); } finally {
this._bindingOperationInProgress = false;
button.progress = false;
}
} }
} }
private async _onUnbindDevicesClick(): Promise<void> { private async _onUnbindDevicesClick(ev: CustomEvent): Promise<void> {
if (this.hass && this._deviceToBind && this.selectedDevice) { const button = ev.currentTarget as any;
await unbindDevices( if (this.hass && this._deviceToBind && this.device) {
this.hass, this._bindingOperationInProgress = true;
this.selectedDevice.ieee, button.progress = true;
this._deviceToBind.ieee try {
); await unbindDevices(
this.hass,
this.device.ieee,
this._deviceToBind.ieee
);
button.actionSuccess();
} catch (err: any) {
button.actionError();
} finally {
this._bindingOperationInProgress = false;
button.progress = false;
}
} }
} }
@ -152,18 +138,6 @@ export class ZHADeviceBindingControl extends LitElement {
width: 100%; width: 100%;
} }
.content {
margin-top: 24px;
}
ha-card {
max-width: 680px;
}
.card-actions.warning ha-call-service-button {
color: var(--error-color);
}
.command-picker { .command-picker {
align-items: center; align-items: center;
padding-left: 28px; padding-left: 28px;
@ -171,33 +145,9 @@ export class ZHADeviceBindingControl extends LitElement {
padding-bottom: 10px; padding-bottom: 10px;
} }
.helpText {
color: grey;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.header { .header {
flex-grow: 1; flex-grow: 1;
} }
.toggle-help-icon {
float: right;
top: -6px;
right: 0;
padding-right: 0px;
color: var(--primary-color);
}
ha-service-description {
display: block;
color: grey;
}
[hidden] {
display: none;
}
`, `,
]; ];
} }

View File

@ -1,12 +1,9 @@
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { computeRTLDirection } from "../../../../../common/util/compute_rtl"; import { computeRTLDirection } from "../../../../../common/util/compute_rtl";
import "../../../../../components/ha-code-editor"; import "../../../../../components/ha-code-editor";
import { createCloseHeading } from "../../../../../components/ha-dialog";
import { haStyleDialog } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types"; import { HomeAssistant } from "../../../../../types";
import { ZHADeviceChildrenDialogParams } from "./show-dialog-zha-device-children";
import "../../../../../components/data-table/ha-data-table"; import "../../../../../components/data-table/ha-data-table";
import type { import type {
DataTableColumnContainer, DataTableColumnContainer,
@ -14,7 +11,6 @@ import type {
} from "../../../../../components/data-table/ha-data-table"; } from "../../../../../components/data-table/ha-data-table";
import "../../../../../components/ha-circular-progress"; import "../../../../../components/ha-circular-progress";
import { fetchDevices, ZHADevice } from "../../../../../data/zha"; import { fetchDevices, ZHADevice } from "../../../../../data/zha";
import { fireEvent } from "../../../../../common/dom/fire_event";
export interface DeviceRowData extends DataTableRowData { export interface DeviceRowData extends DataTableRowData {
id: string; id: string;
@ -22,14 +18,21 @@ export interface DeviceRowData extends DataTableRowData {
lqi: number; lqi: number;
} }
@customElement("dialog-zha-device-children") @customElement("zha-device-children")
class DialogZHADeviceChildren extends LitElement { class ZHADeviceChildren extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _device: ZHADevice | undefined; @property() public device: ZHADevice | undefined;
@state() private _devices: Map<string, ZHADevice> | undefined; @state() private _devices: Map<string, ZHADevice> | undefined;
protected updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (this.hass && changedProperties.has("device")) {
this._fetchData();
}
}
private _deviceChildren = memoizeOne( private _deviceChildren = memoizeOne(
( (
device: ZHADevice | undefined, device: ZHADevice | undefined,
@ -69,70 +72,45 @@ class DialogZHADeviceChildren extends LitElement {
}, },
}; };
public showDialog(params: ZHADeviceChildrenDialogParams): void {
this._device = params.device;
this._fetchData();
}
public closeDialog(): void {
this._device = undefined;
this._devices = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._device) { if (!this.device) {
return html``; return html``;
} }
return html` return html`
<ha-dialog ${!this._devices
hideActions ? html`<ha-circular-progress
open alt="Loading"
@closed=${this.closeDialog} size="large"
.heading=${createCloseHeading( active
this.hass, ></ha-circular-progress>`
this.hass.localize(`ui.dialogs.zha_device_info.device_children`) : html`<ha-data-table
)} .hass=${this.hass}
> .columns=${this._columns}
${!this._devices .data=${this._deviceChildren(this.device, this._devices)}
? html`<ha-circular-progress auto-height
alt="Loading" .dir=${computeRTLDirection(this.hass)}
size="large" .searchLabel=${this.hass.localize(
active "ui.components.data-table.search"
></ha-circular-progress>` )}
: html`<ha-data-table .noDataText=${this.hass.localize(
.hass=${this.hass} "ui.components.data-table.no-data"
.columns=${this._columns} )}
.data=${this._deviceChildren(this._device, this._devices)} ></ha-data-table>`}
auto-height
.dir=${computeRTLDirection(this.hass)}
.searchLabel=${this.hass.localize(
"ui.components.data-table.search"
)}
.noDataText=${this.hass.localize(
"ui.components.data-table.no-data"
)}
></ha-data-table>`}
</ha-dialog>
`; `;
} }
private async _fetchData(): Promise<void> { private async _fetchData(): Promise<void> {
if (this._device && this.hass) { if (this.device && this.hass) {
const devices = await fetchDevices(this.hass!); const devices = await fetchDevices(this.hass!);
this._devices = new Map( this._devices = new Map(
devices.map((device: ZHADevice) => [device.ieee, device]) devices.map((device: ZHADevice) => [device.ieee, device])
); );
} }
} }
static get styles(): CSSResultGroup {
return haStyleDialog;
}
} }
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"dialog-zha-device-children": DialogZHADeviceChildren; "zha-device-children": ZHADeviceChildren;
} }
} }

View File

@ -0,0 +1,47 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../../components/ha-code-editor";
import { ZHADevice } from "../../../../../data/zha";
import { HomeAssistant } from "../../../../../types";
@customElement("zha-device-zigbee-info")
class ZHADeviceZigbeeInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public device: ZHADevice | undefined;
@state() private _signature: any;
protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("device") && this.hass && this.device) {
this._signature = JSON.stringify(
{
...this.device.signature,
manufacturer: this.device.manufacturer,
model: this.device.model,
class: this.device.quirk_class,
},
null,
2
);
}
super.updated(changedProperties);
}
protected render(): TemplateResult {
if (!this._signature) {
return html``;
}
return html`
<ha-code-editor mode="yaml" readOnly .value=${this._signature} dir="ltr">
</ha-code-editor>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-device-zigbee-info": ZHADeviceZigbeeInfo;
}
}

View File

@ -1,5 +1,3 @@
import "@material/mwc-button/mwc-button";
import { mdiHelpCircle } from "@mdi/js";
import { import {
css, css,
CSSResultGroup, CSSResultGroup,
@ -11,37 +9,29 @@ import {
import { customElement, property, state, query } from "lit/decorators"; import { customElement, property, state, query } from "lit/decorators";
import type { HASSDomEvent } from "../../../../../common/dom/fire_event"; import type { HASSDomEvent } from "../../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../../common/dom/stop_propagation"; import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/buttons/ha-progress-button";
import { SelectionChangedEvent } from "../../../../../components/data-table/ha-data-table"; import { SelectionChangedEvent } from "../../../../../components/data-table/ha-data-table";
import "../../../../../components/ha-card"; import "../../../../../components/ha-card";
import "../../../../../components/ha-icon-button";
import "../../../../../components/ha-service-description";
import { import {
bindDeviceToGroup, bindDeviceToGroup,
Cluster, Cluster,
fetchClustersForZhaNode, fetchClustersForZhaDevice,
unbindDeviceFromGroup, unbindDeviceFromGroup,
ZHADevice, ZHADevice,
ZHAGroup, ZHAGroup,
} from "../../../../../data/zha"; } from "../../../../../data/zha";
import { haStyle } from "../../../../../resources/styles"; import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types"; import type { HomeAssistant } from "../../../../../types";
import "../../../ha-config-section";
import { ItemSelectedEvent } from "./types"; import { ItemSelectedEvent } from "./types";
import "./zha-clusters-data-table"; import "./zha-clusters-data-table";
import type { ZHAClustersDataTable } from "./zha-clusters-data-table"; import type { ZHAClustersDataTable } from "./zha-clusters-data-table";
import "@material/mwc-list/mwc-list-item";
@customElement("zha-group-binding-control") @customElement("zha-group-binding-control")
export class ZHAGroupBindingControl extends LitElement { export class ZHAGroupBindingControl extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) public hass?: HomeAssistant;
@property() public isWide?: boolean; @property() public device?: ZHADevice;
@property() public narrow?: boolean;
@property() public selectedDevice?: ZHADevice;
@state() private _showHelp = false;
@state() private _bindTargetIndex = -1; @state() private _bindTargetIndex = -1;
@ -51,6 +41,8 @@ export class ZHAGroupBindingControl extends LitElement {
@state() private _clusters: Cluster[] = []; @state() private _clusters: Cluster[] = [];
@state() private _bindingOperationInProgress = false;
private _groupToBind?: ZHAGroup; private _groupToBind?: ZHAGroup;
private _clustersToBind?: Cluster[]; private _clustersToBind?: Cluster[];
@ -59,38 +51,17 @@ export class ZHAGroupBindingControl extends LitElement {
private _zhaClustersDataTable!: ZHAClustersDataTable; private _zhaClustersDataTable!: ZHAClustersDataTable;
protected updated(changedProperties: PropertyValues): void { protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("selectedDevice")) { if (changedProperties.has("device")) {
this._bindTargetIndex = -1; this._bindTargetIndex = -1;
this._selectedClusters = []; this._selectedClusters = [];
this._clustersToBind = []; this._clustersToBind = [];
this._fetchClustersForZhaNode(); this._fetchClustersForZhaNode();
} }
super.update(changedProperties); super.updated(changedProperties);
} }
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<ha-config-section .isWide=${this.isWide}>
<div class="sectionHeader" slot="header">
<span
>${this.hass!.localize(
"ui.panel.config.zha.group_binding.header"
)}</span
>
<ha-icon-button
class="toggle-help-icon"
@click=${this._onHelpTap}
.path=${mdiHelpCircle}
.label=${this.hass!.localize("ui.common.help")}
>
</ha-icon-button>
</div>
<span slot="introduction"
>${this.hass!.localize(
"ui.panel.config.zha.group_binding.introduction"
)}</span
>
<ha-card class="content"> <ha-card class="content">
<div class="command-picker"> <div class="command-picker">
<ha-select <ha-select
@ -112,66 +83,32 @@ export class ZHAGroupBindingControl extends LitElement {
)} )}
</ha-select> </ha-select>
</div> </div>
${this._showHelp
? html`
<div class="helpText">
${this.hass!.localize(
"ui.panel.config.zha.group_binding.group_picker_help"
)}
</div>
`
: ""}
<div class="command-picker"> <div class="command-picker">
<zha-clusters-data-table <zha-clusters-data-table
.hass=${this.hass} .hass=${this.hass}
.narrow=${this.narrow}
.clusters=${this._clusters} .clusters=${this._clusters}
@selection-changed=${this._handleClusterSelectionChanged} @selection-changed=${this._handleClusterSelectionChanged}
class="menu" class="menu"
></zha-clusters-data-table> ></zha-clusters-data-table>
</div> </div>
${this._showHelp
? html`
<div class="helpText">
${this.hass!.localize(
"ui.panel.config.zha.group_binding.cluster_selection_help"
)}
</div>
`
: ""}
<div class="card-actions"> <div class="card-actions">
<mwc-button <ha-progress-button
@click=${this._onBindGroupClick} @click=${this._onBindGroupClick}
.disabled=${!this._canBind} .disabled=${!this._canBind || this._bindingOperationInProgress}
>${this.hass!.localize( >
"ui.panel.config.zha.group_binding.bind_button_label" ${this.hass!.localize(
)}</mwc-button "ui.panel.config.zha.group_binding.bind_button_label"
> )}
${this._showHelp </ha-progress-button>
? html`
<div class="helpText"> <ha-progress-button
${this.hass!.localize( @click=${this._onUnbindGroupClick}
"ui.panel.config.zha.group_binding.bind_button_help" .disabled=${!this._canBind || this._bindingOperationInProgress}
)} >
</div> ${this.hass!.localize(
` "ui.panel.config.zha.group_binding.unbind_button_label"
: ""} )}
<mwc-button </ha-progress-button>
@click=${this._onUnbindGroupClick}
.disabled=${!this._canBind}
>${this.hass!.localize(
"ui.panel.config.zha.group_binding.unbind_button_label"
)}</mwc-button
>
${this._showHelp
? html`
<div class="helpText">
${this.hass!.localize(
"ui.panel.config.zha.group_binding.unbind_button_help"
)}
</div>
`
: ""}
</div> </div>
</ha-card> </ha-card>
</ha-config-section> </ha-config-section>
@ -186,31 +123,49 @@ export class ZHAGroupBindingControl extends LitElement {
: this.groups[this._bindTargetIndex]; : this.groups[this._bindTargetIndex];
} }
private _onHelpTap(): void { private async _onBindGroupClick(ev: CustomEvent): Promise<void> {
this._showHelp = !this._showHelp; const button = ev.currentTarget as any;
}
private async _onBindGroupClick(): Promise<void> {
if (this.hass && this._canBind) { if (this.hass && this._canBind) {
await bindDeviceToGroup( this._bindingOperationInProgress = true;
this.hass, button.progress = true;
this.selectedDevice!.ieee, try {
this._groupToBind!.group_id, await bindDeviceToGroup(
this._clustersToBind! this.hass,
); this.device!.ieee,
this._zhaClustersDataTable.clearSelection(); this._groupToBind!.group_id,
this._clustersToBind!
);
this._zhaClustersDataTable.clearSelection();
button.actionSuccess();
} catch (err: any) {
button.actionError();
} finally {
this._bindingOperationInProgress = false;
button.progress = false;
}
} }
} }
private async _onUnbindGroupClick(): Promise<void> { private async _onUnbindGroupClick(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
if (this.hass && this._canBind) { if (this.hass && this._canBind) {
await unbindDeviceFromGroup( this._bindingOperationInProgress = true;
this.hass, button.progress = true;
this.selectedDevice!.ieee, try {
this._groupToBind!.group_id, await unbindDeviceFromGroup(
this._clustersToBind! this.hass,
); this.device!.ieee,
this._zhaClustersDataTable.clearSelection(); this._groupToBind!.group_id,
this._clustersToBind!
);
this._zhaClustersDataTable.clearSelection();
button.actionSuccess();
} catch (err: any) {
button.actionError();
} finally {
this._bindingOperationInProgress = false;
button.progress = false;
}
} }
} }
@ -230,9 +185,9 @@ export class ZHAGroupBindingControl extends LitElement {
private async _fetchClustersForZhaNode(): Promise<void> { private async _fetchClustersForZhaNode(): Promise<void> {
if (this.hass) { if (this.hass) {
this._clusters = await fetchClustersForZhaNode( this._clusters = await fetchClustersForZhaDevice(
this.hass, this.hass,
this.selectedDevice!.ieee this.device!.ieee
); );
this._clusters = this._clusters this._clusters = this._clusters
.filter((cluster) => cluster.type.toLowerCase() === "out") .filter((cluster) => cluster.type.toLowerCase() === "out")
@ -245,7 +200,7 @@ export class ZHAGroupBindingControl extends LitElement {
this._groupToBind && this._groupToBind &&
this._clustersToBind && this._clustersToBind &&
this._clustersToBind?.length > 0 && this._clustersToBind?.length > 0 &&
this.selectedDevice this.device
); );
} }
@ -257,18 +212,6 @@ export class ZHAGroupBindingControl extends LitElement {
width: 100%; width: 100%;
} }
.content {
margin-top: 24px;
}
ha-card {
max-width: 680px;
}
.card-actions.warning ha-call-service-button {
color: var(--error-color);
}
.command-picker { .command-picker {
align-items: center; align-items: center;
padding-left: 28px; padding-left: 28px;
@ -285,30 +228,6 @@ export class ZHAGroupBindingControl extends LitElement {
.sectionHeader { .sectionHeader {
flex-grow: 1; flex-grow: 1;
} }
.helpText {
color: grey;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.toggle-help-icon {
float: right;
top: -6px;
right: 0;
padding-right: 0px;
color: var(--primary-color);
}
ha-service-description {
display: block;
color: grey;
}
[hidden] {
display: none;
}
`, `,
]; ];
} }

View File

@ -0,0 +1,198 @@
import "@material/mwc-list/mwc-list-item";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { cache } from "lit/directives/cache";
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import "../../../../../components/ha-card";
import "../../../../../components/ha-select";
import {
Cluster,
fetchClustersForZhaDevice,
ZHADevice,
} from "../../../../../data/zha";
import { haStyle } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types";
import { computeClusterKey } from "./functions";
import "@material/mwc-tab-bar/mwc-tab-bar";
import "@material/mwc-tab/mwc-tab";
import "./zha-cluster-attributes";
import "./zha-cluster-commands";
declare global {
// for fire event
interface HASSDomEvents {
"zha-cluster-selected": {
cluster?: Cluster;
};
}
}
const tabs = ["attributes", "commands"] as const;
@customElement("zha-manage-clusters")
export class ZHAManageClusters extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public isWide?: boolean;
@property() public device?: ZHADevice;
@state() private _selectedClusterIndex = -1;
@state() private _clusters: Cluster[] = [];
@state() private _selectedCluster?: Cluster;
@state() private _currTab: typeof tabs[number] = "attributes";
@state() private _clustersLoaded = false;
protected willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
if (!this.device) {
return;
}
if (!tabs.includes(this._currTab)) {
this._currTab = tabs[0];
}
}
protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("device")) {
this._clusters = [];
this._selectedClusterIndex = -1;
this._clustersLoaded = false;
this._fetchClustersForZhaDevice();
}
super.updated(changedProperties);
}
protected render(): TemplateResult {
if (!this.device || !this._clustersLoaded) {
return html``;
}
return html`
<ha-card class="content">
<div class="node-picker">
<ha-select
.label=${this.hass!.localize("ui.panel.config.zha.common.clusters")}
class="menu"
.value=${String(this._selectedClusterIndex)}
@selected=${this._selectedClusterChanged}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
>
${this._clusters.map(
(entry, idx) => html`
<mwc-list-item .value=${String(idx)}
>${computeClusterKey(entry)}</mwc-list-item
>
`
)}
</ha-select>
</div>
${this._selectedCluster
? html`
<mwc-tab-bar
.activeIndex=${tabs.indexOf(this._currTab)}
@MDCTabBar:activated=${this._handleTabChanged}
>
${tabs.map(
(tab) => html`
<mwc-tab
.label=${this.hass.localize(
`ui.panel.config.zha.clusters.tabs.${tab}`
)}
></mwc-tab>
`
)}
</mwc-tab-bar>
<div class="content" tabindex="-1" dialogInitialFocus>
${cache(
this._currTab === "attributes"
? html`
<zha-cluster-attributes
.hass=${this.hass}
.device=${this.device}
.selectedCluster=${this._selectedCluster}
></zha-cluster-attributes>
`
: html`
<zha-cluster-commands
.hass=${this.hass}
.device=${this.device}
.selectedCluster=${this._selectedCluster}
></zha-cluster-commands>
`
)}
</div>
`
: ""}
</ha-card>
`;
}
private async _fetchClustersForZhaDevice(): Promise<void> {
if (this.hass) {
this._clusters = await fetchClustersForZhaDevice(
this.hass,
this.device!.ieee
);
this._clusters.sort((a, b) => a.name.localeCompare(b.name));
if (this._clusters.length > 0) {
this._selectedClusterIndex = 0;
this._selectedCluster = this._clusters[0];
}
this._clustersLoaded = true;
}
}
private _handleTabChanged(ev: CustomEvent): void {
const newTab = tabs[ev.detail.index];
if (newTab === this._currTab) {
return;
}
this._currTab = newTab;
}
private _selectedClusterChanged(event): void {
this._selectedClusterIndex = Number(event.target!.value);
this._selectedCluster = this._clusters[this._selectedClusterIndex];
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
ha-select {
margin-top: 16px;
}
.menu {
width: 100%;
}
.header {
flex-grow: 1;
}
.node-picker {
align-items: center;
padding-bottom: 10px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-manage-clusters": ZHAManageClusters;
}
}

View File

@ -1002,6 +1002,15 @@
"attribute": "Attribute", "attribute": "Attribute",
"min_max_change": "min/max/change" "min_max_change": "min/max/change"
}, },
"zha_manage_device": {
"heading": "Manage Zigbee Device",
"tabs": {
"clusters": "Clusters",
"bindings": "Bindings",
"signature": "Signature",
"children": "Children"
}
},
"zha_device_info": { "zha_device_info": {
"manuf": "by {manufacturer}", "manuf": "by {manufacturer}",
"no_area": "No Area", "no_area": "No Area",
@ -1010,10 +1019,8 @@
"buttons": { "buttons": {
"add": "Add devices via this device", "add": "Add devices via this device",
"remove": "Remove", "remove": "Remove",
"clusters": "Manage clusters", "manage": "Manage zigbee device",
"reconfigure": "Reconfigure", "reconfigure": "Reconfigure",
"zigbee_information": "Zigbee signature",
"device_children": "View children",
"view_network": "View network" "view_network": "View network"
}, },
"services": { "services": {
@ -3078,17 +3085,17 @@
"clusters": { "clusters": {
"header": "Clusters", "header": "Clusters",
"help_cluster_dropdown": "Select a cluster to view attributes and commands.", "help_cluster_dropdown": "Select a cluster to view attributes and commands.",
"introduction": "Clusters are the building blocks for Zigbee functionality. They separate functionality into logical units. There are client and server types and that are comprised of attributes and commands." "tabs": {
"attributes": "Attributes",
"commands": "Commands"
}
}, },
"cluster_attributes": { "cluster_attributes": {
"header": "Cluster Attributes", "header": "Cluster Attributes",
"introduction": "View and edit cluster attributes.", "introduction": "View and edit cluster attributes.",
"attributes_of_cluster": "Attributes of the selected cluster", "attributes_of_cluster": "Attributes of the selected cluster",
"get_zigbee_attribute": "Get Zigbee Attribute", "read_zigbee_attribute": "Read Attribute",
"set_zigbee_attribute": "Set Zigbee Attribute", "write_zigbee_attribute": "Write Attribute"
"help_attribute_dropdown": "Select an attribute to view or set its value.",
"help_get_zigbee_attribute": "Get the value for the selected attribute.",
"help_set_zigbee_attribute": "Set attribute value for the specified cluster on the specified entity."
}, },
"cluster_commands": { "cluster_commands": {
"header": "Cluster Commands", "header": "Cluster Commands",
@ -3138,6 +3145,11 @@
"enable_physics": "Enable Physics", "enable_physics": "Enable Physics",
"refresh_topology": "Refresh Topology" "refresh_topology": "Refresh Topology"
}, },
"device_binding": {
"bind": "Bind",
"unbind": "Unbind",
"picker_label": "Bindable Devices"
},
"group_binding": { "group_binding": {
"header": "Group Binding", "header": "Group Binding",
"introduction": "Bind and unbind groups.", "introduction": "Bind and unbind groups.",