UNPKG

zwave-js

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

190 lines 7.45 kB
import { MessagePriority, highResTimestamp, isZWaveError, } from "@zwave-js/core"; import { noop } from "@zwave-js/shared"; import { compareNumberOrString, } from "alcalzone-shared/comparable"; import { NodeStatus } from "../node/_Types.js"; /** * Transactions are used to track and correlate messages with their responses. */ export class Transaction { driver; options; constructor(driver, options) { this.driver = driver; this.options = options; // Give the message generator a reference to this transaction options.parts.parent = this; // Initialize class fields this.promise = options.promise; this.message = options.message; this.priority = options.priority; this.parts = options.parts; this.listener = options.listener; // We need create the stack on a temporary object or the Error // class will try to print the message const tmp = { message: "" }; Error.captureStackTrace(tmp, Transaction); this._stack = tmp.stack.replace(/^Error:?\s*\n/, ""); } clone() { const ret = new Transaction(this.driver, this.options); for (const prop of [ "_stack", "_progress", "creationTimestamp", "changeNodeStatusOnTimeout", "pauseSendThread", "priority", "tag", "requestWakeUpOnDemand", ]) { ret[prop] = this[prop]; } // The listener callback now lives on the clone this.listener = undefined; return ret; } /** Will be resolved/rejected by the Send Thread Machine when the entire transaction is handled */ promise; /** The "primary" message this transaction contains, e.g. the un-encapsulated version of a SendData request */ message; /** The message generator to create the actual messages for this transaction */ parts; /** A callback which gets called with state updates of this transaction */ listener; _progress; setProgress(progress) { // Ignore duplicate updates if (this._progress?.state === progress.state) return; this._progress = progress; this.listener?.({ ...progress }); } /** * Returns the current message of this transaction. This is either the currently active partial message * or the primary message if the generator hasn't been started yet. */ getCurrentMessage() { return this.parts.current ?? this.message; } /** * Starts the transaction's message generator if it hasn't been started yet. * Returns `true` when the generator was started, `false` if it was already started before. */ start() { if (!this.parts.self) { this.parts.start(); return true; } return false; } /** * Resets this transaction's message generator */ reset() { this.parts.reset(); } async generateNextMessage(prevResult) { if (!this.parts.self) return; // Get the next message from the generator const { done, value } = await this.parts.self.next(prevResult); if (!done) return value; } /** * Forcefully aborts the message generator by throwing the given result. * Errors will be treated as a rejection of the transaction, everything else as success */ abort(result) { if (this.parts.self) { this.parts.self.throw(result).catch(noop); } else if (isZWaveError(result)) { this.promise.reject(result); } else { this.promise.resolve(result); } } /** The priority of this transaction */ priority; /** The timestamp at which the transaction was created */ creationTimestamp = highResTimestamp(); /** Whether the node status should be updated when this transaction times out */ changeNodeStatusOnTimeout = true; /** Whether the send thread MUST be paused after this transaction was handled */ pauseSendThread = false; /** If a Wake Up On Demand should be requested for the target node. */ requestWakeUpOnDemand = false; /** Internal information used to identify or mark this transaction */ tag; /** The stack trace where the transaction was created */ _stack; get stack() { return this._stack; } /** Compares two transactions in order to plan their transmission sequence */ compareTo(other) { const compareWakeUpPriority = (_this, _other) => { const thisNode = _this.message.tryGetNode(this.driver); const otherNode = _other.message.tryGetNode(this.driver); // We don't require existence of the node object // If any transaction is not for a node, it targets the controller // which is always awake const thisIsAsleep = thisNode?.status === NodeStatus.Asleep; const otherIsAsleep = otherNode?.status === NodeStatus.Asleep; // If both nodes are asleep, the conventional order applies // Asleep nodes always have the lowest priority if (thisIsAsleep && !otherIsAsleep) return 1; if (otherIsAsleep && !thisIsAsleep) return -1; }; // delay messages for sleeping nodes if (this.priority === MessagePriority.WakeUp) { const result = compareWakeUpPriority(this, other); if (result != undefined) return result; } else if (other.priority === MessagePriority.WakeUp) { const result = compareWakeUpPriority(other, this); if (result != undefined) return -result; } const compareNodeQueryPriority = (_this, _other) => { const thisNode = _this.message.tryGetNode(this.driver); const otherNode = _other.message.tryGetNode(this.driver); if (thisNode && otherNode) { // Both nodes exist const thisListening = thisNode.isListening || thisNode.isFrequentListening; const otherListening = otherNode.isListening || otherNode.isFrequentListening; // prioritize (-1) the one node that is listening when the other is not if (thisListening && !otherListening) return -1; if (!thisListening && otherListening) return 1; } }; // delay NodeQuery messages for non-listening nodes if (this.priority === MessagePriority.NodeQuery) { const result = compareNodeQueryPriority(this, other); if (result != undefined) return result; } else if (other.priority === MessagePriority.NodeQuery) { const result = compareNodeQueryPriority(other, this); if (result != undefined) return -result; } // by default, sort by priority if (this.priority < other.priority) return -1; else if (this.priority > other.priority) return 1; // for equal priority, sort by the timestamp return compareNumberOrString(other.creationTimestamp, this.creationTimestamp); } } //# sourceMappingURL=Transaction.js.map