Skip to content

Commit 6c10570

Browse files
authored
refactor: replace xstate with a simple built-in state machine (#7460)
1 parent 6fe88f9 commit 6c10570

18 files changed

+1424
-1823
lines changed

.github/dependabot.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ updates:
1818
# so only apply patch and minor updates automatically
1919
- dependency-name: '@types/node'
2020
update-types: ['version-update:semver-major']
21-
# xstate usually requires manual intervention
22-
- dependency-name: 'xstate'
2321

2422
- package-ecosystem: github-actions
2523
directory: '/'

packages/cc/src/cc/TransportServiceCC.ts

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,10 @@ import type {
1515
} from "@zwave-js/host/safe";
1616
import { Bytes } from "@zwave-js/shared/safe";
1717
import { buffer2hex } from "@zwave-js/shared/safe";
18-
import {
19-
type CCRaw,
20-
type CCResponseRole,
21-
CommandClass,
22-
} from "../lib/CommandClass.js";
18+
import { type CCRaw, CommandClass } from "../lib/CommandClass.js";
2319
import {
2420
CCCommand,
2521
commandClass,
26-
expectedCCResponse,
2722
implementedVersion,
2823
} from "../lib/CommandClassDecorators.js";
2924
import { TransportServiceCommand } from "../lib/_Types.js";
@@ -81,7 +76,7 @@ export function isTransportServiceEncapsulation(
8176
}
8277

8378
@CCCommand(TransportServiceCommand.FirstSegment)
84-
// @expectedCCResponse(TransportServiceCCReport)
79+
// Handling expected responses is done by the RX state machine
8580
export class TransportServiceCCFirstSegment extends TransportServiceCC {
8681
public constructor(
8782
options: WithAddress<TransportServiceCCFirstSegmentOptions>,
@@ -227,7 +222,7 @@ export interface TransportServiceCCSubsequentSegmentOptions
227222
}
228223

229224
@CCCommand(TransportServiceCommand.SubsequentSegment)
230-
// @expectedCCResponse(TransportServiceCCReport)
225+
// Handling expected responses is done by the RX state machine
231226
export class TransportServiceCCSubsequentSegment extends TransportServiceCC {
232227
public constructor(
233228
options: WithAddress<TransportServiceCCSubsequentSegmentOptions>,
@@ -465,23 +460,8 @@ export interface TransportServiceCCSegmentRequestOptions {
465460
datagramOffset: number;
466461
}
467462

468-
function testResponseForSegmentRequest(
469-
sent: TransportServiceCCSegmentRequest,
470-
received: TransportServiceCC,
471-
): CCResponseRole {
472-
return (
473-
(sent.datagramOffset === 0
474-
&& received instanceof TransportServiceCCFirstSegment
475-
&& received.sessionId === sent.sessionId)
476-
|| (sent.datagramOffset > 0
477-
&& received instanceof TransportServiceCCSubsequentSegment
478-
&& sent.datagramOffset === received.datagramOffset
479-
&& received.sessionId === sent.sessionId)
480-
);
481-
}
482-
483463
@CCCommand(TransportServiceCommand.SegmentRequest)
484-
@expectedCCResponse(TransportServiceCC, testResponseForSegmentRequest)
464+
// Handling expected responses is done by the RX state machine
485465
export class TransportServiceCCSegmentRequest extends TransportServiceCC {
486466
public constructor(
487467
options: WithAddress<TransportServiceCCSegmentRequestOptions>,

packages/core/src/fsm/FSM.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
export interface StateMachineTransition<
2+
State extends StateMachineState,
3+
Effect = undefined,
4+
> {
5+
effect?: Effect;
6+
newState: State;
7+
}
8+
9+
export interface StateMachineState {
10+
value: number | string;
11+
done?: boolean;
12+
}
13+
14+
export interface StateMachineInput {
15+
value: number | string;
16+
}
17+
18+
export type StateMachineTransitionMap<
19+
State extends StateMachineState,
20+
Input extends StateMachineInput,
21+
Effect = undefined,
22+
> = (
23+
state: State,
24+
) => (
25+
input: Input,
26+
) => StateMachineTransition<State, Effect | undefined> | undefined;
27+
28+
export type InferStateMachineTransitions<
29+
T extends StateMachine<any, any, any>,
30+
> = T extends StateMachine<infer S, infer I, infer E>
31+
? StateMachineTransitionMap<S, I, E | undefined>
32+
: never;
33+
34+
export class StateMachine<
35+
State extends StateMachineState,
36+
Input extends StateMachineInput,
37+
Effect = undefined,
38+
> {
39+
public constructor(
40+
initialState: State,
41+
transitions: StateMachineTransitionMap<
42+
State,
43+
Input,
44+
Effect | undefined
45+
>,
46+
) {
47+
this._initial = this._state = initialState;
48+
this.transitions = transitions;
49+
}
50+
51+
protected transitions: StateMachineTransitionMap<
52+
State,
53+
Input,
54+
Effect | undefined
55+
>;
56+
57+
/** Restarts the machine from the initial state */
58+
public restart(): void {
59+
this._state = this._initial;
60+
}
61+
62+
/** Determines the next transition to take */
63+
public next(
64+
input: Input,
65+
): StateMachineTransition<State, Effect | undefined> | undefined {
66+
if (this._state.done) return;
67+
return this.transitions(this._state)(input);
68+
}
69+
70+
/** Transitions the machine to the next state. This does not execute effects */
71+
public transition(next?: State): void {
72+
// Allow some convenience by passing the transition's next state directly
73+
if (next == undefined) return;
74+
this._state = next;
75+
}
76+
77+
private _initial: State;
78+
private _state: State;
79+
/** Returns the current state of the state machine */
80+
public get state(): State {
81+
return this._state;
82+
}
83+
84+
/** Returns whether this state machine is done */
85+
public get done(): boolean {
86+
return !!this._state.done;
87+
}
88+
}

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from "./crypto/index.node.js";
33
export * from "./definitions/index.js";
44
export * from "./dsk/index.js";
55
export * from "./error/ZWaveError.js";
6+
export * from "./fsm/FSM.js";
67
export * from "./log/Controller.js";
78
export * from "./log/shared.js";
89
export * from "./log/shared_safe.js";

packages/zwave-js/package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,7 @@
118118
"semver": "^7.6.3",
119119
"serialport": "^12.0.0",
120120
"source-map-support": "^0.5.21",
121-
"winston": "^3.15.0",
122-
"xstate": "4.38.3"
121+
"winston": "^3.15.0"
123122
},
124123
"devDependencies": {
125124
"@alcalzone/esm2cjs": "^1.4.1",
@@ -130,7 +129,6 @@
130129
"@types/semver": "^7.5.8",
131130
"@types/sinon": "^17.0.3",
132131
"@types/source-map-support": "^0.5.10",
133-
"@xstate/test": "^0.5.1",
134132
"@zwave-js/maintenance": "workspace:*",
135133
"@zwave-js/transformers": "workspace:*",
136134
"del-cli": "^6.0.0",

0 commit comments

Comments
 (0)