jmri-client 3.5.3 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -618,6 +618,26 @@ function powerStateToString(state) {
618
618
  return "UNKNOWN";
619
619
  }
620
620
  }
621
+ var TurnoutState = /* @__PURE__ */ ((TurnoutState2) => {
622
+ TurnoutState2[TurnoutState2["UNKNOWN"] = 0] = "UNKNOWN";
623
+ TurnoutState2[TurnoutState2["CLOSED"] = 2] = "CLOSED";
624
+ TurnoutState2[TurnoutState2["THROWN"] = 4] = "THROWN";
625
+ TurnoutState2[TurnoutState2["INCONSISTENT"] = 8] = "INCONSISTENT";
626
+ return TurnoutState2;
627
+ })(TurnoutState || {});
628
+ function turnoutStateToString(state) {
629
+ switch (state) {
630
+ case 2 /* CLOSED */:
631
+ return "CLOSED";
632
+ case 4 /* THROWN */:
633
+ return "THROWN";
634
+ case 8 /* INCONSISTENT */:
635
+ return "INCONSISTENT";
636
+ case 0 /* UNKNOWN */:
637
+ default:
638
+ return "UNKNOWN";
639
+ }
640
+ }
621
641
 
622
642
  // src/mocks/mock-data.ts
623
643
  var mockData = {
@@ -803,6 +823,13 @@ var mockData = {
803
823
  }
804
824
  }
805
825
  },
826
+ "turnout": {
827
+ "list": [
828
+ { "type": "turnout", "data": { "name": "LT1", "userName": "Main Diverge", "state": 2 } },
829
+ { "type": "turnout", "data": { "name": "LT2", "userName": "Yard Lead", "state": 2 } },
830
+ { "type": "turnout", "data": { "name": "LT3", "userName": "Siding Entry", "state": 4 } }
831
+ ]
832
+ },
806
833
  "ping": {
807
834
  "type": "ping"
808
835
  },
@@ -841,6 +868,11 @@ var mockData = {
841
868
  var MockResponseManager = class {
842
869
  constructor(options = {}) {
843
870
  this.throttles = /* @__PURE__ */ new Map();
871
+ this.turnouts = /* @__PURE__ */ new Map([
872
+ ["LT1", 2 /* CLOSED */],
873
+ ["LT2", 2 /* CLOSED */],
874
+ ["LT3", 4 /* THROWN */]
875
+ ]);
844
876
  this.responseDelay = options.responseDelay ?? 50;
845
877
  this.powerState = options.initialPowerState ?? 4 /* OFF */;
846
878
  }
@@ -860,6 +892,8 @@ var MockResponseManager = class {
860
892
  return this.getRosterResponse(message);
861
893
  case "throttle":
862
894
  return this.getThrottleResponse(message);
895
+ case "turnout":
896
+ return this.getTurnoutResponse(message);
863
897
  case "ping":
864
898
  return this.getPingResponse();
865
899
  case "goodbye":
@@ -975,6 +1009,23 @@ var MockResponseManager = class {
975
1009
  data: {}
976
1010
  };
977
1011
  }
1012
+ /**
1013
+ * Get turnout response
1014
+ */
1015
+ getTurnoutResponse(message) {
1016
+ if (message.method === "list") {
1017
+ return JSON.parse(JSON.stringify(mockData.turnout.list));
1018
+ }
1019
+ const name = message.data?.name;
1020
+ if (!name) {
1021
+ return { type: "turnout", data: { name: "", state: 0 /* UNKNOWN */ } };
1022
+ }
1023
+ if (message.method === "post" && message.data?.state !== void 0) {
1024
+ this.turnouts.set(name, message.data.state);
1025
+ }
1026
+ const state = this.turnouts.get(name) ?? 0 /* UNKNOWN */;
1027
+ return { type: "turnout", data: { name, state } };
1028
+ }
978
1029
  /**
979
1030
  * Get ping response (pong)
980
1031
  */
@@ -1005,12 +1056,23 @@ var MockResponseManager = class {
1005
1056
  getThrottles() {
1006
1057
  return this.throttles;
1007
1058
  }
1059
+ /**
1060
+ * Get all turnout states (for testing)
1061
+ */
1062
+ getTurnouts() {
1063
+ return this.turnouts;
1064
+ }
1008
1065
  /**
1009
1066
  * Reset all state (for testing)
1010
1067
  */
1011
1068
  reset() {
1012
1069
  this.powerState = 4 /* OFF */;
1013
1070
  this.throttles.clear();
1071
+ this.turnouts = /* @__PURE__ */ new Map([
1072
+ ["LT1", 2 /* CLOSED */],
1073
+ ["LT2", 2 /* CLOSED */],
1074
+ ["LT3", 4 /* THROWN */]
1075
+ ]);
1014
1076
  }
1015
1077
  /**
1016
1078
  * Delay helper
@@ -1730,6 +1792,104 @@ var ThrottleManager = class extends import_index.default {
1730
1792
  }
1731
1793
  };
1732
1794
 
1795
+ // src/managers/turnout-manager.ts
1796
+ var TurnoutManager = class extends import_index.default {
1797
+ constructor(client) {
1798
+ super();
1799
+ this.turnouts = /* @__PURE__ */ new Map();
1800
+ this.client = client;
1801
+ this.client.on("update", (message) => {
1802
+ if (message.type === "turnout") {
1803
+ this.handleTurnoutUpdate(message);
1804
+ }
1805
+ });
1806
+ }
1807
+ /**
1808
+ * Get the current state of a turnout.
1809
+ * Also registers a server-side listener so subsequent changes are pushed.
1810
+ */
1811
+ async getTurnout(name) {
1812
+ const message = {
1813
+ type: "turnout",
1814
+ data: { name }
1815
+ };
1816
+ const response = await this.client.request(message);
1817
+ const state = response.data?.state ?? 0 /* UNKNOWN */;
1818
+ this.turnouts.set(name, state);
1819
+ return state;
1820
+ }
1821
+ /**
1822
+ * Set a turnout to the given state
1823
+ */
1824
+ async setTurnout(name, state) {
1825
+ const message = {
1826
+ type: "turnout",
1827
+ method: "post",
1828
+ data: { name, state }
1829
+ };
1830
+ await this.client.request(message);
1831
+ const oldState = this.turnouts.get(name);
1832
+ this.turnouts.set(name, state);
1833
+ if (oldState !== state) {
1834
+ this.emit("turnout:changed", name, state);
1835
+ }
1836
+ }
1837
+ /**
1838
+ * Throw a turnout (diverging route)
1839
+ */
1840
+ async throwTurnout(name) {
1841
+ return this.setTurnout(name, 4 /* THROWN */);
1842
+ }
1843
+ /**
1844
+ * Close a turnout (straight through / normal)
1845
+ */
1846
+ async closeTurnout(name) {
1847
+ return this.setTurnout(name, 2 /* CLOSED */);
1848
+ }
1849
+ /**
1850
+ * List all turnouts known to JMRI
1851
+ */
1852
+ async listTurnouts() {
1853
+ const message = {
1854
+ type: "turnout",
1855
+ method: "list"
1856
+ };
1857
+ const response = await this.client.request(message);
1858
+ const entries = Array.isArray(response) ? response.map((r) => r.data ?? r) : [];
1859
+ for (const entry of entries) {
1860
+ if (entry.name && entry.state !== void 0) {
1861
+ this.turnouts.set(entry.name, entry.state);
1862
+ }
1863
+ }
1864
+ return entries;
1865
+ }
1866
+ /**
1867
+ * Get cached turnout state without a network request
1868
+ */
1869
+ getTurnoutState(name) {
1870
+ return this.turnouts.get(name);
1871
+ }
1872
+ /**
1873
+ * Get all cached turnout states
1874
+ */
1875
+ getCachedTurnouts() {
1876
+ return new Map(this.turnouts);
1877
+ }
1878
+ /**
1879
+ * Handle unsolicited turnout state updates from JMRI
1880
+ */
1881
+ handleTurnoutUpdate(message) {
1882
+ const name = message.data?.name;
1883
+ const state = message.data?.state;
1884
+ if (!name || state === void 0) return;
1885
+ const oldState = this.turnouts.get(name);
1886
+ this.turnouts.set(name, state);
1887
+ if (oldState !== state) {
1888
+ this.emit("turnout:changed", name, state);
1889
+ }
1890
+ }
1891
+ };
1892
+
1733
1893
  // src/types/client-options.ts
1734
1894
  var DEFAULT_CLIENT_OPTIONS = {
1735
1895
  host: "localhost",
@@ -1805,6 +1965,7 @@ var JmriClient = class extends import_index.default {
1805
1965
  this.powerManager = new PowerManager(this.wsClient);
1806
1966
  this.rosterManager = new RosterManager(this.wsClient);
1807
1967
  this.throttleManager = new ThrottleManager(this.wsClient);
1968
+ this.turnoutManager = new TurnoutManager(this.wsClient);
1808
1969
  this.wsClient.on("connected", () => this.emit("connected"));
1809
1970
  this.wsClient.on("disconnected", (reason) => this.emit("disconnected", reason));
1810
1971
  this.wsClient.on(
@@ -1828,6 +1989,10 @@ var JmriClient = class extends import_index.default {
1828
1989
  "power:changed",
1829
1990
  (state) => this.emit("power:changed", state)
1830
1991
  );
1992
+ this.turnoutManager.on(
1993
+ "turnout:changed",
1994
+ (name, state) => this.emit("turnout:changed", name, state)
1995
+ );
1831
1996
  this.throttleManager.on(
1832
1997
  "throttle:acquired",
1833
1998
  (id) => this.emit("throttle:acquired", id)
@@ -1931,6 +2096,51 @@ var JmriClient = class extends import_index.default {
1931
2096
  return this.rosterManager.searchRoster(query);
1932
2097
  }
1933
2098
  // ============================================================================
2099
+ // Turnout Control
2100
+ // ============================================================================
2101
+ /**
2102
+ * Get the current state of a turnout
2103
+ */
2104
+ async getTurnout(name) {
2105
+ return this.turnoutManager.getTurnout(name);
2106
+ }
2107
+ /**
2108
+ * Set a turnout to the given state
2109
+ */
2110
+ async setTurnout(name, state) {
2111
+ return this.turnoutManager.setTurnout(name, state);
2112
+ }
2113
+ /**
2114
+ * Throw a turnout (diverging route)
2115
+ */
2116
+ async throwTurnout(name) {
2117
+ return this.turnoutManager.throwTurnout(name);
2118
+ }
2119
+ /**
2120
+ * Close a turnout (straight through / normal)
2121
+ */
2122
+ async closeTurnout(name) {
2123
+ return this.turnoutManager.closeTurnout(name);
2124
+ }
2125
+ /**
2126
+ * List all turnouts known to JMRI
2127
+ */
2128
+ async listTurnouts() {
2129
+ return this.turnoutManager.listTurnouts();
2130
+ }
2131
+ /**
2132
+ * Get cached turnout state without a network request
2133
+ */
2134
+ getTurnoutState(name) {
2135
+ return this.turnoutManager.getTurnoutState(name);
2136
+ }
2137
+ /**
2138
+ * Get all cached turnout states
2139
+ */
2140
+ getCachedTurnouts() {
2141
+ return this.turnoutManager.getCachedTurnouts();
2142
+ }
2143
+ // ============================================================================
1934
2144
  // Throttle Control
1935
2145
  // ============================================================================
1936
2146
  /**
@@ -2030,9 +2240,11 @@ export {
2030
2240
  JmriClient,
2031
2241
  MockResponseManager,
2032
2242
  PowerState,
2243
+ TurnoutState,
2033
2244
  isThrottleFunctionKey,
2034
2245
  isValidSpeed,
2035
2246
  mockData,
2036
2247
  mockResponseManager,
2037
- powerStateToString
2248
+ powerStateToString,
2249
+ turnoutStateToString
2038
2250
  };
@@ -9,6 +9,7 @@ const websocket_client_js_1 = require("./core/websocket-client.js");
9
9
  const power_manager_js_1 = require("./managers/power-manager.js");
10
10
  const roster_manager_js_1 = require("./managers/roster-manager.js");
11
11
  const throttle_manager_js_1 = require("./managers/throttle-manager.js");
12
+ const turnout_manager_js_1 = require("./managers/turnout-manager.js");
12
13
  const client_options_js_1 = require("./types/client-options.js");
13
14
  /**
14
15
  * JMRI WebSocket Client
@@ -41,6 +42,7 @@ class JmriClient extends eventemitter3_1.EventEmitter {
41
42
  this.powerManager = new power_manager_js_1.PowerManager(this.wsClient);
42
43
  this.rosterManager = new roster_manager_js_1.RosterManager(this.wsClient);
43
44
  this.throttleManager = new throttle_manager_js_1.ThrottleManager(this.wsClient);
45
+ this.turnoutManager = new turnout_manager_js_1.TurnoutManager(this.wsClient);
44
46
  // Forward events from WebSocket client
45
47
  this.wsClient.on('connected', () => this.emit('connected'));
46
48
  this.wsClient.on('disconnected', (reason) => this.emit('disconnected', reason));
@@ -54,6 +56,7 @@ class JmriClient extends eventemitter3_1.EventEmitter {
54
56
  this.wsClient.on('hello', (data) => this.emit('hello', data));
55
57
  // Forward events from managers
56
58
  this.powerManager.on('power:changed', (state) => this.emit('power:changed', state));
59
+ this.turnoutManager.on('turnout:changed', (name, state) => this.emit('turnout:changed', name, state));
57
60
  this.throttleManager.on('throttle:acquired', (id) => this.emit('throttle:acquired', id));
58
61
  this.throttleManager.on('throttle:updated', (id, data) => this.emit('throttle:updated', id, data));
59
62
  this.throttleManager.on('throttle:released', (id) => this.emit('throttle:released', id));
@@ -148,6 +151,51 @@ class JmriClient extends eventemitter3_1.EventEmitter {
148
151
  return this.rosterManager.searchRoster(query);
149
152
  }
150
153
  // ============================================================================
154
+ // Turnout Control
155
+ // ============================================================================
156
+ /**
157
+ * Get the current state of a turnout
158
+ */
159
+ async getTurnout(name) {
160
+ return this.turnoutManager.getTurnout(name);
161
+ }
162
+ /**
163
+ * Set a turnout to the given state
164
+ */
165
+ async setTurnout(name, state) {
166
+ return this.turnoutManager.setTurnout(name, state);
167
+ }
168
+ /**
169
+ * Throw a turnout (diverging route)
170
+ */
171
+ async throwTurnout(name) {
172
+ return this.turnoutManager.throwTurnout(name);
173
+ }
174
+ /**
175
+ * Close a turnout (straight through / normal)
176
+ */
177
+ async closeTurnout(name) {
178
+ return this.turnoutManager.closeTurnout(name);
179
+ }
180
+ /**
181
+ * List all turnouts known to JMRI
182
+ */
183
+ async listTurnouts() {
184
+ return this.turnoutManager.listTurnouts();
185
+ }
186
+ /**
187
+ * Get cached turnout state without a network request
188
+ */
189
+ getTurnoutState(name) {
190
+ return this.turnoutManager.getTurnoutState(name);
191
+ }
192
+ /**
193
+ * Get all cached turnout states
194
+ */
195
+ getCachedTurnouts() {
196
+ return this.turnoutManager.getCachedTurnouts();
197
+ }
198
+ // ============================================================================
151
199
  // Throttle Control
152
200
  // ============================================================================
153
201
  /**
package/dist/cjs/index.js CHANGED
@@ -4,13 +4,14 @@
4
4
  * WebSocket client for JMRI with real-time updates and throttle control
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.mockData = exports.mockResponseManager = exports.MockResponseManager = exports.powerStateToString = exports.isValidSpeed = exports.isThrottleFunctionKey = exports.ConnectionState = exports.PowerState = exports.JmriClient = void 0;
7
+ exports.mockData = exports.mockResponseManager = exports.MockResponseManager = exports.turnoutStateToString = exports.powerStateToString = exports.isValidSpeed = exports.isThrottleFunctionKey = exports.ConnectionState = exports.TurnoutState = exports.PowerState = exports.JmriClient = void 0;
8
8
  var client_js_1 = require("./client.js");
9
9
  Object.defineProperty(exports, "JmriClient", { enumerable: true, get: function () { return client_js_1.JmriClient; } });
10
10
  // Export types
11
11
  var index_js_1 = require("./types/index.js");
12
12
  // JMRI message types
13
13
  Object.defineProperty(exports, "PowerState", { enumerable: true, get: function () { return index_js_1.PowerState; } });
14
+ Object.defineProperty(exports, "TurnoutState", { enumerable: true, get: function () { return index_js_1.TurnoutState; } });
14
15
  // Event types
15
16
  Object.defineProperty(exports, "ConnectionState", { enumerable: true, get: function () { return index_js_1.ConnectionState; } });
16
17
  // Export utility functions
@@ -19,6 +20,7 @@ Object.defineProperty(exports, "isThrottleFunctionKey", { enumerable: true, get:
19
20
  Object.defineProperty(exports, "isValidSpeed", { enumerable: true, get: function () { return throttle_js_1.isValidSpeed; } });
20
21
  var jmri_messages_js_1 = require("./types/jmri-messages.js");
21
22
  Object.defineProperty(exports, "powerStateToString", { enumerable: true, get: function () { return jmri_messages_js_1.powerStateToString; } });
23
+ Object.defineProperty(exports, "turnoutStateToString", { enumerable: true, get: function () { return jmri_messages_js_1.turnoutStateToString; } });
22
24
  // Export mock system for testing and demo purposes
23
25
  var index_js_2 = require("./mocks/index.js");
24
26
  Object.defineProperty(exports, "MockResponseManager", { enumerable: true, get: function () { return index_js_2.MockResponseManager; } });
@@ -20,3 +20,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
20
20
  __exportStar(require("./power-manager.js"), exports);
21
21
  __exportStar(require("./roster-manager.js"), exports);
22
22
  __exportStar(require("./throttle-manager.js"), exports);
23
+ __exportStar(require("./turnout-manager.js"), exports);
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ /**
3
+ * Turnout (switch) manager
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TurnoutManager = void 0;
7
+ const eventemitter3_1 = require("eventemitter3");
8
+ const jmri_messages_js_1 = require("../types/jmri-messages.js");
9
+ /**
10
+ * Manages JMRI turnout (track switch) state
11
+ */
12
+ class TurnoutManager extends eventemitter3_1.EventEmitter {
13
+ constructor(client) {
14
+ super();
15
+ this.turnouts = new Map();
16
+ this.client = client;
17
+ this.client.on('update', (message) => {
18
+ if (message.type === 'turnout') {
19
+ this.handleTurnoutUpdate(message);
20
+ }
21
+ });
22
+ }
23
+ /**
24
+ * Get the current state of a turnout.
25
+ * Also registers a server-side listener so subsequent changes are pushed.
26
+ */
27
+ async getTurnout(name) {
28
+ const message = {
29
+ type: 'turnout',
30
+ data: { name }
31
+ };
32
+ const response = await this.client.request(message);
33
+ const state = response.data?.state ?? jmri_messages_js_1.TurnoutState.UNKNOWN;
34
+ this.turnouts.set(name, state);
35
+ return state;
36
+ }
37
+ /**
38
+ * Set a turnout to the given state
39
+ */
40
+ async setTurnout(name, state) {
41
+ const message = {
42
+ type: 'turnout',
43
+ method: 'post',
44
+ data: { name, state }
45
+ };
46
+ await this.client.request(message);
47
+ const oldState = this.turnouts.get(name);
48
+ this.turnouts.set(name, state);
49
+ if (oldState !== state) {
50
+ this.emit('turnout:changed', name, state);
51
+ }
52
+ }
53
+ /**
54
+ * Throw a turnout (diverging route)
55
+ */
56
+ async throwTurnout(name) {
57
+ return this.setTurnout(name, jmri_messages_js_1.TurnoutState.THROWN);
58
+ }
59
+ /**
60
+ * Close a turnout (straight through / normal)
61
+ */
62
+ async closeTurnout(name) {
63
+ return this.setTurnout(name, jmri_messages_js_1.TurnoutState.CLOSED);
64
+ }
65
+ /**
66
+ * List all turnouts known to JMRI
67
+ */
68
+ async listTurnouts() {
69
+ const message = {
70
+ type: 'turnout',
71
+ method: 'list'
72
+ };
73
+ const response = await this.client.request(message);
74
+ const entries = Array.isArray(response) ? response.map((r) => r.data ?? r) : [];
75
+ for (const entry of entries) {
76
+ if (entry.name && entry.state !== undefined) {
77
+ this.turnouts.set(entry.name, entry.state);
78
+ }
79
+ }
80
+ return entries;
81
+ }
82
+ /**
83
+ * Get cached turnout state without a network request
84
+ */
85
+ getTurnoutState(name) {
86
+ return this.turnouts.get(name);
87
+ }
88
+ /**
89
+ * Get all cached turnout states
90
+ */
91
+ getCachedTurnouts() {
92
+ return new Map(this.turnouts);
93
+ }
94
+ /**
95
+ * Handle unsolicited turnout state updates from JMRI
96
+ */
97
+ handleTurnoutUpdate(message) {
98
+ const name = message.data?.name;
99
+ const state = message.data?.state;
100
+ if (!name || state === undefined)
101
+ return;
102
+ const oldState = this.turnouts.get(name);
103
+ this.turnouts.set(name, state);
104
+ if (oldState !== state) {
105
+ this.emit('turnout:changed', name, state);
106
+ }
107
+ }
108
+ }
109
+ exports.TurnoutManager = TurnoutManager;
@@ -188,6 +188,13 @@ exports.mockData = {
188
188
  }
189
189
  }
190
190
  },
191
+ "turnout": {
192
+ "list": [
193
+ { "type": "turnout", "data": { "name": "LT1", "userName": "Main Diverge", "state": 2 } },
194
+ { "type": "turnout", "data": { "name": "LT2", "userName": "Yard Lead", "state": 2 } },
195
+ { "type": "turnout", "data": { "name": "LT3", "userName": "Siding Entry", "state": 4 } }
196
+ ]
197
+ },
191
198
  "ping": {
192
199
  "type": "ping"
193
200
  },
@@ -13,6 +13,11 @@ const mock_data_js_1 = require("./mock-data.js");
13
13
  class MockResponseManager {
14
14
  constructor(options = {}) {
15
15
  this.throttles = new Map();
16
+ this.turnouts = new Map([
17
+ ['LT1', jmri_messages_js_1.TurnoutState.CLOSED],
18
+ ['LT2', jmri_messages_js_1.TurnoutState.CLOSED],
19
+ ['LT3', jmri_messages_js_1.TurnoutState.THROWN]
20
+ ]);
16
21
  this.responseDelay = options.responseDelay ?? 50;
17
22
  this.powerState = options.initialPowerState ?? jmri_messages_js_1.PowerState.OFF;
18
23
  }
@@ -34,6 +39,8 @@ class MockResponseManager {
34
39
  return this.getRosterResponse(message);
35
40
  case 'throttle':
36
41
  return this.getThrottleResponse(message);
42
+ case 'turnout':
43
+ return this.getTurnoutResponse(message);
37
44
  case 'ping':
38
45
  return this.getPingResponse();
39
46
  case 'goodbye':
@@ -160,6 +167,26 @@ class MockResponseManager {
160
167
  data: {}
161
168
  };
162
169
  }
170
+ /**
171
+ * Get turnout response
172
+ */
173
+ getTurnoutResponse(message) {
174
+ // List all turnouts
175
+ if (message.method === 'list') {
176
+ return JSON.parse(JSON.stringify(mock_data_js_1.mockData.turnout.list));
177
+ }
178
+ const name = message.data?.name;
179
+ if (!name) {
180
+ return { type: 'turnout', data: { name: '', state: jmri_messages_js_1.TurnoutState.UNKNOWN } };
181
+ }
182
+ // Set turnout state
183
+ if (message.method === 'post' && message.data?.state !== undefined) {
184
+ this.turnouts.set(name, message.data.state);
185
+ }
186
+ // Get or confirm current state
187
+ const state = this.turnouts.get(name) ?? jmri_messages_js_1.TurnoutState.UNKNOWN;
188
+ return { type: 'turnout', data: { name, state } };
189
+ }
163
190
  /**
164
191
  * Get ping response (pong)
165
192
  */
@@ -190,12 +217,23 @@ class MockResponseManager {
190
217
  getThrottles() {
191
218
  return this.throttles;
192
219
  }
220
+ /**
221
+ * Get all turnout states (for testing)
222
+ */
223
+ getTurnouts() {
224
+ return this.turnouts;
225
+ }
193
226
  /**
194
227
  * Reset all state (for testing)
195
228
  */
196
229
  reset() {
197
230
  this.powerState = jmri_messages_js_1.PowerState.OFF;
198
231
  this.throttles.clear();
232
+ this.turnouts = new Map([
233
+ ['LT1', jmri_messages_js_1.TurnoutState.CLOSED],
234
+ ['LT2', jmri_messages_js_1.TurnoutState.CLOSED],
235
+ ['LT3', jmri_messages_js_1.TurnoutState.THROWN]
236
+ ]);
199
237
  }
200
238
  /**
201
239
  * Delay helper
@@ -4,8 +4,9 @@
4
4
  * Based on JMRI JSON protocol specification
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.PowerState = void 0;
7
+ exports.TurnoutState = exports.PowerState = void 0;
8
8
  exports.powerStateToString = powerStateToString;
9
+ exports.turnoutStateToString = turnoutStateToString;
9
10
  /**
10
11
  * Power state values (from JMRI JSON protocol constants)
11
12
  * UNKNOWN = 0 (state cannot be determined)
@@ -35,3 +36,33 @@ function powerStateToString(state) {
35
36
  return 'UNKNOWN';
36
37
  }
37
38
  }
39
+ /**
40
+ * Turnout state values (from JMRI JSON protocol constants)
41
+ * UNKNOWN = 0 (state cannot be determined)
42
+ * CLOSED = 2 (straight through / normal position)
43
+ * THROWN = 4 (diverging route position)
44
+ * INCONSISTENT = 8 (contradictory feedback state)
45
+ */
46
+ var TurnoutState;
47
+ (function (TurnoutState) {
48
+ TurnoutState[TurnoutState["UNKNOWN"] = 0] = "UNKNOWN";
49
+ TurnoutState[TurnoutState["CLOSED"] = 2] = "CLOSED";
50
+ TurnoutState[TurnoutState["THROWN"] = 4] = "THROWN";
51
+ TurnoutState[TurnoutState["INCONSISTENT"] = 8] = "INCONSISTENT";
52
+ })(TurnoutState || (exports.TurnoutState = TurnoutState = {}));
53
+ /**
54
+ * Convert TurnoutState enum to human-readable string
55
+ */
56
+ function turnoutStateToString(state) {
57
+ switch (state) {
58
+ case TurnoutState.CLOSED:
59
+ return 'CLOSED';
60
+ case TurnoutState.THROWN:
61
+ return 'THROWN';
62
+ case TurnoutState.INCONSISTENT:
63
+ return 'INCONSISTENT';
64
+ case TurnoutState.UNKNOWN:
65
+ default:
66
+ return 'UNKNOWN';
67
+ }
68
+ }
@@ -6,6 +6,7 @@ import { WebSocketClient } from './core/websocket-client.js';
6
6
  import { PowerManager } from './managers/power-manager.js';
7
7
  import { RosterManager } from './managers/roster-manager.js';
8
8
  import { ThrottleManager } from './managers/throttle-manager.js';
9
+ import { TurnoutManager } from './managers/turnout-manager.js';
9
10
  import { mergeOptions } from './types/client-options.js';
10
11
  /**
11
12
  * JMRI WebSocket Client
@@ -38,6 +39,7 @@ export class JmriClient extends EventEmitter {
38
39
  this.powerManager = new PowerManager(this.wsClient);
39
40
  this.rosterManager = new RosterManager(this.wsClient);
40
41
  this.throttleManager = new ThrottleManager(this.wsClient);
42
+ this.turnoutManager = new TurnoutManager(this.wsClient);
41
43
  // Forward events from WebSocket client
42
44
  this.wsClient.on('connected', () => this.emit('connected'));
43
45
  this.wsClient.on('disconnected', (reason) => this.emit('disconnected', reason));
@@ -51,6 +53,7 @@ export class JmriClient extends EventEmitter {
51
53
  this.wsClient.on('hello', (data) => this.emit('hello', data));
52
54
  // Forward events from managers
53
55
  this.powerManager.on('power:changed', (state) => this.emit('power:changed', state));
56
+ this.turnoutManager.on('turnout:changed', (name, state) => this.emit('turnout:changed', name, state));
54
57
  this.throttleManager.on('throttle:acquired', (id) => this.emit('throttle:acquired', id));
55
58
  this.throttleManager.on('throttle:updated', (id, data) => this.emit('throttle:updated', id, data));
56
59
  this.throttleManager.on('throttle:released', (id) => this.emit('throttle:released', id));
@@ -145,6 +148,51 @@ export class JmriClient extends EventEmitter {
145
148
  return this.rosterManager.searchRoster(query);
146
149
  }
147
150
  // ============================================================================
151
+ // Turnout Control
152
+ // ============================================================================
153
+ /**
154
+ * Get the current state of a turnout
155
+ */
156
+ async getTurnout(name) {
157
+ return this.turnoutManager.getTurnout(name);
158
+ }
159
+ /**
160
+ * Set a turnout to the given state
161
+ */
162
+ async setTurnout(name, state) {
163
+ return this.turnoutManager.setTurnout(name, state);
164
+ }
165
+ /**
166
+ * Throw a turnout (diverging route)
167
+ */
168
+ async throwTurnout(name) {
169
+ return this.turnoutManager.throwTurnout(name);
170
+ }
171
+ /**
172
+ * Close a turnout (straight through / normal)
173
+ */
174
+ async closeTurnout(name) {
175
+ return this.turnoutManager.closeTurnout(name);
176
+ }
177
+ /**
178
+ * List all turnouts known to JMRI
179
+ */
180
+ async listTurnouts() {
181
+ return this.turnoutManager.listTurnouts();
182
+ }
183
+ /**
184
+ * Get cached turnout state without a network request
185
+ */
186
+ getTurnoutState(name) {
187
+ return this.turnoutManager.getTurnoutState(name);
188
+ }
189
+ /**
190
+ * Get all cached turnout states
191
+ */
192
+ getCachedTurnouts() {
193
+ return this.turnoutManager.getCachedTurnouts();
194
+ }
195
+ // ============================================================================
148
196
  // Throttle Control
149
197
  // ============================================================================
150
198
  /**
package/dist/esm/index.js CHANGED
@@ -6,11 +6,11 @@ export { JmriClient } from './client.js';
6
6
  // Export types
7
7
  export {
8
8
  // JMRI message types
9
- PowerState,
9
+ PowerState, TurnoutState,
10
10
  // Event types
11
11
  ConnectionState } from './types/index.js';
12
12
  // Export utility functions
13
13
  export { isThrottleFunctionKey, isValidSpeed } from './types/throttle.js';
14
- export { powerStateToString } from './types/jmri-messages.js';
14
+ export { powerStateToString, turnoutStateToString } from './types/jmri-messages.js';
15
15
  // Export mock system for testing and demo purposes
16
16
  export { MockResponseManager, mockResponseManager, mockData } from './mocks/index.js';
@@ -4,3 +4,4 @@
4
4
  export * from './power-manager.js';
5
5
  export * from './roster-manager.js';
6
6
  export * from './throttle-manager.js';
7
+ export * from './turnout-manager.js';
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Turnout (switch) manager
3
+ */
4
+ import { EventEmitter } from 'eventemitter3';
5
+ import { TurnoutState } from '../types/jmri-messages.js';
6
+ /**
7
+ * Manages JMRI turnout (track switch) state
8
+ */
9
+ export class TurnoutManager extends EventEmitter {
10
+ constructor(client) {
11
+ super();
12
+ this.turnouts = new Map();
13
+ this.client = client;
14
+ this.client.on('update', (message) => {
15
+ if (message.type === 'turnout') {
16
+ this.handleTurnoutUpdate(message);
17
+ }
18
+ });
19
+ }
20
+ /**
21
+ * Get the current state of a turnout.
22
+ * Also registers a server-side listener so subsequent changes are pushed.
23
+ */
24
+ async getTurnout(name) {
25
+ const message = {
26
+ type: 'turnout',
27
+ data: { name }
28
+ };
29
+ const response = await this.client.request(message);
30
+ const state = response.data?.state ?? TurnoutState.UNKNOWN;
31
+ this.turnouts.set(name, state);
32
+ return state;
33
+ }
34
+ /**
35
+ * Set a turnout to the given state
36
+ */
37
+ async setTurnout(name, state) {
38
+ const message = {
39
+ type: 'turnout',
40
+ method: 'post',
41
+ data: { name, state }
42
+ };
43
+ await this.client.request(message);
44
+ const oldState = this.turnouts.get(name);
45
+ this.turnouts.set(name, state);
46
+ if (oldState !== state) {
47
+ this.emit('turnout:changed', name, state);
48
+ }
49
+ }
50
+ /**
51
+ * Throw a turnout (diverging route)
52
+ */
53
+ async throwTurnout(name) {
54
+ return this.setTurnout(name, TurnoutState.THROWN);
55
+ }
56
+ /**
57
+ * Close a turnout (straight through / normal)
58
+ */
59
+ async closeTurnout(name) {
60
+ return this.setTurnout(name, TurnoutState.CLOSED);
61
+ }
62
+ /**
63
+ * List all turnouts known to JMRI
64
+ */
65
+ async listTurnouts() {
66
+ const message = {
67
+ type: 'turnout',
68
+ method: 'list'
69
+ };
70
+ const response = await this.client.request(message);
71
+ const entries = Array.isArray(response) ? response.map((r) => r.data ?? r) : [];
72
+ for (const entry of entries) {
73
+ if (entry.name && entry.state !== undefined) {
74
+ this.turnouts.set(entry.name, entry.state);
75
+ }
76
+ }
77
+ return entries;
78
+ }
79
+ /**
80
+ * Get cached turnout state without a network request
81
+ */
82
+ getTurnoutState(name) {
83
+ return this.turnouts.get(name);
84
+ }
85
+ /**
86
+ * Get all cached turnout states
87
+ */
88
+ getCachedTurnouts() {
89
+ return new Map(this.turnouts);
90
+ }
91
+ /**
92
+ * Handle unsolicited turnout state updates from JMRI
93
+ */
94
+ handleTurnoutUpdate(message) {
95
+ const name = message.data?.name;
96
+ const state = message.data?.state;
97
+ if (!name || state === undefined)
98
+ return;
99
+ const oldState = this.turnouts.get(name);
100
+ this.turnouts.set(name, state);
101
+ if (oldState !== state) {
102
+ this.emit('turnout:changed', name, state);
103
+ }
104
+ }
105
+ }
@@ -185,6 +185,13 @@ export const mockData = {
185
185
  }
186
186
  }
187
187
  },
188
+ "turnout": {
189
+ "list": [
190
+ { "type": "turnout", "data": { "name": "LT1", "userName": "Main Diverge", "state": 2 } },
191
+ { "type": "turnout", "data": { "name": "LT2", "userName": "Yard Lead", "state": 2 } },
192
+ { "type": "turnout", "data": { "name": "LT3", "userName": "Siding Entry", "state": 4 } }
193
+ ]
194
+ },
188
195
  "ping": {
189
196
  "type": "ping"
190
197
  },
@@ -2,7 +2,7 @@
2
2
  * Mock Response Manager
3
3
  * Generates mock JMRI responses for testing and demo purposes
4
4
  */
5
- import { PowerState } from '../types/jmri-messages.js';
5
+ import { PowerState, TurnoutState } from '../types/jmri-messages.js';
6
6
  import { mockData } from './mock-data.js';
7
7
  /**
8
8
  * Manages mock responses for JMRI protocol
@@ -10,6 +10,11 @@ import { mockData } from './mock-data.js';
10
10
  export class MockResponseManager {
11
11
  constructor(options = {}) {
12
12
  this.throttles = new Map();
13
+ this.turnouts = new Map([
14
+ ['LT1', TurnoutState.CLOSED],
15
+ ['LT2', TurnoutState.CLOSED],
16
+ ['LT3', TurnoutState.THROWN]
17
+ ]);
13
18
  this.responseDelay = options.responseDelay ?? 50;
14
19
  this.powerState = options.initialPowerState ?? PowerState.OFF;
15
20
  }
@@ -31,6 +36,8 @@ export class MockResponseManager {
31
36
  return this.getRosterResponse(message);
32
37
  case 'throttle':
33
38
  return this.getThrottleResponse(message);
39
+ case 'turnout':
40
+ return this.getTurnoutResponse(message);
34
41
  case 'ping':
35
42
  return this.getPingResponse();
36
43
  case 'goodbye':
@@ -157,6 +164,26 @@ export class MockResponseManager {
157
164
  data: {}
158
165
  };
159
166
  }
167
+ /**
168
+ * Get turnout response
169
+ */
170
+ getTurnoutResponse(message) {
171
+ // List all turnouts
172
+ if (message.method === 'list') {
173
+ return JSON.parse(JSON.stringify(mockData.turnout.list));
174
+ }
175
+ const name = message.data?.name;
176
+ if (!name) {
177
+ return { type: 'turnout', data: { name: '', state: TurnoutState.UNKNOWN } };
178
+ }
179
+ // Set turnout state
180
+ if (message.method === 'post' && message.data?.state !== undefined) {
181
+ this.turnouts.set(name, message.data.state);
182
+ }
183
+ // Get or confirm current state
184
+ const state = this.turnouts.get(name) ?? TurnoutState.UNKNOWN;
185
+ return { type: 'turnout', data: { name, state } };
186
+ }
160
187
  /**
161
188
  * Get ping response (pong)
162
189
  */
@@ -187,12 +214,23 @@ export class MockResponseManager {
187
214
  getThrottles() {
188
215
  return this.throttles;
189
216
  }
217
+ /**
218
+ * Get all turnout states (for testing)
219
+ */
220
+ getTurnouts() {
221
+ return this.turnouts;
222
+ }
190
223
  /**
191
224
  * Reset all state (for testing)
192
225
  */
193
226
  reset() {
194
227
  this.powerState = PowerState.OFF;
195
228
  this.throttles.clear();
229
+ this.turnouts = new Map([
230
+ ['LT1', TurnoutState.CLOSED],
231
+ ['LT2', TurnoutState.CLOSED],
232
+ ['LT3', TurnoutState.THROWN]
233
+ ]);
196
234
  }
197
235
  /**
198
236
  * Delay helper
@@ -31,3 +31,33 @@ export function powerStateToString(state) {
31
31
  return 'UNKNOWN';
32
32
  }
33
33
  }
34
+ /**
35
+ * Turnout state values (from JMRI JSON protocol constants)
36
+ * UNKNOWN = 0 (state cannot be determined)
37
+ * CLOSED = 2 (straight through / normal position)
38
+ * THROWN = 4 (diverging route position)
39
+ * INCONSISTENT = 8 (contradictory feedback state)
40
+ */
41
+ export var TurnoutState;
42
+ (function (TurnoutState) {
43
+ TurnoutState[TurnoutState["UNKNOWN"] = 0] = "UNKNOWN";
44
+ TurnoutState[TurnoutState["CLOSED"] = 2] = "CLOSED";
45
+ TurnoutState[TurnoutState["THROWN"] = 4] = "THROWN";
46
+ TurnoutState[TurnoutState["INCONSISTENT"] = 8] = "INCONSISTENT";
47
+ })(TurnoutState || (TurnoutState = {}));
48
+ /**
49
+ * Convert TurnoutState enum to human-readable string
50
+ */
51
+ export function turnoutStateToString(state) {
52
+ switch (state) {
53
+ case TurnoutState.CLOSED:
54
+ return 'CLOSED';
55
+ case TurnoutState.THROWN:
56
+ return 'THROWN';
57
+ case TurnoutState.INCONSISTENT:
58
+ return 'INCONSISTENT';
59
+ case TurnoutState.UNKNOWN:
60
+ default:
61
+ return 'UNKNOWN';
62
+ }
63
+ }
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { EventEmitter } from 'eventemitter3';
5
5
  import { PartialClientOptions } from './types/client-options.js';
6
- import { PowerState, RosterEntryWrapper } from './types/jmri-messages.js';
6
+ import { PowerState, RosterEntryWrapper, TurnoutState, TurnoutData } from './types/jmri-messages.js';
7
7
  import { ConnectionState } from './types/events.js';
8
8
  import { ThrottleAcquireOptions, ThrottleFunctionKey, ThrottleState } from './types/throttle.js';
9
9
  /**
@@ -16,6 +16,7 @@ export declare class JmriClient extends EventEmitter {
16
16
  private powerManager;
17
17
  private rosterManager;
18
18
  private throttleManager;
19
+ private turnoutManager;
19
20
  /**
20
21
  * Create a new JMRI client
21
22
  *
@@ -82,6 +83,34 @@ export declare class JmriClient extends EventEmitter {
82
83
  * Search roster by partial name match
83
84
  */
84
85
  searchRoster(query: string): Promise<RosterEntryWrapper[]>;
86
+ /**
87
+ * Get the current state of a turnout
88
+ */
89
+ getTurnout(name: string): Promise<TurnoutState>;
90
+ /**
91
+ * Set a turnout to the given state
92
+ */
93
+ setTurnout(name: string, state: TurnoutState): Promise<void>;
94
+ /**
95
+ * Throw a turnout (diverging route)
96
+ */
97
+ throwTurnout(name: string): Promise<void>;
98
+ /**
99
+ * Close a turnout (straight through / normal)
100
+ */
101
+ closeTurnout(name: string): Promise<void>;
102
+ /**
103
+ * List all turnouts known to JMRI
104
+ */
105
+ listTurnouts(): Promise<TurnoutData[]>;
106
+ /**
107
+ * Get cached turnout state without a network request
108
+ */
109
+ getTurnoutState(name: string): TurnoutState | undefined;
110
+ /**
111
+ * Get all cached turnout states
112
+ */
113
+ getCachedTurnouts(): Map<string, TurnoutState>;
85
114
  /**
86
115
  * Acquire a throttle for a locomotive
87
116
  *
@@ -3,8 +3,8 @@
3
3
  * WebSocket client for JMRI with real-time updates and throttle control
4
4
  */
5
5
  export { JmriClient } from './client.js';
6
- export { JmriClientOptions, PartialClientOptions, ReconnectionOptions, HeartbeatOptions, MockOptions, PowerState, RosterEntry, JmriMessage, PowerMessage, ThrottleMessage, RosterMessage, ConnectionState, EventPayloads, ThrottleAcquireOptions, ThrottleFunctionKey, ThrottleState } from './types/index.js';
6
+ export { JmriClientOptions, PartialClientOptions, ReconnectionOptions, HeartbeatOptions, MockOptions, PowerState, TurnoutState, RosterEntry, TurnoutData, JmriMessage, PowerMessage, TurnoutMessage, ThrottleMessage, RosterMessage, ConnectionState, EventPayloads, ThrottleAcquireOptions, ThrottleFunctionKey, ThrottleState } from './types/index.js';
7
7
  export { isThrottleFunctionKey, isValidSpeed } from './types/throttle.js';
8
- export { powerStateToString } from './types/jmri-messages.js';
8
+ export { powerStateToString, turnoutStateToString } from './types/jmri-messages.js';
9
9
  export { MockResponseManager, mockResponseManager, mockData } from './mocks/index.js';
10
10
  export type { MockResponseManagerOptions } from './mocks/index.js';
@@ -4,3 +4,4 @@
4
4
  export * from './power-manager.js';
5
5
  export * from './roster-manager.js';
6
6
  export * from './throttle-manager.js';
7
+ export * from './turnout-manager.js';
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Turnout (switch) manager
3
+ */
4
+ import { EventEmitter } from 'eventemitter3';
5
+ import { WebSocketClient } from '../core/websocket-client.js';
6
+ import { TurnoutState, TurnoutData } from '../types/jmri-messages.js';
7
+ /**
8
+ * Manages JMRI turnout (track switch) state
9
+ */
10
+ export declare class TurnoutManager extends EventEmitter {
11
+ private client;
12
+ private turnouts;
13
+ constructor(client: WebSocketClient);
14
+ /**
15
+ * Get the current state of a turnout.
16
+ * Also registers a server-side listener so subsequent changes are pushed.
17
+ */
18
+ getTurnout(name: string): Promise<TurnoutState>;
19
+ /**
20
+ * Set a turnout to the given state
21
+ */
22
+ setTurnout(name: string, state: TurnoutState): Promise<void>;
23
+ /**
24
+ * Throw a turnout (diverging route)
25
+ */
26
+ throwTurnout(name: string): Promise<void>;
27
+ /**
28
+ * Close a turnout (straight through / normal)
29
+ */
30
+ closeTurnout(name: string): Promise<void>;
31
+ /**
32
+ * List all turnouts known to JMRI
33
+ */
34
+ listTurnouts(): Promise<TurnoutData[]>;
35
+ /**
36
+ * Get cached turnout state without a network request
37
+ */
38
+ getTurnoutState(name: string): TurnoutState | undefined;
39
+ /**
40
+ * Get all cached turnout states
41
+ */
42
+ getCachedTurnouts(): Map<string, TurnoutState>;
43
+ /**
44
+ * Handle unsolicited turnout state updates from JMRI
45
+ */
46
+ private handleTurnoutUpdate;
47
+ }
@@ -263,6 +263,30 @@ export declare const mockData: {
263
263
  };
264
264
  };
265
265
  };
266
+ readonly turnout: {
267
+ readonly list: readonly [{
268
+ readonly type: "turnout";
269
+ readonly data: {
270
+ readonly name: "LT1";
271
+ readonly userName: "Main Diverge";
272
+ readonly state: 2;
273
+ };
274
+ }, {
275
+ readonly type: "turnout";
276
+ readonly data: {
277
+ readonly name: "LT2";
278
+ readonly userName: "Yard Lead";
279
+ readonly state: 2;
280
+ };
281
+ }, {
282
+ readonly type: "turnout";
283
+ readonly data: {
284
+ readonly name: "LT3";
285
+ readonly userName: "Siding Entry";
286
+ readonly state: 4;
287
+ };
288
+ }];
289
+ };
266
290
  readonly ping: {
267
291
  readonly type: "ping";
268
292
  };
@@ -2,7 +2,7 @@
2
2
  * Mock Response Manager
3
3
  * Generates mock JMRI responses for testing and demo purposes
4
4
  */
5
- import { JmriMessage, PowerState } from '../types/jmri-messages.js';
5
+ import { JmriMessage, PowerState, TurnoutState } from '../types/jmri-messages.js';
6
6
  export interface MockResponseManagerOptions {
7
7
  /**
8
8
  * Delay in milliseconds before returning responses (simulates network latency)
@@ -21,6 +21,7 @@ export declare class MockResponseManager {
21
21
  private responseDelay;
22
22
  private powerState;
23
23
  private throttles;
24
+ private turnouts;
24
25
  constructor(options?: MockResponseManagerOptions);
25
26
  /**
26
27
  * Get a mock response for a given message
@@ -42,6 +43,10 @@ export declare class MockResponseManager {
42
43
  * Get throttle response
43
44
  */
44
45
  private getThrottleResponse;
46
+ /**
47
+ * Get turnout response
48
+ */
49
+ private getTurnoutResponse;
45
50
  /**
46
51
  * Get ping response (pong)
47
52
  */
@@ -62,6 +67,10 @@ export declare class MockResponseManager {
62
67
  * Get all throttles (for testing)
63
68
  */
64
69
  getThrottles(): Map<string, any>;
70
+ /**
71
+ * Get all turnout states (for testing)
72
+ */
73
+ getTurnouts(): Map<string, TurnoutState>;
65
74
  /**
66
75
  * Reset all state (for testing)
67
76
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Event types for JmriClient EventEmitter
3
3
  */
4
- import { PowerState } from './jmri-messages.js';
4
+ import { PowerState, TurnoutState } from './jmri-messages.js';
5
5
  import { ThrottleData } from './jmri-messages.js';
6
6
  /**
7
7
  * Connection states
@@ -23,6 +23,7 @@ export interface EventPayloads {
23
23
  'connectionStateChanged': ConnectionState;
24
24
  'error': Error;
25
25
  'power:changed': PowerState;
26
+ 'turnout:changed': [name: string, state: TurnoutState];
26
27
  'throttle:acquired': string;
27
28
  'throttle:updated': [throttleId: string, data: ThrottleData];
28
29
  'throttle:released': string;
@@ -149,6 +149,40 @@ export interface RosterMessage extends JmriMessage {
149
149
  export interface RosterData {
150
150
  [key: string]: RosterEntry;
151
151
  }
152
+ /**
153
+ * Turnout state values (from JMRI JSON protocol constants)
154
+ * UNKNOWN = 0 (state cannot be determined)
155
+ * CLOSED = 2 (straight through / normal position)
156
+ * THROWN = 4 (diverging route position)
157
+ * INCONSISTENT = 8 (contradictory feedback state)
158
+ */
159
+ export declare enum TurnoutState {
160
+ UNKNOWN = 0,
161
+ CLOSED = 2,
162
+ THROWN = 4,
163
+ INCONSISTENT = 8
164
+ }
165
+ /**
166
+ * Convert TurnoutState enum to human-readable string
167
+ */
168
+ export declare function turnoutStateToString(state: TurnoutState): string;
169
+ /**
170
+ * Turnout data structure
171
+ */
172
+ export interface TurnoutData {
173
+ name: string;
174
+ userName?: string;
175
+ state?: TurnoutState;
176
+ inverted?: boolean;
177
+ feedback?: string;
178
+ }
179
+ /**
180
+ * Turnout message
181
+ */
182
+ export interface TurnoutMessage extends JmriMessage {
183
+ type: 'turnout';
184
+ data?: TurnoutData;
185
+ }
152
186
  /**
153
187
  * Ping message (heartbeat)
154
188
  */
@@ -197,4 +231,4 @@ export interface ErrorMessage extends JmriMessage {
197
231
  /**
198
232
  * Union type of all possible JMRI messages
199
233
  */
200
- export type AnyJmriMessage = PowerMessage | ThrottleMessage | RosterMessage | PingMessage | PongMessage | HelloMessage | GoodbyeMessage | ErrorMessage;
234
+ export type AnyJmriMessage = PowerMessage | ThrottleMessage | RosterMessage | TurnoutMessage | PingMessage | PongMessage | HelloMessage | GoodbyeMessage | ErrorMessage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jmri-client",
3
- "version": "3.5.3",
3
+ "version": "3.6.0",
4
4
  "description": "WebSocket client for JMRI with real-time updates and throttle control - works in both Node.js and browsers",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",