Metadata-Version: 2.5
Name: bsln
Version: 0.1.1
Summary: Baseline Robotics client: Python SDK and command line to drive remote robot stations
Author: Baseline Robotics
License-Expression: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: msgpack>=1.0
Requires-Dist: numpy>=1.26
Requires-Dist: pydantic>=2.7
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.7
Requires-Dist: typer>=0.12
Requires-Dist: websockets>=15.0
Provides-Extra: viz
Requires-Dist: rerun-sdk>=0.32.2; extra == 'viz'
Description-Content-Type: text/markdown

# bsln

Python SDK and command line for Baseline Robotics robot stations. Drive a real robot station from your own machine:
stream joint targets from your script, get a report of each session and download the recordings.

This package contains only the client. It does not contain the server, the station agent or robot code.

This page shows how to control a Baseline Robotics robot station from your computer with Python.
You install the `bsln` client, connect with your token, and run a short script on a bimanual YAM station.
The script turns one wrist joint by 0.1 rad (about 6 degrees) and then turns it back.

## 1. Install

You need Python 3.10 or newer. We recommend a virtual environment.

```bash
python3 -m venv .venv
```

```bash
source .venv/bin/activate
```

```bash
pip install bsln
```

## 2. Get access

Baseline Robotics gives you a bsln token. The token starts with `bsln_`.
Keep the token secret. Do not put it in your code or in a Git repository.

Use one of these two methods to connect.

**Method A: log in once.** The command asks for your token.
It saves the URL and the token in `~/.config/bsln/config.json`. Only your user can read this file.

```bash
bsln login --url https://api.baseline-robotics.com
```

**Method B: environment variables.** Set them in each terminal.

```bash
export BSLN_URL=https://api.baseline-robotics.com
```

```bash
export BSLN_TOKEN=<your token>
```

Make sure that the connection works. The command shows your user name and your scopes.

```bash
bsln whoami
```

**Extra tokens.** You can make more tokens for other computers or scripts.
This command makes a token for 30 days and shows it one time. Keep it safe.

```bash
python -c "from bsln.sdk import Client; print(Client().create_token(['read', 'write', 'control'], name='second-computer', ttl_days=30)['token'])"
```

An extra token cannot have more scopes than your token. It expires before your token or at the same time.
To stop a token before it expires, contact Baseline Robotics.

## 3. Find a station

```bash
bsln stations
```

The list shows each station and its `status`. You can start a session only on a station with the status `idle`.
The other values are `busy` (another session is active), `fault` (the station is stopped) and `offline`.

## 4. Run your first session

Save this script as `quickstart.py`.

```python
"""bsln quickstart: start a session, turn the left wrist a little, stream targets, end the session.

    export BSLN_URL=https://api.baseline-robotics.com BSLN_TOKEN=<your bsln token>
    python quickstart.py <station id>
"""

import sys
import time

from bsln.sdk import Client

STATION = sys.argv[1]
WRIST = "left_joint6"  # the left wrist; units: rad
TURN = 0.1  # rad (about 6 degrees)

client = Client()  # uses BSLN_URL and BSLN_TOKEN, or the values from `bsln login`
station = client.station_description(STATION)
print("joints:", station["joint_order"])
print("control rate:", station["control_hz"], "Hz")
high = next(joint["safe_range"][1] for joint in station["joints"] if joint["name"] == WRIST)  # in rad

with client.start_session(STATION) as session:
    print("session:", session.id)
    start = session.get_state()["target"][WRIST]  # where the station holds the wrist now
    turn = TURN if start + TURN <= high else -TURN  # stay in the safe range

    # 1. Send one target. The station keeps it. The safety supervisor limits the speed.
    session.set_joints({WRIST: start + turn})
    time.sleep(2.0)

    # 2. Stream targets at the control rate for 3 s. The wrist goes back to its start position in 2 s.
    #    A streamed target expires after 0.25 s. Then the arm holds its position.
    period = 1.0 / station["control_hz"]
    begin = time.monotonic()
    while (elapsed := time.monotonic() - begin) < 3.0:
        done = min(1.0, elapsed / 2.0)  # 0 at the start, 1 after 2 s
        session.send_targets({WRIST: start + turn * (1.0 - done)})
        time.sleep(period)

    print("wrist now:", round(session.get_joints()[WRIST], 3), "rad")
    usage = session.end()  # stop the session. The arms hold their position.

print("usage:", usage)
print("report: bsln session report", session.id)
```

Run the script. Replace `<station id>` with an `id` from `bsln stations`.

```bash
python quickstart.py <station id>
```

The script does these steps:

1. It reads the station description: the joint names, the units, the safe range of each joint and the control rate.
2. It starts a session. Only you can control the station during your session.
3. It sends one target with `set_joints`. The station keeps this target until you send a new one.
4. It streams targets with `send_targets` for 3 s. A streamed target expires after 0.25 s.
5. It ends the session. The arms hold their position.

The script prints the session ID. You need it for the report and for the data.

## 5. Stop

To stop a session, end it in your script with `session.end()`. You can also use this command:

```bash
bsln session stop <session id>
```

To find the ID of a session, use this command:

```bash
bsln session ls
```

If your script stops or loses the connection, the arms hold their position.
If your script does not connect again in 30 s, bsln ends the session.

## 6. Emergency stop (e-stop)

Use the e-stop when the robot does something that you do not expect. You can send it at any time.
In your script, use `session.estop()`. From a terminal, use one of these commands:

```bash
bsln estop <station id>
```

```bash
bsln session estop <session id>
```

After an e-stop:

- The arms stop and hold their position. The motors stay on.
- Your session ends.
- The station gets the status `fault`. You cannot start a new session on it.
- Only Baseline Robotics can clear the fault. Contact us (see section 9).

## 7. Get the session report and your data

The report is ready after the session ends.
It shows the commands that you sent, the holds, the safety adjustments, the faults, the e-stops and the connection quality.

```bash
bsln session report <session id>
```

This command downloads the recorded data to the folder `./data`. The data includes the report as `report.json`.

```bash
bsln session download <session id> --out ./data
```

## 8. Limits

| Item | Value |
|---|---|
| Joints | 14: `left_joint1` to `left_joint6`, `left_gripper`, `right_joint1` to `right_joint6`, `right_gripper` |
| Units | Arm joints: rad. Do not send degrees. Grippers: 0 (closed) to 1 (open). |
| Control rate | 30 Hz. Send one command for each control step. |
| Messages | 60 messages per second at most. bsln drops more messages and sends you a notice. |
| Deadman | 0.25 s. If no streamed command arrives in this time, the arm holds its position. |
| Safety supervisor | It moves each target into the safe range and limits its speed. It never makes a target larger or faster. |
| Session start | Joints 2 and 3 and the grippers can move a little into the safe range: 0.02 rad or 3 % at most. |
| Faults | A fault stops the station. It stays stopped until Baseline Robotics clears the fault. |
| E-stop | Always permitted. |

The station description (`client.station_description(station_id)`) gives the exact values for each station.

## 9. Get help

Contact Baseline Robotics. Tell us:

- the station ID and the session ID,
- the time of the problem (with your time zone),
- the full error message.

Each error message from bsln includes a request ID, for example `error 404: session not found (request ID 3f2a...)`.
Include this request ID. With it, we can find your request in our logs.

---

Copyright 2026 Baseline Robotics AG. License: Apache-2.0 (see LICENSE and NOTICE).
