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