Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: skip rebuilding routes for nodes with priority return routes #7252

Merged
merged 2 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/api/controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,8 @@ async rebuildNodeRoutes(nodeId: number): Promise<boolean>

Rebuilds routes for a single alive node in the network, updating the neighbor list and assigning fresh routes to association targets. The returned promise resolves to `true` if the process was completed, or `false` if it was unsuccessful.

> [!ATTENTION] Rebuilding routes for a single node will delete existing priority return routes to end nodes and the SUC. It is recommended to first check if priority return routes are known to exist using `getPriorityReturnRoutesCached` and `getPrioritySUCReturnRouteCached` and asking for confirmation before proceeding.

#### `beginRebuildingRoutes`

```ts
Expand All @@ -449,6 +451,8 @@ The `options` argument can be used to skip sleeping nodes:
interface RebuildRoutesOptions {
/** Whether the routes of sleeping nodes should be rebuilt too at the end of the process. Default: true */
includeSleeping?: boolean;
/** Whether nodes with priority return routes should be included, as those will be deleted. Default: false */
deletePriorityReturnRoutes?: boolean;
}
```

Expand Down Expand Up @@ -628,10 +632,12 @@ As mentioned before, there is unfortunately no way to query return routes from a

```ts
getPriorityReturnRouteCached(nodeId: number, destinationNodeId: number): MaybeUnknown<Route> | undefined;
getPriorityReturnRoutesCached(nodeId: number): Record<number, Route>;
getPrioritySUCReturnRouteCached(nodeId: number): MaybeUnknown<Route> | undefined;
```

- `getPriorityReturnRouteCached` returns a priority return route that was set using `assignPriorityReturnRoute`. If a non-priority return route has been set since assigning the priority route, this will return `UNKNOWN_STATE` (`null`).
- `getPriorityReturnRoutesCached` returns an object containing the IDs of all known end node destinations a node has priority return routes for and their respective routes.
- `getPrioritySUCReturnRouteCached` does the same for a route set through `assignPrioritySUCReturnRoute`.

The return type `Route` has the following shape:
Expand Down
39 changes: 39 additions & 0 deletions packages/zwave-js/src/lib/controller/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4579,6 +4579,7 @@ export class ZWaveController
if (existingTask) return false;

options.includeSleeping ??= true;
options.deletePriorityReturnRoutes ??= false;

this.driver.controllerLog.print(
`rebuilding routes${
Expand All @@ -4605,6 +4606,21 @@ export class ZWaveController
);
this._rebuildRoutesProgress.set(id, "skipped");
} else if (!options.includeSleeping && node.canSleep) {
this.driver.controllerLog.logNode(
id,
`Skipping route rebuild because the node is sleeping.`,
);
this._rebuildRoutesProgress.set(id, "skipped");
} else if (
!options.deletePriorityReturnRoutes
&& (this.getPrioritySUCReturnRouteCached(id)
|| Object.keys(this.getPriorityReturnRoutesCached(id))
.length > 0)
) {
this.driver.controllerLog.logNode(
id,
`Skipping route rebuild because the node has priority return routes.`,
);
this._rebuildRoutesProgress.set(id, "skipped");
} else {
this._rebuildRoutesProgress.set(id, "pending");
Expand Down Expand Up @@ -5804,6 +5820,29 @@ export class ZWaveController
);
}

/**
* For the given node, returns all end node destinations and the priority routes to them.
*
* **Note:** This is using cached information, since there's no way to query priority routes from a node.
* If another controller has assigned routes in the meantime, this information may be out of date.
*/
public getPriorityReturnRoutesCached(
nodeId: number,
): Record<number, Route> {
const ret: Record<number, Route> = {};

const routes = this.driver.cacheList<Route>(
cacheKeys.node(nodeId)._priorityReturnRouteBaseKey,
);
for (const [key, route] of Object.entries(routes)) {
const destination = cacheKeyUtils
.destinationFromPriorityReturnRouteKey(key);
if (destination !== undefined) ret[destination] = route;
}

return ret;
}

/**
* Assigns a priority route from an end node to the SUC. This route will always be used for the first transmission attempt.
* @param nodeId The ID of the end node for which to assign the route
Expand Down
2 changes: 2 additions & 0 deletions packages/zwave-js/src/lib/controller/_Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export type RebuildRoutesStatus = "pending" | "done" | "failed" | "skipped";
export interface RebuildRoutesOptions {
/** Whether the routes of sleeping nodes should be rebuilt too at the end of the process. Default: true */
includeSleeping?: boolean;
/** Whether nodes with priority return routes should be included, as those will be deleted. Default: false */
deletePriorityReturnRoutes?: boolean;
}

export type SDKVersion =
Expand Down
29 changes: 29 additions & 0 deletions packages/zwave-js/src/lib/driver/Driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6731,6 +6731,35 @@ ${handlers.length} left`,
}
}

/**
* @internal
* Helper function to find multiple existing values from the network cache
*/
public cacheList<T>(
prefix: string,
options?: {
reviver?: (value: any) => T;
},
): Record<string, T> {
const ret: Record<string, T> = {};
for (const entry of this.networkCache.entries()) {
const key = entry[0];
if (!key.startsWith(prefix)) continue;
let value = entry[1];
if (value === undefined) continue;
if (typeof options?.reviver === "function") {
try {
value = options.reviver(value);
} catch {
// invalid entry
continue;
}
}
ret[key] = value;
}
return ret;
}

private cachePurge(prefix: string): void {
for (const key of this.networkCache.keys()) {
if (key.startsWith(prefix)) {
Expand Down
9 changes: 9 additions & 0 deletions packages/zwave-js/src/lib/driver/NetworkCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const cacheKeys = {
return {
_baseKey: nodeBaseKey,
_securityClassBaseKey: `${nodeBaseKey}securityClasses`,
_priorityReturnRouteBaseKey: `${nodeBaseKey}priorityReturnRoute`,
interviewStage: `${nodeBaseKey}interviewStage`,
deviceClass: `${nodeBaseKey}deviceClass`,
isListening: `${nodeBaseKey}isListening`,
Expand Down Expand Up @@ -117,6 +118,14 @@ export const cacheKeyUtils = {
return parseInt(match.groups!.index, 10);
}
},
destinationFromPriorityReturnRouteKey: (
key: string,
): number | undefined => {
const match = /\.priorityReturnRoute\.(?<nodeId>\d+)$/.exec(key);
if (match) {
return parseInt(match.groups!.nodeId, 10);
}
},
} as const;

function tryParseInterviewStage(value: unknown): InterviewStage | undefined {
Expand Down
Loading