Skip to content

Commit

Permalink
feat: implement waitForMessage to await unsolicited Serial API messag…
Browse files Browse the repository at this point in the history
…es (#3549)
  • Loading branch information
AlCalzone authored Oct 21, 2021
1 parent 4d845ca commit 9058860
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 3 deletions.
15 changes: 14 additions & 1 deletion docs/api/driver.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,19 @@ trySendCommandSupervised(command: CommandClass, options?: SendSupervisedCommandO
If `Supervision CC` is not supported, the returned promise resolves to `undefined`.
### `waitForMessage`
```ts
waitForMessage<T extends Message>(predicate: (msg: Message) => boolean, timeout: number): Promise<T>
```
Waits until an unsolicited serial message is received which matches the given predicate or a timeout has elapsed. Resolves the received message. This method takes two arguments:
- `predicate` - A predicate function that will be called for every received unsolicited message. If the function returns `true`, the returned promise will be resolved with the message.
- `timeout` - The timeout in milliseconds after which the returned promise will be rejected if no matching message has been received.
> [!NOTE] This does not trigger for (Bridge)ApplicationCommandRequests, which are handled differently. To wait for a certain CommandClass, use [`waitForCommand`](#waitForCommand).
### `waitForCommand`
```ts
Expand All @@ -194,7 +207,7 @@ waitForCommand<T extends CommandClass>(predicate: (cc: CommandClass) => boolean,
Waits until an unsolicited command is received which matches the given predicate or a timeout has elapsed. Resolves the received command. This method takes two arguments:
- `predicate` - A predicate function that will be called for every received command. If the function returns true, the returned promise will be resolved with the command.
- `predicate` - A predicate function that will be called for every received command. If the function returns `true`, the returned promise will be resolved with the command.
- `timeout` - The timeout in milliseconds after which the returned promise will be rejected if no matching command has been received.
### `saveNetworkToCache`
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/error/ZWaveError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ export enum ZWaveErrorCodes {
Driver_NoErrorHandler,
Driver_FeatureDisabled,

/** The controller has timed out while waiting for a report from the node */
/** 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,
Expand Down
62 changes: 61 additions & 1 deletion packages/zwave-js/src/lib/driver/Driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,12 @@ interface RequestHandlerEntry<T extends Message = Message> {
oneTime: boolean;
}

interface AwaitedMessageEntry {
promise: DeferredPromise<Message>;
timeout?: NodeJS.Timeout;
predicate: (msg: Message) => boolean;
}

interface AwaitedCommandEntry {
promise: DeferredPromise<CommandClass>;
timeout?: NodeJS.Timeout;
Expand Down Expand Up @@ -411,6 +417,8 @@ export class Driver extends TypedEventEmitter<DriverEventCallbacks> {

/** A map of handlers for all sorts of requests */
private requestHandlers = new Map<FunctionType, RequestHandlerEntry[]>();
/** A map of awaited messages */
private awaitedMessages: AwaitedMessageEntry[] = [];
/** A map of awaited commands */
private awaitedCommands: AwaitedCommandEntry[] = [];

Expand Down Expand Up @@ -2698,6 +2706,17 @@ ${handlers.length} left`,
}
}
} else {
// Check if we have a dynamic handler waiting for this message
for (const entry of this.awaitedMessages) {
if (entry.predicate(msg)) {
// resolve the promise - this will remove the entry from the list
entry.promise.resolve(msg);
return;
}
}

// Otherwise loop through the static handlers

// TODO: This deserves a nicer formatting
this.driverLog.print(
`handling request ${FunctionType[msg.functionType]} (${
Expand Down Expand Up @@ -3157,7 +3176,48 @@ ${handlers.length} left`,
}

/**
* Waits until a command is received or a timeout has elapsed. Returns the received command.
* Waits until an unsolicited serial message is received or a timeout has elapsed. Returns the received message.
*
* **Note:** This does not trigger for [Bridge]ApplicationUpdateRequests, which are handled differently. To wait for a certain CommandClass, use {@link waitForCommand}.
* @param timeout The number of milliseconds to wait. If the timeout elapses, the returned promise will be rejected
* @param predicate A predicate function to test all incoming messages
*/
public waitForMessage<T extends Message>(
predicate: (msg: Message) => boolean,
timeout: number,
): Promise<T> {
return new Promise<T>((resolve, reject) => {
const entry: AwaitedMessageEntry = {
predicate,
promise: createDeferredPromise<Message>(),
timeout: undefined,
};
this.awaitedMessages.push(entry);
const removeEntry = () => {
if (entry.timeout) clearTimeout(entry.timeout);
const index = this.awaitedMessages.indexOf(entry);
if (index !== -1) this.awaitedMessages.splice(index, 1);
};
// When the timeout elapses, remove the wait entry and reject the returned Promise
entry.timeout = setTimeout(() => {
removeEntry();
reject(
new ZWaveError(
`Received no matching message within the provided timeout!`,
ZWaveErrorCodes.Controller_Timeout,
),
);
}, timeout).unref();
// When the promise is resolved, remove the wait entry and resolve the returned Promise
void entry.promise.then((cc) => {
removeEntry();
resolve(cc as T);
});
});
}

/**
* Waits until a CommandClass is received or a timeout has elapsed. Returns the received command.
* @param timeout The number of milliseconds to wait. If the timeout elapses, the returned promise will be rejected
* @param predicate A predicate function to test all incoming command classes
*/
Expand Down

0 comments on commit 9058860

Please sign in to comment.