npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

abb-rws-client

v0.7.2

Published

Typed TypeScript/Node.js client for ABB Robot Web Services — supports both RWS 1.0 (IRC5/RobotWare 6) and RWS 2.0 (OmniCore/RobotWare 7).

Readme

abb-rws-client

A typed TypeScript/Node.js client for ABB Robot Web Services — both protocols ABB ships:

  • RWS 1.0 — IRC5 / RobotWare 6.x → RwsClient
  • RWS 2.0 — OmniCore / RobotWare 7.x → RwsClient2

Compatibility: dual-protocol since v0.7.0. Single-line auto-detection via createClient() if you don't know which one your controller speaks.


VS Code Extension

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.

ABB Robot (RWS) — VS Code Marketplace


Features

  • Dual-protocol — RWS 1.0 (Digest, JSON) and RWS 2.0 (Basic, XHTML;v=2.0)
  • Auto-detectioncreateClient(host) probes the auth challenge and returns the right client
  • Multi-robotMultiRobotManager for orchestrating several controllers in one process
  • Connection lifecycleRobotManager handles port discovery, polling, WebSocket subscriptions with polling fallback, reconnect-on-failure
  • Typed adapter patternIRWSAdapter lets you write code that works across both protocols
  • WebSocket subscriptions for real-time events (panel state, RAPID exec, signals, persvar, elog, jointtarget, …)
  • Session cookie management (IRC5: avoids the controller's 70-session pool fill; OmniCore: avoids 503 lockout from session-pool exhaustion)
  • Automatic /logout on disconnect to release server-side mastership and free the session slot
  • Request rate limiting (< 20 req/sec)
  • Fully typed public API — every method throws RwsError with a typed code
  • Single dependency: ws (only one we don't reimplement)

Installation

npm install abb-rws-client

Requirements: Node.js 18+.


Quick Start (auto-detect)

The simplest path: createClient probes the controller and returns the right protocol's client.

import { createClient, RwsClient2 } from 'abb-rws-client';

const client = await createClient({
  host: '192.168.125.1',
  // username/password default to 'Admin' / 'robotics' (built-in admin account, full UAS grants)
});

console.log(`Connected via ${client instanceof RwsClient2 ? 'RWS 2.0' : 'RWS 1.0'}`);

const state = await client.getControllerState();   // 'motoron' | 'motoroff' | …
const mode  = await client.getOperationMode();     // 'AUTO' | 'MANR' | 'MANF'
const joints = await client.getJointPositions();   // { rax_1, …, rax_6 }

await client.disconnect();

If you only target one protocol, skip the helper and instantiate the client directly. See examples/ for runnable scripts.


Choosing a client

| Controller | Protocol | Class | Auth | Default port | |---|---|---|---|---| | IRC5 (RobotWare 6.x) | RWS 1.0 | RwsClient | HTTP Digest | 80 (real), 80 / 11811 (VC) | | OmniCore (RobotWare 7.x) | RWS 2.0 | RwsClient2 | HTTP Basic | 443 (real), 5466 (VC HTTPS) |

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.

If you need a single typed reference that holds either:

import { createAdapter, type IRWSAdapter } from 'abb-rws-client';

const adapter: IRWSAdapter = await createAdapter({ host: '192.168.125.1' });
// adapter is RWS1Adapter or RWS2Adapter — both implement IRWSAdapter

RWS 1.0 explicit usage

import { RwsClient, RwsError } from 'abb-rws-client';

const client = new RwsClient({
  host: '192.168.125.1',
  username: 'Admin',
  password: 'robotics',
});

await client.connect();

// Controller state
const state = await client.getControllerState(); // 'motoron' | 'motoroff' | ...
const mode  = await client.getOperationMode();   // 'AUTO' | 'MANR' | 'MANF'

// Motion
const joints    = await client.getJointPositions();    // rax_1..rax_6 in degrees
const tcp       = await client.getCartesianPosition(); // x/y/z mm + quaternion
const tcpConfig = await client.getCartesianFull();     // + j1/j4/j6/jx config flags

// RAPID
await client.startRapid();
await client.stopRapid();
await client.resetRapid(); // PP to Main

// Variables
const val = await client.getRapidVariable('T_ROB1', 'user', 'reg1');
await client.setRapidVariable('T_ROB1', 'user', 'reg1', '42');

// I/O signals
const signals = await client.listAllSignals();
await client.writeSignal('Local', 'DRV_1', 'DO_1', '1');

// Real-time subscriptions
const unsubscribe = await client.subscribe(
  ['execution', 'controllerstate', { type: 'signal', name: 'Local/DRV_1/DI_1' }],
  (event) => console.log(event.resource, '=', event.value),
);
await unsubscribe();

await client.disconnect();

RWS 2.0 explicit usage

import { RwsClient2 } from 'abb-rws-client';

// RWS 2.0 takes a base URL (scheme + host + port).
//   Real OmniCore:  https://<host>:443
//   OmniCore VC:    https://127.0.0.1:5466
const client = new RwsClient2(
  'https://127.0.0.1:5466',
  'Admin',
  'robotics',
);

await client.connect();

// Same method names as RWS 1.0 — only the underlying protocol differs.
console.log('state:', await client.getControllerState());
console.log('joints:', await client.getJointPositions());

// RAPID variable read — RWS 2.0 symbol API uses suffix-style URLs internally
// (`/rw/rapid/symbol/{symburl}/data`); the method shape is the same.
const tool0 = await client.getRapidVariable('T_ROB1', 'BASE', 'tool0');

// WebSocket subscriptions over `robapi2_subscription` subprotocol.
const unsubscribe = await client.subscribe(
  ['controllerstate', 'execution'],
  (event) => console.log(event.resource, '=', event.value),
);

await unsubscribe();
await client.disconnect();

Notable RWS 2.0 quirks (handled automatically)

These are documented because they bite anyone who tries to write an RWS 2.0 client from scratch:

  • HTTP Basic auth, not Digest (RWS 1.0).
  • XHTML responses onlyAccept: application/json returns 406. Library uses Accept: application/xhtml+xml;v=2.0.
  • Path-based actions/rw/rapid/execution/stop, not ?action=stop.
  • Mastership domains collapsed — both 'rapid' and 'cfg' map to 'edit'. The adapter maps internally so either name works.
  • File service home is 'HOME', not '$HOME'.
  • Symbol API path is suffix-style/rw/rapid/symbol/{symburl}/data (RWS 1.0 puts /data at the front).
  • Module unload is POST /rw/rapid/tasks/{task}/unloadmod with body, NOT DELETE on the module URL (returns 405).
  • Self-signed TLS on virtual controllers — library uses rejectUnauthorized: false for HTTPS.
  • WebSocket subscription URL comes from the Location header (real hardware) or the XHTML body (VC). Subprotocol: robapi2_subscription.

Multi-robot orchestration

For applications that talk to several controllers, use MultiRobotManager:

import { MultiRobotManager } from 'abb-rws-client';

const multi = MultiRobotManager.fromConfigs([
  { id: 'cell-A', name: 'Cell A IRB120',  host: '192.168.125.1', port: 80,   useHttps: false, username: 'Admin', password: 'robotics' },
  { id: 'cell-B', name: 'Cell B IRB1200', host: '192.168.125.2', port: 443,  useHttps: true,  username: 'Admin', password: 'robotics' },
]);

multi.onError((msg, actions) => {
  console.error(`Robot error: ${msg}`);
  return Promise.resolve(undefined); // headless: just log
});

multi.onDidChange(() => {
  console.log(`active=${multi.activeId} state=${multi.state.ctrlstate}`);
});

for (const { id } of multi.entries) {
  await multi.connectRobot(id);
}
// One robot is "active" at a time (handy for UIs); state for all is polled.
multi.setActive('cell-B');

MultiRobotManager wraps individual RobotManager instances. Each RobotManager handles its own:

  • Auto port discovery (probes 5466 / 9403 / 443 / 80 / 11811 in that order, plus a wide-scan fallback when none of those answer — RobotStudio assigns random VC ports above 30000)
  • Protocol auto-detection (WWW-Authenticate: Digest → RWS 1.0; Basic → RWS 2.0)
  • Hybrid polling cadence: 5 s when WebSocket subscriptions are active (positions only — state-change resources stream over WS); 1 s when subscriptions failed (full state coverage via polling)
  • WebSocket subscriptions with automatic polling fallback (RWS 2.0 VC notably rejects the robapi2_subscription subprotocol — fallback kicks in)
  • Reconnect-on-failure (3-strike, surfaces via onError listener)
  • Clean GET /logout on disconnect — releases server-side mastership and frees the session slot

You can also create a RobotManager directly if you only have one robot.


RobotManager — higher-level surface

RobotManager wraps either client with operational helpers that handle mastership, polling, and protocol differences for you. In addition to delegating every protocol method to the underlying client, it exposes:

  • getRmmpPrivilege(), requestRmmp(level) — Remote Mastership Privilege management. Required on OmniCore in AUTO mode for any modify op.
  • getMastershipStatus(domain?) — read who currently holds rapid/cfg/motion mastership (uid + application name).
  • setOperationMode('AUTO' | 'MANR' | 'MANF') — VC-only switch with auto-routing through MANR for AUTO ↔ MANF (the controller rejects direct transitions). Acquires edit mastership for the higher-privilege direction.
  • setSpeedRatio(0..100) — wraps edit mastership; uses the live-verified ?action=setspeedratio&speed-ratio=N form on RWS 2.0 (the bare endpoint returns 400).
  • createBackup(name), restoreBackup(name), getBackupStatus(), listBackups()/ctrl/backup/...
  • callServiceRoutine(task, name, args?) — invoke a service routine remotely (calibration, brake check, etc.).
  • calcJointsFromCartesian(...) — inverse kinematics. calcCartesianFromJoints(...) — forward kinematics.
  • setActiveTool(mechunit, name), setActiveWobj(mechunit, name) — switch active persistent tooldata / wobjdata.
  • CFG writesetCfgInstance / createCfgInstance / removeCfgInstance / loadCfgFile / saveCfgFile. Each acquires edit mastership for the duration.
  • DIPClistDipcQueues / createDipcQueue / sendDipcMessage / readDipcMessage / removeDipcQueue. Bidirectional messaging between RAPID and external clients.
  • listFileVolumes() — every controller volume (HOME, BACKUP, DATA, ADDINDATA, PRODUCTS, RAMDISK, TEMP).
  • getModuleSource(task, name) — pull a module's RAPID text in one call.
  • compressPath(source, dest) — controller-side compression.
  • validateRapidValue(task, value, datatype) — pre-flight a literal before writing.

Every method returns Promise<...> and throws RwsError on failure.


Logging

The lib ships a no-op logger by default. Hosts (CLIs, services, the VS Code extension) install their own:

import { setLogger } from 'abb-rws-client';

setLogger({
  info:  (msg) => console.log(`[info]  ${msg}`),
  warn:  (msg) => console.warn(`[warn]  ${msg}`),
  error: (msg, err) => console.error(`[error] ${msg}`, err),
  show:  () => { /* bring log surface to front; no-op for CLIs */ },
});

Internal lifecycle events (connect/disconnect, polling cycles, subscription state, error recovery) flow through this.


API Reference

Constructor

new RwsClient(options: RwsClientOptions)

| Option | Type | Default | Description | |--------|------|---------|-------------| | host | string | — | Controller IP or hostname | | port | number | 80 | HTTP port | | username | string | 'Admin' | RWS username | | password | string | 'robotics' | RWS password | | requestIntervalMs | number | 55 | Min ms between requests (enforces < 20 req/sec) | | timeout | number | 5000 | Request timeout in ms | | sessionCookie | string | — | Saved cookie to reuse an existing session slot |


Connection

| Method | Returns | Description | |--------|---------|-------------| | connect() | void | Establish session and authenticate | | disconnect() | void | Close WebSocket subscriptions and clear session | | getSessionCookie() | string \| null | Current session cookie — persist to avoid 70-session limit |


Controller State & Panel

| Method | Returns | Description | |--------|---------|-------------| | getControllerState() | ControllerState | Motor state: motoron / motoroff / guardstop / emergencystop / … | | setControllerState(state) | void | Set motor state — requires mastership | | getOperationMode() | OperationMode | AUTO / MANR / MANF | | lockOperationMode(pin, permanent?) | void | Lock FlexPendant key switch with PIN | | unlockOperationMode() | void | Unlock FlexPendant key switch | | getSpeedRatio() | number | Speed override 0–100 | | setSpeedRatio(ratio) | void | Set speed override — AUTO mode only | | getCollisionDetectionState() | CollisionDetectionState | INIT / TRIGGERED / CONFIRMED / TRIGGERED_ACK | | restartController(mode?) | void | restart / istart / pstart / bstart | | getControllerIdentity() | ControllerIdentity | Name, ID, type, MAC address | | getSystemInfo() | SystemInfo | RobotWare version, options, system ID | | getControllerClock() | ControllerClock | Current date/time (UTC) | | setControllerClock(y,mo,d,h,mi,s) | void | Set controller date/time (UTC) |


RAPID Execution

| Method | Returns | Description | |--------|---------|-------------| | getRapidExecutionState() | ExecutionState | 'running' | 'stopped' | | getRapidExecutionInfo() | ExecutionInfo | State + current cycle mode | | getRapidTasks() | RapidTask[] | All tasks with name, type, state, active flag | | startRapid() | void | Start execution (AUTO + motors on required) | | stopRapid() | void | Stop execution | | resetRapid() | void | Reset program pointer to Main | | setExecutionCycle(cycle) | void | 'once' | 'forever' | 'asis' | | activateRapidTask(task) | void | Activate a task (multitasking) | | deactivateRapidTask(task) | void | Deactivate a task | | activateAllRapidTasks() | void | Activate all tasks | | deactivateAllRapidTasks() | void | Deactivate all tasks |


RAPID Variables & Symbols

| Method | Returns | Description | |--------|---------|-------------| | getRapidVariable(task, module, symbol) | string | Read variable as RAPID-syntax string | | setRapidVariable(task, module, symbol, value) | void | Write variable (RAPID syntax: '42', '"hello"', '[1,0,0,0]') | | validateRapidValue(task, value, datatype) | boolean | Validate value against datatype before writing | | getRapidSymbolProperties(task, module, symbol) | RapidSymbolProperties | Type, dims, storage class, flags | | searchRapidSymbols(params) | RapidSymbolInfo[] | Search by task, type, datatype, or regex | | getActiveUiInstruction() | UiInstruction \| null | Detect if RAPID is waiting for operator input | | setUiInstructionParam(stackurl, param, value) | void | Respond to a UI instruction (TPReadNum, TPReadFK…) |


RAPID Modules

| Method | Returns | Description | |--------|---------|-------------| | listModules(taskName) | string[] | Names of all loaded modules in a task | | loadModule(task, modulePath, replace?) | void | Load module from controller filesystem into task | | unloadModule(task, moduleName) | void | Unload module from task (RAPID must be stopped) |


Motion

| Method | Returns | Description | |--------|---------|-------------| | getJointPositions(mechunit?) | JointTarget | rax_1–rax_6 in degrees (default mechunit: ROB_1) | | getCartesianPosition(mechunit?, tool?, wobj?) | RobTarget | TCP x/y/z (mm) + q1–q4 quaternion | | getCartesianFull(mechunit?) | CartesianFull | TCP pose + j1/j4/j6/jx configuration flags |


File System

| Method | Returns | Description | |--------|---------|-------------| | listDirectory(remotePath) | FileEntry[] | Browse a directory ($HOME, $TEMP, …) | | readFile(remotePath) | string | Download file as UTF-8 string | | uploadModule(remotePath, content) | void | Upload file content (max 800 MB) | | deleteFile(remotePath) | void | Delete file | | createDirectory(parentPath, dirName) | void | Create directory | | copyFile(sourcePath, destPath) | void | Copy file on controller |


I/O Signals

| Method | Returns | Description | |--------|---------|-------------| | listAllSignals(start?, limit?) | Signal[] | Paginated flat list of all signals | | readSignal(network, device, name) | Signal | Read a specific signal by address | | writeSignal(network, device, name, value) | void | Write DO/AO/GO — value as string: '1', '0', '3.14' | | listNetworks() | IoNetwork[] | All I/O networks | | listDevices(network) | IoDevice[] | Devices on a network |

Pass '' for network and device to use the flat signal path (works by signal name alone).


Event Log

| Method | Returns | Description | |--------|---------|-------------| | getEventLog(domain?, lang?) | ElogMessage[] | Read log messages (domain 0 = main, newest first) | | clearEventLog(domain?) | void | Clear messages in one domain | | clearAllEventLogs() | void | Clear all domains |


Mastership

Required before modifying motor state, speed ratio, or certain RAPID operations.

| Method | Returns | Description | |--------|---------|-------------| | requestMastership(domain) | void | Take control — 'cfg' | 'motion' | 'rapid' | | releaseMastership(domain) | void | Release control — always call in finally |

await client.requestMastership('rapid');
try {
  await client.setControllerState('motoron');
} finally {
  await client.releaseMastership('rapid');
}

WebSocket Subscriptions

const unsubscribe = await client.subscribe(resources, handler);
// ...
await unsubscribe();

| Resource | Type | Description | |----------|------|-------------| | 'execution' | string | RAPID execution state changes | | 'controllerstate' | string | Motor state changes | | 'operationmode' | string | Operation mode changes | | 'speedratio' | string | Speed ratio changes | | 'coldetstate' | string | Collision detection state | | 'uiinstr' | string | Active UI instruction changes | | { type: 'execycle' } | object | Execution cycle mode changes | | { type: 'taskchange', task } | object | Task state changes | | { type: 'signal', name } | object | I/O signal value changes | | { type: 'persvar', name } | object | RAPID persistent variable changes | | { type: 'elog', domain } | object | New event log entries |

SubscriptionEvent: { resource: string, value: string, timestamp: Date }


Error Handling

All public methods throw RwsError — never a plain Error.

| Code | Meaning | |------|---------| | 'AUTH_FAILED' | Wrong credentials or session rejected | | 'SESSION_EXPIRED' | Session timed out | | 'MOTORS_OFF' | Action requires motors on | | 'MODULE_NOT_FOUND' | Module file not found on controller | | 'CONTROLLER_BUSY' | Controller returned 503 — retry later | | 'RATE_LIMITED' | Too many requests (429) | | 'NETWORK_ERROR' | TCP / timeout / WebSocket error | | 'PARSE_ERROR' | Unexpected XML response format | | 'UNKNOWN' | Unmapped error — check httpStatus and rwsDetail |

try {
  await client.startRapid();
} catch (e) {
  if (e instanceof RwsError) {
    if (e.code === 'MOTORS_OFF') console.error('Enable motors first');
    else throw e;
  }
}

Session Persistence

IRC5 controllers allow a maximum of 70 concurrent RWS sessions; OmniCore controllers also have a finite session pool (the exact number isn't published, but heavy probing without /logout returns HTTP 503 once it fills). Persist the session cookie across restarts to always reuse the same slot — the lib calls /logout on disconnect() to free the slot cleanly, but a saved cookie is the most robust path:

import fs from 'fs';

// Save after connecting
const cookie = client.getSessionCookie();
if (cookie) fs.writeFileSync('.rws-session', cookie, 'utf8');

// Restore on next start
let sessionCookie: string | undefined;
try { sessionCookie = fs.readFileSync('.rws-session', 'utf8'); } catch {}

const client = new RwsClient({ host, username, password, sessionCookie });
await client.connect(); // reuses the existing session slot

Rate Limits

| Constraint | Value | Source | |------------|-------|--------| | Max request rate | 20 req/sec | Both protocols (controller-enforced) | | Client-enforced interval | 55 ms between requests | This package, default | | Max concurrent sessions | 70 (IRC5) / finite (OmniCore) | Controller-enforced | | Session inactivity timeout | 5 minutes (IRC5) | Controller-enforced | | Max session lifetime | 25 minutes (IRC5) | Controller-enforced | | Max file upload | 800 MB (IRC5) | Controller-enforced; OmniCore is similar |

OmniCore-specific limits aren't fully documented by ABB; what the lib observes empirically matches the IRC5 numbers within an order of magnitude.


Compatibility

| | RobotWare 6.x (RWS 1.0) | RobotWare 7.x (RWS 2.0) | |--|--|--| | This package | ✅ RwsClient | ✅ RwsClient2 | | IRC5 controller (real) | ✅ | n/a | | OmniCore controller (real) | n/a | ✅ | | RobotStudio virtual controller | ✅ (RW6.x VC) | ✅ (RW7.x VC) | | Auto-detect from one entry point | ✅ via createClient() | ✅ via createClient() |

Live-tested matrix as of v0.7.1: RobotWare 7.21 (OmniCore VC, RWS 2.0), RobotWare 6.16 (IRC5 VC, RWS 1.0). 116 unit tests + 339 live protocol-coverage tests pass against both.


Resources


License

MIT — see LICENSE