UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

270 lines (244 loc) 9.1 kB
import { isObject } from "alcalzone-shared/typeguards"; import type { ICommandClass } from "../abstractions/ICommandClass"; import type { ProtocolDataRate } from "../capabilities/Protocols"; import type { Duration } from "../values/Duration"; /** The priority of messages, sorted from high (0) to low (>0) */ export enum MessagePriority { // Outgoing nonces have the highest priority because they are part of other transactions // which may already be in progress. // Some nodes don't respond to our requests if they are waiting for a nonce, so those need to be handled first. Nonce = 0, // Controller commands usually finish quickly and should be preferred over node queries Controller, // Multistep controller commands typically require user interaction but still // should happen at a higher priority than any node data exchange MultistepController, // Supervision responses must be prioritized over other messages because the nodes requesting them // will get impatient otherwise. Supervision, // Pings (NoOP) are used for device probing at startup and for network diagnostics Ping, // Whenever sleeping devices wake up, their queued messages must be handled quickly // because they want to go to sleep soon. So prioritize them over non-sleeping devices WakeUp, // Normal operation and node data exchange Normal, // Node querying is expensive and happens whenever a new node is discovered. // In order to keep the system responsive, give them a lower priority NodeQuery, // Some devices need their state to be polled at regular intervals. Only do that when // nothing else needs to be done Poll, } export function isMessagePriority(val: unknown): val is MessagePriority { return typeof val === "number" && val in MessagePriority; } export type MulticastDestination = [number, number, ...number[]]; export enum TransmitOptions { NotSet = 0, ACK = 1 << 0, LowPower = 1 << 1, AutoRoute = 1 << 2, NoRoute = 1 << 4, Explore = 1 << 5, DEFAULT = ACK | AutoRoute | Explore, DEFAULT_NOACK = DEFAULT & ~ACK, } export enum TransmitStatus { OK = 0x00, NoAck = 0x01, Fail = 0x02, NotIdle = 0x03, NoRoute = 0x04, } /** A number between -128 and +124 dBm or one of the special values in {@link RssiError} indicating an error */ export type RSSI = number | RssiError; export enum RssiError { NotAvailable = 127, ReceiverSaturated = 126, NoSignalDetected = 125, } export function isRssiError(rssi: RSSI): rssi is RssiError { return rssi >= RssiError.NoSignalDetected; } /** * Converts an RSSI value to a human readable format, i.e. the measurement including the unit or the corresponding error message. */ export function rssiToString(rssi: RSSI): string { switch (rssi) { case RssiError.NotAvailable: return "N/A"; case RssiError.ReceiverSaturated: return "Receiver saturated"; case RssiError.NoSignalDetected: return "No signal detected"; default: return `${rssi} dBm`; } } export interface TXReport { /** Transmission time in ticks (multiples of 10ms) */ txTicks: number; /** Number of repeaters used in the route to the destination, 0 for direct range */ numRepeaters: number; /** RSSI value of the acknowledgement frame */ ackRSSI?: RSSI; /** RSSI values of the incoming acknowledgement frame, measured by repeater 0...3 */ ackRepeaterRSSI?: [RSSI?, RSSI?, RSSI?, RSSI?]; /** Channel number the acknowledgement frame is received on */ ackChannelNo?: number; /** Channel number used to transmit the data */ txChannelNo: number; /** State of the route resolution for the transmission attempt. Encoding is manufacturer specific. */ routeSchemeState: number; /** Node IDs of the repeater 0..3 used in the route. */ repeaterNodeIds: [number?, number?, number?, number?]; /** Whether the destination requires a 1000ms beam to be reached */ beam1000ms: boolean; /** Whether the destination requires a 250ms beam to be reached */ beam250ms: boolean; /** Transmission speed used in the route */ routeSpeed: ProtocolDataRate; /** How many routing attempts have been made to transmit the payload */ routingAttempts: number; /** When a route failed, this indicates the last functional Node ID in the last used route */ failedRouteLastFunctionalNodeId?: number; /** When a route failed, this indicates the first non-functional Node ID in the last used route */ failedRouteFirstNonFunctionalNodeId?: number; /** Transmit power used for the transmission in dBm */ txPower?: number; /** Measured noise floor during the outgoing transmission */ measuredNoiseFloor?: RSSI; /** TX power in dBm used by the destination to transmit the ACK */ destinationAckTxPower?: number; /** Measured RSSI of the acknowledgement frame received from the destination */ destinationAckMeasuredRSSI?: RSSI; /** Noise floor measured by the destination during the ACK transmission */ destinationAckMeasuredNoiseFloor?: RSSI; } export interface SendMessageOptions { /** The priority of the message to send. If none is given, the defined default priority of the message class will be used. */ priority?: MessagePriority; /** If an exception should be thrown when the message to send is not supported. Setting this to false is is useful if the capabilities haven't been determined yet. Default: true */ supportCheck?: boolean; /** * Whether the driver should update the node status to asleep or dead when a transaction is not acknowledged (repeatedly). * Setting this to false will cause the simply transaction to be rejected on failure. * Default: true */ changeNodeStatusOnMissingACK?: boolean; /** Sets the number of milliseconds after which a message expires. When the expiration timer elapses, the promise is rejected with the error code `Controller_MessageExpired`. */ expire?: number; /** * @internal * Information used to identify or mark this transaction */ tag?: any; /** * @internal * Whether the send thread MUST be paused after this message was handled */ pauseSendThread?: boolean; /** If a Wake Up On Demand should be requested for the target node. */ requestWakeUpOnDemand?: boolean; /** * When a message sent to a node results in a TX report to be received, this callback will be called. * For multi-stage messages, the callback may be called multiple times. */ onTXReport?: (report: TXReport) => void; } export enum EncapsulationFlags { None = 0, Supervision = 1 << 0, // Multi Channel is tracked through the endpoint index Security = 1 << 1, CRC16 = 1 << 2, } export type SupervisionOptions = | ({ /** Whether supervision may be used. `false` disables supervision. Default: `"auto"`. */ useSupervision?: "auto"; } & ( | { requestStatusUpdates?: false; } | { requestStatusUpdates: true; onUpdate: SupervisionUpdateHandler; } )) | { useSupervision: false; }; export type SendCommandOptions = SendMessageOptions & SupervisionOptions & { /** How many times the driver should try to send the message. Defaults to the configured Driver option */ maxSendAttempts?: number; /** Whether the driver should automatically handle the encapsulation. Default: true */ autoEncapsulate?: boolean; /** Used to send a response with the same encapsulation flags as the corresponding request. */ encapsulationFlags?: EncapsulationFlags; /** Overwrite the default transmit options */ transmitOptions?: TransmitOptions; }; export type SendCommandReturnType<TResponse extends ICommandClass | undefined> = undefined extends TResponse ? SupervisionResult | undefined : TResponse | undefined; export enum SupervisionStatus { NoSupport = 0x00, Working = 0x01, Fail = 0x02, Success = 0xff, } export type SupervisionResult = | { status: | SupervisionStatus.NoSupport | SupervisionStatus.Fail | SupervisionStatus.Success; remainingDuration?: undefined; } | { status: SupervisionStatus.Working; remainingDuration: Duration; }; export type SupervisionUpdateHandler = (update: SupervisionResult) => void; export function isSupervisionResult(obj: unknown): obj is SupervisionResult { return ( isObject(obj) && "status" in obj && typeof SupervisionStatus[obj.status as any] === "string" ); } export function supervisedCommandSucceeded( result: unknown, ): result is SupervisionResult & { status: SupervisionStatus.Success | SupervisionStatus.Working; } { return ( isSupervisionResult(result) && (result.status === SupervisionStatus.Success || result.status === SupervisionStatus.Working) ); } export function supervisedCommandFailed( result: unknown, ): result is SupervisionResult & { status: SupervisionStatus.Fail | SupervisionStatus.NoSupport; } { return ( isSupervisionResult(result) && (result.status === SupervisionStatus.Fail || result.status === SupervisionStatus.NoSupport) ); } export function isUnsupervisedOrSucceeded( result: SupervisionResult | undefined, ): result is | undefined | (SupervisionResult & { status: SupervisionStatus.Success | SupervisionStatus.Working; }) { return !result || supervisedCommandSucceeded(result); }