Skip to content

Commit

Permalink
Allow messages to timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone committed May 1, 2019
1 parent 5ac34f4 commit 56aa44e
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 102 deletions.
4 changes: 3 additions & 1 deletion src/lib/commandclass/VersionCC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ export class VersionCC extends CommandClass {
if (kind & StateKind.Static) {
const cc = new VersionCCGet(driver, { nodeId: node.id });
const request = new SendDataRequest(driver, { command: cc });
await driver.sendMessage(request, MessagePriority.NodeQuery);
await driver.sendMessage(request, {
priority: MessagePriority.NodeQuery,
});
}
}
}
Expand Down
25 changes: 15 additions & 10 deletions src/lib/controller/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ export class ZWaveController extends EventEmitter {
log("controller", `querying version info...`, "debug");
const version = await this.driver.sendMessage<
GetControllerVersionResponse
>(new GetControllerVersionRequest(this.driver), "none");
>(new GetControllerVersionRequest(this.driver), {
supportCheck: false,
});
this._libraryVersion = version.libraryVersion;
this._type = version.controllerType;
log("controller", `received version info:`, "debug");
Expand All @@ -196,7 +198,7 @@ export class ZWaveController extends EventEmitter {
log("controller", `querying controller IDs...`, "debug");
const ids = await this.driver.sendMessage<GetControllerIdResponse>(
new GetControllerIdRequest(this.driver),
"none",
{ supportCheck: false },
);
this._homeId = ids.homeId;
this._ownNodeId = ids.ownNodeId;
Expand All @@ -208,7 +210,9 @@ export class ZWaveController extends EventEmitter {
log("controller", `querying controller capabilities...`, "debug");
const ctrlCaps = await this.driver.sendMessage<
GetControllerCapabilitiesResponse
>(new GetControllerCapabilitiesRequest(this.driver), "none");
>(new GetControllerCapabilitiesRequest(this.driver), {
supportCheck: false,
});
this._isSecondary = ctrlCaps.isSecondary;
this._isUsingHomeIdFromOtherNetwork =
ctrlCaps.isUsingHomeIdFromOtherNetwork;
Expand Down Expand Up @@ -248,7 +252,9 @@ export class ZWaveController extends EventEmitter {
log("controller", `querying API capabilities...`, "debug");
const apiCaps = await this.driver.sendMessage<
GetSerialApiCapabilitiesResponse
>(new GetSerialApiCapabilitiesRequest(this.driver), "none");
>(new GetSerialApiCapabilitiesRequest(this.driver), {
supportCheck: false,
});
this._serialApiVersion = apiCaps.serialApiVersion;
this._manufacturerId = apiCaps.manufacturerId;
this._productType = apiCaps.productType;
Expand Down Expand Up @@ -290,7 +296,7 @@ export class ZWaveController extends EventEmitter {
log("controller", `finding SUC...`, "debug");
const suc = await this.driver.sendMessage<GetSUCNodeIdResponse>(
new GetSUCNodeIdRequest(this.driver),
"none",
{ supportCheck: false },
);
this._sucNodeId = suc.sucNodeId;
if (this._sucNodeId === 0) {
Expand Down Expand Up @@ -400,11 +406,10 @@ export class ZWaveController extends EventEmitter {
0x00, // length
]),
});
await this.driver.sendMessage(
appInfoMsg,
MessagePriority.Controller,
"none",
);
await this.driver.sendMessage(appInfoMsg, {
priority: MessagePriority.Controller,
supportCheck: false,
});
}

log("controller", "interview completed", "debug");
Expand Down
121 changes: 62 additions & 59 deletions src/lib/driver/Driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "../commandclass/CommandClass";
import { CommandClasses } from "../commandclass/CommandClasses";
import { isCommandClassContainer } from "../commandclass/ICommandClassContainer";
import { NoOperationCC } from "../commandclass/NoOperationCC";
import { WakeUpCC } from "../commandclass/WakeUpCC";
import { ApplicationCommandRequest } from "../controller/ApplicationCommandRequest";
import {
Expand Down Expand Up @@ -84,16 +85,25 @@ function applyDefaultOptions(
}

export type MessageSupportCheck = "loud" | "silent" | "none";
function isMessageSupportCheck(val: any): val is MessageSupportCheck {
return val === "loud" || val === "silent" || val === "none";
}
// function isMessageSupportCheck(val: any): val is MessageSupportCheck {
// return val === "loud" || val === "silent" || val === "none";
// }

export type RequestHandler<T extends Message = Message> = (msg: T) => boolean;
interface RequestHandlerEntry<T extends Message = Message> {
invoke: RequestHandler<T>;
oneTime: boolean;
}

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;
/** Setting timeout to a positive number causes the transaction to be rejected if no response is received before the timeout elapses */
timeout?: number;
}

// TODO: Interface the emitted events

export class Driver extends EventEmitter implements IDriver {
Expand Down Expand Up @@ -1059,84 +1069,53 @@ export class Driver extends EventEmitter implements IDriver {
/**
* Sends a message with default priority to the Z-Wave stick
* @param msg The message to send
* @param supportCheck How to check for the support of the message to send. If the message is not supported:
* * "loud" means the call will throw
* * "silent" means the call will resolve with `undefined`
* * "none" means the message will be sent anyways. This is useful if the capabilities haven't been determined yet.
* @param priority The priority of the message to send. If none is given, the defined default priority of the message
* class will be used.
*/
public async sendMessage<TResponse extends Message = Message>(
msg: Message,
priority?: MessagePriority,
): Promise<TResponse>;

public async sendMessage<TResponse extends Message = Message>(
msg: Message,
supportCheck?: MessageSupportCheck,
): Promise<TResponse>;

public async sendMessage<TResponse extends Message = Message>(
msg: Message,
priority: MessagePriority,
supportCheck: MessageSupportCheck,
): Promise<TResponse>;

public async sendMessage<TResponse extends Message = Message>(
msg: Message,
priorityOrCheck?: MessagePriority | MessageSupportCheck,
supportCheck?: MessageSupportCheck,
): Promise<TResponse | undefined> {
// sort out the arguments
if (isMessageSupportCheck(priorityOrCheck)) {
supportCheck = priorityOrCheck;
priorityOrCheck = undefined;
}
// now priorityOrCheck is either undefined or a MessagePriority
const priority: MessagePriority | undefined =
priorityOrCheck != undefined
? priorityOrCheck
: getDefaultPriority(msg);
if (supportCheck == undefined) supportCheck = "loud";

options: SendMessageOptions = {},
): Promise<TResponse> {
this.ensureReady();

if (priority == undefined) {
if (options.priority == undefined)
options.priority = getDefaultPriority(msg);
if (options.priority == undefined) {
const className = msg.constructor.name;
const msgTypeName = FunctionType[msg.functionType];
throw new ZWaveError(
`No default priority has been defined for ${className} (${msgTypeName}), so you have to provide one for your message`,
ZWaveErrorCodes.Driver_NoPriority,
);
}
if (options.supportCheck == undefined) options.supportCheck = true;

if (
supportCheck !== "none" &&
options.supportCheck &&
this.controller != undefined &&
!this.controller.isFunctionSupported(msg.functionType)
) {
if (supportCheck === "loud") {
throw new ZWaveError(
`Your hardware does not support the ${
FunctionType[msg.functionType]
} function`,
ZWaveErrorCodes.Driver_NotSupported,
);
} else {
return undefined;
}
throw new ZWaveError(
`Your hardware does not support the ${
FunctionType[msg.functionType]
} function`,
ZWaveErrorCodes.Driver_NotSupported,
);
}

log(
"driver",
`sending message ${stringify(msg)} with priority ${
MessagePriority[priority]
} (${priority})`,
MessagePriority[options.priority]
} (${options.priority})`,
"debug",
);
// create the transaction and enqueue it
const promise = createDeferredPromise<TResponse>();
const transaction = new Transaction(this, msg, promise, priority);
const transaction = new Transaction(
this,
msg,
promise,
options.priority,
);

this.sendQueue.add(transaction);
log(
Expand All @@ -1156,12 +1135,12 @@ export class Driver extends EventEmitter implements IDriver {
// wotan-disable-next-line no-misused-generics
public async sendCommand<TResponse extends CommandClass = CommandClass>(
command: CommandClass,
priority?: MessagePriority,
options: SendMessageOptions = {},
): Promise<TResponse | undefined> {
const msg = new SendDataRequest(this, {
command,
});
const resp = await this.sendMessage(msg, priority);
const resp = await this.sendMessage(msg, options);
if (isCommandClassContainer(resp)) {
return resp.command as TResponse;
}
Expand Down Expand Up @@ -1241,6 +1220,19 @@ export class Driver extends EventEmitter implements IDriver {
// Mark the transaction as being sent
this.currentTransaction.sendAttempts = 1;
this.doSend(data);
// If the transaction has a timeout configured, start it
if (this.currentTransaction.timeout) {
setTimeout(
() =>
this.rejectCurrentTransaction(
new ZWaveError(
"The transaction timed out",
ZWaveErrorCodes.Controller_MessageTimeout,
),
),
this.currentTransaction.timeout,
);
}

// to avoid any deadlocks we didn't think of, re-call this later
this.sendQueueTimer = setTimeout(
Expand Down Expand Up @@ -1279,14 +1271,25 @@ export class Driver extends EventEmitter implements IDriver {

/** Moves all messages for a given node into the wakeup queue */
private moveMessagesToWakeupQueue(nodeId: number): void {
const pingsToRemove: Transaction[] = [];
for (const transaction of this.sendQueue) {
const msg = transaction.message;
const targetNodeId = msg.getNodeId();
if (targetNodeId === nodeId) {
// Change the priority to WakeUp
transaction.priority = MessagePriority.WakeUp;
if (
isCommandClassContainer(msg) &&
msg.command instanceof NoOperationCC
) {
pingsToRemove.push(transaction);
} else {
// Change the priority to WakeUp
transaction.priority = MessagePriority.WakeUp;
}
}
}
// Remove all pings that would clutter the send queue
this.sendQueue.remove(...pingsToRemove);

// Changing the priority has an effect on the order, so re-sort the send queue
this.sortSendQueue();

Expand Down
17 changes: 3 additions & 14 deletions src/lib/driver/IDriver.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
import { CommandClasses } from "../commandclass/CommandClasses";
import { ZWaveController } from "../controller/Controller";
import { MessagePriority } from "../message/Constants";
import { Message } from "../message/Message";
import { MessageSupportCheck } from "./Driver";
import { SendMessageOptions } from "./Driver";

export interface IDriver {
controller: ZWaveController | undefined;

getSafeCCVersionForNode(nodeId: number, cc: CommandClasses): number;

// wotan-disable no-misused-generics
// wotan-disable-next-line no-misused-generics
sendMessage<TResponse extends Message = Message>(
msg: Message,
priority?: MessagePriority,
options?: SendMessageOptions,
): Promise<TResponse>;
sendMessage<TResponse extends Message = Message>(
msg: Message,
supportCheck?: MessageSupportCheck,
): Promise<TResponse>;
sendMessage<TResponse extends Message = Message>(
msg: Message,
priority: MessagePriority,
supportCheck: MessageSupportCheck,
): Promise<TResponse>;
// wotan-enable no-misused-generics

// Add more signatures as needed
}
4 changes: 4 additions & 0 deletions src/lib/driver/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ export class Transaction implements Comparable<Transaction> {
message: Message,
promise: DeferredPromise<Message | void>,
priority: MessagePriority,
timeout?: number,
);
public constructor(
private readonly driver: IDriver,
public readonly message: Message,
public readonly promise: DeferredPromise<Message | void>,
public priority: MessagePriority,
public readonly timeout?: number,
public timestamp: number = highResTimestamp(),
/**
* The previously received partial responses of a multistep command
Expand All @@ -40,6 +42,8 @@ export class Transaction implements Comparable<Transaction> {
) {
if (message.maxSendAttempts)
this.maxSendAttempts = message.maxSendAttempts;
if (typeof this.timeout === "number" && this.timeout < 1)
this.timeout = undefined;
}

private _maxSendAttempts: number = MAX_SEND_ATTEMPTS;
Expand Down
1 change: 1 addition & 0 deletions src/lib/error/ZWaveError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export enum ZWaveErrorCodes {
Driver_NotSupported,
Driver_NoPriority,
Driver_InvalidCache,
Controller_MessageTimeout,

Controller_MessageDropped,
Controller_InclusionFailed,
Expand Down
Loading

0 comments on commit 56aa44e

Please sign in to comment.