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

Merged
merged 10 commits into from
Nov 11, 2024
Merged
39 changes: 39 additions & 0 deletions src/data/zwave_js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,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 @@ -638,6 +647,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,219 @@
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 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 _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">Signed</ha-list-item>
<ha-list-item value="1">Unsigned</ha-list-item>
<ha-list-item value="2">Enumerated</ha-list-item>
<ha-list-item value="3">Bitfield</ha-list-item>
MindFreeze marked this conversation as resolved.
Show resolved Hide resolved
</ha-select>
</div>
<div class="custom-config-buttons">
<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 = "";
try {
const value = await getZwaveNodeRawConfigParameter(
this.hass,
this.deviceId,
this._customParamNumber
);
this._value = value;
} catch (err: any) {
this._error = err?.message || "Unknown error";
}
}

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 = "";
try {
await setZwaveNodeRawConfigParameter(
this.hass,
this.deviceId,
this._customParamNumber,
this._value,
this._valueSize,
this._valueFormat
);
} catch (err: any) {
this._error = err?.message || "Unknown error";
}
}

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

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
.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 @@ -43,6 +43,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 @@ -179,6 +180,17 @@ class ZWaveJSNodeConfig extends LitElement {
</ha-card>
</div>`
)}
<h3>
${this.hass.localize(
"ui.panel.config.zwave_js.node_config.custom_config"
)}
</h3>
<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 @@ -525,6 +537,10 @@ class ZWaveJSNodeConfig extends LitElement {
.switch {
text-align: right;
}

.custom-config {
padding: 16px;
}
`,
];
}
Expand Down
9 changes: 8 additions & 1 deletion src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4935,13 +4935,20 @@
"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",
"get_value": "Get value",
"set_value": "Set value"
},
"network_status": {
"connected": "Connected",
Expand Down
Loading