Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow GET/SET custom config param in Z-Wave device configuration #22364

Open
wants to merge 7 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/data/zwave_js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,15 @@ export interface ZWaveJSSetConfigParamData {
value: string | number;
}

export interface ZWaveJSSetRawConfigParamData {
type: string;
device_id: string;
property: number;
value: number;
value_size: number;
value_format: number;
}

export interface ZWaveJSSetConfigParamResult {
value_id?: string;
status?: string;
Expand Down Expand Up @@ -677,6 +686,36 @@ export const setZwaveNodeConfigParameter = (
return hass.callWS(data);
};

export const setZwaveNodeRawConfigParameter = (
hass: HomeAssistant,
device_id: string,
property: number,
value: number,
value_size: number,
value_format: number
): Promise<ZWaveJSSetConfigParamResult> => {
const data: ZWaveJSSetRawConfigParamData = {
type: "zwave_js/set_raw_config_parameter",
device_id,
property,
value,
value_size,
value_format,
};
return hass.callWS(data);
};

export const getZwaveNodeRawConfigParameter = (
hass: HomeAssistant,
device_id: string,
property: number
): Promise<number> =>
hass.callWS({
type: "zwave_js/get_raw_config_parameter",
device_id,
property,
});

export const reinterviewZwaveNode = (
hass: HomeAssistant,
device_id: string,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
import { LitElement, html, css, type CSSResultGroup, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { mdiCloseCircle } from "@mdi/js";
import "../../../../../components/ha-textfield";
import "../../../../../components/ha-select";
import "../../../../../components/ha-button";
import "../../../../../components/ha-circular-progress";
import "../../../../../components/ha-list-item";
import type { HomeAssistant } from "../../../../../types";
import {
getZwaveNodeRawConfigParameter,
setZwaveNodeRawConfigParameter,
} from "../../../../../data/zwave_js";

@customElement("zwave_js-custom-param")
wendevlin marked this conversation as resolved.
Show resolved Hide resolved
class ZWaveJSCustomParam extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;

@property() public deviceId!: string;

@state() private _customParamNumber?: number;

@state() private _valueSize = 1;

@state() private _value?: number;

@state() private _valueFormat = 0;

@state() private _isLoading = false;

@state() private _error = "";

protected render() {
return html`
<div class="custom-config-form">
<ha-textfield
.label=${this.hass.localize(
"ui.panel.config.zwave_js.node_config.parameter"
)}
.value=${this._customParamNumber ?? ""}
@input=${this._customParamNumberChanged}
type="number"
></ha-textfield>
<ha-select
.label=${this.hass.localize(
"ui.panel.config.zwave_js.node_config.size"
)}
.value=${String(this._valueSize)}
@selected=${this._customValueSizeChanged}
>
<ha-list-item value="1">1</ha-list-item>
<ha-list-item value="2">2</ha-list-item>
<ha-list-item value="4">4</ha-list-item>
</ha-select>
<ha-textfield
.label=${this.hass.localize(
"ui.panel.config.zwave_js.node_config.value"
)}
.value=${this._value ?? ""}
@input=${this._customValueChanged}
type="number"
></ha-textfield>
<ha-select
.label=${this.hass.localize(
"ui.panel.config.zwave_js.node_config.format"
)}
.value=${String(this._valueFormat)}
@selected=${this._customValueFormatChanged}
>
<ha-list-item value="0"
>${this.hass.localize(
"ui.panel.config.zwave_js.node_config.signed"
)}</ha-list-item
>
<ha-list-item value="1"
>${this.hass.localize(
"ui.panel.config.zwave_js.node_config.unsigned"
)}</ha-list-item
>
<ha-list-item value="2"
>${this.hass.localize(
"ui.panel.config.zwave_js.node_config.enumerated"
)}</ha-list-item
>
<ha-list-item value="3"
>${this.hass.localize(
"ui.panel.config.zwave_js.node_config.bitfield"
)}</ha-list-item
>
</ha-select>
</div>
<div class="custom-config-buttons">
${this._isLoading
? html`<ha-circular-progress indeterminate></ha-circular-progress>`
: nothing}
<ha-button @click=${this._getCustomConfigValue}>
${this.hass.localize(
"ui.panel.config.zwave_js.node_config.get_value"
)}
</ha-button>
<ha-button @click=${this._setCustomConfigValue}>
${this.hass.localize(
"ui.panel.config.zwave_js.node_config.set_value"
)}
</ha-button>
</div>
<div class="error">
${this._error
? html`<ha-svg-icon
.path=${mdiCloseCircle}
class="error-icon"
slot="item-icon"
></ha-svg-icon
><em>${this._error}</em>`
: nothing}
</div>
`;
}

private _customParamNumberChanged(ev: Event) {
this._customParamNumber =
Number((ev.target as HTMLInputElement).value) || undefined;
}

private _customValueSizeChanged(ev: Event) {
this._valueSize = Number((ev.target as HTMLSelectElement).value) || 1;
}

private _customValueChanged(ev: Event) {
this._value = Number((ev.target as HTMLInputElement).value) || undefined;
}

private _customValueFormatChanged(ev: Event) {
this._valueFormat = Number((ev.target as HTMLSelectElement).value) || 0;
}

private async _getCustomConfigValue() {
if (this._customParamNumber === undefined) {
this._error = this.hass.localize(
"ui.panel.config.zwave_js.node_config.error_required",
{
entity: this.hass.localize(
"ui.panel.config.zwave_js.node_config.parameter"
),
}
);
return;
}
this._error = "";
this._isLoading = true;
try {
const value = await getZwaveNodeRawConfigParameter(
this.hass,
this.deviceId,
this._customParamNumber
);
this._value = value;
} catch (err: any) {
this._error = err?.message || "Unknown error";
} finally {
this._isLoading = false;
}
}

private async _setCustomConfigValue() {
if (this._customParamNumber === undefined) {
this._error = this.hass.localize(
"ui.panel.config.zwave_js.node_config.error_required",
{
entity: this.hass.localize(
"ui.panel.config.zwave_js.node_config.parameter"
),
}
);
return;
}
if (this._value === undefined) {
this._error = this.hass.localize(
"ui.panel.config.zwave_js.node_config.error_required",
{
entity: this.hass.localize(
"ui.panel.config.zwave_js.node_config.value"
),
}
);
return;
}
this._error = "";
this._isLoading = true;
try {
await setZwaveNodeRawConfigParameter(
this.hass,
this.deviceId,
this._customParamNumber,
this._value,
this._valueSize,
this._valueFormat
);
} catch (err: any) {
this._error = err?.message || "Unknown error";
} finally {
this._isLoading = false;
}
}

static get styles(): CSSResultGroup {
return css`
.custom-config-form {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin-bottom: 8px;
}

ha-textfield,
ha-select {
flex-grow: 1;
flex-basis: calc(50% - 8px);
min-width: 120px;
}

@media (min-width: 681px) {
.custom-config-form {
flex-wrap: nowrap;
}

ha-textfield,
ha-select {
flex-basis: 0;
}
}

MindFreeze marked this conversation as resolved.
Show resolved Hide resolved
.custom-config-buttons {
display: flex;
justify-content: flex-end;
align-items: center;
}

.error {
color: var(--error-color);
}

.error-icon {
margin-right: 8px;
}
`;
}
}

declare global {
interface HTMLElementTagNameMap {
"zwave_js-custom-param": ZWaveJSCustomParam;
MindFreeze marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section";
import { configTabs } from "./zwave_js-config-router";
import "./zwave_js-custom-param";

const icons = {
accepted: mdiCheckCircle,
Expand Down Expand Up @@ -174,6 +175,22 @@ class ZWaveJSNodeConfig extends LitElement {
</ha-card>
</div>`
)}
<h3>
${this.hass.localize(
"ui.panel.config.zwave_js.node_config.custom_config"
)}
</h3>
<span class="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.node_config.custom_config_description"
)}
</span>
<ha-card class="custom-config">
<zwave_js-custom-param
.hass=${this.hass}
.deviceId=${this.deviceId}
></zwave_js-custom-param>
MindFreeze marked this conversation as resolved.
Show resolved Hide resolved
</ha-card>
</ha-config-section>
</hass-tabs-subpage>
`;
Expand Down Expand Up @@ -520,6 +537,10 @@ class ZWaveJSNodeConfig extends LitElement {
.switch {
text-align: right;
}

.custom-config {
padding: 16px;
}
`,
];
}
Expand Down
14 changes: 13 additions & 1 deletion src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4958,13 +4958,25 @@
"parameter_is_read_only": "This parameter is read-only.",
"between_min_max": "Between {min} and {max}",
"error_not_in_range": "Value must be between {min} and {max}",
"error_required": "{entity} is required",
"error_device_not_found": "Device not found",
"set_param_accepted": "The parameter has been updated.",
"set_param_queued": "The parameter change has been queued, and will be updated when the device wakes up.",
"set_param_error": "An error occurred.",
"parameter": "Parameter",
"bitmask": "Bitmask",
"default": "Default"
"size": "Size",
"value": "Value",
"format": "Format",
"default": "Default",
"custom_config": "Custom configuration",
"custom_config_description": "You can use this section to get/set custom configuration parameters for your device. This is useful when working with devices that are not fully supported.",
"get_value": "Get value",
"set_value": "Set value",
"signed": "Signed",
"unsigned": "Unsigned",
"enumerated": "Enumerated",
"bitfield": "Bitfield"
},
"network_status": {
"connected": "Connected",
Expand Down
Loading