Skip to content

Commit

Permalink
feat: add ability to pass options to setValue calls (#2894)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone committed Jun 22, 2021
1 parent 3c00c57 commit fd30f5d
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 21 deletions.
19 changes: 14 additions & 5 deletions docs/api/node.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,23 +55,32 @@ When building a user interface for a Z-Wave application, you might need to know
### `setValue`
```ts
async setValue(valueId: ValueID, value: unknown): Promise<boolean>
async setValue(valueId: ValueID, value: unknown, options?: SetValueAPIOptions): Promise<boolean>
```
Updates a value on the node. This method takes two arguments:
Updates a value on the node. This method takes the following arguments:
- `valueId: ValueID` - specifies which value to update
- `value: unknown` - The new value to set
- `options?: SetValueAPIOptions` - Optional options for the resulting commands
This method automatically figures out which commands to send to the node, so you don't have to use the specific commands yourself. The returned promise resolves to `true` after the value was successfully updated on the node. It resolves to `false` if any of the following conditions are met:
<!-- TODO: Document API and setValue API -->
- The `setValue` API is not implemented in the required Command Class
- The required Command Class is not supported by the node/endpoint
- The required Command Class is not implemented in this library yet
- The API for the required Command Class is not implemented in this library yet
<!-- TODO: Check what happens if the CC is not supported by the node -->
The `options` bag contains options that influence the resulting commands, for example a transition duration. Each implementation will choose the options that are relevant for it, so you can use the same options everywhere.
<!-- #import SetValueAPIOptions from "zwave-js" -->
```ts
type SetValueAPIOptions = Partial<{
/** A duration to be used for transitions like dimming lights or activating scenes. */
transitionDuration: Duration | string;
}>;
```

### `pollValue`

Expand Down
13 changes: 12 additions & 1 deletion packages/zwave-js/src/lib/commandclass/API.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ValueID } from "@zwave-js/core";
import type { Duration, ValueID } from "@zwave-js/core";
import {
CommandClasses,
Maybe,
Expand All @@ -21,8 +21,19 @@ export const SET_VALUE: unique symbol = Symbol.for("CCAPI_SET_VALUE");
export type SetValueImplementation = (
property: ValueIDProperties,
value: unknown,
options?: SetValueAPIOptions,
) => Promise<void>;

/**
* A generic options bag for the `setValue` API.
* Each implementation will choose the options that are relevant for it, so you can use the same options everywhere.
* @publicAPI
*/
export type SetValueAPIOptions = Partial<{
/** A duration to be used for transitions like dimming lights or activating scenes. */
transitionDuration: Duration | string;
}>;

/** Used to identify the method on the CC API class that handles polling values from nodes */
export const POLL_VALUE: unique symbol = Symbol.for("CCAPI_POLL_VALUE");
export type PollValueImplementation<T extends unknown = unknown> = (
Expand Down
6 changes: 3 additions & 3 deletions packages/zwave-js/src/lib/commandclass/BinarySwitchCC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,16 @@ export class BinarySwitchCCAPI extends CCAPI {
protected [SET_VALUE]: SetValueImplementation = async (
{ property },
value,
options,
): Promise<void> => {
if (property !== "targetValue") {
throwUnsupportedProperty(this.ccId, property);
}
if (typeof value !== "boolean") {
throwWrongValueType(this.ccId, property, "boolean", typeof value);
}
await this.set(value);
const duration = Duration.from(options?.transitionDuration);
await this.set(value, duration);

// If the command did not fail, assume that it succeeded and update the currentValue accordingly
// so UIs have immediate feedback
Expand All @@ -138,8 +140,6 @@ export class BinarySwitchCCAPI extends CCAPI {
}

// Verify the current value after a delay
// TODO: #1321
const duration = undefined as Duration | undefined;
// We query currentValue instead of targetValue to make sure that unsolicited updates cancel the scheduled poll
// wotan-disable-next-line no-useless-predicate
if (property === "targetValue") property = "currentValue";
Expand Down
12 changes: 6 additions & 6 deletions packages/zwave-js/src/lib/commandclass/ColorSwitchCC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,8 +362,10 @@ export class ColorSwitchCCAPI extends CCAPI {
protected [SET_VALUE]: SetValueImplementation = async (
{ property, propertyKey },
value,
options,
) => {
if (property === "targetColor") {
const duration = Duration.from(options?.transitionDuration);
if (propertyKey != undefined) {
// Single color component, only accepts numbers
if (typeof propertyKey !== "number") {
Expand All @@ -380,13 +382,10 @@ export class ColorSwitchCCAPI extends CCAPI {
typeof value,
);
}

await this.set({ [propertyKey]: value });
await this.set({ [propertyKey]: value, duration });

if (this.isSinglecast()) {
// Verify the current value after a delay
// TODO: #1321
const duration = undefined as Duration | undefined;
this.schedulePoll(
{ property, propertyKey },
duration?.toMilliseconds(),
Expand Down Expand Up @@ -425,7 +424,7 @@ export class ColorSwitchCCAPI extends CCAPI {
// Avoid sending empty commands
if (Object.keys(value as any).length === 0) return;

await this.set(value as ColorTable);
await this.set({ ...(value as ColorTable), duration });

// We're not going to poll each color component separately
}
Expand All @@ -440,7 +439,8 @@ export class ColorSwitchCCAPI extends CCAPI {
);
}

await this.set({ hexColor: value });
const duration = Duration.from(options?.transitionDuration);
await this.set({ hexColor: value, duration });
} else {
throwUnsupportedProperty(this.ccId, property);
}
Expand Down
8 changes: 5 additions & 3 deletions packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ export class MultilevelSwitchCCAPI extends CCAPI {
protected [SET_VALUE]: SetValueImplementation = async (
{ property },
value,
options,
): Promise<void> => {
if (property === "targetValue") {
if (typeof value !== "number") {
Expand All @@ -306,7 +307,8 @@ export class MultilevelSwitchCCAPI extends CCAPI {
typeof value,
);
}
const completed = await this.set(value);
const duration = Duration.from(options?.transitionDuration);
const completed = await this.set(value, duration);

// If the command did not fail, assume that it succeeded and update the currentValue accordingly
// so UIs have immediate feedback
Expand All @@ -332,8 +334,6 @@ export class MultilevelSwitchCCAPI extends CCAPI {
.getNodeUnsafe()
?.supportsCC(CommandClasses.Supervision)
) {
// TODO: #1321
const duration = undefined as Duration | undefined;
// We query currentValue instead of targetValue to make sure that unsolicited updates cancel the scheduled poll
// wotan-disable-next-line no-useless-predicate
if (property === "targetValue")
Expand Down Expand Up @@ -409,10 +409,12 @@ export class MultilevelSwitchCCAPI extends CCAPI {
getCurrentValueValueId(this.endpoint.index),
);
// And perform the level change
const duration = Duration.from(options?.transitionDuration);
await this.startLevelChange({
direction,
ignoreStartLevel: true,
startLevel,
duration,
});
} else {
await this.stopLevelChange();
Expand Down
4 changes: 3 additions & 1 deletion packages/zwave-js/src/lib/commandclass/SceneActivationCC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,16 @@ export class SceneActivationCCAPI extends CCAPI {
protected [SET_VALUE]: SetValueImplementation = async (
{ property },
value,
options,
): Promise<void> => {
if (property !== "sceneId") {
throwUnsupportedProperty(this.ccId, property);
}
if (typeof value !== "number") {
throwWrongValueType(this.ccId, property, "number", typeof value);
}
await this.set(value);
const duration = Duration.from(options?.transitionDuration);
await this.set(value, duration);
};

/**
Expand Down
1 change: 1 addition & 0 deletions packages/zwave-js/src/lib/commandclass/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export {
} from "./AlarmSensorCC";
export type { AlarmSensorValueMetadata } from "./AlarmSensorCC";
export { CCAPI } from "./API";
export type { SetValueAPIOptions } from "./API";
export {
AssociationCC,
AssociationCCGet,
Expand Down
13 changes: 11 additions & 2 deletions packages/zwave-js/src/lib/node/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ import { padStart } from "alcalzone-shared/strings";
import { isArray, isObject } from "alcalzone-shared/typeguards";
import { randomBytes } from "crypto";
import { EventEmitter } from "events";
import type { CCAPI, PollValueImplementation } from "../commandclass/API";
import type {
CCAPI,
PollValueImplementation,
SetValueAPIOptions,
} from "../commandclass/API";
import { getHasLifelineValueId } from "../commandclass/AssociationCC";
import {
BasicCC,
Expand Down Expand Up @@ -729,7 +733,11 @@ export class ZWaveNode extends Endpoint {
* Updates a value for a given property of a given CommandClass on the node.
* This will communicate with the node!
*/
public async setValue(valueId: ValueID, value: unknown): Promise<boolean> {
public async setValue(
valueId: ValueID,
value: unknown,
options?: SetValueAPIOptions,
): Promise<boolean> {
// Try to retrieve the corresponding CC API
try {
// Access the CC API by name
Expand All @@ -747,6 +755,7 @@ export class ZWaveNode extends Endpoint {
propertyKey: valueId.propertyKey,
},
value,
options,
);
if (api.isSetValueOptimistic(valueId)) {
// If the call did not throw, assume that the call was successful and remember the new value
Expand Down

0 comments on commit fd30f5d

Please sign in to comment.