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 +15 -0
- package/dist/browser/jmri-client.js +71 -17
- package/dist/cjs/client.js +33 -8
- package/dist/cjs/managers/index.js +1 -0
- package/dist/cjs/managers/power-manager.js +14 -8
- package/dist/cjs/managers/system-connections-manager.js +28 -0
- package/dist/cjs/managers/throttle-manager.js +2 -1
- package/dist/esm/client.js +33 -8
- package/dist/esm/managers/index.js +1 -0
- package/dist/esm/managers/power-manager.js +14 -8
- package/dist/esm/managers/system-connections-manager.js +24 -0
- package/dist/esm/managers/throttle-manager.js +2 -1
- package/dist/types/client.d.ts +24 -5
- package/dist/types/managers/index.d.ts +1 -0
- package/dist/types/managers/power-manager.d.ts +9 -4
- package/dist/types/managers/system-connections-manager.d.ts +15 -0
- package/dist/types/types/jmri-messages.d.ts +18 -1
- package/dist/types/types/throttle.d.ts +6 -0
- package/docs/API.md +39 -4
- package/docs/EXAMPLES.md +42 -0
- package/package.json +1 -1
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
|
|
2235
|
-
return this.
|
|
2288
|
+
async getSystemConnections() {
|
|
2289
|
+
return this.systemConnectionsManager.getSystemConnections();
|
|
2236
2290
|
}
|
|
2237
2291
|
// ============================================================================
|
|
2238
2292
|
// Roster Management
|
package/dist/cjs/client.js
CHANGED
|
@@ -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);
|
package/dist/esm/client.js
CHANGED
|
@@ -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
|
|
@@ -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);
|
package/dist/types/client.d.ts
CHANGED
|
@@ -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
|
*/
|
|
@@ -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.
|
|
89
|
-
|
|
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.
|
|
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",
|