Skip to content

Commit

Permalink
fix: gate SmartStart behind SDK 6.81+, add method to test support (zw…
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone authored Nov 3, 2021
1 parent c35499b commit 6569fdd
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 96 deletions.
24 changes: 20 additions & 4 deletions docs/api/controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@ The controller instance contains information about the controller and a list of

## Controller methods

### `supportsFeature`

```ts
supportsFeature(feature: ZWaveFeature): boolean | undefined
```

Some Z-Wave features are not available on all controllers and can potentially create unwanted situations. The `supportsFeature` method must be used to check for support before using certain features. It returns a boolean indicating whether the feature is supported or `undefined` if this information isn't known yet.

The available features to test for are:

<!-- #import ZWaveFeature from "zwave-js" -->

```ts
enum ZWaveFeature {
SmartStart,
}
```

### `beginInclusion`

```ts
Expand All @@ -17,10 +35,6 @@ The options parameter is used to specify the inclusion strategy and provide call
- `InclusionStrategy.Default`: Prefer _Security S2_ if supported, use _Security S0_ if absolutely necessary (e.g. for legacy locks) or if opted in with the `forceSecurity` flag, don't use encryption otherwise.
**This is the recommended** strategy and should be used unless there is a good reason not to.

- `InclusionStrategy.SmartStart`: Include using SmartStart (requires Security S2). Can't include devices that do not support SmartStart.
**Should be preferred** over `Default` if supported, because it is easier for the user.
**WARNING:** This is not supported yet!

- `InclusionStrategy.Insecure`: Don't use encryption, even if supported.
**Not recommended**, because S2 should be used where possible.

Expand Down Expand Up @@ -178,6 +192,8 @@ provisionSmartStartNode(entry: PlannedProvisioningEntry): void

Adds the given entry (DSK and security classes) to the controller's SmartStart provisioning list or replaces an existing entry. The node will be included out of band when it powers up.

> [!ATTENTION] This method will throw when SmartStart is not supported by the controller!
The parameter has the following shape:

<!-- #import PlannedProvisioningEntry from "zwave-js" -->
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,6 @@ export function mergeDeep(

/** Pads a firmware version string, so it can be compared with semver */
export function padVersion(version: string): string {
if (version.split(".").length === 3) return version;
return version + ".0";
}
1 change: 1 addition & 0 deletions packages/zwave-js/src/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export type {
ZWaveController,
} from "./lib/controller/Controller";
export type { ControllerStatistics } from "./lib/controller/ControllerStatistics";
export { ZWaveFeature } from "./lib/controller/Features";
export * from "./lib/controller/Inclusion";
export type { ZWaveLibraryTypes } from "./lib/controller/ZWaveLibraryTypes";
export { RFRegion } from "./lib/serialapi/misc/SerialAPISetupMessages";
92 changes: 70 additions & 22 deletions packages/zwave-js/src/lib/controller/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ import {
} from "./ControllerStatistics";
import { DeleteReturnRouteRequest } from "./DeleteReturnRouteMessages";
import { DeleteSUCReturnRouteRequest } from "./DeleteSUCReturnRouteMessages";
import { minFeatureVersions, ZWaveFeature } from "./Features";
import {
GetControllerCapabilitiesRequest,
GetControllerCapabilitiesResponse,
Expand Down Expand Up @@ -241,9 +242,12 @@ import {
} from "./SetSerialApiTimeoutsMessages";
import { SetSUCNodeIdRequest } from "./SetSUCNodeIDMessages";
import { ZWaveLibraryTypes } from "./ZWaveLibraryTypes";
import { protocolVersionToSDKVersion } from "./ZWaveSDKVersions";

export type HealNodeStatus = "pending" | "done" | "failed" | "skipped";
type SerialAPIVersion = `${number}.${number}`;
export type SerialAPIVersion =
| `${number}.${number}`
| `${number}.${number}.${number}`;

export type ThrowingMap<K, V> = Map<K, V> & { getOrThrow(key: K): V };
export type ReadonlyThrowingMap<K, V> = ReadonlyMap<K, V> & {
Expand Down Expand Up @@ -370,46 +374,42 @@ export class ZWaveController extends TypedEventEmitter<ControllerEventCallbacks>

/** Checks if the Serial API version is greater than the given one */
public serialApiGt(version: SerialAPIVersion): boolean | undefined {
if (this._serialApiVersion === undefined) {
// TODO: Rename these to sdkVersionGt(e) etc...
if (this._libraryVersion === undefined) {
return undefined;
}
return semver.gt(
padVersion(this._serialApiVersion),
padVersion(version),
);
const sdkVersion = protocolVersionToSDKVersion(this._libraryVersion);
return semver.gt(padVersion(sdkVersion), padVersion(version));
}

/** Checks if the Serial API version is greater than or equal to the given one */
public serialApiGte(version: SerialAPIVersion): boolean | undefined {
if (this._serialApiVersion === undefined) {
// TODO: Rename these to sdkVersionGt(e) etc...
if (this._libraryVersion === undefined) {
return undefined;
}
return semver.gte(
padVersion(this._serialApiVersion),
padVersion(version),
);
const sdkVersion = protocolVersionToSDKVersion(this._libraryVersion);
return semver.gte(padVersion(sdkVersion), padVersion(version));
}

/** Checks if the Serial API version is lower than the given one */
public serialApiLt(version: SerialAPIVersion): boolean | undefined {
if (this._serialApiVersion === undefined) {
// TODO: Rename these to sdkVersionGt(e) etc...
if (this._libraryVersion === undefined) {
return undefined;
}
return semver.lt(
padVersion(this._serialApiVersion),
padVersion(version),
);
const sdkVersion = protocolVersionToSDKVersion(this._libraryVersion);
return semver.lt(padVersion(sdkVersion), padVersion(version));
}

/** Checks if the Serial API version is lower than or equal to the given one */
public serialApiLte(version: SerialAPIVersion): boolean | undefined {
if (this._serialApiVersion === undefined) {
// TODO: Rename these to sdkVersionGt(e) etc...
if (this._libraryVersion === undefined) {
return undefined;
}
return semver.lte(
padVersion(this._serialApiVersion),
padVersion(version),
);
const sdkVersion = protocolVersionToSDKVersion(this._libraryVersion);
return semver.lte(padVersion(sdkVersion), padVersion(version));
}

private _manufacturerId: number | undefined;
Expand Down Expand Up @@ -465,6 +465,27 @@ export class ZWaveController extends TypedEventEmitter<ControllerEventCallbacks>
return this._supportedSerialAPISetupCommands.indexOf(command) > -1;
}

/**
* Tests if the controller supports a certain feature.
* Returns `undefined` if this information isn't known yet.
*/
public supportsFeature(feature: ZWaveFeature): boolean | undefined {
switch (feature) {
case ZWaveFeature.SmartStart:
return this.serialApiGte(minFeatureVersions[feature]);
}
}

/** Throws if the controller does not support a certain feature */
private assertFeature(feature: ZWaveFeature): void {
if (!this.supportsFeature(feature)) {
throw new ZWaveError(
`The controller does not support the ${feature} feature`,
ZWaveErrorCodes.Controller_NotSupported,
);
}
}

private _sucNodeId: number | undefined;
public get sucNodeId(): number | undefined {
return this._sucNodeId;
Expand Down Expand Up @@ -533,6 +554,9 @@ export class ZWaveController extends TypedEventEmitter<ControllerEventCallbacks>

/** Adds the given entry (DSK and security classes) to the controller's SmartStart provisioning list or replaces an existing entry */
public provisionSmartStartNode(entry: PlannedProvisioningEntry): void {
// Make sure the controller supports SmartStart
this.assertFeature(ZWaveFeature.SmartStart);

const index = this._provisioningList.findIndex(
(e) => e.dsk === entry.dsk,
);
Expand Down Expand Up @@ -603,6 +627,9 @@ export class ZWaveController extends TypedEventEmitter<ControllerEventCallbacks>
* Automatically starts smart start inclusion if there are nodes pending inclusion.
*/
public autoProvisionSmartStart(): void {
// Make sure the controller supports SmartStart
if (!this.supportsFeature(ZWaveFeature.SmartStart)) return;

if (this.hasPlannedProvisioningEntries()) {
// SmartStart should be enabled
// eslint-disable-next-line @typescript-eslint/no-empty-function
Expand Down Expand Up @@ -701,6 +728,15 @@ export class ZWaveController extends TypedEventEmitter<ControllerEventCallbacks>
library version: ${this._libraryVersion}`,
);

this.driver.controllerLog.print(
`supported Z-Wave features: ${Object.keys(ZWaveFeature)
.filter((k) => /^\d+$/.test(k))
.map((k) => parseInt(k) as ZWaveFeature)
.filter((feat) => this.supportsFeature(feat))
.map((feat) => `\n · ${getEnumMemberName(ZWaveFeature, feat)}`)
.join("")}`,
);

// find out what the controller can do
this.driver.controllerLog.print(`querying controller capabilities...`);
const ctrlCaps =
Expand Down Expand Up @@ -1038,7 +1074,11 @@ export class ZWaveController extends TypedEventEmitter<ControllerEventCallbacks>
private setInclusionState(state: InclusionState): void {
if (this._inclusionState === state) return;
this._inclusionState = state;
if (state === InclusionState.Idle && this._smartStartEnabled) {
if (
state === InclusionState.Idle &&
this._smartStartEnabled &&
this.supportsFeature(ZWaveFeature.SmartStart)
) {
// If Smart Start was enabled before the inclusion/exclusion,
// enable it again and ignore errors

Expand Down Expand Up @@ -1315,6 +1355,13 @@ export class ZWaveController extends TypedEventEmitter<ControllerEventCallbacks>
* Resolves to `true` when the listening mode is started or was active, and `false` if it is scheduled for later activation.
*/
private async enableSmartStart(): Promise<boolean> {
if (!this.supportsFeature(ZWaveFeature.SmartStart)) {
this.driver.controllerLog.print(
`Smart Start is not supported by this controller, NOT enabling listening mode...`,
"warn",
);
}

this._smartStartEnabled = true;

if (this._inclusionState === InclusionState.Idle) {
Expand Down Expand Up @@ -1357,6 +1404,7 @@ export class ZWaveController extends TypedEventEmitter<ControllerEventCallbacks>
* Resolves to `true` when the listening mode is stopped, and `false` if was not active.
*/
private async disableSmartStart(): Promise<boolean> {
if (!this.supportsFeature(ZWaveFeature.SmartStart)) return true;
this._smartStartEnabled = false;

if (this._inclusionState === InclusionState.SmartStart) {
Expand Down
11 changes: 11 additions & 0 deletions packages/zwave-js/src/lib/controller/Features.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { SerialAPIVersion } from "./Controller";

/** A named list of Z-Wave features */
export enum ZWaveFeature {
// Available starting with Z-Wave SDK 6.81
SmartStart,
}

export const minFeatureVersions: Record<ZWaveFeature, SerialAPIVersion> = {
[ZWaveFeature.SmartStart]: "6.81",
};
Loading

0 comments on commit 6569fdd

Please sign in to comment.