From 19d721f193bb97d9fa45a3d73ce60c8e8c8df15f Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Wed, 22 Feb 2023 11:54:53 -0500 Subject: [PATCH] Add support for zwave_js controller firmware updates (#15515) --- src/data/zwave_js.ts | 28 ++++- .../zwave_js/device-actions.ts | 111 ++++++++++-------- .../dialog-zwave_js-update-firmware-node.ts | 74 +++++++----- src/translations/en.json | 11 +- 4 files changed, 140 insertions(+), 84 deletions(-) diff --git a/src/data/zwave_js.ts b/src/data/zwave_js.ts index b6ad8ce800..2b0b88d8cc 100644 --- a/src/data/zwave_js.ts +++ b/src/data/zwave_js.ts @@ -108,7 +108,7 @@ enum RFRegion { "Default (EU)" = 0xff, } -export enum FirmwareUpdateStatus { +export enum NodeFirmwareUpdateStatus { Error_Timeout = -1, Error_Checksum = 0, Error_TransmissionFailed = 1, @@ -124,6 +124,19 @@ export enum FirmwareUpdateStatus { OK_RestartPending = 0xff, } +export enum ControllerFirmwareUpdateStatus { + // An expected response was not received from the controller in time + Error_Timeout = 0, + /** The maximum number of retry attempts for a firmware fragments were reached */ + Error_RetryLimitReached, + /** The update was aborted by the bootloader */ + Error_Aborted, + /** This controller does not support firmware updates */ + Error_NotSupported, + + OK = 0xff, +} + export interface QRProvisioningInformation { version: QRCodeVersion; securityClasses: SecurityClass[]; @@ -322,7 +335,7 @@ export interface ZWaveJSNodeStatusUpdatedMessage { status: NodeStatus; } -export interface ZWaveJSNodeFirmwareUpdateProgressMessage { +export interface ZWaveJSFirmwareUpdateProgressMessage { event: "firmware update progress"; current_file: number; total_files: number; @@ -333,12 +346,18 @@ export interface ZWaveJSNodeFirmwareUpdateProgressMessage { export interface ZWaveJSNodeFirmwareUpdateFinishedMessage { event: "firmware update finished"; - status: FirmwareUpdateStatus; + status: NodeFirmwareUpdateStatus; success: boolean; wait_time?: number; reinterview: boolean; } +export interface ZWaveJSControllerFirmwareUpdateFinishedMessage { + event: "firmware update finished"; + status: ControllerFirmwareUpdateStatus; + success: boolean; +} + export type ZWaveJSNodeFirmwareUpdateCapabilities = | { firmware_upgradable: false } | { @@ -760,8 +779,9 @@ export const subscribeZwaveNodeFirmwareUpdate = ( device_id: string, callbackFunction: ( message: + | ZWaveJSFirmwareUpdateProgressMessage + | ZWaveJSControllerFirmwareUpdateFinishedMessage | ZWaveJSNodeFirmwareUpdateFinishedMessage - | ZWaveJSNodeFirmwareUpdateProgressMessage ) => void ): Promise => hass.connection.subscribeMessage( diff --git a/src/panels/config/devices/device-detail/integration-elements/zwave_js/device-actions.ts b/src/panels/config/devices/device-detail/integration-elements/zwave_js/device-actions.ts index a6fd80387f..ff37acf6f9 100644 --- a/src/panels/config/devices/device-detail/integration-elements/zwave_js/device-actions.ts +++ b/src/panels/config/devices/device-detail/integration-elements/zwave_js/device-actions.ts @@ -43,59 +43,68 @@ export const getZwaveDeviceActions = async ( const nodeStatus = await fetchZwaveNodeStatus(hass, device.id); - if (!nodeStatus || nodeStatus.is_controller_node) { + if (!nodeStatus) { return []; } - const actions = [ - { - label: hass.localize( - "ui.panel.config.zwave_js.device_info.device_config" - ), - icon: mdiCog, - href: `/config/zwave_js/node_config/${device.id}?config_entry=${entryId}`, - }, - { - label: hass.localize( - "ui.panel.config.zwave_js.device_info.reinterview_device" - ), - icon: mdiChatQuestion, - action: () => - showZWaveJSReinterviewNodeDialog(el, { - device_id: device.id, - }), - }, - { - label: hass.localize("ui.panel.config.zwave_js.device_info.heal_node"), - icon: mdiHospitalBox, - action: () => - showZWaveJSHealNodeDialog(el, { - device, - }), - }, - { - label: hass.localize( - "ui.panel.config.zwave_js.device_info.remove_failed" - ), - icon: mdiDeleteForever, - action: () => - showZWaveJSRemoveFailedNodeDialog(el, { - device_id: device.id, - }), - }, - { - label: hass.localize( - "ui.panel.config.zwave_js.device_info.node_statistics" - ), - icon: mdiInformation, - action: () => - showZWaveJSNodeStatisticsDialog(el, { - device, - }), - }, - ]; + const actions: DeviceAction[] = []; - if (!nodeStatus.ready || !nodeStatus.has_firmware_update_cc) { + if (!nodeStatus.is_controller_node) { + actions.push( + { + label: hass.localize( + "ui.panel.config.zwave_js.device_info.device_config" + ), + icon: mdiCog, + href: `/config/zwave_js/node_config/${device.id}?config_entry=${entryId}`, + }, + { + label: hass.localize( + "ui.panel.config.zwave_js.device_info.reinterview_device" + ), + icon: mdiChatQuestion, + action: () => + showZWaveJSReinterviewNodeDialog(el, { + device_id: device.id, + }), + }, + { + label: hass.localize("ui.panel.config.zwave_js.device_info.heal_node"), + icon: mdiHospitalBox, + action: () => + showZWaveJSHealNodeDialog(el, { + device, + }), + }, + { + label: hass.localize( + "ui.panel.config.zwave_js.device_info.remove_failed" + ), + icon: mdiDeleteForever, + action: () => + showZWaveJSRemoveFailedNodeDialog(el, { + device_id: device.id, + }), + }, + { + label: hass.localize( + "ui.panel.config.zwave_js.device_info.node_statistics" + ), + icon: mdiInformation, + action: () => + showZWaveJSNodeStatisticsDialog(el, { + device, + }), + } + ); + } + + if ( + !( + nodeStatus.ready && + (nodeStatus.is_controller_node || nodeStatus.has_firmware_update_cc) + ) + ) { return actions; } @@ -117,7 +126,9 @@ export const getZwaveDeviceActions = async ( (await fetchZwaveIsNodeFirmwareUpdateInProgress(hass, device.id)) || (await showConfirmationDialog(el, { text: hass.localize( - "ui.panel.config.zwave_js.update_firmware.warning" + `ui.panel.config.zwave_js.update_firmware.${ + nodeStatus.is_controller_node ? "warning_controller" : "warning" + }` ), dismissText: hass.localize("ui.common.no"), confirmText: hass.localize("ui.common.yes"), diff --git a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-update-firmware-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-update-firmware-node.ts index 4c8b194b92..d58c1822b1 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-update-firmware-node.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-update-firmware-node.ts @@ -15,17 +15,19 @@ import { } from "../../../../../data/device_registry"; import { abortZwaveNodeFirmwareUpdate, + ControllerFirmwareUpdateStatus, fetchZwaveIsNodeFirmwareUpdateInProgress, fetchZwaveNodeStatus, - FirmwareUpdateStatus, + NodeFirmwareUpdateStatus, NodeStatus, subscribeZwaveNodeStatus, subscribeZwaveNodeFirmwareUpdate, uploadFirmwareAndBeginUpdate, ZWaveJSNodeFirmwareUpdateFinishedMessage, - ZWaveJSNodeFirmwareUpdateProgressMessage, + ZWaveJSFirmwareUpdateProgressMessage, ZWaveJSNodeStatusUpdatedMessage, ZWaveJSNodeStatus, + ZWaveJSControllerFirmwareUpdateFinishedMessage, } from "../../../../../data/zwave_js"; import { haStyleDialog } from "../../../../../resources/styles"; import { HomeAssistant } from "../../../../../types"; @@ -44,10 +46,12 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement { @state() private _uploading = false; @state() - private _updateFinishedMessage?: ZWaveJSNodeFirmwareUpdateFinishedMessage; + private _updateFinishedMessage?: + | ZWaveJSNodeFirmwareUpdateFinishedMessage + | ZWaveJSControllerFirmwareUpdateFinishedMessage; @state() - private _updateProgressMessage?: ZWaveJSNodeFirmwareUpdateProgressMessage; + private _updateProgressMessage?: ZWaveJSFirmwareUpdateProgressMessage; @state() private _updateInProgress = false; @@ -71,12 +75,11 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement { public closeDialog(): void { this._unsubscribeNodeFirmwareUpdate(); this._unsubscribeNodeStatus(); - this.device = - this._updateProgressMessage = - this._updateFinishedMessage = - this._firmwareFile = - this._nodeStatus = - undefined; + this.device = undefined; + this._updateProgressMessage = undefined; + this._updateFinishedMessage = undefined; + this._firmwareFile = undefined; + this._nodeStatus = undefined; this._uploading = this._updateInProgress = false; fireEvent(this, "dialog-closed", { dialog: this.localName }); @@ -111,18 +114,26 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement { )} `; - const abortFirmwareUpdateButton = html` - - ${this.hass.localize("ui.panel.config.zwave_js.update_firmware.abort")} - - `; - const status = this._updateFinishedMessage - ? FirmwareUpdateStatus[this._updateFinishedMessage.status] - .split("_")[0] - .toLowerCase() + ? this._updateFinishedMessage.success + ? "success" + : "error" : undefined; + const localizationKeySuffix = this._nodeStatus.is_controller_node + ? "_controller" + : ""; + + const abortFirmwareUpdateButton = this._nodeStatus.is_controller_node + ? html`` + : html` + + ${this.hass.localize( + "ui.panel.config.zwave_js.update_firmware.abort" + )} + + `; + return html` ${this.hass.localize( - "ui.panel.config.zwave_js.update_firmware.introduction", + `ui.panel.config.zwave_js.update_firmware.introduction${localizationKeySuffix}`, { device: html`${this._deviceName}`, } @@ -210,7 +221,9 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement { : html`
@@ -221,9 +234,13 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement { device: html`${this._deviceName}`, message: this.hass.localize( `ui.panel.config.zwave_js.update_firmware.finished_status.${ - FirmwareUpdateStatus[ - this._updateFinishedMessage!.status - ] + this._nodeStatus.is_controller_node + ? ControllerFirmwareUpdateStatus[ + this._updateFinishedMessage!.status + ] + : NodeFirmwareUpdateStatus[ + this._updateFinishedMessage!.status + ] }` ), } @@ -231,10 +248,10 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement {

- ${status === "ok" + ${this._updateFinishedMessage!.success ? html`

${this.hass.localize( - "ui.panel.config.zwave_js.update_firmware.finished_status.done" + `ui.panel.config.zwave_js.update_firmware.finished_status.done${localizationKeySuffix}` )}

` : html`

@@ -345,8 +362,9 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement { this.device.id, ( message: + | ZWaveJSFirmwareUpdateProgressMessage + | ZWaveJSControllerFirmwareUpdateFinishedMessage | ZWaveJSNodeFirmwareUpdateFinishedMessage - | ZWaveJSNodeFirmwareUpdateProgressMessage ) => { if (message.event === "firmware update progress") { if (!this._updateFinishedMessage) { @@ -378,7 +396,7 @@ class DialogZWaveJSUpdateFirmwareNode extends LitElement { return [ haStyleDialog, css` - .ok { + .success { color: var(--success-color); } diff --git a/src/translations/en.json b/src/translations/en.json index 86166dd30e..b2d36c0337 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3688,7 +3688,9 @@ "update_firmware": { "title": "Update Device Firmware", "warning": "WARNING: Firmware updates can brick your device if you do not correctly follow the manufacturer's guidance. The Home Assistant and Z-Wave JS teams do not take any responsibility for any damages to your device as a result of the firmware update and will not be able to help you if you brick your device. Would you still like to continue?", + "warning_controller": "WARNING: Firmware updates can brick your controller if you do not use the right firmware files, or if you attempt to stop the firmware update before it completes. The Home Assistant and Z-Wave JS teams do not take any responsibility for any damages to your controller as a result of the firmware update and will not be able to help you if you brick your controller. Would you still like to continue?", "introduction": "Select the firmware file you would like to use to update {device}.", + "introduction_controller": "Select the firmware file you would like to use to update {device}. Note that once you start a firmware update, you MUST wait for the update to complete.", "upload_firmware": "Upload Firmware", "upload_failed": "Upload Failed", "begin_update": "Begin Firmware Update", @@ -3701,10 +3703,11 @@ "abort_failed": "Abort Failed", "confirm_abort": "Are you sure you want to abort the firmware update on {device}?", "finished_status": { - "ok": "Successfully updated firmware on {device}: {message}.", + "success": "Successfully updated firmware on {device}: {message}.", "error": "Unable to update firmware on {device}: {message}.", "try_again": "To attempt the firmware update again, select the new firmware file you would like to use.", "done": "The firmware update is complete! If you want to attempt another firmware update on this device, please wait until it gets re-interviewed.", + "done_controller": "The firmware update is complete! Your controller is being restarted and your network will temporarily be unavailable.", "Error_Timeout": "Timed Out", "Error_Checksum": "Checksum Error", "Error_TransmissionFailed": "Transmission Failed", @@ -3717,7 +3720,11 @@ "Error_InvalidHardwareVersion": "Invalid Hardware Version", "OK_WaitingForActivation": "Waiting for Activation", "OK_NoRestart": "No Restart", - "OK_RestartPending": "Restart Pending" + "OK_RestartPending": "Restart Pending", + "Error_RetryLimitReached": "Retry Limit Reached", + "Error_Aborted": "Update Aborted by Bootloader", + "Error_NotSupported": "Firmware Updates Not Supported", + "OK": "Success" } }, "logs": {