inventoresed
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
260 lines (225 loc) • 9.11 kB
text/typescript
import { padStart } from "alcalzone-shared/strings";
/**
* Used to identify errors from this library without relying on the specific wording of the error message
*/
export enum ZWaveErrorCodes {
PacketFormat_Truncated,
PacketFormat_Invalid,
PacketFormat_Checksum,
// This differs from the above three. It means that the packet has a valid format and checksum,
// but the data does not match the expectations. This error does not reset the Z-Wave stack
PacketFormat_InvalidPayload,
PacketFormat_DecryptionFailed,
/** The driver failed to start */
Driver_Failed = 100,
Driver_Reset,
Driver_Destroyed,
Driver_NotReady,
Driver_InvalidDataReceived,
Driver_NotSupported,
Driver_NoPriority,
Driver_InvalidCache,
Driver_InvalidOptions,
/** The driver tried to do something that requires security */
Driver_NoSecurity,
Driver_NoErrorHandler,
Driver_FeatureDisabled,
/** There was a timeout while waiting for a message from the controller */
Controller_Timeout = 200,
/** There was a timeout while waiting for a response from a node */
Controller_NodeTimeout,
Controller_MessageDropped,
Controller_ResponseNOK,
Controller_CallbackNOK,
Controller_InclusionFailed,
Controller_ExclusionFailed,
/** Tried to do something the controller does not support */
Controller_NotSupported,
/** The interview for this node was restarted by the user */
Controller_InterviewRestarted,
/** The node with the given node ID was not found */
Controller_NodeNotFound,
/** The endpoint with the given index was not found on the node */
Controller_EndpointNotFound,
/** The node was removed from the network */
Controller_NodeRemoved,
/** Communication with the node will be insecure (no security configured) */
Controller_NodeInsecureCommunication,
/** The message has expired (the given timeout has elapsed) */
Controller_MessageExpired,
/** A Serial API command resulted in an error response */
Controller_CommandError,
/** Could not fetch some information to determine firmware upgrades from a node */
FWUpdateService_MissingInformation = 260,
/** Any error related to HTTP requests during firmware update communication */
FWUpdateService_RequestError,
/** The integrity check of the downloaded firmware update failed */
FWUpdateService_IntegrityCheckFailed,
/** The given NVM version/format is unsupported */
NVM_NotSupported = 280,
/** Could not parse the JSON representation of an NVM due to invalid data */
NVM_InvalidJSON,
/** A required NVM3 object was not found while deserializing the NVM */
NVM_ObjectNotFound,
/** The parsed NVM or NVM content has an invalid format */
NVM_InvalidFormat,
/** Not enough space in the NVM */
NVM_NoSpace,
CC_Invalid = 300,
CC_NoNodeID,
CC_NotSupported,
CC_NotImplemented,
CC_NoAPI,
Deserialization_NotImplemented = 320,
Arithmetic,
Argument_Invalid,
Config_Invalid = 340,
Config_NotFound,
/** A compound config file has circular imports */
Config_CircularImport,
/** Failed to download the npm registry info for config updates */
Config_Update_RegistryError,
/** Could not detect which package manager to use for updates */
Config_Update_PackageManagerNotFound,
/** Installing the configuration update failed */
Config_Update_InstallFailed,
// Here follow message specific errors
/** The removal process could not be started or completed due to one or several reasons */
RemoveFailedNode_Failed = 360,
/** The removal process was aborted because the node has responded */
RemoveFailedNode_NodeOK,
/** The replace process could not be started or completed due to one or several reasons */
ReplaceFailedNode_Failed,
/** The replace process was aborted because the node has responded */
ReplaceFailedNode_NodeOK,
// Here follow CC specific errors
/**
* Used to report the first existing parameter number
* available in a node's configuration
*/
ConfigurationCC_FirstParameterNumber = 1000,
/**
* Used to report that a V3+ node should not have its parameters scanned with get/set commands
*/
ConfigurationCC_NoLegacyScanOnNewDevices,
/**
* Used to report that a node using V3 or less MUST not use the resetToDefault flag
*/
ConfigurationCC_NoResetToDefaultOnLegacyDevices,
/**
* Used to report that the command was not executed by the target node
*/
SupervisionCC_CommandFailed = 1100,
/**
* Used to report that a ManufacturerProprietaryCC could not be instantiated
* because of a missing manufacturer ID.
*/
ManufacturerProprietaryCC_NoManufacturerId = 1200,
/**
* Used to report that an invalid group ID was used to address a (Multi Channel) Association
*/
AssociationCC_InvalidGroup = 1300,
/** Cannot add an association because it is not allowed */
AssociationCC_NotAllowed,
/** Used to report that no nonce exists */
SecurityCC_NoNonce = 1400,
/** Used to report that no SPAN is established between the nodes yet. The context should be an object that contains the peer node ID */
Security2CC_NoSPAN,
/** Used to report that the inner state required for this action was not initialized */
Security2CC_NotInitialized,
/** Used to report that secure communication with a node is not possible because the node is not secure */
Security2CC_NotSecure,
/** Gets thrown when a Security S2 command is missing a required extension */
Security2CC_MissingExtension,
/** Gets thrown when a Security S2 encapsulated command cannot be decoded by the target node */
Security2CC_CannotDecode,
/** Gets thrown when parsing an invalid QR code */
Security2CC_InvalidQRCode,
/** The firmware update process is already active */
FirmwareUpdateCC_Busy = 1500,
/** The selected firmware target is not upgradable */
FirmwareUpdateCC_NotUpgradable,
/** The selected firmware target does not exist */
FirmwareUpdateCC_TargetNotFound,
/** The node reported that it could not start the update */
FirmwareUpdateCC_FailedToStart,
/** The node did not confirm the aborted update */
FirmwareUpdateCC_FailedToAbort,
/** The node did not confirm the completed update or the process stalled for too long */
FirmwareUpdateCC_Timeout,
/** An invalid firmware file was provided that cannot be handled by this library */
Invalid_Firmware_File,
/** An firmware file with an unsupported format was provided */
Unsupported_Firmware_Format,
/** Unsupported target node for a powerlevel test */
PowerlevelCC_UnsupportedTestNode = 1600,
}
export function getErrorSuffix(code: ZWaveErrorCodes): string {
return `ZW${padStart(code.toString(), 4, "0")}`;
}
function appendErrorSuffix(message: string, code: ZWaveErrorCodes): string {
const suffix = ` (${getErrorSuffix(code)})`;
if (!message.endsWith(suffix)) message += suffix;
return message;
}
/**
* Errors thrown in this library are of this type. The `code` property identifies what went wrong.
*/
export class ZWaveError extends Error {
public constructor(
public readonly message: string,
public readonly code: ZWaveErrorCodes,
/** Additional info required to handle this error (e.g. the Z-Wave message indicating the failure) */
public readonly context?: unknown,
/** If this error corresponds to a failed transaction, this contains the stack where it was created */
public readonly transactionSource?: string,
) {
super();
// Add the error code to the message to be able to identify it even when the stack trace is garbled somehow
this.message = appendErrorSuffix(message, code);
// We need to set the prototype explicitly
Object.setPrototypeOf(this, ZWaveError.prototype);
Object.getPrototypeOf(this).name = "ZWaveError";
// If there's a better stack, use it
if (typeof transactionSource === "string") {
this.stack = `ZWaveError: ${this.message}\n${transactionSource}`;
}
}
}
export function isZWaveError(e: unknown): e is ZWaveError {
return e instanceof Error && Object.getPrototypeOf(e).name === "ZWaveError";
}
export function isTransmissionError(e: unknown): e is ZWaveError & {
code:
| ZWaveErrorCodes.Controller_Timeout
| ZWaveErrorCodes.Controller_MessageDropped
| ZWaveErrorCodes.Controller_CallbackNOK
| ZWaveErrorCodes.Controller_ResponseNOK
| ZWaveErrorCodes.Controller_NodeTimeout
| ZWaveErrorCodes.Security2CC_CannotDecode;
} {
return (
isZWaveError(e) &&
(e.code === ZWaveErrorCodes.Controller_Timeout ||
e.code === ZWaveErrorCodes.Controller_MessageDropped ||
e.code === ZWaveErrorCodes.Controller_CallbackNOK ||
e.code === ZWaveErrorCodes.Controller_ResponseNOK ||
e.code === ZWaveErrorCodes.Controller_NodeTimeout ||
e.code === ZWaveErrorCodes.Security2CC_CannotDecode)
);
}
/**
* Tests is the given error is a "recoverable" error - i.e. something that shouldn't happen unless
* someone interacted with zwave-js in a weird way, but something we can deal with.
*
* This explicitly does not include transmission errors.
*/
export function isRecoverableZWaveError(e: unknown): e is ZWaveError {
if (!isZWaveError(e)) return false;
switch (e.code) {
case ZWaveErrorCodes.Controller_InterviewRestarted:
case ZWaveErrorCodes.Controller_NodeRemoved:
return true;
}
return false;
}