Skip to content

Commit

Permalink
feat: enable hardware watchdog on 700/800 series controllers (#6954)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone authored Jun 24, 2024
1 parent 686c698 commit 43735f8
Show file tree
Hide file tree
Showing 10 changed files with 347 additions and 52 deletions.
14 changes: 13 additions & 1 deletion packages/core/src/error/ZWaveError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export enum ZWaveErrorCodes {

/** The driver failed to start */
Driver_Failed = 100,
Driver_Reset,
Driver_Reset, // FIXME: This is not used
Driver_Destroyed,
Driver_NotReady,
Driver_InvalidDataReceived,
Expand All @@ -35,6 +35,9 @@ export enum ZWaveErrorCodes {
Controller_ResponseNOK,
Controller_CallbackNOK,
Controller_Jammed,
/** The controller was reset in the middle of a Serial API command */
Controller_Reset,

Controller_InclusionFailed,
Controller_ExclusionFailed,

Expand Down Expand Up @@ -294,6 +297,15 @@ export function isMissingControllerACK(
&& e.context === "ACK";
}

export function wasControllerReset(
e: unknown,
): e is ZWaveError & {
code: ZWaveErrorCodes.Controller_Reset;
} {
return isZWaveError(e)
&& e.code === ZWaveErrorCodes.Controller_Reset;
}

export function isMissingControllerResponse(
e: unknown,
): e is ZWaveError & {
Expand Down
11 changes: 6 additions & 5 deletions packages/serial/src/message/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,9 @@ export enum FunctionType {

UNKNOWN_FUNC_UNKNOWN_0xB4 = 0xb4, // ??

UNKNOWN_FUNC_WATCH_DOG_ENABLE = 0xb6,
UNKNOWN_FUNC_WATCH_DOG_DISABLE = 0xb7,
UNKNOWN_FUNC_WATCH_DOG_KICK = 0xb8,
EnableWatchdog500 = 0xb6, // Enable Watchdog (500 series and older)
DisableWatchdog500 = 0xb7, // Disable Watchdog (500 series and older)
KickWatchdog500 = 0xb8, // Kick Watchdog (500 series and older)
UNKNOWN_FUNC_UNKNOWN_0xB9 = 0xb9, // ??
UNKNOWN_FUNC_RF_POWERLEVEL_GET = 0xba, // Get RF Power level

Expand All @@ -170,8 +170,9 @@ export enum FunctionType {
FUNC_ID_ZW_SET_PROMISCUOUS_MODE = 0xd0, // Set controller into promiscuous mode to listen to all messages
FUNC_ID_PROMISCUOUS_APPLICATION_COMMAND_HANDLER = 0xd1,

UNKNOWN_FUNC_UNKNOWN_0xD2 = 0xd2, // ??
UNKNOWN_FUNC_UNKNOWN_0xD3 = 0xd3, // ??
StartWatchdog = 0xd2, // Start Hardware Watchdog (700 series and newer)
StopWatchdog = 0xd3, // Stop Hardware Watchdog (700 series and newer)

UNKNOWN_FUNC_UNKNOWN_0xD4 = 0xd4, // ??

Shutdown = 0xd9, // Instruct the Z-Wave API to shut down in order to safely remove the power
Expand Down
6 changes: 6 additions & 0 deletions packages/serial/src/message/Message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,12 @@ export class Message {
}
}

/** Tests whether this message expects an ACK from the controller */
public expectsAck(): boolean {
// By default, all commands expect an ACK
return true;
}

/** Tests whether this message expects a response from the controller */
public expectsResponse(): boolean {
return !!this.expectedResponse;
Expand Down
118 changes: 73 additions & 45 deletions packages/zwave-js/src/lib/controller/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,7 @@ import {
ApplicationUpdateRequestSmartStartHomeIDReceived,
ApplicationUpdateRequestSmartStartLongRangeHomeIDReceived,
} from "../serialapi/application/ApplicationUpdateRequest";
import {
type SerialAPIStartedRequest,
SerialAPIWakeUpReason,
} from "../serialapi/application/SerialAPIStartedRequest";

import {
ShutdownRequest,
type ShutdownResponse,
Expand Down Expand Up @@ -216,6 +213,10 @@ import {
SetSerialApiTimeoutsRequest,
type SetSerialApiTimeoutsResponse,
} from "../serialapi/misc/SetSerialApiTimeoutsMessages";
import {
StartWatchdogRequest,
StopWatchdogRequest,
} from "../serialapi/misc/WatchdogMessages";
import {
AddNodeDSKToNetworkRequest,
AddNodeStatus,
Expand Down Expand Up @@ -449,10 +450,6 @@ export class ZWaveController
FunctionType.ReplaceFailedNode,
this.handleReplaceNodeStatusReport.bind(this),
);
driver.registerRequestHandler(
FunctionType.SerialAPIStarted,
this.handleSerialAPIStartedUnexpectedly.bind(this),
);
}

private _type: MaybeNotKnown<ZWaveLibraryTypes>;
Expand Down Expand Up @@ -739,6 +736,10 @@ export class ZWaveController
public get nodeIdType(): NodeIDType {
return this._nodeIdType;
}
/** @internal */
public set nodeIdType(value: NodeIDType) {
this._nodeIdType = value;
}

/** Returns the node with the given DSK */
public getNodeByDSK(dsk: Buffer | string): ZWaveNode | undefined {
Expand Down Expand Up @@ -1888,6 +1889,63 @@ export class ZWaveController
}
}

/**
* Starts the hardware watchdog on supporting 700+ series controllers.
* Returns whether the operation was successful.
*/
public async startWatchdog(): Promise<boolean> {
if (
this.sdkVersionGte("7.0")
&& this.isFunctionSupported(FunctionType.StartWatchdog)
) {
try {
this.driver.controllerLog.print(
"Starting hardware watchdog...",
);
await this.driver.sendMessage(
new StartWatchdogRequest(this.driver),
);

return true;
} catch (e) {
this.driver.controllerLog.print(
`Starting the hardware watchdog failed: ${
getErrorMessage(e)
}`,
"error",
);
}
}
return false;
}

/**
* Stops the hardware watchdog on supporting controllers.
* Returns whether the operation was successful.
*/
public async stopWatchdog(): Promise<boolean> {
if (this.isFunctionSupported(FunctionType.StopWatchdog)) {
try {
this.driver.controllerLog.print(
"Stopping hardware watchdog...",
);
await this.driver.sendMessage(
new StopWatchdogRequest(this.driver),
);

return true;
} catch (e) {
this.driver.controllerLog.print(
`Stopping the hardware watchdog failed: ${
getErrorMessage(e)
}`,
"error",
);
}
}
return false;
}

private _inclusionState: InclusionState = InclusionState.Idle;
public get inclusionState(): InclusionState {
return this._inclusionState;
Expand Down Expand Up @@ -4331,43 +4389,6 @@ supported CCs: ${
return false;
}

/**
* Is called when the Serial API restart unexpectedly.
*/
private async handleSerialAPIStartedUnexpectedly(
msg: SerialAPIStartedRequest,
): Promise<boolean> {
// Normally, the soft reset command includes waiting for this message.
// If we end up here, it is unexpected.

switch (msg.wakeUpReason) {
// All wakeup reasons that indicate a reset of the Serial API
// need to be handled here, so we interpret node IDs correctly.
case SerialAPIWakeUpReason.Reset:
case SerialAPIWakeUpReason.WatchdogReset:
case SerialAPIWakeUpReason.SoftwareReset:
case SerialAPIWakeUpReason.EmergencyWatchdogReset:
case SerialAPIWakeUpReason.BrownoutCircuit: {
// The Serial API restarted unexpectedly
if (this._nodeIdType === NodeIDType.Long) {
this.driver.controllerLog.print(
`Serial API restarted unexpectedly.`,
"warn",
);

// We previously used 16 bit node IDs, but the controller was reset.
// Remember this and try to go back to 16 bit.
this._nodeIdType = NodeIDType.Short;
await this.trySetNodeIDType(NodeIDType.Long);
}

return true; // Don't invoke any more handlers
}
}

return false; // Not handled
}

private _rebuildRoutesProgress = new Map<number, RebuildRoutesStatus>();
/**
* If routes are currently being rebuilt for the entire network, this returns the current progress.
Expand Down Expand Up @@ -6945,13 +6966,17 @@ ${associatedNodes.join(", ")}`,
);
}

// Disable watchdog to prevent resets during NVM access
await this.stopWatchdog();

let ret: Buffer;
try {
if (this.sdkVersionGte("7.0")) {
ret = await this.backupNVMRaw700(onProgress);
// All 7.xx versions so far seem to have a bug where the NVM is not properly closed after reading
// resulting in extremely strange controller behavior after a backup. To work around this, restart the stick if possible
await this.driver.trySoftReset();
// Soft-resetting will enable the watchdog again
} else {
ret = await this.backupNVMRaw500(onProgress);
}
Expand Down Expand Up @@ -7071,6 +7096,9 @@ ${associatedNodes.join(", ")}`,
);
}

// Disable watchdog to prevent resets during NVM access
await this.stopWatchdog();

// Restoring a potentially incompatible NVM happens in three steps:
// 1. the current NVM is read
// 2. the given NVM data is converted to match the current format
Expand Down
Loading

0 comments on commit 43735f8

Please sign in to comment.