jmri-client 4.2.0-beta.2 → 5.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 +3 -1
- package/dist/browser/jmri-client.js +88 -28
- package/dist/cjs/index.js +2442 -31
- package/dist/esm/index.js +2393 -17
- package/dist/types/client.d.ts +9 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/managers/roster-manager.d.ts +9 -1
- package/dist/types/mocks/mock-data.d.ts +30 -6
- package/dist/types/mocks/mock-response-manager.d.ts +7 -2
- package/dist/types/types/jmri-messages.d.ts +22 -0
- package/docs/API.md +8 -0
- package/docs/BROWSER.md +4 -4
- package/docs/MIGRATION.md +30 -1
- package/docs/MOCK_MODE.md +15 -9
- package/package.json +17 -18
- package/dist/cjs/client.js +0 -366
- package/dist/cjs/core/connection-state-manager.js +0 -84
- package/dist/cjs/core/heartbeat-manager.js +0 -79
- package/dist/cjs/core/index.js +0 -25
- package/dist/cjs/core/message-queue.js +0 -59
- package/dist/cjs/core/reconnection-manager.js +0 -97
- package/dist/cjs/core/websocket-adapter.js +0 -135
- package/dist/cjs/core/websocket-client.js +0 -388
- package/dist/cjs/managers/index.js +0 -25
- package/dist/cjs/managers/light-manager.js +0 -111
- package/dist/cjs/managers/power-manager.js +0 -90
- package/dist/cjs/managers/roster-manager.js +0 -118
- package/dist/cjs/managers/system-connections-manager.js +0 -28
- package/dist/cjs/managers/throttle-manager.js +0 -233
- package/dist/cjs/managers/turnout-manager.js +0 -111
- package/dist/cjs/mocks/index.js +0 -12
- package/dist/cjs/mocks/mock-data.js +0 -237
- package/dist/cjs/mocks/mock-response-manager.js +0 -290
- package/dist/cjs/types/client-options.js +0 -66
- package/dist/cjs/types/events.js +0 -16
- package/dist/cjs/types/index.js +0 -23
- package/dist/cjs/types/jmri-messages.js +0 -95
- package/dist/cjs/types/throttle.js +0 -19
- package/dist/cjs/utils/exponential-backoff.js +0 -40
- package/dist/cjs/utils/index.js +0 -21
- package/dist/cjs/utils/message-id.js +0 -40
- package/dist/esm/client.js +0 -362
- package/dist/esm/core/connection-state-manager.js +0 -80
- package/dist/esm/core/heartbeat-manager.js +0 -75
- package/dist/esm/core/index.js +0 -9
- package/dist/esm/core/message-queue.js +0 -55
- package/dist/esm/core/reconnection-manager.js +0 -93
- package/dist/esm/core/websocket-adapter.js +0 -98
- package/dist/esm/core/websocket-client.js +0 -384
- package/dist/esm/managers/index.js +0 -9
- package/dist/esm/managers/light-manager.js +0 -107
- package/dist/esm/managers/power-manager.js +0 -86
- package/dist/esm/managers/roster-manager.js +0 -114
- package/dist/esm/managers/system-connections-manager.js +0 -24
- package/dist/esm/managers/throttle-manager.js +0 -229
- package/dist/esm/managers/turnout-manager.js +0 -107
- package/dist/esm/mocks/index.js +0 -6
- package/dist/esm/mocks/mock-data.js +0 -234
- package/dist/esm/mocks/mock-response-manager.js +0 -286
- package/dist/esm/types/client-options.js +0 -62
- package/dist/esm/types/events.js +0 -13
- package/dist/esm/types/index.js +0 -7
- package/dist/esm/types/jmri-messages.js +0 -89
- package/dist/esm/types/throttle.js +0 -15
- package/dist/esm/utils/exponential-backoff.js +0 -36
- package/dist/esm/utils/index.js +0 -5
- package/dist/esm/utils/message-id.js +0 -36
|
@@ -1,24 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Throttle control manager
|
|
3
|
-
*/
|
|
4
|
-
import { EventEmitter } from 'eventemitter3';
|
|
5
|
-
import { isThrottleFunctionKey, isValidSpeed } from '../types/throttle.js';
|
|
6
|
-
/**
|
|
7
|
-
* Manages multiple throttles
|
|
8
|
-
*/
|
|
9
|
-
export class ThrottleManager extends EventEmitter {
|
|
10
|
-
constructor(client) {
|
|
11
|
-
super();
|
|
12
|
-
this.throttles = new Map();
|
|
13
|
-
this.client = client;
|
|
14
|
-
// Generate a unique client ID
|
|
15
|
-
this.clientId = `jmri-client-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
16
|
-
// Listen for throttle updates
|
|
17
|
-
this.client.on('update', (message) => {
|
|
18
|
-
if (message.type === 'throttle') {
|
|
19
|
-
this.handleThrottleUpdate(message);
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
// Clean up throttles on disconnect
|
|
23
|
-
this.client.on('disconnected', () => {
|
|
24
|
-
this.handleDisconnect();
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Acquire a throttle for a locomotive
|
|
29
|
-
*/
|
|
30
|
-
async acquireThrottle(options) {
|
|
31
|
-
// Generate a unique throttle name using client ID and address
|
|
32
|
-
const throttleName = `${this.clientId}-${options.address}`;
|
|
33
|
-
const message = {
|
|
34
|
-
type: 'throttle',
|
|
35
|
-
data: {
|
|
36
|
-
name: throttleName,
|
|
37
|
-
address: options.address,
|
|
38
|
-
...(options.prefix !== undefined && { prefix: options.prefix })
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
const response = await this.client.request(message);
|
|
42
|
-
// JMRI returns the throttle ID in the "throttle" field, or we can use our name
|
|
43
|
-
const throttleId = response.data?.throttle || throttleName;
|
|
44
|
-
if (!throttleId) {
|
|
45
|
-
throw new Error('Failed to acquire throttle: no throttle ID returned');
|
|
46
|
-
}
|
|
47
|
-
// Initialize throttle state
|
|
48
|
-
const state = {
|
|
49
|
-
id: throttleId,
|
|
50
|
-
address: options.address,
|
|
51
|
-
speed: 0,
|
|
52
|
-
forward: true,
|
|
53
|
-
functions: new Map(),
|
|
54
|
-
acquired: true
|
|
55
|
-
};
|
|
56
|
-
this.throttles.set(throttleId, state);
|
|
57
|
-
this.emit('throttle:acquired', throttleId);
|
|
58
|
-
return throttleId;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Release a throttle
|
|
62
|
-
*/
|
|
63
|
-
async releaseThrottle(throttleId) {
|
|
64
|
-
const state = this.throttles.get(throttleId);
|
|
65
|
-
if (!state) {
|
|
66
|
-
throw new Error(`Throttle not found: ${throttleId}`);
|
|
67
|
-
}
|
|
68
|
-
const message = {
|
|
69
|
-
type: 'throttle',
|
|
70
|
-
data: {
|
|
71
|
-
throttle: throttleId,
|
|
72
|
-
release: null
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
await this.client.request(message);
|
|
76
|
-
this.throttles.delete(throttleId);
|
|
77
|
-
this.emit('throttle:released', throttleId);
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Set throttle speed (0.0 to 1.0)
|
|
81
|
-
*/
|
|
82
|
-
async setSpeed(throttleId, speed) {
|
|
83
|
-
const state = this.throttles.get(throttleId);
|
|
84
|
-
if (!state) {
|
|
85
|
-
throw new Error(`Throttle not found: ${throttleId}`);
|
|
86
|
-
}
|
|
87
|
-
if (!isValidSpeed(speed)) {
|
|
88
|
-
throw new Error(`Invalid speed: ${speed}. Must be between 0.0 and 1.0`);
|
|
89
|
-
}
|
|
90
|
-
const message = {
|
|
91
|
-
type: 'throttle',
|
|
92
|
-
data: {
|
|
93
|
-
throttle: throttleId,
|
|
94
|
-
speed
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
// JMRI doesn't send responses for throttle control commands, just send
|
|
98
|
-
this.client.send(message);
|
|
99
|
-
state.speed = speed;
|
|
100
|
-
this.emit('throttle:updated', throttleId, { speed });
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Set throttle direction
|
|
104
|
-
*/
|
|
105
|
-
async setDirection(throttleId, forward) {
|
|
106
|
-
const state = this.throttles.get(throttleId);
|
|
107
|
-
if (!state) {
|
|
108
|
-
throw new Error(`Throttle not found: ${throttleId}`);
|
|
109
|
-
}
|
|
110
|
-
const message = {
|
|
111
|
-
type: 'throttle',
|
|
112
|
-
data: {
|
|
113
|
-
throttle: throttleId,
|
|
114
|
-
forward
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
// JMRI doesn't send responses for throttle control commands, just send
|
|
118
|
-
this.client.send(message);
|
|
119
|
-
state.forward = forward;
|
|
120
|
-
this.emit('throttle:updated', throttleId, { forward });
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Set throttle function (F0-F28)
|
|
124
|
-
*/
|
|
125
|
-
async setFunction(throttleId, functionKey, value) {
|
|
126
|
-
const state = this.throttles.get(throttleId);
|
|
127
|
-
if (!state) {
|
|
128
|
-
throw new Error(`Throttle not found: ${throttleId}`);
|
|
129
|
-
}
|
|
130
|
-
if (!isThrottleFunctionKey(functionKey)) {
|
|
131
|
-
throw new Error(`Invalid function key: ${functionKey}`);
|
|
132
|
-
}
|
|
133
|
-
const data = {
|
|
134
|
-
throttle: throttleId,
|
|
135
|
-
[functionKey]: value
|
|
136
|
-
};
|
|
137
|
-
const message = {
|
|
138
|
-
type: 'throttle',
|
|
139
|
-
data
|
|
140
|
-
};
|
|
141
|
-
// JMRI doesn't send responses for throttle control commands, just send
|
|
142
|
-
this.client.send(message);
|
|
143
|
-
state.functions.set(functionKey, value);
|
|
144
|
-
this.emit('throttle:updated', throttleId, { [functionKey]: value });
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Emergency stop for a throttle (speed to 0)
|
|
148
|
-
*/
|
|
149
|
-
async emergencyStop(throttleId) {
|
|
150
|
-
await this.setSpeed(throttleId, 0);
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Set throttle to idle (speed to 0, maintain direction)
|
|
154
|
-
*/
|
|
155
|
-
async idle(throttleId) {
|
|
156
|
-
await this.setSpeed(throttleId, 0);
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Get throttle state
|
|
160
|
-
*/
|
|
161
|
-
getThrottleState(throttleId) {
|
|
162
|
-
return this.throttles.get(throttleId);
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Get all throttle IDs
|
|
166
|
-
*/
|
|
167
|
-
getThrottleIds() {
|
|
168
|
-
return Array.from(this.throttles.keys());
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Get all throttle states
|
|
172
|
-
*/
|
|
173
|
-
getAllThrottles() {
|
|
174
|
-
return Array.from(this.throttles.values());
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Release all throttles
|
|
178
|
-
*/
|
|
179
|
-
async releaseAllThrottles() {
|
|
180
|
-
const throttleIds = this.getThrottleIds();
|
|
181
|
-
for (const throttleId of throttleIds) {
|
|
182
|
-
try {
|
|
183
|
-
await this.releaseThrottle(throttleId);
|
|
184
|
-
}
|
|
185
|
-
catch (error) {
|
|
186
|
-
// Continue releasing others even if one fails
|
|
187
|
-
this.emit('error', error);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Handle unsolicited throttle updates from JMRI
|
|
193
|
-
*/
|
|
194
|
-
handleThrottleUpdate(message) {
|
|
195
|
-
const throttleId = message.data?.throttle;
|
|
196
|
-
if (!throttleId) {
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
const state = this.throttles.get(throttleId);
|
|
200
|
-
if (!state) {
|
|
201
|
-
// Unknown throttle, possibly lost
|
|
202
|
-
this.emit('throttle:lost', throttleId);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
// Update state from message
|
|
206
|
-
if (message.data.speed !== undefined) {
|
|
207
|
-
state.speed = message.data.speed;
|
|
208
|
-
}
|
|
209
|
-
if (message.data.forward !== undefined) {
|
|
210
|
-
state.forward = message.data.forward;
|
|
211
|
-
}
|
|
212
|
-
// Update functions
|
|
213
|
-
for (let i = 0; i <= 28; i++) {
|
|
214
|
-
const key = `F${i}`;
|
|
215
|
-
if (message.data[key] !== undefined) {
|
|
216
|
-
state.functions.set(key, message.data[key]);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
this.emit('throttle:updated', throttleId, message.data);
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Handle disconnect - mark all throttles as not acquired
|
|
223
|
-
*/
|
|
224
|
-
handleDisconnect() {
|
|
225
|
-
for (const state of this.throttles.values()) {
|
|
226
|
-
state.acquired = false;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Turnout (switch) manager
|
|
3
|
-
*/
|
|
4
|
-
import { EventEmitter } from 'eventemitter3';
|
|
5
|
-
import { TurnoutState } from '../types/jmri-messages.js';
|
|
6
|
-
/**
|
|
7
|
-
* Manages JMRI turnout (track switch) state
|
|
8
|
-
*/
|
|
9
|
-
export class TurnoutManager extends EventEmitter {
|
|
10
|
-
constructor(client) {
|
|
11
|
-
super();
|
|
12
|
-
this.turnouts = new Map();
|
|
13
|
-
this.client = client;
|
|
14
|
-
this.client.on('update', (message) => {
|
|
15
|
-
if (message.type === 'turnout') {
|
|
16
|
-
this.handleTurnoutUpdate(message);
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Get the current state of a turnout.
|
|
22
|
-
* Also registers a server-side listener so subsequent changes are pushed.
|
|
23
|
-
*/
|
|
24
|
-
async getTurnout(name) {
|
|
25
|
-
const message = {
|
|
26
|
-
type: 'turnout',
|
|
27
|
-
data: { name }
|
|
28
|
-
};
|
|
29
|
-
const response = await this.client.request(message);
|
|
30
|
-
const state = response.data?.state ?? TurnoutState.UNKNOWN;
|
|
31
|
-
this.turnouts.set(name, state);
|
|
32
|
-
return state;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Set a turnout to the given state
|
|
36
|
-
*/
|
|
37
|
-
async setTurnout(name, state) {
|
|
38
|
-
const message = {
|
|
39
|
-
type: 'turnout',
|
|
40
|
-
method: 'post',
|
|
41
|
-
data: { name, state }
|
|
42
|
-
};
|
|
43
|
-
await this.client.request(message);
|
|
44
|
-
const oldState = this.turnouts.get(name);
|
|
45
|
-
this.turnouts.set(name, state);
|
|
46
|
-
if (oldState !== state) {
|
|
47
|
-
this.emit('turnout:changed', name, state);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Throw a turnout (diverging route)
|
|
52
|
-
*/
|
|
53
|
-
async throwTurnout(name) {
|
|
54
|
-
return this.setTurnout(name, TurnoutState.THROWN);
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Close a turnout (straight through / normal)
|
|
58
|
-
*/
|
|
59
|
-
async closeTurnout(name) {
|
|
60
|
-
return this.setTurnout(name, TurnoutState.CLOSED);
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* List all turnouts known to JMRI
|
|
64
|
-
*/
|
|
65
|
-
async listTurnouts() {
|
|
66
|
-
const message = {
|
|
67
|
-
type: 'turnout',
|
|
68
|
-
method: 'list'
|
|
69
|
-
};
|
|
70
|
-
const response = await this.client.request(message);
|
|
71
|
-
const entries = Array.isArray(response?.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.turnouts.set(entry.name, entry.state);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return entries;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Get cached turnout state without a network request
|
|
83
|
-
*/
|
|
84
|
-
getTurnoutState(name) {
|
|
85
|
-
return this.turnouts.get(name);
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Get all cached turnout states
|
|
89
|
-
*/
|
|
90
|
-
getCachedTurnouts() {
|
|
91
|
-
return new Map(this.turnouts);
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Handle unsolicited turnout state updates from JMRI
|
|
95
|
-
*/
|
|
96
|
-
handleTurnoutUpdate(message) {
|
|
97
|
-
const name = message.data?.name;
|
|
98
|
-
const state = message.data?.state;
|
|
99
|
-
if (!name || state === undefined)
|
|
100
|
-
return;
|
|
101
|
-
const oldState = this.turnouts.get(name);
|
|
102
|
-
this.turnouts.set(name, state);
|
|
103
|
-
if (oldState !== state) {
|
|
104
|
-
this.emit('turnout:changed', name, state);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
package/dist/esm/mocks/index.js
DELETED
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mock data for JMRI responses
|
|
3
|
-
* Used for testing and demo mode
|
|
4
|
-
*/
|
|
5
|
-
export const mockData = {
|
|
6
|
-
"hello": {
|
|
7
|
-
"type": "hello",
|
|
8
|
-
"data": {
|
|
9
|
-
"JMRI": "5.9.2",
|
|
10
|
-
"json": "5.0",
|
|
11
|
-
"version": "v5",
|
|
12
|
-
"heartbeat": 13500,
|
|
13
|
-
"railroad": "Demo Railroad",
|
|
14
|
-
"node": "jmri-server",
|
|
15
|
-
"activeProfile": "Demo Profile"
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
"power": {
|
|
19
|
-
"get": {
|
|
20
|
-
"on": {
|
|
21
|
-
"type": "power",
|
|
22
|
-
"data": {
|
|
23
|
-
"state": 2
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
"off": {
|
|
27
|
-
"type": "power",
|
|
28
|
-
"data": {
|
|
29
|
-
"state": 4
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
"post": {
|
|
34
|
-
"success": {
|
|
35
|
-
"type": "power",
|
|
36
|
-
"data": {
|
|
37
|
-
"state": 2
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
"roster": {
|
|
43
|
-
"list": [
|
|
44
|
-
{
|
|
45
|
-
"type": "rosterEntry",
|
|
46
|
-
"data": {
|
|
47
|
-
"name": "CSX754",
|
|
48
|
-
"address": "754",
|
|
49
|
-
"isLongAddress": true,
|
|
50
|
-
"road": "CSX",
|
|
51
|
-
"number": "754",
|
|
52
|
-
"mfg": "Athearn",
|
|
53
|
-
"decoderModel": "DH163D",
|
|
54
|
-
"decoderFamily": "Digitrax DH163",
|
|
55
|
-
"model": "GP38-2",
|
|
56
|
-
"comment": "Blue and yellow scheme",
|
|
57
|
-
"maxSpeedPct": 100,
|
|
58
|
-
"image": null,
|
|
59
|
-
"icon": "/roster/CSX754/icon",
|
|
60
|
-
"shuntingFunction": "",
|
|
61
|
-
"owner": "",
|
|
62
|
-
"dateModified": "2026-02-10T00:00:00.000+00:00",
|
|
63
|
-
"functionKeys": [
|
|
64
|
-
{ "name": "F0", "label": "Headlight", "lockable": true, "icon": null, "selectedIcon": null },
|
|
65
|
-
{ "name": "F1", "label": "Bell", "lockable": true, "icon": null, "selectedIcon": null },
|
|
66
|
-
{ "name": "F2", "label": "Horn", "lockable": false, "icon": null, "selectedIcon": null },
|
|
67
|
-
{ "name": "F3", "label": null, "lockable": false, "icon": null, "selectedIcon": null },
|
|
68
|
-
{ "name": "F4", "label": "Dynamic Brake", "lockable": true, "icon": null, "selectedIcon": null },
|
|
69
|
-
{ "name": "F5", "label": null, "lockable": false, "icon": null, "selectedIcon": null }
|
|
70
|
-
],
|
|
71
|
-
"attributes": [],
|
|
72
|
-
"rosterGroups": []
|
|
73
|
-
},
|
|
74
|
-
"id": 1
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
"type": "rosterEntry",
|
|
78
|
-
"data": {
|
|
79
|
-
"name": "UP3985",
|
|
80
|
-
"address": "3985",
|
|
81
|
-
"isLongAddress": true,
|
|
82
|
-
"road": "Union Pacific",
|
|
83
|
-
"number": "3985",
|
|
84
|
-
"mfg": "Rivarossi",
|
|
85
|
-
"decoderModel": "Sound decoder",
|
|
86
|
-
"decoderFamily": "ESU LokSound",
|
|
87
|
-
"model": "Challenger 4-6-6-4",
|
|
88
|
-
"comment": "Steam locomotive",
|
|
89
|
-
"maxSpeedPct": 100,
|
|
90
|
-
"image": null,
|
|
91
|
-
"icon": "/roster/UP3985/icon",
|
|
92
|
-
"shuntingFunction": "",
|
|
93
|
-
"owner": "",
|
|
94
|
-
"dateModified": "2026-02-10T00:00:00.000+00:00",
|
|
95
|
-
"functionKeys": [
|
|
96
|
-
{ "name": "F0", "label": "Headlight", "lockable": true, "icon": null, "selectedIcon": null },
|
|
97
|
-
{ "name": "F1", "label": "Bell", "lockable": true, "icon": null, "selectedIcon": null },
|
|
98
|
-
{ "name": "F2", "label": "Whistle", "lockable": false, "icon": null, "selectedIcon": null },
|
|
99
|
-
{ "name": "F3", "label": "Steam", "lockable": true, "icon": null, "selectedIcon": null },
|
|
100
|
-
{ "name": "F4", "label": null, "lockable": false, "icon": null, "selectedIcon": null }
|
|
101
|
-
],
|
|
102
|
-
"attributes": [],
|
|
103
|
-
"rosterGroups": []
|
|
104
|
-
},
|
|
105
|
-
"id": 2
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
"type": "rosterEntry",
|
|
109
|
-
"data": {
|
|
110
|
-
"name": "BNSF5240",
|
|
111
|
-
"address": "5240",
|
|
112
|
-
"isLongAddress": true,
|
|
113
|
-
"road": "BNSF",
|
|
114
|
-
"number": "5240",
|
|
115
|
-
"mfg": "Kato",
|
|
116
|
-
"decoderModel": "DCC Sound",
|
|
117
|
-
"decoderFamily": "Kato",
|
|
118
|
-
"model": "SD40-2",
|
|
119
|
-
"comment": "Heritage II paint",
|
|
120
|
-
"maxSpeedPct": 100,
|
|
121
|
-
"image": null,
|
|
122
|
-
"icon": "/roster/BNSF5240/icon",
|
|
123
|
-
"shuntingFunction": "",
|
|
124
|
-
"owner": "",
|
|
125
|
-
"dateModified": "2026-02-10T00:00:00.000+00:00",
|
|
126
|
-
"functionKeys": [
|
|
127
|
-
{ "name": "F0", "label": "Headlight", "lockable": true, "icon": null, "selectedIcon": null },
|
|
128
|
-
{ "name": "F1", "label": "Bell", "lockable": true, "icon": null, "selectedIcon": null },
|
|
129
|
-
{ "name": "F2", "label": "Horn", "lockable": false, "icon": null, "selectedIcon": null },
|
|
130
|
-
{ "name": "F3", "label": "Dynamic Brake", "lockable": true, "icon": null, "selectedIcon": null },
|
|
131
|
-
{ "name": "F4", "label": null, "lockable": false, "icon": null, "selectedIcon": null },
|
|
132
|
-
{ "name": "F5", "label": "Mars Light", "lockable": true, "icon": null, "selectedIcon": null }
|
|
133
|
-
],
|
|
134
|
-
"attributes": [],
|
|
135
|
-
"rosterGroups": []
|
|
136
|
-
},
|
|
137
|
-
"id": 3
|
|
138
|
-
}
|
|
139
|
-
]
|
|
140
|
-
},
|
|
141
|
-
"throttle": {
|
|
142
|
-
"acquire": {
|
|
143
|
-
"success": {
|
|
144
|
-
"type": "throttle",
|
|
145
|
-
"data": {
|
|
146
|
-
"throttle": "{THROTTLE_ID}",
|
|
147
|
-
"address": "{ADDRESS}",
|
|
148
|
-
"speed": 0,
|
|
149
|
-
"forward": true,
|
|
150
|
-
"F0": false,
|
|
151
|
-
"F1": false,
|
|
152
|
-
"F2": false,
|
|
153
|
-
"F3": false,
|
|
154
|
-
"F4": false
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
"release": {
|
|
159
|
-
"success": {
|
|
160
|
-
"type": "throttle",
|
|
161
|
-
"data": {}
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
"control": {
|
|
165
|
-
"speed": {
|
|
166
|
-
"type": "throttle",
|
|
167
|
-
"data": {
|
|
168
|
-
"throttle": "{THROTTLE_ID}",
|
|
169
|
-
"speed": "{SPEED}"
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
"direction": {
|
|
173
|
-
"type": "throttle",
|
|
174
|
-
"data": {
|
|
175
|
-
"throttle": "{THROTTLE_ID}",
|
|
176
|
-
"forward": "{FORWARD}"
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
"function": {
|
|
180
|
-
"type": "throttle",
|
|
181
|
-
"data": {
|
|
182
|
-
"throttle": "{THROTTLE_ID}",
|
|
183
|
-
"{FUNCTION}": "{VALUE}"
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
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
|
-
},
|
|
195
|
-
"turnout": {
|
|
196
|
-
"list": [
|
|
197
|
-
{ "type": "turnout", "data": { "name": "LT1", "userName": "Main Diverge", "state": 2 } },
|
|
198
|
-
{ "type": "turnout", "data": { "name": "LT2", "userName": "Yard Lead", "state": 2 } },
|
|
199
|
-
{ "type": "turnout", "data": { "name": "LT3", "userName": "Siding Entry", "state": 4 } }
|
|
200
|
-
]
|
|
201
|
-
},
|
|
202
|
-
"ping": {
|
|
203
|
-
"type": "ping"
|
|
204
|
-
},
|
|
205
|
-
"pong": {
|
|
206
|
-
"type": "pong"
|
|
207
|
-
},
|
|
208
|
-
"goodbye": {
|
|
209
|
-
"type": "goodbye"
|
|
210
|
-
},
|
|
211
|
-
"error": {
|
|
212
|
-
"throttleNotFound": {
|
|
213
|
-
"type": "error",
|
|
214
|
-
"data": {
|
|
215
|
-
"code": 404,
|
|
216
|
-
"message": "Throttle not found"
|
|
217
|
-
}
|
|
218
|
-
},
|
|
219
|
-
"invalidSpeed": {
|
|
220
|
-
"type": "error",
|
|
221
|
-
"data": {
|
|
222
|
-
"code": 400,
|
|
223
|
-
"message": "Invalid speed value"
|
|
224
|
-
}
|
|
225
|
-
},
|
|
226
|
-
"connectionError": {
|
|
227
|
-
"type": "error",
|
|
228
|
-
"data": {
|
|
229
|
-
"code": 500,
|
|
230
|
-
"message": "Connection error"
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
};
|