jmri-client 4.1.1 → 4.2.0-beta.2

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
+ - ✅ **Multi-connection** - Target specific hardware connections by prefix when multiple are configured
22
23
  - ✅ **Extensible** - Subclass `JmriClient` to add support for additional JMRI object types
23
24
 
24
25
  ## Installation
@@ -106,6 +107,20 @@ client.on('reconnecting', (attempt, delay) => {
106
107
  });
107
108
  ```
108
109
 
110
+ ### Multi-Connection Support
111
+
112
+ When JMRI has multiple hardware connections configured, use `getSystemConnections()` to discover their prefixes and pass them to power and throttle commands:
113
+
114
+ ```typescript
115
+ const connections = await client.getSystemConnections();
116
+ // [{ name: 'LocoNet', prefix: 'L' }, { name: 'DCC++', prefix: 'D' }]
117
+
118
+ await client.powerOn('L'); // LocoNet only
119
+ const throttle = await client.acquireThrottle({ address: 3, prefix: 'L' });
120
+ ```
121
+
122
+ When no prefix is supplied the command routes to JMRI's default connection manager, which is the correct behaviour for single-connection layouts.
123
+
109
124
  ### Extending JmriClient
110
125
 
111
126
  `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):
@@ -1481,10 +1481,12 @@ var PowerManager = class extends import_index.default {
1481
1481
  }
1482
1482
  /**
1483
1483
  * Get current track power state
1484
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
1484
1485
  */
1485
- async getPower() {
1486
+ async getPower(prefix) {
1486
1487
  const message = {
1487
- type: "power"
1488
+ type: "power",
1489
+ ...prefix !== void 0 && { data: { state: 0 /* UNKNOWN */, prefix } }
1488
1490
  };
1489
1491
  const response = await this.client.request(message);
1490
1492
  if (response.data?.state !== void 0) {
@@ -1494,12 +1496,14 @@ var PowerManager = class extends import_index.default {
1494
1496
  }
1495
1497
  /**
1496
1498
  * Set track power state
1499
+ * @param state - The desired power state
1500
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
1497
1501
  */
1498
- async setPower(state) {
1502
+ async setPower(state, prefix) {
1499
1503
  const message = {
1500
1504
  type: "power",
1501
1505
  method: "post",
1502
- data: { state }
1506
+ data: { state, ...prefix !== void 0 && { prefix } }
1503
1507
  };
1504
1508
  await this.client.request(message);
1505
1509
  const oldState = this.currentState;
@@ -1510,15 +1514,17 @@ var PowerManager = class extends import_index.default {
1510
1514
  }
1511
1515
  /**
1512
1516
  * Turn track power on
1517
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
1513
1518
  */
1514
- async powerOn() {
1515
- await this.setPower(2 /* ON */);
1519
+ async powerOn(prefix) {
1520
+ await this.setPower(2 /* ON */, prefix);
1516
1521
  }
1517
1522
  /**
1518
1523
  * Turn track power off
1524
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
1519
1525
  */
1520
- async powerOff() {
1521
- await this.setPower(4 /* OFF */);
1526
+ async powerOff(prefix) {
1527
+ await this.setPower(4 /* OFF */, prefix);
1522
1528
  }
1523
1529
  /**
1524
1530
  * Get cached power state (no network request)
@@ -1672,7 +1678,8 @@ var ThrottleManager = class extends import_index.default {
1672
1678
  type: "throttle",
1673
1679
  data: {
1674
1680
  name: throttleName,
1675
- address: options.address
1681
+ address: options.address,
1682
+ ...options.prefix !== void 0 && { prefix: options.prefix }
1676
1683
  }
1677
1684
  };
1678
1685
  const response = await this.client.request(message);
@@ -2051,6 +2058,29 @@ var LightManager = class extends import_index.default {
2051
2058
  }
2052
2059
  };
2053
2060
 
2061
+ // src/managers/system-connections-manager.ts
2062
+ var SystemConnectionsManager = class {
2063
+ constructor(client) {
2064
+ this.client = client;
2065
+ }
2066
+ /**
2067
+ * List all available JMRI system connections and their prefixes.
2068
+ * Use the returned prefix values with power and throttle commands to
2069
+ * target a specific hardware connection when multiple are configured.
2070
+ */
2071
+ async getSystemConnections() {
2072
+ const message = {
2073
+ type: "systemConnections",
2074
+ method: "list"
2075
+ };
2076
+ const response = await this.client.request(message);
2077
+ if (!response.data) {
2078
+ return [];
2079
+ }
2080
+ return Array.isArray(response.data) ? response.data : [response.data];
2081
+ }
2082
+ };
2083
+
2054
2084
  // src/types/client-options.ts
2055
2085
  var DEFAULT_CLIENT_OPTIONS = {
2056
2086
  host: "localhost",
@@ -2128,6 +2158,7 @@ var JmriClient = class extends import_index.default {
2128
2158
  this.throttleManager = new ThrottleManager(this.wsClient);
2129
2159
  this.turnoutManager = new TurnoutManager(this.wsClient);
2130
2160
  this.lightManager = new LightManager(this.wsClient);
2161
+ this.systemConnectionsManager = new SystemConnectionsManager(this.wsClient);
2131
2162
  this.wsClient.on("connected", () => this.emit("connected"));
2132
2163
  this.wsClient.on("disconnected", (reason) => this.emit("disconnected", reason));
2133
2164
  this.wsClient.on(
@@ -2212,27 +2243,50 @@ var JmriClient = class extends import_index.default {
2212
2243
  // ============================================================================
2213
2244
  /**
2214
2245
  * Get current track power state
2246
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
2215
2247
  */
2216
- async getPower() {
2217
- return this.powerManager.getPower();
2248
+ async getPower(prefix) {
2249
+ return this.powerManager.getPower(prefix);
2218
2250
  }
2219
2251
  /**
2220
2252
  * Set track power state
2253
+ * @param state - The desired power state
2254
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
2221
2255
  */
2222
- async setPower(state) {
2223
- return this.powerManager.setPower(state);
2256
+ async setPower(state, prefix) {
2257
+ return this.powerManager.setPower(state, prefix);
2224
2258
  }
2225
2259
  /**
2226
2260
  * Turn track power on
2261
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
2227
2262
  */
2228
- async powerOn() {
2229
- return this.powerManager.powerOn();
2263
+ async powerOn(prefix) {
2264
+ return this.powerManager.powerOn(prefix);
2230
2265
  }
2231
2266
  /**
2232
2267
  * Turn track power off
2268
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
2269
+ */
2270
+ async powerOff(prefix) {
2271
+ return this.powerManager.powerOff(prefix);
2272
+ }
2273
+ // ============================================================================
2274
+ // System Connections
2275
+ // ============================================================================
2276
+ /**
2277
+ * List all available JMRI system connections and their prefixes.
2278
+ * Use the returned prefix values with power and throttle commands to
2279
+ * target a specific hardware connection when multiple are configured.
2280
+ *
2281
+ * @example
2282
+ * ```typescript
2283
+ * const connections = await client.getSystemConnections();
2284
+ * // [{ name: 'LocoNet', prefix: 'L' }, { name: 'DCC++', prefix: 'D' }]
2285
+ * await client.powerOn('L'); // power on via LocoNet only
2286
+ * ```
2233
2287
  */
2234
- async powerOff() {
2235
- return this.powerManager.powerOff();
2288
+ async getSystemConnections() {
2289
+ return this.systemConnectionsManager.getSystemConnections();
2236
2290
  }
2237
2291
  // ============================================================================
2238
2292
  // Roster Management
@@ -11,6 +11,7 @@ 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
13
  const light_manager_js_1 = require("./managers/light-manager.js");
14
+ const system_connections_manager_js_1 = require("./managers/system-connections-manager.js");
14
15
  const client_options_js_1 = require("./types/client-options.js");
15
16
  /**
16
17
  * JMRI WebSocket Client
@@ -45,6 +46,7 @@ class JmriClient extends eventemitter3_1.EventEmitter {
45
46
  this.throttleManager = new throttle_manager_js_1.ThrottleManager(this.wsClient);
46
47
  this.turnoutManager = new turnout_manager_js_1.TurnoutManager(this.wsClient);
47
48
  this.lightManager = new light_manager_js_1.LightManager(this.wsClient);
49
+ this.systemConnectionsManager = new system_connections_manager_js_1.SystemConnectionsManager(this.wsClient);
48
50
  // Forward events from WebSocket client
49
51
  this.wsClient.on('connected', () => this.emit('connected'));
50
52
  this.wsClient.on('disconnected', (reason) => this.emit('disconnected', reason));
@@ -104,27 +106,50 @@ class JmriClient extends eventemitter3_1.EventEmitter {
104
106
  // ============================================================================
105
107
  /**
106
108
  * Get current track power state
109
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
107
110
  */
108
- async getPower() {
109
- return this.powerManager.getPower();
111
+ async getPower(prefix) {
112
+ return this.powerManager.getPower(prefix);
110
113
  }
111
114
  /**
112
115
  * Set track power state
116
+ * @param state - The desired power state
117
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
113
118
  */
114
- async setPower(state) {
115
- return this.powerManager.setPower(state);
119
+ async setPower(state, prefix) {
120
+ return this.powerManager.setPower(state, prefix);
116
121
  }
117
122
  /**
118
123
  * Turn track power on
124
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
119
125
  */
120
- async powerOn() {
121
- return this.powerManager.powerOn();
126
+ async powerOn(prefix) {
127
+ return this.powerManager.powerOn(prefix);
122
128
  }
123
129
  /**
124
130
  * Turn track power off
131
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
125
132
  */
126
- async powerOff() {
127
- return this.powerManager.powerOff();
133
+ async powerOff(prefix) {
134
+ return this.powerManager.powerOff(prefix);
135
+ }
136
+ // ============================================================================
137
+ // System Connections
138
+ // ============================================================================
139
+ /**
140
+ * List all available JMRI system connections and their prefixes.
141
+ * Use the returned prefix values with power and throttle commands to
142
+ * target a specific hardware connection when multiple are configured.
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * const connections = await client.getSystemConnections();
147
+ * // [{ name: 'LocoNet', prefix: 'L' }, { name: 'DCC++', prefix: 'D' }]
148
+ * await client.powerOn('L'); // power on via LocoNet only
149
+ * ```
150
+ */
151
+ async getSystemConnections() {
152
+ return this.systemConnectionsManager.getSystemConnections();
128
153
  }
129
154
  // ============================================================================
130
155
  // Roster Management
@@ -22,3 +22,4 @@ __exportStar(require("./roster-manager.js"), exports);
22
22
  __exportStar(require("./throttle-manager.js"), exports);
23
23
  __exportStar(require("./turnout-manager.js"), exports);
24
24
  __exportStar(require("./light-manager.js"), exports);
25
+ __exportStar(require("./system-connections-manager.js"), exports);
@@ -23,10 +23,12 @@ class PowerManager extends eventemitter3_1.EventEmitter {
23
23
  }
24
24
  /**
25
25
  * Get current track power state
26
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
26
27
  */
27
- async getPower() {
28
+ async getPower(prefix) {
28
29
  const message = {
29
- type: 'power'
30
+ type: 'power',
31
+ ...(prefix !== undefined && { data: { state: jmri_messages_js_1.PowerState.UNKNOWN, prefix } })
30
32
  };
31
33
  const response = await this.client.request(message);
32
34
  if (response.data?.state !== undefined) {
@@ -36,12 +38,14 @@ class PowerManager extends eventemitter3_1.EventEmitter {
36
38
  }
37
39
  /**
38
40
  * Set track power state
41
+ * @param state - The desired power state
42
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
39
43
  */
40
- async setPower(state) {
44
+ async setPower(state, prefix) {
41
45
  const message = {
42
46
  type: 'power',
43
47
  method: 'post',
44
- data: { state }
48
+ data: { state, ...(prefix !== undefined && { prefix }) }
45
49
  };
46
50
  await this.client.request(message);
47
51
  const oldState = this.currentState;
@@ -52,15 +56,17 @@ class PowerManager extends eventemitter3_1.EventEmitter {
52
56
  }
53
57
  /**
54
58
  * Turn track power on
59
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
55
60
  */
56
- async powerOn() {
57
- await this.setPower(jmri_messages_js_1.PowerState.ON);
61
+ async powerOn(prefix) {
62
+ await this.setPower(jmri_messages_js_1.PowerState.ON, prefix);
58
63
  }
59
64
  /**
60
65
  * Turn track power off
66
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
61
67
  */
62
- async powerOff() {
63
- await this.setPower(jmri_messages_js_1.PowerState.OFF);
68
+ async powerOff(prefix) {
69
+ await this.setPower(jmri_messages_js_1.PowerState.OFF, prefix);
64
70
  }
65
71
  /**
66
72
  * Get cached power state (no network request)
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ /**
3
+ * System connections manager — discovers available JMRI hardware connection prefixes
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SystemConnectionsManager = void 0;
7
+ class SystemConnectionsManager {
8
+ constructor(client) {
9
+ this.client = client;
10
+ }
11
+ /**
12
+ * List all available JMRI system connections and their prefixes.
13
+ * Use the returned prefix values with power and throttle commands to
14
+ * target a specific hardware connection when multiple are configured.
15
+ */
16
+ async getSystemConnections() {
17
+ const message = {
18
+ type: 'systemConnections',
19
+ method: 'list'
20
+ };
21
+ const response = await this.client.request(message);
22
+ if (!response.data) {
23
+ return [];
24
+ }
25
+ return Array.isArray(response.data) ? response.data : [response.data];
26
+ }
27
+ }
28
+ exports.SystemConnectionsManager = SystemConnectionsManager;
@@ -37,7 +37,8 @@ class ThrottleManager extends eventemitter3_1.EventEmitter {
37
37
  type: 'throttle',
38
38
  data: {
39
39
  name: throttleName,
40
- address: options.address
40
+ address: options.address,
41
+ ...(options.prefix !== undefined && { prefix: options.prefix })
41
42
  }
42
43
  };
43
44
  const response = await this.client.request(message);
@@ -8,6 +8,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
10
  import { LightManager } from './managers/light-manager.js';
11
+ import { SystemConnectionsManager } from './managers/system-connections-manager.js';
11
12
  import { mergeOptions } from './types/client-options.js';
12
13
  /**
13
14
  * JMRI WebSocket Client
@@ -42,6 +43,7 @@ export class JmriClient extends EventEmitter {
42
43
  this.throttleManager = new ThrottleManager(this.wsClient);
43
44
  this.turnoutManager = new TurnoutManager(this.wsClient);
44
45
  this.lightManager = new LightManager(this.wsClient);
46
+ this.systemConnectionsManager = new SystemConnectionsManager(this.wsClient);
45
47
  // Forward events from WebSocket client
46
48
  this.wsClient.on('connected', () => this.emit('connected'));
47
49
  this.wsClient.on('disconnected', (reason) => this.emit('disconnected', reason));
@@ -101,27 +103,50 @@ export class JmriClient extends EventEmitter {
101
103
  // ============================================================================
102
104
  /**
103
105
  * Get current track power state
106
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
104
107
  */
105
- async getPower() {
106
- return this.powerManager.getPower();
108
+ async getPower(prefix) {
109
+ return this.powerManager.getPower(prefix);
107
110
  }
108
111
  /**
109
112
  * Set track power state
113
+ * @param state - The desired power state
114
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
110
115
  */
111
- async setPower(state) {
112
- return this.powerManager.setPower(state);
116
+ async setPower(state, prefix) {
117
+ return this.powerManager.setPower(state, prefix);
113
118
  }
114
119
  /**
115
120
  * Turn track power on
121
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
116
122
  */
117
- async powerOn() {
118
- return this.powerManager.powerOn();
123
+ async powerOn(prefix) {
124
+ return this.powerManager.powerOn(prefix);
119
125
  }
120
126
  /**
121
127
  * Turn track power off
128
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
122
129
  */
123
- async powerOff() {
124
- return this.powerManager.powerOff();
130
+ async powerOff(prefix) {
131
+ return this.powerManager.powerOff(prefix);
132
+ }
133
+ // ============================================================================
134
+ // System Connections
135
+ // ============================================================================
136
+ /**
137
+ * List all available JMRI system connections and their prefixes.
138
+ * Use the returned prefix values with power and throttle commands to
139
+ * target a specific hardware connection when multiple are configured.
140
+ *
141
+ * @example
142
+ * ```typescript
143
+ * const connections = await client.getSystemConnections();
144
+ * // [{ name: 'LocoNet', prefix: 'L' }, { name: 'DCC++', prefix: 'D' }]
145
+ * await client.powerOn('L'); // power on via LocoNet only
146
+ * ```
147
+ */
148
+ async getSystemConnections() {
149
+ return this.systemConnectionsManager.getSystemConnections();
125
150
  }
126
151
  // ============================================================================
127
152
  // Roster Management
@@ -6,3 +6,4 @@ export * from './roster-manager.js';
6
6
  export * from './throttle-manager.js';
7
7
  export * from './turnout-manager.js';
8
8
  export * from './light-manager.js';
9
+ export * from './system-connections-manager.js';
@@ -20,10 +20,12 @@ export class PowerManager extends EventEmitter {
20
20
  }
21
21
  /**
22
22
  * Get current track power state
23
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
23
24
  */
24
- async getPower() {
25
+ async getPower(prefix) {
25
26
  const message = {
26
- type: 'power'
27
+ type: 'power',
28
+ ...(prefix !== undefined && { data: { state: PowerState.UNKNOWN, prefix } })
27
29
  };
28
30
  const response = await this.client.request(message);
29
31
  if (response.data?.state !== undefined) {
@@ -33,12 +35,14 @@ export class PowerManager extends EventEmitter {
33
35
  }
34
36
  /**
35
37
  * Set track power state
38
+ * @param state - The desired power state
39
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
36
40
  */
37
- async setPower(state) {
41
+ async setPower(state, prefix) {
38
42
  const message = {
39
43
  type: 'power',
40
44
  method: 'post',
41
- data: { state }
45
+ data: { state, ...(prefix !== undefined && { prefix }) }
42
46
  };
43
47
  await this.client.request(message);
44
48
  const oldState = this.currentState;
@@ -49,15 +53,17 @@ export class PowerManager extends EventEmitter {
49
53
  }
50
54
  /**
51
55
  * Turn track power on
56
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
52
57
  */
53
- async powerOn() {
54
- await this.setPower(PowerState.ON);
58
+ async powerOn(prefix) {
59
+ await this.setPower(PowerState.ON, prefix);
55
60
  }
56
61
  /**
57
62
  * Turn track power off
63
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
58
64
  */
59
- async powerOff() {
60
- await this.setPower(PowerState.OFF);
65
+ async powerOff(prefix) {
66
+ await this.setPower(PowerState.OFF, prefix);
61
67
  }
62
68
  /**
63
69
  * Get cached power state (no network request)
@@ -0,0 +1,24 @@
1
+ /**
2
+ * System connections manager — discovers available JMRI hardware connection prefixes
3
+ */
4
+ export class SystemConnectionsManager {
5
+ constructor(client) {
6
+ this.client = client;
7
+ }
8
+ /**
9
+ * List all available JMRI system connections and their prefixes.
10
+ * Use the returned prefix values with power and throttle commands to
11
+ * target a specific hardware connection when multiple are configured.
12
+ */
13
+ async getSystemConnections() {
14
+ const message = {
15
+ type: 'systemConnections',
16
+ method: 'list'
17
+ };
18
+ const response = await this.client.request(message);
19
+ if (!response.data) {
20
+ return [];
21
+ }
22
+ return Array.isArray(response.data) ? response.data : [response.data];
23
+ }
24
+ }
@@ -34,7 +34,8 @@ export class ThrottleManager extends EventEmitter {
34
34
  type: 'throttle',
35
35
  data: {
36
36
  name: throttleName,
37
- address: options.address
37
+ address: options.address,
38
+ ...(options.prefix !== undefined && { prefix: options.prefix })
38
39
  }
39
40
  };
40
41
  const response = await this.client.request(message);
@@ -4,7 +4,7 @@
4
4
  import { EventEmitter } from 'eventemitter3';
5
5
  import { WebSocketClient } from './core/websocket-client.js';
6
6
  import { PartialClientOptions } from './types/client-options.js';
7
- import { PowerState, RosterEntryWrapper, TurnoutState, TurnoutData, LightState, LightData } from './types/jmri-messages.js';
7
+ import { PowerState, RosterEntryWrapper, TurnoutState, TurnoutData, LightState, LightData, SystemConnectionData } from './types/jmri-messages.js';
8
8
  import { ConnectionState } from './types/events.js';
9
9
  import { ThrottleAcquireOptions, ThrottleFunctionKey, ThrottleState } from './types/throttle.js';
10
10
  /**
@@ -19,6 +19,7 @@ export declare class JmriClient extends EventEmitter {
19
19
  private throttleManager;
20
20
  private turnoutManager;
21
21
  private lightManager;
22
+ private systemConnectionsManager;
22
23
  /**
23
24
  * Create a new JMRI client
24
25
  *
@@ -55,20 +56,38 @@ export declare class JmriClient extends EventEmitter {
55
56
  isConnected(): boolean;
56
57
  /**
57
58
  * Get current track power state
59
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
58
60
  */
59
- getPower(): Promise<PowerState>;
61
+ getPower(prefix?: string): Promise<PowerState>;
60
62
  /**
61
63
  * Set track power state
64
+ * @param state - The desired power state
65
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
62
66
  */
63
- setPower(state: PowerState): Promise<void>;
67
+ setPower(state: PowerState, prefix?: string): Promise<void>;
64
68
  /**
65
69
  * Turn track power on
70
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
66
71
  */
67
- powerOn(): Promise<void>;
72
+ powerOn(prefix?: string): Promise<void>;
68
73
  /**
69
74
  * Turn track power off
75
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
70
76
  */
71
- powerOff(): Promise<void>;
77
+ powerOff(prefix?: string): Promise<void>;
78
+ /**
79
+ * List all available JMRI system connections and their prefixes.
80
+ * Use the returned prefix values with power and throttle commands to
81
+ * target a specific hardware connection when multiple are configured.
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * const connections = await client.getSystemConnections();
86
+ * // [{ name: 'LocoNet', prefix: 'L' }, { name: 'DCC++', prefix: 'D' }]
87
+ * await client.powerOn('L'); // power on via LocoNet only
88
+ * ```
89
+ */
90
+ getSystemConnections(): Promise<SystemConnectionData[]>;
72
91
  /**
73
92
  * Get all roster entries
74
93
  */
@@ -6,3 +6,4 @@ export * from './roster-manager.js';
6
6
  export * from './throttle-manager.js';
7
7
  export * from './turnout-manager.js';
8
8
  export * from './light-manager.js';
9
+ export * from './system-connections-manager.js';
@@ -13,20 +13,25 @@ export declare class PowerManager extends EventEmitter {
13
13
  constructor(client: WebSocketClient);
14
14
  /**
15
15
  * Get current track power state
16
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
16
17
  */
17
- getPower(): Promise<PowerState>;
18
+ getPower(prefix?: string): Promise<PowerState>;
18
19
  /**
19
20
  * Set track power state
21
+ * @param state - The desired power state
22
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
20
23
  */
21
- setPower(state: PowerState): Promise<void>;
24
+ setPower(state: PowerState, prefix?: string): Promise<void>;
22
25
  /**
23
26
  * Turn track power on
27
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
24
28
  */
25
- powerOn(): Promise<void>;
29
+ powerOn(prefix?: string): Promise<void>;
26
30
  /**
27
31
  * Turn track power off
32
+ * @param prefix - Optional JMRI connection prefix to target a specific hardware connection
28
33
  */
29
- powerOff(): Promise<void>;
34
+ powerOff(prefix?: string): Promise<void>;
30
35
  /**
31
36
  * Get cached power state (no network request)
32
37
  */
@@ -0,0 +1,15 @@
1
+ /**
2
+ * System connections manager — discovers available JMRI hardware connection prefixes
3
+ */
4
+ import { WebSocketClient } from '../core/websocket-client.js';
5
+ import { SystemConnectionData } from '../types/jmri-messages.js';
6
+ export declare class SystemConnectionsManager {
7
+ private client;
8
+ constructor(client: WebSocketClient);
9
+ /**
10
+ * List all available JMRI system connections and their prefixes.
11
+ * Use the returned prefix values with power and throttle commands to
12
+ * target a specific hardware connection when multiple are configured.
13
+ */
14
+ getSystemConnections(): Promise<SystemConnectionData[]>;
15
+ }
@@ -33,6 +33,7 @@ export declare function powerStateToString(state: PowerState): string;
33
33
  */
34
34
  export interface PowerData {
35
35
  state: PowerState;
36
+ prefix?: string;
36
37
  }
37
38
  /**
38
39
  * Power message
@@ -41,6 +42,21 @@ export interface PowerMessage extends JmriMessage {
41
42
  type: 'power';
42
43
  data?: PowerData;
43
44
  }
45
+ /**
46
+ * System connection data (from JMRI systemConnections list)
47
+ */
48
+ export interface SystemConnectionData {
49
+ name: string;
50
+ prefix: string;
51
+ userName?: string;
52
+ }
53
+ /**
54
+ * System connections message
55
+ */
56
+ export interface SystemConnectionsMessage extends JmriMessage {
57
+ type: 'systemConnections';
58
+ data?: SystemConnectionData | SystemConnectionData[];
59
+ }
44
60
  /**
45
61
  * Throttle data structure
46
62
  */
@@ -51,6 +67,7 @@ export interface ThrottleData {
51
67
  forward?: boolean;
52
68
  release?: null;
53
69
  status?: string;
70
+ prefix?: string;
54
71
  F0?: boolean;
55
72
  F1?: boolean;
56
73
  F2?: boolean;
@@ -263,4 +280,4 @@ export interface ErrorMessage extends JmriMessage {
263
280
  /**
264
281
  * Union type of all possible JMRI messages
265
282
  */
266
- export type AnyJmriMessage = PowerMessage | ThrottleMessage | RosterMessage | TurnoutMessage | LightMessage | PingMessage | PongMessage | HelloMessage | GoodbyeMessage | ErrorMessage;
283
+ export type AnyJmriMessage = PowerMessage | ThrottleMessage | RosterMessage | TurnoutMessage | LightMessage | SystemConnectionsMessage | PingMessage | PongMessage | HelloMessage | GoodbyeMessage | ErrorMessage;
@@ -13,6 +13,12 @@ export interface ThrottleAcquireOptions {
13
13
  * Whether this is a long address (default: true for addresses > 127)
14
14
  */
15
15
  isLongAddress?: boolean;
16
+ /**
17
+ * JMRI connection prefix to target a specific hardware connection.
18
+ * When omitted, routes to the default connection manager.
19
+ * Use getSystemConnections() to discover available prefixes.
20
+ */
21
+ prefix?: string;
16
22
  }
17
23
  /**
18
24
  * Throttle function key (F0-F28)
package/docs/API.md CHANGED
@@ -65,6 +65,9 @@ client.on('heartbeat:timeout', () => { });
65
65
  // Get current power state
66
66
  const state: PowerState = await client.getPower();
67
67
 
68
+ // Get power state for a specific hardware connection (see System Connections)
69
+ const state: PowerState = await client.getPower('L'); // LocoNet prefix
70
+
68
71
  // PowerState enum values (from JMRI JSON protocol):
69
72
  // PowerState.UNKNOWN = 0 (state cannot be determined)
70
73
  // PowerState.ON = 2 (power is on)
@@ -83,10 +86,15 @@ switch (state) {
83
86
  break;
84
87
  }
85
88
 
86
- // Set power
89
+ // Set power (all connections, or a specific one)
87
90
  await client.setPower(PowerState.ON);
88
- await client.powerOn(); // Convenience method
89
- await client.powerOff(); // Convenience method
91
+ await client.setPower(PowerState.ON, 'L'); // LocoNet only
92
+
93
+ // Convenience methods — all accept an optional prefix
94
+ await client.powerOn(); // All connections
95
+ await client.powerOn('L'); // LocoNet only
96
+ await client.powerOff(); // All connections
97
+ await client.powerOff('D'); // DCC++ only
90
98
 
91
99
  // Listen for power changes (including UNKNOWN states)
92
100
  client.on('power:changed', (state: PowerState) => {
@@ -96,6 +104,29 @@ client.on('power:changed', (state: PowerState) => {
96
104
  });
97
105
  ```
98
106
 
107
+ ## System Connections
108
+
109
+ Discover the hardware connections JMRI has configured and their short prefix strings. Use these prefixes to target power and throttle commands at a specific connection when multiple are active.
110
+
111
+ ```typescript
112
+ // List all available system connections
113
+ const connections: SystemConnectionData[] = await client.getSystemConnections();
114
+ // [
115
+ // { name: 'LocoNet', prefix: 'L', userName: 'LocoNet' },
116
+ // { name: 'DCC++', prefix: 'D' }
117
+ // ]
118
+
119
+ // Use a prefix to target a specific connection
120
+ const [loconet] = connections.filter(c => c.name === 'LocoNet');
121
+ await client.powerOn(loconet.prefix);
122
+
123
+ // Acquire a throttle on a specific connection
124
+ const throttleId = await client.acquireThrottle({
125
+ address: 3,
126
+ prefix: loconet.prefix
127
+ });
128
+ ```
129
+
99
130
  ## Roster Management
100
131
 
101
132
  ```typescript
@@ -150,6 +181,9 @@ client.on('light:changed', (name: string, state: LightState) => {
150
181
  // Acquire throttle
151
182
  const throttleId = await client.acquireThrottle({ address: 3 });
152
183
 
184
+ // Acquire throttle on a specific hardware connection
185
+ const throttleId = await client.acquireThrottle({ address: 3, prefix: 'L' });
186
+
153
187
  // Control speed (0.0 to 1.0)
154
188
  await client.setThrottleSpeed(throttleId, 0.0); // Stopped
155
189
  await client.setThrottleSpeed(throttleId, 0.5); // Half speed
@@ -237,6 +271,7 @@ import {
237
271
  TurnoutData,
238
272
  ThrottleState,
239
273
  ThrottleFunctionKey,
240
- ConnectionState
274
+ ConnectionState,
275
+ SystemConnectionData
241
276
  } from 'jmri-client';
242
277
  ```
package/docs/EXAMPLES.md CHANGED
@@ -283,6 +283,48 @@ async function runLayout() {
283
283
  runLayout();
284
284
  ```
285
285
 
286
+ ## Multi-Connection / Hardware Prefix
287
+
288
+ When JMRI is configured with more than one hardware connection (e.g. both LocoNet and DCC++), use `getSystemConnections()` to discover the available prefixes, then pass them to power and throttle commands to target a specific connection.
289
+
290
+ ```typescript
291
+ import { JmriClient } from 'jmri-client';
292
+
293
+ const client = new JmriClient({ host: 'jmri.local' });
294
+
295
+ client.on('connected', async () => {
296
+ // Discover what hardware connections JMRI has configured
297
+ const connections = await client.getSystemConnections();
298
+ console.log('Available connections:');
299
+ for (const conn of connections) {
300
+ console.log(` ${conn.name} (prefix: "${conn.prefix}")`);
301
+ }
302
+ // Available connections:
303
+ // LocoNet (prefix: "L")
304
+ // DCC++ (prefix: "D")
305
+
306
+ // Power on via LocoNet only
307
+ await client.powerOn('L');
308
+
309
+ // Acquire a throttle on LocoNet
310
+ const throttle = await client.acquireThrottle({ address: 3, prefix: 'L' });
311
+ await client.setThrottleDirection(throttle, true);
312
+ await client.setThrottleSpeed(throttle, 0.3);
313
+
314
+ await new Promise(resolve => setTimeout(resolve, 5000));
315
+
316
+ await client.setThrottleSpeed(throttle, 0);
317
+ await client.releaseThrottle(throttle);
318
+
319
+ // Power off via LocoNet only
320
+ await client.powerOff('L');
321
+
322
+ await client.disconnect();
323
+ });
324
+ ```
325
+
326
+ Single-connection layouts work exactly as before — just omit the prefix and JMRI routes to the default connection.
327
+
286
328
  ## CommonJS Usage
287
329
 
288
330
  ```javascript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jmri-client",
3
- "version": "4.1.1",
3
+ "version": "4.2.0-beta.2",
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",