abb-rws-client 0.2.0 → 0.7.1

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.
Files changed (71) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/README.md +364 -143
  3. package/dist/HttpSession.d.ts.map +1 -1
  4. package/dist/HttpSession.js +18 -1
  5. package/dist/HttpSession.js.map +1 -1
  6. package/dist/IRWSAdapter.d.ts +421 -0
  7. package/dist/IRWSAdapter.d.ts.map +1 -0
  8. package/dist/IRWSAdapter.js +2 -0
  9. package/dist/IRWSAdapter.js.map +1 -0
  10. package/dist/Logger.d.ts +35 -0
  11. package/dist/Logger.d.ts.map +1 -0
  12. package/dist/Logger.js +24 -0
  13. package/dist/Logger.js.map +1 -0
  14. package/dist/MultiRobotManager.d.ts +56 -0
  15. package/dist/MultiRobotManager.d.ts.map +1 -0
  16. package/dist/MultiRobotManager.js +129 -0
  17. package/dist/MultiRobotManager.js.map +1 -0
  18. package/dist/RWS1Adapter.d.ts +297 -0
  19. package/dist/RWS1Adapter.d.ts.map +1 -0
  20. package/dist/RWS1Adapter.js +870 -0
  21. package/dist/RWS1Adapter.js.map +1 -0
  22. package/dist/RWS2Adapter.d.ts +20 -0
  23. package/dist/RWS2Adapter.d.ts.map +1 -0
  24. package/dist/RWS2Adapter.js +19 -0
  25. package/dist/RWS2Adapter.js.map +1 -0
  26. package/dist/ResourceMapper.d.ts +226 -2
  27. package/dist/ResourceMapper.d.ts.map +1 -1
  28. package/dist/ResourceMapper.js +286 -3
  29. package/dist/ResourceMapper.js.map +1 -1
  30. package/dist/ResponseParser.d.ts +81 -2
  31. package/dist/ResponseParser.d.ts.map +1 -1
  32. package/dist/ResponseParser.js +309 -3
  33. package/dist/ResponseParser.js.map +1 -1
  34. package/dist/RobotManager.d.ts +536 -0
  35. package/dist/RobotManager.d.ts.map +1 -0
  36. package/dist/RobotManager.js +1523 -0
  37. package/dist/RobotManager.js.map +1 -0
  38. package/dist/RwsClient.d.ts +280 -4
  39. package/dist/RwsClient.d.ts.map +1 -1
  40. package/dist/RwsClient.js +698 -10
  41. package/dist/RwsClient.js.map +1 -1
  42. package/dist/RwsClient2.d.ts +438 -0
  43. package/dist/RwsClient2.d.ts.map +1 -0
  44. package/dist/RwsClient2.js +1672 -0
  45. package/dist/RwsClient2.js.map +1 -0
  46. package/dist/WsSubscriber.d.ts.map +1 -1
  47. package/dist/WsSubscriber.js +18 -11
  48. package/dist/WsSubscriber.js.map +1 -1
  49. package/dist/XhtmlParser.d.ts +24 -0
  50. package/dist/XhtmlParser.d.ts.map +1 -0
  51. package/dist/XhtmlParser.js +56 -0
  52. package/dist/XhtmlParser.js.map +1 -0
  53. package/dist/detect.d.ts +54 -0
  54. package/dist/detect.d.ts.map +1 -0
  55. package/dist/detect.js +187 -0
  56. package/dist/detect.js.map +1 -0
  57. package/dist/index.d.ts +30 -3
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +30 -2
  60. package/dist/index.js.map +1 -1
  61. package/dist/types.d.ts +190 -3
  62. package/dist/types.d.ts.map +1 -1
  63. package/dist/types.js +1 -0
  64. package/dist/types.js.map +1 -1
  65. package/examples/01-quickstart-auto.mjs +20 -0
  66. package/examples/02-rws1-explicit.mjs +30 -0
  67. package/examples/03-rws2-explicit.mjs +26 -0
  68. package/examples/04-multi-robot.mjs +35 -0
  69. package/examples/05-remote-control-rmmp.mjs +52 -0
  70. package/examples/06-pull-module-source.mjs +38 -0
  71. package/package.json +31 -5
package/README.md CHANGED
@@ -1,30 +1,35 @@
1
1
  # abb-rws-client
2
2
 
3
- A typed TypeScript/Node.js HTTP and WebSocket client for **ABB IRC5 robot controllers** using [Robot Web Services (RWS) 1.0](https://developercenter.robotstudio.com/api/rwsApi/).
3
+ A typed TypeScript/Node.js client for **ABB Robot Web Services** both protocols ABB ships:
4
4
 
5
- > **Compatibility:** RWS 1.0 / RobotWare 6.x only.
6
- > **Not compatible** with OmniCore controllers, RobotWare 7.x, or RWS 2.0.
5
+ - **RWS 1.0** — IRC5 / RobotWare 6.x → `RwsClient`
6
+ - **RWS 2.0** OmniCore / RobotWare 7.x `RwsClient2`
7
+
8
+ > **Compatibility:** dual-protocol since v0.7.0. Single-line auto-detection via `createClient()` if you don't know which one your controller speaks.
7
9
 
8
10
  ---
9
11
 
10
12
  ## VS Code Extension
11
13
 
12
- Prefer a GUI? The companion VS Code extension gives you live status, joint positions, RAPID control, and module management directly from the sidebar — no code required.
14
+ Prefer a GUI? The companion VS Code extension gives you live status, motion data, RAPID control, I/O signals, event log, file management, and CFG database editing directly from the sidebar — no code required. Works against both IRC5 and OmniCore.
13
15
 
14
- **[ABB Robot (RWS) — VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=merajsafari.abb-rws-vscode)**
16
+ **[ABB Robot (RWS) — VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=merajsafari.abb-rws)**
15
17
 
16
18
  ---
17
19
 
18
20
  ## Features
19
21
 
20
- - HTTP Digest Authentication (RFC 2617) no external auth libraries
21
- - Session cookie management (`ABBCX`, `-http-session-`)
22
- - Request rate limiting (< 20 req/sec, configurable)
23
- - Automatic session re-authentication after 5-minute inactivity
24
- - WebSocket subscriptions for real-time events (execution state, I/O signals, etc.)
25
- - Auto-reconnect on WebSocket disconnect (3 retries, exponential backoff)
22
+ - **Dual-protocol** RWS 1.0 (Digest, JSON) and RWS 2.0 (Basic, XHTML;v=2.0)
23
+ - **Auto-detection** `createClient(host)` probes the auth challenge and returns the right client
24
+ - **Multi-robot** `MultiRobotManager` for orchestrating several controllers in one process
25
+ - **Connection lifecycle** `RobotManager` handles port discovery, polling, WebSocket subscriptions with polling fallback, reconnect-on-failure
26
+ - **Typed adapter pattern** `IRWSAdapter` lets you write code that works across both protocols
27
+ - **WebSocket subscriptions** for real-time events (panel state, RAPID exec, signals, persvar, elog, jointtarget, …)
28
+ - Session cookie management (avoids the controller's 70-session pool fill)
29
+ - Automatic `/logout` on disconnect to release server-side mastership and free the session slot
30
+ - Request rate limiting (< 20 req/sec)
26
31
  - Fully typed public API — every method throws `RwsError` with a typed `code`
27
- - Zero runtime dependencies Node 18+ built-ins only
32
+ - Single dependency: `ws` (only one we don't reimplement)
28
33
 
29
34
  ---
30
35
 
@@ -34,213 +39,419 @@ Prefer a GUI? The companion VS Code extension gives you live status, joint posit
34
39
  npm install abb-rws-client
35
40
  ```
36
41
 
37
- **Requirements:** Node.js 21+ (or Node 18 with `--experimental-websocket` for WebSocket subscription support).
42
+ **Requirements:** Node.js 18+.
43
+
44
+ ---
45
+
46
+ ## Quick Start (auto-detect)
47
+
48
+ The simplest path: `createClient` probes the controller and returns the right protocol's client.
49
+
50
+ ```ts
51
+ import { createClient, RwsClient2 } from 'abb-rws-client';
52
+
53
+ const client = await createClient({
54
+ host: '192.168.125.1',
55
+ // username/password default to 'Admin' / 'robotics' (built-in admin account, full UAS grants)
56
+ });
57
+
58
+ console.log(`Connected via ${client instanceof RwsClient2 ? 'RWS 2.0' : 'RWS 1.0'}`);
59
+
60
+ const state = await client.getControllerState(); // 'motoron' | 'motoroff' | …
61
+ const mode = await client.getOperationMode(); // 'AUTO' | 'MANR' | 'MANF'
62
+ const joints = await client.getJointPositions(); // { rax_1, …, rax_6 }
63
+
64
+ await client.disconnect();
65
+ ```
66
+
67
+ If you only target one protocol, skip the helper and instantiate the client directly. See [`examples/`](./examples/) for runnable scripts.
68
+
69
+ ---
70
+
71
+ ## Choosing a client
72
+
73
+ | Controller | Protocol | Class | Auth | Default port |
74
+ |---|---|---|---|---|
75
+ | **IRC5** (RobotWare 6.x) | RWS 1.0 | `RwsClient` | HTTP Digest | 80 (real), 80 / 11811 (VC) |
76
+ | **OmniCore** (RobotWare 7.x) | RWS 2.0 | `RwsClient2` | HTTP Basic | 443 (real), 5466 (VC HTTPS) |
77
+
78
+ Both classes expose **the same method names** for ~140 endpoints (controller state, RAPID execution, modules, variables, motion, I/O, file service, CFG database, mastership, event log, etc.). The protocol differences (URL shapes, response format, mastership-domain naming, `$HOME` vs `HOME`) are handled internally — your code looks the same.
79
+
80
+ If you need a single typed reference that holds either:
81
+
82
+ ```ts
83
+ import { createAdapter, type IRWSAdapter } from 'abb-rws-client';
84
+
85
+ const adapter: IRWSAdapter = await createAdapter({ host: '192.168.125.1' });
86
+ // adapter is RWS1Adapter or RWS2Adapter — both implement IRWSAdapter
87
+ ```
38
88
 
39
89
  ---
40
90
 
41
- ## Quick Start RobotStudio virtual controller
91
+ ## RWS 1.0 explicit usage
42
92
 
43
93
  ```ts
44
94
  import { RwsClient, RwsError } from 'abb-rws-client';
45
95
 
46
96
  const client = new RwsClient({
47
- host: '127.0.0.1', // RobotStudio default
48
- port: 80,
49
- username: 'Default User',
97
+ host: '192.168.125.1',
98
+ username: 'Admin',
50
99
  password: 'robotics',
51
100
  });
52
101
 
53
- try {
54
- await client.connect();
102
+ await client.connect();
55
103
 
56
- // Read state
57
- const state = await client.getControllerState();
58
- console.log('Controller state:', state); // e.g. 'motoron'
104
+ // Controller state
105
+ const state = await client.getControllerState(); // 'motoron' | 'motoroff' | ...
106
+ const mode = await client.getOperationMode(); // 'AUTO' | 'MANR' | 'MANF'
59
107
 
60
- const mode = await client.getOperationMode();
61
- console.log('Operation mode:', mode); // e.g. 'AUTO'
108
+ // Motion
109
+ const joints = await client.getJointPositions(); // rax_1..rax_6 in degrees
110
+ const tcp = await client.getCartesianPosition(); // x/y/z mm + quaternion
111
+ const tcpConfig = await client.getCartesianFull(); // + j1/j4/j6/jx config flags
62
112
 
63
- // Read positions
64
- const joints = await client.getJointPositions();
65
- console.log('J1:', joints.rax_1, 'degrees');
113
+ // RAPID
114
+ await client.startRapid();
115
+ await client.stopRapid();
116
+ await client.resetRapid(); // PP to Main
66
117
 
67
- const tcp = await client.getCartesianPosition();
68
- console.log('TCP:', tcp.x, tcp.y, tcp.z, 'mm');
118
+ // Variables
119
+ const val = await client.getRapidVariable('T_ROB1', 'user', 'reg1');
120
+ await client.setRapidVariable('T_ROB1', 'user', 'reg1', '42');
69
121
 
70
- // RAPID execution
71
- await client.startRapid();
72
- await client.stopRapid();
73
-
74
- // I/O signals
75
- const di = await client.readSignal('Local', 'DRV_1', 'DI_1');
76
- console.log('DI_1 =', di.value);
77
- await client.writeSignal('Local', 'DRV_1', 'DO_1', '1');
78
-
79
- // Upload and load a RAPID module
80
- const modSource = `MODULE MyMod\n PROC main()\n TPWrite "Hello";\n ENDPROC\nENDMODULE`;
81
- await client.uploadModule('$HOME/MyMod.mod', modSource);
82
- await client.loadModule('T_ROB1', '$HOME/MyMod.mod');
83
-
84
- // Subscribe to real-time events
85
- const unsubscribe = await client.subscribe(
86
- ['execution', 'controllerstate'],
87
- (event) => {
88
- console.log(`[${event.timestamp.toISOString()}] ${event.resource} = ${event.value}`);
89
- }
90
- );
91
-
92
- // ... later, unsubscribe and disconnect
93
- await unsubscribe();
94
- await client.disconnect();
122
+ // I/O signals
123
+ const signals = await client.listAllSignals();
124
+ await client.writeSignal('Local', 'DRV_1', 'DO_1', '1');
95
125
 
96
- } catch (e) {
97
- if (e instanceof RwsError) {
98
- console.error(`[${e.code}] ${e.message} (HTTP ${e.httpStatus ?? 'N/A'})`);
99
- } else {
100
- throw e;
101
- }
126
+ // Real-time subscriptions
127
+ const unsubscribe = await client.subscribe(
128
+ ['execution', 'controllerstate', { type: 'signal', name: 'Local/DRV_1/DI_1' }],
129
+ (event) => console.log(event.resource, '=', event.value),
130
+ );
131
+ await unsubscribe();
132
+
133
+ await client.disconnect();
134
+ ```
135
+
136
+ ---
137
+
138
+ ## RWS 2.0 explicit usage
139
+
140
+ ```ts
141
+ import { RwsClient2 } from 'abb-rws-client';
142
+
143
+ // RWS 2.0 takes a base URL (scheme + host + port).
144
+ // Real OmniCore: https://<host>:443
145
+ // OmniCore VC: https://127.0.0.1:5466
146
+ const client = new RwsClient2(
147
+ 'https://127.0.0.1:5466',
148
+ 'Admin',
149
+ 'robotics',
150
+ );
151
+
152
+ await client.connect();
153
+
154
+ // Same method names as RWS 1.0 — only the underlying protocol differs.
155
+ console.log('state:', await client.getControllerState());
156
+ console.log('joints:', await client.getJointPositions());
157
+
158
+ // RAPID variable read — RWS 2.0 symbol API uses suffix-style URLs internally
159
+ // (`/rw/rapid/symbol/{symburl}/data`); the method shape is the same.
160
+ const tool0 = await client.getRapidVariable('T_ROB1', 'BASE', 'tool0');
161
+
162
+ // WebSocket subscriptions over `robapi2_subscription` subprotocol.
163
+ const unsubscribe = await client.subscribe(
164
+ ['controllerstate', 'execution'],
165
+ (event) => console.log(event.resource, '=', event.value),
166
+ );
167
+
168
+ await unsubscribe();
169
+ await client.disconnect();
170
+ ```
171
+
172
+ ### Notable RWS 2.0 quirks (handled automatically)
173
+
174
+ These are documented because they bite anyone who tries to write an RWS 2.0 client from scratch:
175
+
176
+ - **HTTP Basic auth**, not Digest (RWS 1.0).
177
+ - **XHTML responses only** — `Accept: application/json` returns 406. Library uses `Accept: application/xhtml+xml;v=2.0`.
178
+ - **Path-based actions** — `/rw/rapid/execution/stop`, not `?action=stop`.
179
+ - **Mastership domains collapsed** — both `'rapid'` and `'cfg'` map to `'edit'`. The adapter maps internally so either name works.
180
+ - **File service home** is `'HOME'`, not `'$HOME'`.
181
+ - **Symbol API path is suffix-style** — `/rw/rapid/symbol/{symburl}/data` (RWS 1.0 puts `/data` at the front).
182
+ - **Module unload** is `POST /rw/rapid/tasks/{task}/unloadmod` with body, NOT `DELETE` on the module URL (returns 405).
183
+ - **Self-signed TLS** on virtual controllers — library uses `rejectUnauthorized: false` for HTTPS.
184
+ - **WebSocket subscription URL** comes from the `Location` header (real hardware) or the XHTML body (VC). Subprotocol: `robapi2_subscription`.
185
+
186
+ ---
187
+
188
+ ## Multi-robot orchestration
189
+
190
+ For applications that talk to several controllers, use `MultiRobotManager`:
191
+
192
+ ```ts
193
+ import { MultiRobotManager } from 'abb-rws-client';
194
+
195
+ const multi = MultiRobotManager.fromConfigs([
196
+ { id: 'cell-A', name: 'Cell A IRB120', host: '192.168.125.1', port: 80, useHttps: false, username: 'Admin', password: 'robotics' },
197
+ { id: 'cell-B', name: 'Cell B IRB1200', host: '192.168.125.2', port: 443, useHttps: true, username: 'Admin', password: 'robotics' },
198
+ ]);
199
+
200
+ multi.onError((msg, actions) => {
201
+ console.error(`Robot error: ${msg}`);
202
+ return Promise.resolve(undefined); // headless: just log
203
+ });
204
+
205
+ multi.onDidChange(() => {
206
+ console.log(`active=${multi.activeId} state=${multi.state.ctrlstate}`);
207
+ });
208
+
209
+ for (const { id } of multi.entries) {
210
+ await multi.connectRobot(id);
102
211
  }
212
+ // One robot is "active" at a time (handy for UIs); state for all is polled.
213
+ multi.setActive('cell-B');
214
+ ```
215
+
216
+ `MultiRobotManager` wraps individual `RobotManager` instances. Each `RobotManager` handles its own:
217
+ - Auto port discovery (probes 5466/9403/443/80/11811, plus a wide-scan fallback)
218
+ - Protocol auto-detection (Digest vs Basic challenge)
219
+ - 5-second polling for non-subscription state
220
+ - WebSocket subscriptions with automatic polling fallback
221
+ - Reconnect-on-failure (3-strike)
222
+ - Clean `/logout` on disconnect (avoids orphan mastership locks)
223
+
224
+ You can also create a `RobotManager` directly if you only have one robot.
225
+
226
+ ---
227
+
228
+ ## Logging
229
+
230
+ The lib ships a no-op logger by default. Hosts (CLIs, services, the VS Code extension) install their own:
231
+
232
+ ```ts
233
+ import { setLogger } from 'abb-rws-client';
234
+
235
+ setLogger({
236
+ info: (msg) => console.log(`[info] ${msg}`),
237
+ warn: (msg) => console.warn(`[warn] ${msg}`),
238
+ error: (msg, err) => console.error(`[error] ${msg}`, err),
239
+ show: () => { /* bring log surface to front; no-op for CLIs */ },
240
+ });
103
241
  ```
104
242
 
243
+ Internal lifecycle events (connect/disconnect, polling cycles, subscription state, error recovery) flow through this.
244
+
105
245
  ---
106
246
 
107
247
  ## API Reference
108
248
 
109
- ### `new RwsClient(options)`
249
+ ### Constructor
250
+
251
+ ```ts
252
+ new RwsClient(options: RwsClientOptions)
253
+ ```
110
254
 
111
255
  | Option | Type | Default | Description |
112
- |---|---|---|---|
256
+ |--------|------|---------|-------------|
113
257
  | `host` | `string` | — | Controller IP or hostname |
114
258
  | `port` | `number` | `80` | HTTP port |
115
- | `username` | `string` | `'Default User'` | RWS username |
259
+ | `username` | `string` | `'Admin'` | RWS username |
116
260
  | `password` | `string` | `'robotics'` | RWS password |
117
- | `requestIntervalMs` | `number` | `55` | Minimum ms between requests (enforces < 20 req/sec) |
261
+ | `requestIntervalMs` | `number` | `55` | Min ms between requests (enforces < 20 req/sec) |
118
262
  | `timeout` | `number` | `5000` | Request timeout in ms |
119
- | `sessionCookie` | `string` | — | Saved cookie string to reuse an existing session slot (avoids the 70-session controller limit) |
263
+ | `sessionCookie` | `string` | — | Saved cookie to reuse an existing session slot |
120
264
 
121
265
  ---
122
266
 
123
267
  ### Connection
124
268
 
125
269
  | Method | Returns | Description |
126
- |---|---|---|
127
- | `connect()` | `Promise<void>` | Establish session and authenticate |
128
- | `disconnect()` | `Promise<void>` | Close subscriptions and clear session |
129
- | `getSessionCookie()` | `string \| null` | Current session cookie — save and pass as `sessionCookie` on next start to reuse the slot |
270
+ |--------|---------|-------------|
271
+ | `connect()` | `void` | Establish session and authenticate |
272
+ | `disconnect()` | `void` | Close WebSocket subscriptions and clear session |
273
+ | `getSessionCookie()` | `string \| null` | Current session cookie — persist to avoid 70-session limit |
130
274
 
131
275
  ---
132
276
 
133
- ### Controller State
277
+ ### Controller State & Panel
134
278
 
135
279
  | Method | Returns | Description |
136
- |---|---|---|
137
- | `getControllerState()` | `Promise<ControllerState>` | `'init'` \| `'motoroff'` \| `'motoron'` \| `'guardstop'` \| `'emergencystop'` \| `'emergencystopreset'` \| `'sysfail'` |
138
- | `getOperationMode()` | `Promise<OperationMode>` | `'AUTO'` \| `'MANR'` \| `'MANF'` |
280
+ |--------|---------|-------------|
281
+ | `getControllerState()` | `ControllerState` | Motor state: motoron / motoroff / guardstop / emergencystop / |
282
+ | `setControllerState(state)` | `void` | Set motor state requires mastership |
283
+ | `getOperationMode()` | `OperationMode` | AUTO / MANR / MANF |
284
+ | `lockOperationMode(pin, permanent?)` | `void` | Lock FlexPendant key switch with PIN |
285
+ | `unlockOperationMode()` | `void` | Unlock FlexPendant key switch |
286
+ | `getSpeedRatio()` | `number` | Speed override 0–100 |
287
+ | `setSpeedRatio(ratio)` | `void` | Set speed override — AUTO mode only |
288
+ | `getCollisionDetectionState()` | `CollisionDetectionState` | INIT / TRIGGERED / CONFIRMED / TRIGGERED_ACK |
289
+ | `restartController(mode?)` | `void` | restart / istart / pstart / bstart |
290
+ | `getControllerIdentity()` | `ControllerIdentity` | Name, ID, type, MAC address |
291
+ | `getSystemInfo()` | `SystemInfo` | RobotWare version, options, system ID |
292
+ | `getControllerClock()` | `ControllerClock` | Current date/time (UTC) |
293
+ | `setControllerClock(y,mo,d,h,mi,s)` | `void` | Set controller date/time (UTC) |
139
294
 
140
295
  ---
141
296
 
142
297
  ### RAPID Execution
143
298
 
144
299
  | Method | Returns | Description |
145
- |---|---|---|
146
- | `getRapidExecutionState()` | `Promise<ExecutionState>` | `'running'` \| `'stopped'` |
147
- | `getRapidTasks()` | `Promise<RapidTask[]>` | All RAPID tasks and their states |
148
- | `startRapid()` | `Promise<void>` | Start RAPID execution (requires AUTO + motors on) |
149
- | `stopRapid()` | `Promise<void>` | Stop RAPID execution |
150
- | `resetRapid()` | `Promise<void>` | Reset program pointer to main |
300
+ |--------|---------|-------------|
301
+ | `getRapidExecutionState()` | `ExecutionState` | `'running'` \| `'stopped'` |
302
+ | `getRapidExecutionInfo()` | `ExecutionInfo` | State + current cycle mode |
303
+ | `getRapidTasks()` | `RapidTask[]` | All tasks with name, type, state, active flag |
304
+ | `startRapid()` | `void` | Start execution (AUTO + motors on required) |
305
+ | `stopRapid()` | `void` | Stop execution |
306
+ | `resetRapid()` | `void` | Reset program pointer to Main |
307
+ | `setExecutionCycle(cycle)` | `void` | `'once'` \| `'forever'` \| `'asis'` |
308
+ | `activateRapidTask(task)` | `void` | Activate a task (multitasking) |
309
+ | `deactivateRapidTask(task)` | `void` | Deactivate a task |
310
+ | `activateAllRapidTasks()` | `void` | Activate all tasks |
311
+ | `deactivateAllRapidTasks()` | `void` | Deactivate all tasks |
312
+
313
+ ---
314
+
315
+ ### RAPID Variables & Symbols
316
+
317
+ | Method | Returns | Description |
318
+ |--------|---------|-------------|
319
+ | `getRapidVariable(task, module, symbol)` | `string` | Read variable as RAPID-syntax string |
320
+ | `setRapidVariable(task, module, symbol, value)` | `void` | Write variable (RAPID syntax: `'42'`, `'"hello"'`, `'[1,0,0,0]'`) |
321
+ | `validateRapidValue(task, value, datatype)` | `boolean` | Validate value against datatype before writing |
322
+ | `getRapidSymbolProperties(task, module, symbol)` | `RapidSymbolProperties` | Type, dims, storage class, flags |
323
+ | `searchRapidSymbols(params)` | `RapidSymbolInfo[]` | Search by task, type, datatype, or regex |
324
+ | `getActiveUiInstruction()` | `UiInstruction \| null` | Detect if RAPID is waiting for operator input |
325
+ | `setUiInstructionParam(stackurl, param, value)` | `void` | Respond to a UI instruction (TPReadNum, TPReadFK…) |
326
+
327
+ ---
328
+
329
+ ### RAPID Modules
330
+
331
+ | Method | Returns | Description |
332
+ |--------|---------|-------------|
333
+ | `listModules(taskName)` | `string[]` | Names of all loaded modules in a task |
334
+ | `loadModule(task, modulePath, replace?)` | `void` | Load module from controller filesystem into task |
335
+ | `unloadModule(task, moduleName)` | `void` | Unload module from task (RAPID must be stopped) |
151
336
 
152
337
  ---
153
338
 
154
339
  ### Motion
155
340
 
156
341
  | Method | Returns | Description |
157
- |---|---|---|
158
- | `getJointPositions(mechunit?)` | `Promise<JointTarget>` | Joint angles in degrees for all 6 axes |
159
- | `getCartesianPosition(mechunit?, tool?, wobj?)` | `Promise<RobTarget>` | TCP position (mm) and orientation (quaternion) |
342
+ |--------|---------|-------------|
343
+ | `getJointPositions(mechunit?)` | `JointTarget` | rax_1–rax_6 in degrees (default mechunit: ROB_1) |
344
+ | `getCartesianPosition(mechunit?, tool?, wobj?)` | `RobTarget` | TCP x/y/z (mm) + q1–q4 quaternion |
345
+ | `getCartesianFull(mechunit?)` | `CartesianFull` | TCP pose + j1/j4/j6/jx configuration flags |
160
346
 
161
347
  ---
162
348
 
163
- ### Modules
349
+ ### File System
164
350
 
165
351
  | Method | Returns | Description |
166
- |---|---|---|
167
- | `uploadModule(remotePath, content)` | `Promise<void>` | Upload RAPID `.mod` source to controller filesystem |
168
- | `loadModule(taskName, modulePath)` | `Promise<void>` | Load an uploaded module into a RAPID task |
169
- | `unloadModule(taskName, moduleName)` | `Promise<void>` | Unload a module from a task (RAPID must be stopped) |
170
- | `listModules(taskName)` | `Promise<string[]>` | Names of all loaded modules in a task |
171
- | `readFile(remotePath)` | `Promise<string>` | Download a file from the controller filesystem as a string |
352
+ |--------|---------|-------------|
353
+ | `listDirectory(remotePath)` | `FileEntry[]` | Browse a directory (`$HOME`, `$TEMP`, …) |
354
+ | `readFile(remotePath)` | `string` | Download file as UTF-8 string |
355
+ | `uploadModule(remotePath, content)` | `void` | Upload file content (max 800 MB) |
356
+ | `deleteFile(remotePath)` | `void` | Delete file |
357
+ | `createDirectory(parentPath, dirName)` | `void` | Create directory |
358
+ | `copyFile(sourcePath, destPath)` | `void` | Copy file on controller |
172
359
 
173
360
  ---
174
361
 
175
362
  ### I/O Signals
176
363
 
177
364
  | Method | Returns | Description |
178
- |---|---|---|
179
- | `readSignal(network, device, name)` | `Promise<Signal>` | Read current signal value |
180
- | `writeSignal(network, device, name, value)` | `Promise<void>` | Write a value to an output signal |
365
+ |--------|---------|-------------|
366
+ | `listAllSignals(start?, limit?)` | `Signal[]` | Paginated flat list of all signals |
367
+ | `readSignal(network, device, name)` | `Signal` | Read a specific signal by address |
368
+ | `writeSignal(network, device, name, value)` | `void` | Write DO/AO/GO — value as string: `'1'`, `'0'`, `'3.14'` |
369
+ | `listNetworks()` | `IoNetwork[]` | All I/O networks |
370
+ | `listDevices(network)` | `IoDevice[]` | Devices on a network |
371
+
372
+ > Pass `''` for `network` and `device` to use the flat signal path (works by signal name alone).
373
+
374
+ ---
375
+
376
+ ### Event Log
377
+
378
+ | Method | Returns | Description |
379
+ |--------|---------|-------------|
380
+ | `getEventLog(domain?, lang?)` | `ElogMessage[]` | Read log messages (domain 0 = main, newest first) |
381
+ | `clearEventLog(domain?)` | `void` | Clear messages in one domain |
382
+ | `clearAllEventLogs()` | `void` | Clear all domains |
181
383
 
182
384
  ---
183
385
 
184
- ### Subscriptions
386
+ ### Mastership
387
+
388
+ Required before modifying motor state, speed ratio, or certain RAPID operations.
389
+
390
+ | Method | Returns | Description |
391
+ |--------|---------|-------------|
392
+ | `requestMastership(domain)` | `void` | Take control — `'cfg'` \| `'motion'` \| `'rapid'` |
393
+ | `releaseMastership(domain)` | `void` | Release control — always call in `finally` |
185
394
 
186
395
  ```ts
187
- subscribe(
188
- resources: SubscriptionResource[],
189
- handler: (event: SubscriptionEvent) => void
190
- ): Promise<() => Promise<void>>
396
+ await client.requestMastership('rapid');
397
+ try {
398
+ await client.setControllerState('motoron');
399
+ } finally {
400
+ await client.releaseMastership('rapid');
401
+ }
191
402
  ```
192
403
 
193
- Subscribe to real-time RWS events. Returns an async unsubscribe function.
404
+ ---
194
405
 
195
- **`SubscriptionResource`** can be:
196
- - `'execution'` — RAPID execution state changes
197
- - `'controllerstate'` — controller state changes
198
- - `'operationmode'` — operation mode changes
199
- - `{ type: 'signal'; name: 'network/device/signalname' }` — I/O signal changes
200
- - `{ type: 'persvar'; name: 'task/varname' }` — RAPID persistent variable changes
406
+ ### WebSocket Subscriptions
201
407
 
202
- **`SubscriptionEvent`**:
203
408
  ```ts
204
- {
205
- resource: string; // RWS path of the changed resource
206
- value: string; // New value as string
207
- timestamp: Date; // When the event was received
208
- }
409
+ const unsubscribe = await client.subscribe(resources, handler);
410
+ // ...
411
+ await unsubscribe();
209
412
  ```
210
413
 
414
+ | Resource | Type | Description |
415
+ |----------|------|-------------|
416
+ | `'execution'` | string | RAPID execution state changes |
417
+ | `'controllerstate'` | string | Motor state changes |
418
+ | `'operationmode'` | string | Operation mode changes |
419
+ | `'speedratio'` | string | Speed ratio changes |
420
+ | `'coldetstate'` | string | Collision detection state |
421
+ | `'uiinstr'` | string | Active UI instruction changes |
422
+ | `{ type: 'execycle' }` | object | Execution cycle mode changes |
423
+ | `{ type: 'taskchange', task }` | object | Task state changes |
424
+ | `{ type: 'signal', name }` | object | I/O signal value changes |
425
+ | `{ type: 'persvar', name }` | object | RAPID persistent variable changes |
426
+ | `{ type: 'elog', domain }` | object | New event log entries |
427
+
428
+ `SubscriptionEvent`: `{ resource: string, value: string, timestamp: Date }`
429
+
211
430
  ---
212
431
 
213
432
  ### Error Handling
214
433
 
215
- All public methods throw `RwsError` (never plain `Error`) with a typed `code`:
434
+ All public methods throw `RwsError` never a plain `Error`.
216
435
 
217
436
  | Code | Meaning |
218
- |---|---|
437
+ |------|---------|
219
438
  | `'AUTH_FAILED'` | Wrong credentials or session rejected |
220
- | `'SESSION_EXPIRED'` | Session timed out and re-auth was not possible |
221
- | `'MOTORS_OFF'` | Action requires motors to be on |
222
- | `'MODULE_NOT_FOUND'` | Module file not found on controller filesystem |
223
- | `'CONTROLLER_BUSY'` | Controller returned 503; retry later |
439
+ | `'SESSION_EXPIRED'` | Session timed out |
440
+ | `'MOTORS_OFF'` | Action requires motors on |
441
+ | `'MODULE_NOT_FOUND'` | Module file not found on controller |
442
+ | `'CONTROLLER_BUSY'` | Controller returned 503 retry later |
224
443
  | `'RATE_LIMITED'` | Too many requests (429) |
225
- | `'NETWORK_ERROR'` | TCP/timeout/WebSocket error |
444
+ | `'NETWORK_ERROR'` | TCP / timeout / WebSocket error |
226
445
  | `'PARSE_ERROR'` | Unexpected XML response format |
227
- | `'UNKNOWN'` | Unmapped error; check `httpStatus` and `rwsDetail` |
446
+ | `'UNKNOWN'` | Unmapped error check `httpStatus` and `rwsDetail` |
228
447
 
229
448
  ```ts
230
449
  try {
231
450
  await client.startRapid();
232
451
  } catch (e) {
233
452
  if (e instanceof RwsError) {
234
- switch (e.code) {
235
- case 'MOTORS_OFF':
236
- console.error('Enable motors on the FlexPendant first');
237
- break;
238
- case 'AUTH_FAILED':
239
- console.error('Check username/password');
240
- break;
241
- default:
242
- console.error(`Unexpected error [${e.code}]: ${e.message}`);
243
- }
453
+ if (e.code === 'MOTORS_OFF') console.error('Enable motors first');
454
+ else throw e;
244
455
  }
245
456
  }
246
457
  ```
@@ -249,43 +460,53 @@ try {
249
460
 
250
461
  ## Session Persistence
251
462
 
252
- The IRC5 controller allows a maximum of **70 concurrent RWS sessions**. To avoid exhausting this limit across process restarts, persist the session cookie:
463
+ The IRC5 controller allows a maximum of **70 concurrent RWS sessions**. Persist the session cookie across restarts to always reuse the same slot:
253
464
 
254
465
  ```ts
255
466
  import fs from 'fs';
256
467
 
257
- // After connecting — save the cookie
468
+ // Save after connecting
258
469
  const cookie = client.getSessionCookie();
259
470
  if (cookie) fs.writeFileSync('.rws-session', cookie, 'utf8');
260
471
 
261
- // On next start — reuse it
472
+ // Restore on next start
262
473
  let sessionCookie: string | undefined;
263
474
  try { sessionCookie = fs.readFileSync('.rws-session', 'utf8'); } catch {}
264
475
 
265
476
  const client = new RwsClient({ host, username, password, sessionCookie });
266
- await client.connect(); // reuses the existing slot, no new session created
477
+ await client.connect(); // reuses the existing session slot
267
478
  ```
268
479
 
269
480
  ---
270
481
 
271
- ## Compatibility
482
+ ## Rate Limits
483
+
484
+ | Constraint | Value |
485
+ |------------|-------|
486
+ | Max request rate | 20 req/sec |
487
+ | Client enforced interval | 55 ms between requests |
488
+ | Max concurrent sessions | 70 |
489
+ | Session inactivity timeout | 5 minutes |
490
+ | Max session lifetime | 25 minutes |
491
+ | Max file upload | 800 MB |
272
492
 
273
- | Feature | RobotWare 6.x (RWS 1.0) | RobotWare 7.x (RWS 2.0) |
274
- |---|---|---|
275
- | This package | ✓ Supported | ✗ Not compatible |
276
- | Controller: IRC5 | ✓ | n/a |
277
- | Controller: OmniCore | n/a | ✗ |
278
- | RobotStudio (virtual) | ✓ (127.0.0.1) | ✗ |
493
+ ---
494
+
495
+ ## Compatibility
279
496
 
280
- RWS 2.0 uses a completely different API structure. This package implements RWS 1.0 only and will not function with RobotWare 7.x or OmniCore controllers.
497
+ | | RobotWare 6.x (RWS 1.0) | RobotWare 7.x (RWS 2.0) |
498
+ |--|--|--|
499
+ | This package | ✅ Supported | ❌ Not compatible |
500
+ | IRC5 controller | ✅ | n/a |
501
+ | OmniCore controller | n/a | ❌ |
502
+ | RobotStudio virtual | ✅ (127.0.0.1) | ❌ |
281
503
 
282
504
  ---
283
505
 
284
506
  ## Resources
285
507
 
286
- - [ABB RWS 1.0 API Reference](https://developercenter.robotstudio.com/api/rwsApi/)
287
- - [ABB Developer Center](https://developercenter.robotstudio.com/api/RWS)
288
- - [VALIDATION.md](./VALIDATION.md) — Manual test procedures against a real IRC5 / RobotStudio controller
508
+ - [ABB Developer Center — RWS API](https://developercenter.robotstudio.com/api/rwsApi/)
509
+ - [Companion VS Code Extension](https://marketplace.visualstudio.com/items?itemName=merajsafari.abb-rws)
289
510
 
290
511
  ---
291
512