# BRW UR Bridge

Plays a trajectory simulated in **BabaCAD Robotics Web** (BRW) on a real **Universal Robots UR3, UR5 or UR10e**.

| BRW model | Real robot | Joint range | Max joint speed (datasheet) |
|-----------|------------|-------------|-----------------------------|
| UR3  | UR3 (CB3)       | ±360° all joints | 180°/s base–elbow, 360°/s wrists |
| UR5  | UR5 (CB3)       | ±360° all joints | 180°/s all joints |
| UR10e| UR10e (e-Series)| ±360° all joints | 120°/s base and shoulder, 180°/s elbow and wrists |

The e-Series UR3e / UR5e and the CB3 UR10 are not supported. Their link lengths differ from the BRW
models (UR3e vs UR3: about 19 mm and 10 mm on the wrist links), so the same joint angles would put the
tool somewhere else. The controller reports "UR3" for a UR3e as well, so the bridge cannot tell them
apart: make sure the robot is the model you simulated.

```
 BabaCAD Robotics Web (browser)               this computer                       UR controller
 https://www.babacad.com/robotics/web
 brw-plugin.js, User Command 1  ── WebSocket ──>  BRW UR Bridge   ── TCP 29999 dashboard ──>  robot / safety mode
                                ws://127.0.0.1:8765  (Node.js app)   ── TCP 30002 URScript  ──>  movej + servoj program
                                <── progress ───                     <── TCP 30003 real-time ─  joint positions, 125/500 Hz
                                                                     <── TCP 30020 (stream mode only, robot connects back)
```

1. **User Command 1** in BRW checks that the active arm is a UR3, UR5 or UR10e with its standard joint mapping, reads its simulated trajectory
   (`getRobotTrajData()`: one joint-delta step per simulation frame), turns it into absolute joint
   angles and sends it to the bridge over a WebSocket.
2. The **bridge** validates it (joint range ±360°, no IK jumps, the model's joint speed limits), checks
   that the connected robot is the same model size, powered, in normal safety mode and (e-Series) in
   remote control, and sends a URScript
   program: a slow `movej` to the start pose, then one `servoj` per trajectory point at the
   simulation rate.
3. Progress and the result appear in the BRW console.

| Command in BRW    | What it does                                   |
|-------------------|------------------------------------------------|
| User Command 1    | send the active arm's simulation to the robot  |
| User Command 2    | stop the robot (`stopj`)                       |
| User Command 3    | show bridge / robot status and robot joints    |

> ⚠️ **Safety.** The real arm moves the way the simulation does, including through anything that was not
> modelled in BRW. Clear the work cell, lower `speedScale` for first runs, and keep the teach pendant's
> emergency stop in reach. User Command 2 and typing `stop` in the bridge window are conveniences,
> not safety functions.

## Install (users)

1. Unzip the package for your computer:
   - `BRW-UR-Bridge-win-x64.zip` (Windows), `BRW-UR-Bridge-mac-arm64.zip` (Apple silicon Mac),
     `BRW-UR-Bridge-mac-x64.zip` (Intel Mac). These contain Node.js; nothing else to install.
   - `BRW-UR-Bridge-no-node.zip` if Node.js 18 or newer is already installed.
2. Open `app/config.json` and set `"robotIp"` to your robot's IP address
   (teach pendant: *Settings → System → Network*, CB3: *Setup Robot → Network*).
3. Start the bridge:
   - Windows: double-click `start-bridge.bat`. Allow Node.js through the Windows firewall when asked
     (needed for stream mode).
   - Mac: double-click `start-bridge.command`. If macOS refuses to open it, right-click → *Open* once.
   - Linux / terminal: `./start-bridge.sh`
   
   The window must say `Connected to UR robot ...`. Leave it open while you work.

You can also start it with options, e.g. `start-bridge.command --robot-ip 192.168.0.5 --dry-run`.

## Prepare the robot

- Connect the PC and the robot controller with an Ethernet cable (or the same network) and give the PC
  an address in the robot's subnet, e.g. robot `192.168.1.10`, PC `192.168.1.100 / 255.255.255.0`.
- Power on the robot and release the brakes (robot mode **RUNNING**).
- **UR10e (e-Series):** switch the pendant to **Remote Control** (top right: Local → Remote; enable it
  first under *Settings → System → Remote Control*). In Local mode the controller refuses external
  scripts on ports 30001-30003.
- The CB3 UR3 and UR5 need nothing extra.

## Use in BRW

1. Open https://www.babacad.com/robotics/web, pick the **UR3**, **UR5** or **UR10e** model (the one
   matching your robot). With several arms in the scene, click the one to send so it is the active arm.
2. *Import / Export* → select the folder containing `brw-plugin.js` (folder `brw-plugin` in this package).
3. Build the posture queue and press **Play full** once, so BRW computes the trajectory. Check it on screen.
4. Press **User Command 3** first: it shows whether the bridge sees the robot, and the robot's actual
   joint angles. Compare them with BRW's joint read-out (see *Joint convention* below).
5. Press **User Command 1**. The robot moves slowly to the trajectory's start pose, then plays it.

Chrome and Edge (version 142+) ask once for **"local network access"** for babacad.com the first time
the plugin connects to `127.0.0.1` - choose *Allow*. If the prompt was dismissed, re-enable it with the
site-settings icon in the address bar.

### Where the trajectory starts

`getRobotTrajData()` returns steps (deltas), so the plugin reconstructs the start pose:
a *Play full* run ends at posture 1 (the queue closes the loop), so start = posture 1 − Σ steps, which
is verified to be a queued posture. For other trajectories (a single move, a plugin's
`simulateRobotMotion`) the end is taken to be the arm's current simulated pose - so do not move the
simulated arm between playing such a trajectory and sending it.

### Joint convention and limits

BRW converts every joint angle of the plugin API to the real robot's convention with the model's
mapping, `real = sign · sim + offset` (`.rob` columns 14/15), so the plugin sends the angles unchanged.
The mappings of the three models (`.rob` files in the robot library; `robot_data/UR3.txt`, `UR5.txt`,
`UR10e.txt`):

| Joint | J1 | J2 | J3 | J4 | J5 | J6 |
|-------|----|----|----|----|----|----|
| offset (all three) | 0° | −90° | −90° | 0° | 90° | 0° |
| sign UR3 | 1 | −1 | −1 | −1 | 1 | 1 |
| sign UR5, UR10e | 1 | 1 | 1 | 1 | 1 | 1 |

For all three, BRW's home pose is the UR pose `0, −90, −90, 0, 90, 0°`. The UR3's joints 2–4 have
sign −1 because its `.rob` rows use the +y axis, while the UR5 and UR10e rows use −y. User Command 1
reads the arm's mapping with `getJointAnglesMapping()` and sends nothing if it differs from its
model's row (for example after `setJointAnglesMapping()`); set `CONFIG.allowOtherMapping = true` in
the plugin to send anyway.

**Limits:** the plugin sends `getRobotJointLimits()` (±360° on every joint for all three models;
wrist 3 is unlimited on the CB3 UR3, but ±360° is used). The bridge enforces those limits, narrowed by its own
(`jointLimitsDeg` in `config.json`, default ±360°).

**Speeds:** `maxJointSpeed` from `config.json` applies, but never above the model's datasheet values in
the table at the top.

**Right robot:** before moving, the bridge asks the controller for its model (dashboard command
`get robot model`, which answers e.g. `UR10` for a UR10e). If the size differs from the BRW model, it
refuses. Older CB3 software does not know that command; set `"robotModel"` in `config.json` (e.g.
`"UR5"`) so the bridge accepts only trajectories for that model.

**Full turns:** BRW wraps its simulator angles to ±180° before mapping, so a joint that can turn ±360°
can be reported one full turn away from the real joint. For example, J2 = −260° in BRW is the same
pose as +100° on the robot. For each joint, the bridge shifts the whole trajectory by the multiple of
360° that puts its start closest to the robot's actual angle, as long as the path stays inside the
limits. It logs every joint it shifts. Without that, the approach `movej` would turn the joint a full
revolution.

To check the model against the robot once, put the real robot in a pose, press User Command 3, and
set the same angles in BRW's Joint Control. The arm must look the same.

## Configuration (`app/config.json`)

| Key | Default | Meaning |
|-----|---------|---------|
| `robotIp` | `192.168.1.10` | robot controller address |
| `robotModel` | `""` | `UR3`, `UR5` or `UR10e`: accept only trajectories for this model. Recommended for CB3 controllers, which may not report their model |
| `wsPort` | `8765` | WebSocket port for BRW (plugin `CONFIG.bridgeUrl` must match) |
| `allowedOrigins` | babacad.com, localhost | web pages allowed to connect; others are rejected so no other site can move the robot |
| `mode` | `auto` | `script`: all points inside the program. `stream`: robot reads points from the PC over TCP. `auto`: script up to `scriptModeMaxPoints`, stream above |
| `scriptModeMaxPoints` | `3000` | ~50 s at 60 Hz; larger programs compile slowly on the controller |
| `streamPort` | `30020` | port the robot connects back to (stream mode). Must be open in the PC firewall |
| `pcIp` | `""` | the PC address the robot connects to; empty = detected from the robot connection |
| `speedScale` | `1.0` | 0.5 = half the simulation speed, 1.0 = same speed |
| `maxJointSpeed` | `[1,1,1,1.5,1.5,1.5]` rad/s | the trajectory is slowed down automatically so no joint goes faster; capped at the model's datasheet speed |
| `jointLimitsDeg` | ±360 per joint | optional `[[min,max], ...]` in degrees; the bridge's own limits, BRW's model limits apply as well |
| `maxJointStep` | `0.25` rad | a larger jump between two samples is rejected (IK configuration flip) |
| `approachSpeed` / `approachAcc` | `0.25` rad/s / `0.5` rad/s² | `movej` to the start pose |
| `maxApproachDistanceDeg` | `180` | refuse to start if the robot is further than this from the start pose |
| `servoLookahead` / `servoGain` | `0.1` / `300` | `servoj` smoothing and stiffness |
| `stopDecel` | `2.0` rad/s² | `stopj` deceleration |
| `dryRun` | `false` | only write the URScript to `app/out/last-program.script`, send nothing |
| `skipPreflight` | `false` | skip the dashboard checks (not recommended) |

Every generated program is saved in `app/out/last-program.script` - open it to see exactly what the
robot receives.

## Plugin ↔ bridge protocol

JSON text messages on `ws://127.0.0.1:8765`:

| from plugin | replies |
|-------------|---------|
| `{type:"status", id}` | `{type:"status", id, version, busy, dryRun, robot:{connected, ip, robotMode, safetyMode, q}}` |
| `{type:"run", id, robot:{model, configType, robotIdx}, dt, points:[[q1..q6], ...]}` | `accepted`, then `progress` `{message}` / `{phase:"moving", elapsed, total, q}`, then `done` `{message, duration}` or `error` `{message, cancelled}` |
| `{type:"stop", id}` | `{type:"stopped", id}` |

`points` are absolute joint angles in rad, first point = start pose, `dt` = seconds per point.

## Testing without a robot

- **Fake controller** (included, checks the communication only):
  ```
  node app/tools/fake-ur.js                      # terminal 1
  node app/server.js --robot-ip 127.0.0.1        # terminal 2
  node app/tools/test-plugin.js 1 240 UR5        # terminal 3: runs brw-plugin.js with a mocked brwAPI (UR3 | UR5 | UR10e)
  ```
- **URSim**, Universal Robots' offline simulator (free download from universal-robots.com, or the
  `universalrobots/ursim_cb3` / `ursim_e-series` Docker images) behaves like the real controller; point
  `robotIp` at it.
- `--dry-run` generates the program without sending it.

## Troubleshooting

| Message | Fix |
|---------|-----|
| *cannot reach the BRW UR Bridge* | the bridge is not running, or the browser blocked local network access (see above). Safari may block `ws://` from an https page; use Chrome, Edge or Firefox |
| *robot ... not reachable* | wrong `robotIp`, cable, or PC not in the robot's subnet (`ping` the robot) |
| *robot is in LOCAL mode* | e-Series: switch the pendant to Remote Control |
| *the connected robot is a UR10, the BRW trajectory is for a UR5* | pick the BRW model that matches the robot, or connect the right robot |
| *the active arm ... supported are UR3, UR5, UR10e* | select (click) the UR arm in BRW so it is the active arm |
| *robot is not running* | power on and release the brakes on the pendant |
| *did not connect back* (stream mode) | allow Node.js in the firewall for port 30020, or set `pcIp` |
| *the robot is ... away from the trajectory start* | jog the robot near the start pose, or the joint convention does not match |
| *trajectory rejected: ... jumps* | the IK solution flips between two samples - change the postures in BRW |
| *the robot stopped before the end* | protective stop or script error; read the log on the pendant |

## Building the zips (developers)

```
build/package.sh          # downloads the latest Node.js 24 from nodejs.org, verifies SHA-256, writes dist/*.zip
PLATS=no-node build/package.sh
```

## Files

```
brw-plugin/brw-plugin.js   the BRW plugin (User Commands 1-3)
app/server.js              bridge entry point
app/lib/ws-server.js       WebSocket server (no npm dependencies)
app/lib/ur-client.js       UR dashboard / real-time / URScript connections
app/lib/trajectory.js      validation and timing
app/lib/models.js          supported models, datasheet speeds and limits
app/lib/urscript.js        program generation
app/tools/fake-ur.js       fake controller for tests
app/tools/test-plugin.js   runs the plugin in Node with a mocked brwAPI
start-bridge.bat / .command / .sh   launchers (use ./node when bundled)
build/package.sh           creates the release zips
```
