diff --git a/src/data/zwave_js.ts b/src/data/zwave_js.ts index 8cc704db2abb..dc4b835d3815 100644 --- a/src/data/zwave_js.ts +++ b/src/data/zwave_js.ts @@ -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; @@ -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 => { + 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 => + hass.callWS({ + type: "zwave_js/get_raw_config_parameter", + device_id, + property, + }); + export const reinterviewZwaveNode = ( hass: HomeAssistant, device_id: string, diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-custom-param.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-custom-param.ts new file mode 100644 index 000000000000..2bfba45a42e6 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-custom-param.ts @@ -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") +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` +
+ + + 1 + 2 + 4 + + + + ${this.hass.localize( + "ui.panel.config.zwave_js.node_config.signed" + )} + ${this.hass.localize( + "ui.panel.config.zwave_js.node_config.unsigned" + )} + ${this.hass.localize( + "ui.panel.config.zwave_js.node_config.enumerated" + )} + ${this.hass.localize( + "ui.panel.config.zwave_js.node_config.bitfield" + )} + +
+
+ ${this._isLoading + ? html`` + : nothing} + + ${this.hass.localize( + "ui.panel.config.zwave_js.node_config.get_value" + )} + + + ${this.hass.localize( + "ui.panel.config.zwave_js.node_config.set_value" + )} + +
+
+ ${this._error + ? html`${this._error}` + : nothing} +
+ `; + } + + 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; + } + } + + .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; + } +} diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts index 7f3336854c4f..6ae6654b4181 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts @@ -40,6 +40,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"; import { showConfirmationDialog } from "../../../../../dialogs/generic/show-dialog-box"; import { fireEvent } from "../../../../../common/dom/fire_event"; @@ -193,6 +194,22 @@ class ZWaveJSNodeConfig extends LitElement { )} +

+ ${this.hass.localize( + "ui.panel.config.zwave_js.node_config.custom_config" + )} +

+ + ${this.hass.localize( + "ui.panel.config.zwave_js.node_config.custom_config_description" + )} + + + + `; @@ -600,6 +617,10 @@ class ZWaveJSNodeConfig extends LitElement { text-align: right; } + .custom-config { + padding: 16px; + } + .reset { display: flex; justify-content: flex-end; diff --git a/src/translations/en.json b/src/translations/en.json index b72a7a54d118..be57c6213908 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4959,12 +4959,24 @@ "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", + "size": "Size", + "value": "Value", + "format": "Format", + "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", "reset_to_default": { "button_label": "Reset to default configuration", "dialog": {