jmri-client 3.7.1 → 4.1.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.
package/README.md CHANGED
@@ -19,6 +19,7 @@ WebSocket client for [JMRI](http://jmri.sourceforge.net/) with real-time updates
19
19
  - ✅ **Heartbeat monitoring** - Automatic ping/pong keepalive
20
20
  - ✅ **TypeScript** - Full type definitions included
21
21
  - ✅ **Dual module support** - ESM and CommonJS
22
+ - ✅ **Extensible** - Subclass `JmriClient` to add support for additional JMRI object types
22
23
 
23
24
  ## Installation
24
25
 
@@ -105,6 +106,37 @@ client.on('reconnecting', (attempt, delay) => {
105
106
  });
106
107
  ```
107
108
 
109
+ ### Extending JmriClient
110
+
111
+ `JmriClient` exposes its `wsClient` as `protected`, so you can subclass it to add support for JMRI object types not yet built in (e.g., sensors, lights, routes, blocks):
112
+
113
+ ```typescript
114
+ import { JmriClient } from 'jmri-client';
115
+ import type { PartialClientOptions } from 'jmri-client';
116
+
117
+ class MyExtendedClient extends JmriClient {
118
+ constructor(options?: PartialClientOptions) {
119
+ super(options);
120
+
121
+ // this.wsClient is available — use it to send/receive JMRI JSON messages
122
+ this.wsClient.on('update', (message: any) => {
123
+ if (message.type === 'sensor') {
124
+ this.emit('sensor:changed', message.data.name, message.data.state);
125
+ }
126
+ });
127
+ }
128
+
129
+ async listSensors() {
130
+ const response = await this.wsClient.request({ type: 'sensor', method: 'list' });
131
+ return Array.isArray(response?.data)
132
+ ? response.data.map((r: any) => r.data ?? r)
133
+ : [];
134
+ }
135
+ }
136
+ ```
137
+
138
+ `WebSocketClient` is also exported for direct use if you need it. See its `send()`, `request()`, and `on('update', ...)` API for low-level messaging.
139
+
108
140
  ## Testing
109
141
 
110
142
  **Unit Tests** (no hardware required):
@@ -638,6 +638,23 @@ function turnoutStateToString(state) {
638
638
  return "UNKNOWN";
639
639
  }
640
640
  }
641
+ var LightState = /* @__PURE__ */ ((LightState2) => {
642
+ LightState2[LightState2["UNKNOWN"] = 0] = "UNKNOWN";
643
+ LightState2[LightState2["ON"] = 2] = "ON";
644
+ LightState2[LightState2["OFF"] = 4] = "OFF";
645
+ return LightState2;
646
+ })(LightState || {});
647
+ function lightStateToString(state) {
648
+ switch (state) {
649
+ case 2 /* ON */:
650
+ return "ON";
651
+ case 4 /* OFF */:
652
+ return "OFF";
653
+ case 0 /* UNKNOWN */:
654
+ default:
655
+ return "UNKNOWN";
656
+ }
657
+ }
641
658
 
642
659
  // src/mocks/mock-data.ts
643
660
  var mockData = {
@@ -823,6 +840,13 @@ var mockData = {
823
840
  }
824
841
  }
825
842
  },
843
+ "light": {
844
+ "list": [
845
+ { "type": "light", "data": { "name": "IL1", "userName": "Yard Light", "comment": null, "properties": [], "state": 4 } },
846
+ { "type": "light", "data": { "name": "IL2", "userName": "Platform Light", "comment": null, "properties": [], "state": 4 } },
847
+ { "type": "light", "data": { "name": "IL3", "userName": "Signal Lamp", "comment": null, "properties": [], "state": 2 } }
848
+ ]
849
+ },
826
850
  "turnout": {
827
851
  "list": [
828
852
  { "type": "turnout", "data": { "name": "LT1", "userName": "Main Diverge", "state": 2 } },
@@ -868,6 +892,11 @@ var mockData = {
868
892
  var MockResponseManager = class {
869
893
  constructor(options = {}) {
870
894
  this.throttles = /* @__PURE__ */ new Map();
895
+ this.lights = /* @__PURE__ */ new Map([
896
+ ["IL1", 4 /* OFF */],
897
+ ["IL2", 4 /* OFF */],
898
+ ["IL3", 2 /* ON */]
899
+ ]);
871
900
  this.turnouts = /* @__PURE__ */ new Map([
872
901
  ["LT1", 2 /* CLOSED */],
873
902
  ["LT2", 2 /* CLOSED */],
@@ -892,6 +921,8 @@ var MockResponseManager = class {
892
921
  return this.getRosterResponse(message);
893
922
  case "throttle":
894
923
  return this.getThrottleResponse(message);
924
+ case "light":
925
+ return this.getLightResponse(message);
895
926
  case "turnout":
896
927
  return this.getTurnoutResponse(message);
897
928
  case "ping":
@@ -1007,6 +1038,26 @@ var MockResponseManager = class {
1007
1038
  data: {}
1008
1039
  };
1009
1040
  }
1041
+ /**
1042
+ * Get light response
1043
+ */
1044
+ getLightResponse(message) {
1045
+ if (message.method === "list") {
1046
+ return {
1047
+ type: "light",
1048
+ data: JSON.parse(JSON.stringify(mockData.light.list))
1049
+ };
1050
+ }
1051
+ const name = message.data?.name;
1052
+ if (!name) {
1053
+ return { type: "light", data: { name: "", state: 0 /* UNKNOWN */ } };
1054
+ }
1055
+ if (message.method === "post" && message.data?.state !== void 0) {
1056
+ this.lights.set(name, message.data.state);
1057
+ }
1058
+ const state = this.lights.get(name) ?? 0 /* UNKNOWN */;
1059
+ return { type: "light", data: { name, state } };
1060
+ }
1010
1061
  /**
1011
1062
  * Get turnout response
1012
1063
  */
@@ -1057,6 +1108,12 @@ var MockResponseManager = class {
1057
1108
  getThrottles() {
1058
1109
  return this.throttles;
1059
1110
  }
1111
+ /**
1112
+ * Get all light states (for testing)
1113
+ */
1114
+ getLights() {
1115
+ return this.lights;
1116
+ }
1060
1117
  /**
1061
1118
  * Get all turnout states (for testing)
1062
1119
  */
@@ -1069,6 +1126,11 @@ var MockResponseManager = class {
1069
1126
  reset() {
1070
1127
  this.powerState = 4 /* OFF */;
1071
1128
  this.throttles.clear();
1129
+ this.lights = /* @__PURE__ */ new Map([
1130
+ ["IL1", 4 /* OFF */],
1131
+ ["IL2", 4 /* OFF */],
1132
+ ["IL3", 2 /* ON */]
1133
+ ]);
1072
1134
  this.turnouts = /* @__PURE__ */ new Map([
1073
1135
  ["LT1", 2 /* CLOSED */],
1074
1136
  ["LT2", 2 /* CLOSED */],
@@ -1891,6 +1953,104 @@ var TurnoutManager = class extends import_index.default {
1891
1953
  }
1892
1954
  };
1893
1955
 
1956
+ // src/managers/light-manager.ts
1957
+ var LightManager = class extends import_index.default {
1958
+ constructor(client) {
1959
+ super();
1960
+ this.lights = /* @__PURE__ */ new Map();
1961
+ this.client = client;
1962
+ this.client.on("update", (message) => {
1963
+ if (message.type === "light") {
1964
+ this.handleLightUpdate(message);
1965
+ }
1966
+ });
1967
+ }
1968
+ /**
1969
+ * Get the current state of a light.
1970
+ * Also registers a server-side listener so subsequent changes are pushed.
1971
+ */
1972
+ async getLight(name) {
1973
+ const message = {
1974
+ type: "light",
1975
+ data: { name }
1976
+ };
1977
+ const response = await this.client.request(message);
1978
+ const state = response.data?.state ?? 0 /* UNKNOWN */;
1979
+ this.lights.set(name, state);
1980
+ return state;
1981
+ }
1982
+ /**
1983
+ * Set a light to the given state
1984
+ */
1985
+ async setLight(name, state) {
1986
+ const message = {
1987
+ type: "light",
1988
+ method: "post",
1989
+ data: { name, state }
1990
+ };
1991
+ await this.client.request(message);
1992
+ const oldState = this.lights.get(name);
1993
+ this.lights.set(name, state);
1994
+ if (oldState !== state) {
1995
+ this.emit("light:changed", name, state);
1996
+ }
1997
+ }
1998
+ /**
1999
+ * Turn a light on
2000
+ */
2001
+ async turnOnLight(name) {
2002
+ return this.setLight(name, 2 /* ON */);
2003
+ }
2004
+ /**
2005
+ * Turn a light off
2006
+ */
2007
+ async turnOffLight(name) {
2008
+ return this.setLight(name, 4 /* OFF */);
2009
+ }
2010
+ /**
2011
+ * List all lights known to JMRI
2012
+ */
2013
+ async listLights() {
2014
+ const message = {
2015
+ type: "light",
2016
+ method: "list"
2017
+ };
2018
+ const response = await this.client.request(message);
2019
+ const entries = Array.isArray(response?.data) ? response.data.map((r) => r.data ?? r) : [];
2020
+ for (const entry of entries) {
2021
+ if (entry.name && entry.state !== void 0) {
2022
+ this.lights.set(entry.name, entry.state);
2023
+ }
2024
+ }
2025
+ return entries;
2026
+ }
2027
+ /**
2028
+ * Get cached light state without a network request
2029
+ */
2030
+ getLightState(name) {
2031
+ return this.lights.get(name);
2032
+ }
2033
+ /**
2034
+ * Get all cached light states
2035
+ */
2036
+ getCachedLights() {
2037
+ return new Map(this.lights);
2038
+ }
2039
+ /**
2040
+ * Handle unsolicited light state updates from JMRI
2041
+ */
2042
+ handleLightUpdate(message) {
2043
+ const name = message.data?.name;
2044
+ const state = message.data?.state;
2045
+ if (!name || state === void 0) return;
2046
+ const oldState = this.lights.get(name);
2047
+ this.lights.set(name, state);
2048
+ if (oldState !== state) {
2049
+ this.emit("light:changed", name, state);
2050
+ }
2051
+ }
2052
+ };
2053
+
1894
2054
  // src/types/client-options.ts
1895
2055
  var DEFAULT_CLIENT_OPTIONS = {
1896
2056
  host: "localhost",
@@ -1967,6 +2127,7 @@ var JmriClient = class extends import_index.default {
1967
2127
  this.rosterManager = new RosterManager(this.wsClient);
1968
2128
  this.throttleManager = new ThrottleManager(this.wsClient);
1969
2129
  this.turnoutManager = new TurnoutManager(this.wsClient);
2130
+ this.lightManager = new LightManager(this.wsClient);
1970
2131
  this.wsClient.on("connected", () => this.emit("connected"));
1971
2132
  this.wsClient.on("disconnected", (reason) => this.emit("disconnected", reason));
1972
2133
  this.wsClient.on(
@@ -1994,6 +2155,10 @@ var JmriClient = class extends import_index.default {
1994
2155
  "turnout:changed",
1995
2156
  (name, state) => this.emit("turnout:changed", name, state)
1996
2157
  );
2158
+ this.lightManager.on(
2159
+ "light:changed",
2160
+ (name, state) => this.emit("light:changed", name, state)
2161
+ );
1997
2162
  this.throttleManager.on(
1998
2163
  "throttle:acquired",
1999
2164
  (id) => this.emit("throttle:acquired", id)
@@ -2142,6 +2307,51 @@ var JmriClient = class extends import_index.default {
2142
2307
  return this.turnoutManager.getCachedTurnouts();
2143
2308
  }
2144
2309
  // ============================================================================
2310
+ // Light Control
2311
+ // ============================================================================
2312
+ /**
2313
+ * Get the current state of a light
2314
+ */
2315
+ async getLight(name) {
2316
+ return this.lightManager.getLight(name);
2317
+ }
2318
+ /**
2319
+ * Set a light to the given state
2320
+ */
2321
+ async setLight(name, state) {
2322
+ return this.lightManager.setLight(name, state);
2323
+ }
2324
+ /**
2325
+ * Turn a light on
2326
+ */
2327
+ async turnOnLight(name) {
2328
+ return this.lightManager.turnOnLight(name);
2329
+ }
2330
+ /**
2331
+ * Turn a light off
2332
+ */
2333
+ async turnOffLight(name) {
2334
+ return this.lightManager.turnOffLight(name);
2335
+ }
2336
+ /**
2337
+ * List all lights known to JMRI
2338
+ */
2339
+ async listLights() {
2340
+ return this.lightManager.listLights();
2341
+ }
2342
+ /**
2343
+ * Get cached light state without a network request
2344
+ */
2345
+ getLightState(name) {
2346
+ return this.lightManager.getLightState(name);
2347
+ }
2348
+ /**
2349
+ * Get all cached light states
2350
+ */
2351
+ getCachedLights() {
2352
+ return this.lightManager.getCachedLights();
2353
+ }
2354
+ // ============================================================================
2145
2355
  // Throttle Control
2146
2356
  // ============================================================================
2147
2357
  /**
@@ -2239,11 +2449,14 @@ var JmriClient = class extends import_index.default {
2239
2449
  export {
2240
2450
  ConnectionState,
2241
2451
  JmriClient,
2452
+ LightState,
2242
2453
  MockResponseManager,
2243
2454
  PowerState,
2244
2455
  TurnoutState,
2456
+ WebSocketClient,
2245
2457
  isThrottleFunctionKey,
2246
2458
  isValidSpeed,
2459
+ lightStateToString,
2247
2460
  mockData,
2248
2461
  mockResponseManager,
2249
2462
  powerStateToString,
@@ -10,6 +10,7 @@ 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
12
  const turnout_manager_js_1 = require("./managers/turnout-manager.js");
13
+ const light_manager_js_1 = require("./managers/light-manager.js");
13
14
  const client_options_js_1 = require("./types/client-options.js");
14
15
  /**
15
16
  * JMRI WebSocket Client
@@ -43,6 +44,7 @@ class JmriClient extends eventemitter3_1.EventEmitter {
43
44
  this.rosterManager = new roster_manager_js_1.RosterManager(this.wsClient);
44
45
  this.throttleManager = new throttle_manager_js_1.ThrottleManager(this.wsClient);
45
46
  this.turnoutManager = new turnout_manager_js_1.TurnoutManager(this.wsClient);
47
+ this.lightManager = new light_manager_js_1.LightManager(this.wsClient);
46
48
  // Forward events from WebSocket client
47
49
  this.wsClient.on('connected', () => this.emit('connected'));
48
50
  this.wsClient.on('disconnected', (reason) => this.emit('disconnected', reason));
@@ -57,6 +59,7 @@ class JmriClient extends eventemitter3_1.EventEmitter {
57
59
  // Forward events from managers
58
60
  this.powerManager.on('power:changed', (state) => this.emit('power:changed', state));
59
61
  this.turnoutManager.on('turnout:changed', (name, state) => this.emit('turnout:changed', name, state));
62
+ this.lightManager.on('light:changed', (name, state) => this.emit('light:changed', name, state));
60
63
  this.throttleManager.on('throttle:acquired', (id) => this.emit('throttle:acquired', id));
61
64
  this.throttleManager.on('throttle:updated', (id, data) => this.emit('throttle:updated', id, data));
62
65
  this.throttleManager.on('throttle:released', (id) => this.emit('throttle:released', id));
@@ -196,6 +199,51 @@ class JmriClient extends eventemitter3_1.EventEmitter {
196
199
  return this.turnoutManager.getCachedTurnouts();
197
200
  }
198
201
  // ============================================================================
202
+ // Light Control
203
+ // ============================================================================
204
+ /**
205
+ * Get the current state of a light
206
+ */
207
+ async getLight(name) {
208
+ return this.lightManager.getLight(name);
209
+ }
210
+ /**
211
+ * Set a light to the given state
212
+ */
213
+ async setLight(name, state) {
214
+ return this.lightManager.setLight(name, state);
215
+ }
216
+ /**
217
+ * Turn a light on
218
+ */
219
+ async turnOnLight(name) {
220
+ return this.lightManager.turnOnLight(name);
221
+ }
222
+ /**
223
+ * Turn a light off
224
+ */
225
+ async turnOffLight(name) {
226
+ return this.lightManager.turnOffLight(name);
227
+ }
228
+ /**
229
+ * List all lights known to JMRI
230
+ */
231
+ async listLights() {
232
+ return this.lightManager.listLights();
233
+ }
234
+ /**
235
+ * Get cached light state without a network request
236
+ */
237
+ getLightState(name) {
238
+ return this.lightManager.getLightState(name);
239
+ }
240
+ /**
241
+ * Get all cached light states
242
+ */
243
+ getCachedLights() {
244
+ return this.lightManager.getCachedLights();
245
+ }
246
+ // ============================================================================
199
247
  // Throttle Control
200
248
  // ============================================================================
201
249
  /**
package/dist/cjs/index.js CHANGED
@@ -4,14 +4,17 @@
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.turnoutStateToString = exports.powerStateToString = exports.isValidSpeed = exports.isThrottleFunctionKey = exports.ConnectionState = exports.TurnoutState = exports.PowerState = exports.JmriClient = void 0;
7
+ exports.mockData = exports.mockResponseManager = exports.MockResponseManager = exports.lightStateToString = exports.turnoutStateToString = exports.powerStateToString = exports.isValidSpeed = exports.isThrottleFunctionKey = exports.ConnectionState = exports.LightState = exports.TurnoutState = exports.PowerState = exports.WebSocketClient = 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
+ var websocket_client_js_1 = require("./core/websocket-client.js");
11
+ Object.defineProperty(exports, "WebSocketClient", { enumerable: true, get: function () { return websocket_client_js_1.WebSocketClient; } });
10
12
  // Export types
11
13
  var index_js_1 = require("./types/index.js");
12
14
  // JMRI message types
13
15
  Object.defineProperty(exports, "PowerState", { enumerable: true, get: function () { return index_js_1.PowerState; } });
14
16
  Object.defineProperty(exports, "TurnoutState", { enumerable: true, get: function () { return index_js_1.TurnoutState; } });
17
+ Object.defineProperty(exports, "LightState", { enumerable: true, get: function () { return index_js_1.LightState; } });
15
18
  // Event types
16
19
  Object.defineProperty(exports, "ConnectionState", { enumerable: true, get: function () { return index_js_1.ConnectionState; } });
17
20
  // Export utility functions
@@ -21,6 +24,7 @@ Object.defineProperty(exports, "isValidSpeed", { enumerable: true, get: function
21
24
  var jmri_messages_js_1 = require("./types/jmri-messages.js");
22
25
  Object.defineProperty(exports, "powerStateToString", { enumerable: true, get: function () { return jmri_messages_js_1.powerStateToString; } });
23
26
  Object.defineProperty(exports, "turnoutStateToString", { enumerable: true, get: function () { return jmri_messages_js_1.turnoutStateToString; } });
27
+ Object.defineProperty(exports, "lightStateToString", { enumerable: true, get: function () { return jmri_messages_js_1.lightStateToString; } });
24
28
  // Export mock system for testing and demo purposes
25
29
  var index_js_2 = require("./mocks/index.js");
26
30
  Object.defineProperty(exports, "MockResponseManager", { enumerable: true, get: function () { return index_js_2.MockResponseManager; } });
@@ -21,3 +21,4 @@ __exportStar(require("./power-manager.js"), exports);
21
21
  __exportStar(require("./roster-manager.js"), exports);
22
22
  __exportStar(require("./throttle-manager.js"), exports);
23
23
  __exportStar(require("./turnout-manager.js"), exports);
24
+ __exportStar(require("./light-manager.js"), exports);
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ /**
3
+ * Light manager
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.LightManager = void 0;
7
+ const eventemitter3_1 = require("eventemitter3");
8
+ const jmri_messages_js_1 = require("../types/jmri-messages.js");
9
+ /**
10
+ * Manages JMRI light state
11
+ */
12
+ class LightManager extends eventemitter3_1.EventEmitter {
13
+ constructor(client) {
14
+ super();
15
+ this.lights = new Map();
16
+ this.client = client;
17
+ this.client.on('update', (message) => {
18
+ if (message.type === 'light') {
19
+ this.handleLightUpdate(message);
20
+ }
21
+ });
22
+ }
23
+ /**
24
+ * Get the current state of a light.
25
+ * Also registers a server-side listener so subsequent changes are pushed.
26
+ */
27
+ async getLight(name) {
28
+ const message = {
29
+ type: 'light',
30
+ data: { name }
31
+ };
32
+ const response = await this.client.request(message);
33
+ const state = response.data?.state ?? jmri_messages_js_1.LightState.UNKNOWN;
34
+ this.lights.set(name, state);
35
+ return state;
36
+ }
37
+ /**
38
+ * Set a light to the given state
39
+ */
40
+ async setLight(name, state) {
41
+ const message = {
42
+ type: 'light',
43
+ method: 'post',
44
+ data: { name, state }
45
+ };
46
+ await this.client.request(message);
47
+ const oldState = this.lights.get(name);
48
+ this.lights.set(name, state);
49
+ if (oldState !== state) {
50
+ this.emit('light:changed', name, state);
51
+ }
52
+ }
53
+ /**
54
+ * Turn a light on
55
+ */
56
+ async turnOnLight(name) {
57
+ return this.setLight(name, jmri_messages_js_1.LightState.ON);
58
+ }
59
+ /**
60
+ * Turn a light off
61
+ */
62
+ async turnOffLight(name) {
63
+ return this.setLight(name, jmri_messages_js_1.LightState.OFF);
64
+ }
65
+ /**
66
+ * List all lights known to JMRI
67
+ */
68
+ async listLights() {
69
+ const message = {
70
+ type: 'light',
71
+ method: 'list'
72
+ };
73
+ const response = await this.client.request(message);
74
+ const entries = Array.isArray(response?.data)
75
+ ? response.data.map((r) => r.data ?? r)
76
+ : [];
77
+ for (const entry of entries) {
78
+ if (entry.name && entry.state !== undefined) {
79
+ this.lights.set(entry.name, entry.state);
80
+ }
81
+ }
82
+ return entries;
83
+ }
84
+ /**
85
+ * Get cached light state without a network request
86
+ */
87
+ getLightState(name) {
88
+ return this.lights.get(name);
89
+ }
90
+ /**
91
+ * Get all cached light states
92
+ */
93
+ getCachedLights() {
94
+ return new Map(this.lights);
95
+ }
96
+ /**
97
+ * Handle unsolicited light state updates from JMRI
98
+ */
99
+ handleLightUpdate(message) {
100
+ const name = message.data?.name;
101
+ const state = message.data?.state;
102
+ if (!name || state === undefined)
103
+ return;
104
+ const oldState = this.lights.get(name);
105
+ this.lights.set(name, state);
106
+ if (oldState !== state) {
107
+ this.emit('light:changed', name, state);
108
+ }
109
+ }
110
+ }
111
+ exports.LightManager = LightManager;
@@ -188,6 +188,13 @@ exports.mockData = {
188
188
  }
189
189
  }
190
190
  },
191
+ "light": {
192
+ "list": [
193
+ { "type": "light", "data": { "name": "IL1", "userName": "Yard Light", "comment": null, "properties": [], "state": 4 } },
194
+ { "type": "light", "data": { "name": "IL2", "userName": "Platform Light", "comment": null, "properties": [], "state": 4 } },
195
+ { "type": "light", "data": { "name": "IL3", "userName": "Signal Lamp", "comment": null, "properties": [], "state": 2 } }
196
+ ]
197
+ },
191
198
  "turnout": {
192
199
  "list": [
193
200
  { "type": "turnout", "data": { "name": "LT1", "userName": "Main Diverge", "state": 2 } },
@@ -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.lights = new Map([
17
+ ['IL1', jmri_messages_js_1.LightState.OFF],
18
+ ['IL2', jmri_messages_js_1.LightState.OFF],
19
+ ['IL3', jmri_messages_js_1.LightState.ON]
20
+ ]);
16
21
  this.turnouts = new Map([
17
22
  ['LT1', jmri_messages_js_1.TurnoutState.CLOSED],
18
23
  ['LT2', jmri_messages_js_1.TurnoutState.CLOSED],
@@ -39,6 +44,8 @@ class MockResponseManager {
39
44
  return this.getRosterResponse(message);
40
45
  case 'throttle':
41
46
  return this.getThrottleResponse(message);
47
+ case 'light':
48
+ return this.getLightResponse(message);
42
49
  case 'turnout':
43
50
  return this.getTurnoutResponse(message);
44
51
  case 'ping':
@@ -164,6 +171,29 @@ class MockResponseManager {
164
171
  data: {}
165
172
  };
166
173
  }
174
+ /**
175
+ * Get light response
176
+ */
177
+ getLightResponse(message) {
178
+ // List all lights
179
+ if (message.method === 'list') {
180
+ return {
181
+ type: 'light',
182
+ data: JSON.parse(JSON.stringify(mock_data_js_1.mockData.light.list))
183
+ };
184
+ }
185
+ const name = message.data?.name;
186
+ if (!name) {
187
+ return { type: 'light', data: { name: '', state: jmri_messages_js_1.LightState.UNKNOWN } };
188
+ }
189
+ // Set light state
190
+ if (message.method === 'post' && message.data?.state !== undefined) {
191
+ this.lights.set(name, message.data.state);
192
+ }
193
+ // Get or confirm current state
194
+ const state = this.lights.get(name) ?? jmri_messages_js_1.LightState.UNKNOWN;
195
+ return { type: 'light', data: { name, state } };
196
+ }
167
197
  /**
168
198
  * Get turnout response
169
199
  */
@@ -217,6 +247,12 @@ class MockResponseManager {
217
247
  getThrottles() {
218
248
  return this.throttles;
219
249
  }
250
+ /**
251
+ * Get all light states (for testing)
252
+ */
253
+ getLights() {
254
+ return this.lights;
255
+ }
220
256
  /**
221
257
  * Get all turnout states (for testing)
222
258
  */
@@ -229,6 +265,11 @@ class MockResponseManager {
229
265
  reset() {
230
266
  this.powerState = jmri_messages_js_1.PowerState.OFF;
231
267
  this.throttles.clear();
268
+ this.lights = new Map([
269
+ ['IL1', jmri_messages_js_1.LightState.OFF],
270
+ ['IL2', jmri_messages_js_1.LightState.OFF],
271
+ ['IL3', jmri_messages_js_1.LightState.ON]
272
+ ]);
232
273
  this.turnouts = new Map([
233
274
  ['LT1', jmri_messages_js_1.TurnoutState.CLOSED],
234
275
  ['LT2', jmri_messages_js_1.TurnoutState.CLOSED],
@@ -4,9 +4,10 @@
4
4
  * Based on JMRI JSON protocol specification
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.TurnoutState = exports.PowerState = void 0;
7
+ exports.LightState = exports.TurnoutState = exports.PowerState = void 0;
8
8
  exports.powerStateToString = powerStateToString;
9
9
  exports.turnoutStateToString = turnoutStateToString;
10
+ exports.lightStateToString = lightStateToString;
10
11
  /**
11
12
  * Power state values (from JMRI JSON protocol constants)
12
13
  * UNKNOWN = 0 (state cannot be determined)
@@ -66,3 +67,29 @@ function turnoutStateToString(state) {
66
67
  return 'UNKNOWN';
67
68
  }
68
69
  }
70
+ /**
71
+ * Light state values (from JMRI JSON protocol constants)
72
+ * UNKNOWN = 0 (state cannot be determined)
73
+ * ON = 2 (light is on)
74
+ * OFF = 4 (light is off)
75
+ */
76
+ var LightState;
77
+ (function (LightState) {
78
+ LightState[LightState["UNKNOWN"] = 0] = "UNKNOWN";
79
+ LightState[LightState["ON"] = 2] = "ON";
80
+ LightState[LightState["OFF"] = 4] = "OFF";
81
+ })(LightState || (exports.LightState = LightState = {}));
82
+ /**
83
+ * Convert LightState enum to human-readable string
84
+ */
85
+ function lightStateToString(state) {
86
+ switch (state) {
87
+ case LightState.ON:
88
+ return 'ON';
89
+ case LightState.OFF:
90
+ return 'OFF';
91
+ case LightState.UNKNOWN:
92
+ default:
93
+ return 'UNKNOWN';
94
+ }
95
+ }
@@ -7,6 +7,7 @@ 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
9
  import { TurnoutManager } from './managers/turnout-manager.js';
10
+ import { LightManager } from './managers/light-manager.js';
10
11
  import { mergeOptions } from './types/client-options.js';
11
12
  /**
12
13
  * JMRI WebSocket Client
@@ -40,6 +41,7 @@ export class JmriClient extends EventEmitter {
40
41
  this.rosterManager = new RosterManager(this.wsClient);
41
42
  this.throttleManager = new ThrottleManager(this.wsClient);
42
43
  this.turnoutManager = new TurnoutManager(this.wsClient);
44
+ this.lightManager = new LightManager(this.wsClient);
43
45
  // Forward events from WebSocket client
44
46
  this.wsClient.on('connected', () => this.emit('connected'));
45
47
  this.wsClient.on('disconnected', (reason) => this.emit('disconnected', reason));
@@ -54,6 +56,7 @@ export class JmriClient extends EventEmitter {
54
56
  // Forward events from managers
55
57
  this.powerManager.on('power:changed', (state) => this.emit('power:changed', state));
56
58
  this.turnoutManager.on('turnout:changed', (name, state) => this.emit('turnout:changed', name, state));
59
+ this.lightManager.on('light:changed', (name, state) => this.emit('light: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));
@@ -193,6 +196,51 @@ export class JmriClient extends EventEmitter {
193
196
  return this.turnoutManager.getCachedTurnouts();
194
197
  }
195
198
  // ============================================================================
199
+ // Light Control
200
+ // ============================================================================
201
+ /**
202
+ * Get the current state of a light
203
+ */
204
+ async getLight(name) {
205
+ return this.lightManager.getLight(name);
206
+ }
207
+ /**
208
+ * Set a light to the given state
209
+ */
210
+ async setLight(name, state) {
211
+ return this.lightManager.setLight(name, state);
212
+ }
213
+ /**
214
+ * Turn a light on
215
+ */
216
+ async turnOnLight(name) {
217
+ return this.lightManager.turnOnLight(name);
218
+ }
219
+ /**
220
+ * Turn a light off
221
+ */
222
+ async turnOffLight(name) {
223
+ return this.lightManager.turnOffLight(name);
224
+ }
225
+ /**
226
+ * List all lights known to JMRI
227
+ */
228
+ async listLights() {
229
+ return this.lightManager.listLights();
230
+ }
231
+ /**
232
+ * Get cached light state without a network request
233
+ */
234
+ getLightState(name) {
235
+ return this.lightManager.getLightState(name);
236
+ }
237
+ /**
238
+ * Get all cached light states
239
+ */
240
+ getCachedLights() {
241
+ return this.lightManager.getCachedLights();
242
+ }
243
+ // ============================================================================
196
244
  // Throttle Control
197
245
  // ============================================================================
198
246
  /**
package/dist/esm/index.js CHANGED
@@ -3,14 +3,15 @@
3
3
  * WebSocket client for JMRI with real-time updates and throttle control
4
4
  */
5
5
  export { JmriClient } from './client.js';
6
+ export { WebSocketClient } from './core/websocket-client.js';
6
7
  // Export types
7
8
  export {
8
9
  // JMRI message types
9
- PowerState, TurnoutState,
10
+ PowerState, TurnoutState, LightState,
10
11
  // Event types
11
12
  ConnectionState } from './types/index.js';
12
13
  // Export utility functions
13
14
  export { isThrottleFunctionKey, isValidSpeed } from './types/throttle.js';
14
- export { powerStateToString, turnoutStateToString } from './types/jmri-messages.js';
15
+ export { powerStateToString, turnoutStateToString, lightStateToString } from './types/jmri-messages.js';
15
16
  // Export mock system for testing and demo purposes
16
17
  export { MockResponseManager, mockResponseManager, mockData } from './mocks/index.js';
@@ -5,3 +5,4 @@ export * from './power-manager.js';
5
5
  export * from './roster-manager.js';
6
6
  export * from './throttle-manager.js';
7
7
  export * from './turnout-manager.js';
8
+ export * from './light-manager.js';
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Light manager
3
+ */
4
+ import { EventEmitter } from 'eventemitter3';
5
+ import { LightState } from '../types/jmri-messages.js';
6
+ /**
7
+ * Manages JMRI light state
8
+ */
9
+ export class LightManager extends EventEmitter {
10
+ constructor(client) {
11
+ super();
12
+ this.lights = new Map();
13
+ this.client = client;
14
+ this.client.on('update', (message) => {
15
+ if (message.type === 'light') {
16
+ this.handleLightUpdate(message);
17
+ }
18
+ });
19
+ }
20
+ /**
21
+ * Get the current state of a light.
22
+ * Also registers a server-side listener so subsequent changes are pushed.
23
+ */
24
+ async getLight(name) {
25
+ const message = {
26
+ type: 'light',
27
+ data: { name }
28
+ };
29
+ const response = await this.client.request(message);
30
+ const state = response.data?.state ?? LightState.UNKNOWN;
31
+ this.lights.set(name, state);
32
+ return state;
33
+ }
34
+ /**
35
+ * Set a light to the given state
36
+ */
37
+ async setLight(name, state) {
38
+ const message = {
39
+ type: 'light',
40
+ method: 'post',
41
+ data: { name, state }
42
+ };
43
+ await this.client.request(message);
44
+ const oldState = this.lights.get(name);
45
+ this.lights.set(name, state);
46
+ if (oldState !== state) {
47
+ this.emit('light:changed', name, state);
48
+ }
49
+ }
50
+ /**
51
+ * Turn a light on
52
+ */
53
+ async turnOnLight(name) {
54
+ return this.setLight(name, LightState.ON);
55
+ }
56
+ /**
57
+ * Turn a light off
58
+ */
59
+ async turnOffLight(name) {
60
+ return this.setLight(name, LightState.OFF);
61
+ }
62
+ /**
63
+ * List all lights known to JMRI
64
+ */
65
+ async listLights() {
66
+ const message = {
67
+ type: 'light',
68
+ method: 'list'
69
+ };
70
+ const response = await this.client.request(message);
71
+ const entries = Array.isArray(response?.data)
72
+ ? response.data.map((r) => r.data ?? r)
73
+ : [];
74
+ for (const entry of entries) {
75
+ if (entry.name && entry.state !== undefined) {
76
+ this.lights.set(entry.name, entry.state);
77
+ }
78
+ }
79
+ return entries;
80
+ }
81
+ /**
82
+ * Get cached light state without a network request
83
+ */
84
+ getLightState(name) {
85
+ return this.lights.get(name);
86
+ }
87
+ /**
88
+ * Get all cached light states
89
+ */
90
+ getCachedLights() {
91
+ return new Map(this.lights);
92
+ }
93
+ /**
94
+ * Handle unsolicited light state updates from JMRI
95
+ */
96
+ handleLightUpdate(message) {
97
+ const name = message.data?.name;
98
+ const state = message.data?.state;
99
+ if (!name || state === undefined)
100
+ return;
101
+ const oldState = this.lights.get(name);
102
+ this.lights.set(name, state);
103
+ if (oldState !== state) {
104
+ this.emit('light:changed', name, state);
105
+ }
106
+ }
107
+ }
@@ -185,6 +185,13 @@ export const mockData = {
185
185
  }
186
186
  }
187
187
  },
188
+ "light": {
189
+ "list": [
190
+ { "type": "light", "data": { "name": "IL1", "userName": "Yard Light", "comment": null, "properties": [], "state": 4 } },
191
+ { "type": "light", "data": { "name": "IL2", "userName": "Platform Light", "comment": null, "properties": [], "state": 4 } },
192
+ { "type": "light", "data": { "name": "IL3", "userName": "Signal Lamp", "comment": null, "properties": [], "state": 2 } }
193
+ ]
194
+ },
188
195
  "turnout": {
189
196
  "list": [
190
197
  { "type": "turnout", "data": { "name": "LT1", "userName": "Main Diverge", "state": 2 } },
@@ -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, TurnoutState } from '../types/jmri-messages.js';
5
+ import { PowerState, TurnoutState, LightState } 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.lights = new Map([
14
+ ['IL1', LightState.OFF],
15
+ ['IL2', LightState.OFF],
16
+ ['IL3', LightState.ON]
17
+ ]);
13
18
  this.turnouts = new Map([
14
19
  ['LT1', TurnoutState.CLOSED],
15
20
  ['LT2', TurnoutState.CLOSED],
@@ -36,6 +41,8 @@ export class MockResponseManager {
36
41
  return this.getRosterResponse(message);
37
42
  case 'throttle':
38
43
  return this.getThrottleResponse(message);
44
+ case 'light':
45
+ return this.getLightResponse(message);
39
46
  case 'turnout':
40
47
  return this.getTurnoutResponse(message);
41
48
  case 'ping':
@@ -161,6 +168,29 @@ export class MockResponseManager {
161
168
  data: {}
162
169
  };
163
170
  }
171
+ /**
172
+ * Get light response
173
+ */
174
+ getLightResponse(message) {
175
+ // List all lights
176
+ if (message.method === 'list') {
177
+ return {
178
+ type: 'light',
179
+ data: JSON.parse(JSON.stringify(mockData.light.list))
180
+ };
181
+ }
182
+ const name = message.data?.name;
183
+ if (!name) {
184
+ return { type: 'light', data: { name: '', state: LightState.UNKNOWN } };
185
+ }
186
+ // Set light state
187
+ if (message.method === 'post' && message.data?.state !== undefined) {
188
+ this.lights.set(name, message.data.state);
189
+ }
190
+ // Get or confirm current state
191
+ const state = this.lights.get(name) ?? LightState.UNKNOWN;
192
+ return { type: 'light', data: { name, state } };
193
+ }
164
194
  /**
165
195
  * Get turnout response
166
196
  */
@@ -214,6 +244,12 @@ export class MockResponseManager {
214
244
  getThrottles() {
215
245
  return this.throttles;
216
246
  }
247
+ /**
248
+ * Get all light states (for testing)
249
+ */
250
+ getLights() {
251
+ return this.lights;
252
+ }
217
253
  /**
218
254
  * Get all turnout states (for testing)
219
255
  */
@@ -226,6 +262,11 @@ export class MockResponseManager {
226
262
  reset() {
227
263
  this.powerState = PowerState.OFF;
228
264
  this.throttles.clear();
265
+ this.lights = new Map([
266
+ ['IL1', LightState.OFF],
267
+ ['IL2', LightState.OFF],
268
+ ['IL3', LightState.ON]
269
+ ]);
229
270
  this.turnouts = new Map([
230
271
  ['LT1', TurnoutState.CLOSED],
231
272
  ['LT2', TurnoutState.CLOSED],
@@ -61,3 +61,29 @@ export function turnoutStateToString(state) {
61
61
  return 'UNKNOWN';
62
62
  }
63
63
  }
64
+ /**
65
+ * Light state values (from JMRI JSON protocol constants)
66
+ * UNKNOWN = 0 (state cannot be determined)
67
+ * ON = 2 (light is on)
68
+ * OFF = 4 (light is off)
69
+ */
70
+ export var LightState;
71
+ (function (LightState) {
72
+ LightState[LightState["UNKNOWN"] = 0] = "UNKNOWN";
73
+ LightState[LightState["ON"] = 2] = "ON";
74
+ LightState[LightState["OFF"] = 4] = "OFF";
75
+ })(LightState || (LightState = {}));
76
+ /**
77
+ * Convert LightState enum to human-readable string
78
+ */
79
+ export function lightStateToString(state) {
80
+ switch (state) {
81
+ case LightState.ON:
82
+ return 'ON';
83
+ case LightState.OFF:
84
+ return 'OFF';
85
+ case LightState.UNKNOWN:
86
+ default:
87
+ return 'UNKNOWN';
88
+ }
89
+ }
@@ -2,8 +2,9 @@
2
2
  * Main JMRI client class
3
3
  */
4
4
  import { EventEmitter } from 'eventemitter3';
5
+ import { WebSocketClient } from './core/websocket-client.js';
5
6
  import { PartialClientOptions } from './types/client-options.js';
6
- import { PowerState, RosterEntryWrapper, TurnoutState, TurnoutData } from './types/jmri-messages.js';
7
+ import { PowerState, RosterEntryWrapper, TurnoutState, TurnoutData, LightState, LightData } from './types/jmri-messages.js';
7
8
  import { ConnectionState } from './types/events.js';
8
9
  import { ThrottleAcquireOptions, ThrottleFunctionKey, ThrottleState } from './types/throttle.js';
9
10
  /**
@@ -12,11 +13,12 @@ import { ThrottleAcquireOptions, ThrottleFunctionKey, ThrottleState } from './ty
12
13
  */
13
14
  export declare class JmriClient extends EventEmitter {
14
15
  private options;
15
- private wsClient;
16
+ protected wsClient: WebSocketClient;
16
17
  private powerManager;
17
18
  private rosterManager;
18
19
  private throttleManager;
19
20
  private turnoutManager;
21
+ private lightManager;
20
22
  /**
21
23
  * Create a new JMRI client
22
24
  *
@@ -111,6 +113,34 @@ export declare class JmriClient extends EventEmitter {
111
113
  * Get all cached turnout states
112
114
  */
113
115
  getCachedTurnouts(): Map<string, TurnoutState>;
116
+ /**
117
+ * Get the current state of a light
118
+ */
119
+ getLight(name: string): Promise<LightState>;
120
+ /**
121
+ * Set a light to the given state
122
+ */
123
+ setLight(name: string, state: LightState): Promise<void>;
124
+ /**
125
+ * Turn a light on
126
+ */
127
+ turnOnLight(name: string): Promise<void>;
128
+ /**
129
+ * Turn a light off
130
+ */
131
+ turnOffLight(name: string): Promise<void>;
132
+ /**
133
+ * List all lights known to JMRI
134
+ */
135
+ listLights(): Promise<LightData[]>;
136
+ /**
137
+ * Get cached light state without a network request
138
+ */
139
+ getLightState(name: string): LightState | undefined;
140
+ /**
141
+ * Get all cached light states
142
+ */
143
+ getCachedLights(): Map<string, LightState>;
114
144
  /**
115
145
  * Acquire a throttle for a locomotive
116
146
  *
@@ -3,8 +3,9 @@
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, TurnoutState, RosterEntry, TurnoutData, JmriMessage, PowerMessage, TurnoutMessage, ThrottleMessage, RosterMessage, ConnectionState, EventPayloads, ThrottleAcquireOptions, ThrottleFunctionKey, ThrottleState } from './types/index.js';
6
+ export { WebSocketClient } from './core/websocket-client.js';
7
+ export { JmriClientOptions, PartialClientOptions, ReconnectionOptions, HeartbeatOptions, MockOptions, PowerState, TurnoutState, LightState, RosterEntry, TurnoutData, LightData, JmriMessage, PowerMessage, TurnoutMessage, LightMessage, ThrottleMessage, RosterMessage, ConnectionState, EventPayloads, ThrottleAcquireOptions, ThrottleFunctionKey, ThrottleState } from './types/index.js';
7
8
  export { isThrottleFunctionKey, isValidSpeed } from './types/throttle.js';
8
- export { powerStateToString, turnoutStateToString } from './types/jmri-messages.js';
9
+ export { powerStateToString, turnoutStateToString, lightStateToString } from './types/jmri-messages.js';
9
10
  export { MockResponseManager, mockResponseManager, mockData } from './mocks/index.js';
10
11
  export type { MockResponseManagerOptions } from './mocks/index.js';
@@ -5,3 +5,4 @@ export * from './power-manager.js';
5
5
  export * from './roster-manager.js';
6
6
  export * from './throttle-manager.js';
7
7
  export * from './turnout-manager.js';
8
+ export * from './light-manager.js';
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Light manager
3
+ */
4
+ import { EventEmitter } from 'eventemitter3';
5
+ import { WebSocketClient } from '../core/websocket-client.js';
6
+ import { LightState, LightData } from '../types/jmri-messages.js';
7
+ /**
8
+ * Manages JMRI light state
9
+ */
10
+ export declare class LightManager extends EventEmitter {
11
+ private client;
12
+ private lights;
13
+ constructor(client: WebSocketClient);
14
+ /**
15
+ * Get the current state of a light.
16
+ * Also registers a server-side listener so subsequent changes are pushed.
17
+ */
18
+ getLight(name: string): Promise<LightState>;
19
+ /**
20
+ * Set a light to the given state
21
+ */
22
+ setLight(name: string, state: LightState): Promise<void>;
23
+ /**
24
+ * Turn a light on
25
+ */
26
+ turnOnLight(name: string): Promise<void>;
27
+ /**
28
+ * Turn a light off
29
+ */
30
+ turnOffLight(name: string): Promise<void>;
31
+ /**
32
+ * List all lights known to JMRI
33
+ */
34
+ listLights(): Promise<LightData[]>;
35
+ /**
36
+ * Get cached light state without a network request
37
+ */
38
+ getLightState(name: string): LightState | undefined;
39
+ /**
40
+ * Get all cached light states
41
+ */
42
+ getCachedLights(): Map<string, LightState>;
43
+ /**
44
+ * Handle unsolicited light state updates from JMRI
45
+ */
46
+ private handleLightUpdate;
47
+ }
@@ -263,6 +263,36 @@ export declare const mockData: {
263
263
  };
264
264
  };
265
265
  };
266
+ readonly light: {
267
+ readonly list: readonly [{
268
+ readonly type: "light";
269
+ readonly data: {
270
+ readonly name: "IL1";
271
+ readonly userName: "Yard Light";
272
+ readonly comment: null;
273
+ readonly properties: readonly [];
274
+ readonly state: 4;
275
+ };
276
+ }, {
277
+ readonly type: "light";
278
+ readonly data: {
279
+ readonly name: "IL2";
280
+ readonly userName: "Platform Light";
281
+ readonly comment: null;
282
+ readonly properties: readonly [];
283
+ readonly state: 4;
284
+ };
285
+ }, {
286
+ readonly type: "light";
287
+ readonly data: {
288
+ readonly name: "IL3";
289
+ readonly userName: "Signal Lamp";
290
+ readonly comment: null;
291
+ readonly properties: readonly [];
292
+ readonly state: 2;
293
+ };
294
+ }];
295
+ };
266
296
  readonly turnout: {
267
297
  readonly list: readonly [{
268
298
  readonly type: "turnout";
@@ -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, TurnoutState } from '../types/jmri-messages.js';
5
+ import { JmriMessage, PowerState, TurnoutState, LightState } 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 lights;
24
25
  private turnouts;
25
26
  constructor(options?: MockResponseManagerOptions);
26
27
  /**
@@ -43,6 +44,10 @@ export declare class MockResponseManager {
43
44
  * Get throttle response
44
45
  */
45
46
  private getThrottleResponse;
47
+ /**
48
+ * Get light response
49
+ */
50
+ private getLightResponse;
46
51
  /**
47
52
  * Get turnout response
48
53
  */
@@ -67,6 +72,10 @@ export declare class MockResponseManager {
67
72
  * Get all throttles (for testing)
68
73
  */
69
74
  getThrottles(): Map<string, any>;
75
+ /**
76
+ * Get all light states (for testing)
77
+ */
78
+ getLights(): Map<string, LightState>;
70
79
  /**
71
80
  * Get all turnout states (for testing)
72
81
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Event types for JmriClient EventEmitter
3
3
  */
4
- import { PowerState, TurnoutState } from './jmri-messages.js';
4
+ import { PowerState, TurnoutState, LightState } from './jmri-messages.js';
5
5
  import { ThrottleData } from './jmri-messages.js';
6
6
  /**
7
7
  * Connection states
@@ -24,6 +24,7 @@ export interface EventPayloads {
24
24
  'error': Error;
25
25
  'power:changed': PowerState;
26
26
  'turnout:changed': [name: string, state: TurnoutState];
27
+ 'light:changed': [name: string, state: LightState];
27
28
  'throttle:acquired': string;
28
29
  'throttle:updated': [throttleId: string, data: ThrottleData];
29
30
  'throttle:released': string;
@@ -183,6 +183,38 @@ export interface TurnoutMessage extends JmriMessage {
183
183
  type: 'turnout';
184
184
  data?: TurnoutData;
185
185
  }
186
+ /**
187
+ * Light state values (from JMRI JSON protocol constants)
188
+ * UNKNOWN = 0 (state cannot be determined)
189
+ * ON = 2 (light is on)
190
+ * OFF = 4 (light is off)
191
+ */
192
+ export declare enum LightState {
193
+ UNKNOWN = 0,
194
+ ON = 2,
195
+ OFF = 4
196
+ }
197
+ /**
198
+ * Convert LightState enum to human-readable string
199
+ */
200
+ export declare function lightStateToString(state: LightState): string;
201
+ /**
202
+ * Light data structure
203
+ */
204
+ export interface LightData {
205
+ name: string;
206
+ userName?: string;
207
+ comment?: string | null;
208
+ properties?: any[];
209
+ state?: LightState;
210
+ }
211
+ /**
212
+ * Light message
213
+ */
214
+ export interface LightMessage extends JmriMessage {
215
+ type: 'light';
216
+ data?: LightData;
217
+ }
186
218
  /**
187
219
  * Ping message (heartbeat)
188
220
  */
@@ -231,4 +263,4 @@ export interface ErrorMessage extends JmriMessage {
231
263
  /**
232
264
  * Union type of all possible JMRI messages
233
265
  */
234
- export type AnyJmriMessage = PowerMessage | ThrottleMessage | RosterMessage | TurnoutMessage | PingMessage | PongMessage | HelloMessage | GoodbyeMessage | ErrorMessage;
266
+ export type AnyJmriMessage = PowerMessage | ThrottleMessage | RosterMessage | TurnoutMessage | LightMessage | PingMessage | PongMessage | HelloMessage | GoodbyeMessage | ErrorMessage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jmri-client",
3
- "version": "3.7.1",
3
+ "version": "4.1.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",