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

refactor: reorganize definitions in @zwave-js/core #7376

Merged
merged 3 commits into from
Nov 6, 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
433 changes: 0 additions & 433 deletions packages/core/src/consts/Transmission.ts

This file was deleted.

7 changes: 7 additions & 0 deletions packages/core/src/definitions/EncapsulationFlags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export enum EncapsulationFlags {
None = 0,
Supervision = 1 << 0,
// Multi Channel is tracked through the endpoint index
Security = 1 << 1,
CRC16 = 1 << 2,
}
2 changes: 2 additions & 0 deletions packages/core/src/definitions/Frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ export enum BeamingInfo {
LongContinuous = 0b10,
Fragmented = 0b100,
}

export type FrameType = "singlecast" | "broadcast" | "multicast";
33 changes: 33 additions & 0 deletions packages/core/src/definitions/MessagePriority.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/** The priority of messages, sorted from high (0) to low (>0) */

export enum MessagePriority {
// High-priority controller commands that must be handled before all other commands.
// We use this priority to decide which messages go onto the immediate queue.
ControllerImmediate = 0,
// Controller commands finish quickly and should be preferred over node queries
Controller,
// Some node commands like nonces, responses to Supervision and Transport Service
// need to be handled before all node commands.
// We use this priority to decide which messages go onto the immediate queue.
Immediate,
// To avoid S2 collisions, some commands that normally have Immediate priority
// have to go onto the normal queue, but still before all other messages
ImmediateLow,
// Pings (NoOP) are used for device probing at startup and for network diagnostics
Ping,
// Whenever sleeping devices wake up, their queued messages must be handled quickly
// because they want to go to sleep soon. So prioritize them over non-sleeping devices
WakeUp,
// Normal operation and node data exchange
Normal,
// Node querying is expensive and happens whenever a new node is discovered.
// In order to keep the system responsive, give them a lower priority
NodeQuery,
// Some devices need their state to be polled at regular intervals. Only do that when
// nothing else needs to be done
Poll,
}

export function isMessagePriority(val: unknown): val is MessagePriority {
return typeof val === "number" && val in MessagePriority;
}
18 changes: 18 additions & 0 deletions packages/core/src/definitions/NodeID.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { MAX_NODES } from "./consts.js";

export enum NodeIDType {
Short = 0x01,
Long = 0x02,
}

/** The broadcast target node id */
export const NODE_ID_BROADCAST = 0xff;

/** The broadcast target node id for Z-Wave LR */
export const NODE_ID_BROADCAST_LR = 0xfff;

/** The highest allowed node id */
// FIXME: Rename probably
export const NODE_ID_MAX = MAX_NODES;

export type MulticastDestination = [number, number, ...number[]];
2 changes: 1 addition & 1 deletion packages/core/src/definitions/NodeInfo.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Bytes } from "@zwave-js/shared/safe";
import { sum } from "@zwave-js/shared/safe";
import { NodeIDType } from "../consts/index.js";
import { type BasicDeviceClass } from "../registries/DeviceClasses.js";
import { validatePayload } from "../util/misc.js";
import { CommandClasses } from "./CommandClasses.js";
import { NodeIDType } from "./NodeID.js";
import { type ProtocolVersion } from "./Protocol.js";

export interface ApplicationNodeInformation {
Expand Down
55 changes: 55 additions & 0 deletions packages/core/src/definitions/RSSI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/** A number between -128 and +124 dBm or one of the special values in {@link RssiError} indicating an error */

export type RSSI = number | RssiError;

export enum RssiError {
NotAvailable = 127,
ReceiverSaturated = 126,
NoSignalDetected = 125,
}

export function isRssiError(rssi: RSSI): rssi is RssiError {
return rssi >= RssiError.NoSignalDetected;
}
/** Averages RSSI measurements using an exponential moving average with the given weight for the accumulator */

export function averageRSSI(
acc: number | undefined,
rssi: RSSI,
weight: number,
): number {
if (isRssiError(rssi)) {
switch (rssi) {
case RssiError.NotAvailable:
// If we don't have a value yet, return 0
return acc ?? 0;
case RssiError.ReceiverSaturated:
// Assume rssi is 0 dBm
rssi = 0;
break;
case RssiError.NoSignalDetected:
// Assume rssi is -128 dBm
rssi = -128;
break;
}
}

if (acc == undefined) return rssi;
return Math.round(acc * weight + rssi * (1 - weight));
}
/**
* Converts an RSSI value to a human readable format, i.e. the measurement including the unit or the corresponding error message.
*/

export function rssiToString(rssi: RSSI): string {
switch (rssi) {
case RssiError.NotAvailable:
return "N/A";
case RssiError.ReceiverSaturated:
return "Receiver saturated";
case RssiError.NoSignalDetected:
return "No signal detected";
default:
return `${rssi} dBm`;
}
}
3 changes: 3 additions & 0 deletions packages/core/src/definitions/Route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ export function isEmptyRoute(route: Route): boolean {
&& route.routeSpeed === ZWaveDataRate["9k6"]
);
}

/** How many repeaters can appear in a route */
export const MAX_REPEATERS = 4;
42 changes: 42 additions & 0 deletions packages/core/src/definitions/RoutingScheme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { num2hex } from "@zwave-js/shared/safe";

/**
* How the controller transmitted a frame to a node.
*/

export enum RoutingScheme {
Idle,
Direct,
Priority,
LWR,
NLWR,
Auto,
ResortDirect,
Explore,
}
/**
* Converts a routing scheme value to a human readable format.
*/

export function routingSchemeToString(scheme: RoutingScheme): string {
switch (scheme) {
case RoutingScheme.Idle:
return "Idle";
case RoutingScheme.Direct:
return "Direct";
case RoutingScheme.Priority:
return "Priority Route";
case RoutingScheme.LWR:
return "LWR";
case RoutingScheme.NLWR:
return "NLWR";
case RoutingScheme.Auto:
return "Auto Route";
case RoutingScheme.ResortDirect:
return "Resort to Direct";
case RoutingScheme.Explore:
return "Explorer Frame";
default:
return `Unknown (${num2hex(scheme)})`;
}
}
108 changes: 108 additions & 0 deletions packages/core/src/definitions/Supervision.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { isObject } from "alcalzone-shared/typeguards";
import { Duration } from "../values/Duration.js";

export enum SupervisionStatus {
NoSupport = 0x00,
Working = 0x01,
Fail = 0x02,
Success = 0xff,
}

export type SupervisionResult =
| {
status:
| SupervisionStatus.NoSupport
| SupervisionStatus.Fail
| SupervisionStatus.Success;
remainingDuration?: undefined;
}
| {
status: SupervisionStatus.Working;
remainingDuration: Duration;
};

export type SupervisionUpdateHandler = (update: SupervisionResult) => void;

export function isSupervisionResult(obj: unknown): obj is SupervisionResult {
return (
isObject(obj)
&& "status" in obj
&& typeof SupervisionStatus[obj.status as any] === "string"
);
}

export function supervisedCommandSucceeded(
result: unknown,
): result is SupervisionResult & {
status: SupervisionStatus.Success | SupervisionStatus.Working;
} {
return (
isSupervisionResult(result)
&& (result.status === SupervisionStatus.Success
|| result.status === SupervisionStatus.Working)
);
}

export function supervisedCommandFailed(
result: unknown,
): result is SupervisionResult & {
status: SupervisionStatus.Fail | SupervisionStatus.NoSupport;
} {
return (
isSupervisionResult(result)
&& (result.status === SupervisionStatus.Fail
|| result.status === SupervisionStatus.NoSupport)
);
}

export function isUnsupervisedOrSucceeded(
result: SupervisionResult | undefined,
): result is
| undefined
| (SupervisionResult & {
status: SupervisionStatus.Success | SupervisionStatus.Working;
})
{
return !result || supervisedCommandSucceeded(result);
}

/** Figures out the final supervision result from an array of things that may be supervision results */
export function mergeSupervisionResults(
results: unknown[],
): SupervisionResult | undefined {
const supervisionResults = results.filter(isSupervisionResult);
if (!supervisionResults.length) return undefined;

if (supervisionResults.some((r) => r.status === SupervisionStatus.Fail)) {
return {
status: SupervisionStatus.Fail,
};
} else if (
supervisionResults.some((r) => r.status === SupervisionStatus.NoSupport)
) {
return {
status: SupervisionStatus.NoSupport,
};
}
const working = supervisionResults.filter(
(r): r is SupervisionResult & { status: SupervisionStatus.Working } =>
r.status === SupervisionStatus.Working,
);
if (working.length > 0) {
const durations = working.map((r) =>
r.remainingDuration.serializeSet()
);
const maxDuration = (durations.length > 0
&& Duration.parseReport(Math.max(...durations)))
|| Duration.unknown();
return {
status: SupervisionStatus.Working,
remainingDuration: maxDuration,
};
}
return {
status: SupervisionStatus.Success,
};
}

export const MAX_SUPERVISION_SESSION_ID = 0b111111;
49 changes: 49 additions & 0 deletions packages/core/src/definitions/TXReport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { ProtocolDataRate } from "./Protocol.js";
import type { RSSI } from "./RSSI.js";
import type { RoutingScheme } from "./RoutingScheme.js";

/** Information about the transmission as received by the controller */

export interface TXReport {
/** Transmission time in ticks (multiples of 10ms) */
txTicks: number;
/** RSSI value of the acknowledgement frame */
ackRSSI?: RSSI;
/** RSSI values of the incoming acknowledgement frame, measured by repeater 0...3 */
ackRepeaterRSSI?: [RSSI?, RSSI?, RSSI?, RSSI?];
/** Channel number the acknowledgement frame is received on */
ackChannelNo?: number;
/** Channel number used to transmit the data */
txChannelNo: number;
/** State of the route resolution for the transmission attempt. Encoding is manufacturer specific. Z-Wave JS uses the Silicon Labs interpretation. */
routeSchemeState: RoutingScheme;
/** Node IDs of the repeater 0..3 used in the route. */
repeaterNodeIds: [number?, number?, number?, number?];
/** Whether the destination requires a 1000ms beam to be reached */
beam1000ms: boolean;
/** Whether the destination requires a 250ms beam to be reached */
beam250ms: boolean;
/** Transmission speed used in the route */
routeSpeed: ProtocolDataRate;
/** How many routing attempts have been made to transmit the payload */
routingAttempts: number;
/** When a route failed, this indicates the last functional Node ID in the last used route */
failedRouteLastFunctionalNodeId?: number;
/** When a route failed, this indicates the first non-functional Node ID in the last used route */
failedRouteFirstNonFunctionalNodeId?: number;
/** Transmit power used for the transmission in dBm */
txPower?: number;
/** Measured noise floor during the outgoing transmission */
measuredNoiseFloor?: RSSI;
/** TX power in dBm used by the destination to transmit the ACK */
destinationAckTxPower?: number;
/** Measured RSSI of the acknowledgement frame received from the destination */
destinationAckMeasuredRSSI?: RSSI;
/** Noise floor measured by the destination during the ACK transmission */
destinationAckMeasuredNoiseFloor?: RSSI;
}
/** Information about the transmission, but for serialization in mocks */

export type SerializableTXReport =
& Partial<Omit<TXReport, "numRepeaters">>
& Pick<TXReport, "txTicks" | "routeSpeed">;
29 changes: 29 additions & 0 deletions packages/core/src/definitions/Transactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* The state a transaction is in.
*/

export enum TransactionState {
/** The transaction is currently queued */
Queued,
/** The transaction is currently being handled */
Active,
/** The transaction was completed */
Completed,
/** The transaction failed */
Failed,
}

export type TransactionProgress = {
state:
| TransactionState.Queued
| TransactionState.Active
| TransactionState.Completed;
} | {
state: TransactionState.Failed;
/** Why the transaction failed */
reason?: string;
};

export type TransactionProgressListener = (
progress: TransactionProgress,
) => void;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { test } from "vitest";
import { MessagePriority, isMessagePriority } from "./Transmission.js";
import { MessagePriority, isMessagePriority } from "./MessagePriority.js";

test("isMessagePriority() should detect numbers in the enum range as a message priority", (t) => {
const numericKeys = Object.keys(MessagePriority)
Expand Down
Loading
Loading