trossen_arm_sim
Runs trossen_arm in simulation. The server speaks the wire protocol of the WidowX AI (wxai_v0) arm controller (firmware v1.9 and v1.10), so the unmodified trossen_arm driver connects to it as if it were real hardware — the arm runs in MuJoCo instead.
Install
pip install trossen_arm_sim
Clients need trossen_arm 1.9 or 1.10. The driver rejects firmware whose major.minor version differs from its own, so the simulator reports the version of the trossen_arm installed alongside it. To serve clients running the other version, pass --driver-version.
Usage
trossen-arm-sim # default scene: arm on a ground plane
trossen-arm-sim scene.xml # custom scene
mjpython -m trossen_arm_sim --viewer # MuJoCo viewer (macOS needs mjpython)
trossen-arm-sim --driver-version 1.9 # clients running trossen_arm 1.9
Point any trossen_arm client at 127.0.0.1 instead of the robot's IP:
driver.configure(
trossen_arm.Model.wxai_v0,
trossen_arm.StandardEndEffector.wxai_v0_follower,
"127.0.0.1",
False,
)
A custom scene is MJCF that includes the arm via the WIDOWX_XML placeholder, replaced with the installed arm model path at load time (TROSSEN_ARM_ASSET_DIR likewise for the packaged asset directory):
<mujoco>
<include file="WIDOWX_XML"/>
<worldbody>
<light pos="0 0 1.5" dir="0 0 -1" directional="true"/>
<geom name="floor" size="0 0 0.05" type="plane" rgba="0.5 0.5 0.5 1"/>
</worldbody>
</mujoco>
Example
Numpad scene plus a client that presses its keys and prints every collision the simulator reports (see Collision stream below):
trossen-arm-sim examples/scene_numpad.xml # terminal 1
python examples/press_button.py 1 2 3 enter # terminal 2
Collision stream
The simulator streams contacts between geoms over a WebSocket, so a client can tell when the arm touches something, what it touched, and how hard.
Connecting
Connect to ws://<host>:50002 (--ws-port changes the port). The server sends JSON text messages and ignores anything the client sends. Any WebSocket library works; with websockets:
import json
from websockets.sync.client import connect
with connect("ws://127.0.0.1:50002") as ws:
for message in ws:
event = json.loads(message)
print(event["type"], event.get("geom1"), event.get("geom2"))
Messages
type |
When | Content |
|---|---|---|
snapshot |
Once, right after connecting | time and contacts, the list of contact pairs active at that moment (each with the fields below except type, time, duration, peak_force) |
begin |
A pair of geoms starts touching | The contact fields; duration is 0 and peak_force equals force |
end |
The pair has not touched for 20 ms | The contact as last seen; duration and peak_force cover the whole contact |
{"type": "snapshot", "time": 3.2, "contacts": []}
{"type": "begin", "time": 10.736, "geom1": "carriage_right", "body1": "carriage_right", "geom2": "key_5", "body2": "numpad", "force": 0.78, "pos": [0.39, 0.296, 0.026], "duration": 0.0, "peak_force": 0.78}
{"type": "end", "time": 11.404, "geom1": "carriage_right", "body1": "carriage_right", "geom2": "key_5", "body2": "numpad", "force": 0.0, "pos": [0.39, 0.296, 0.026], "duration": 0.65, "peak_force": 0.78}
{"type": "begin", "time": 15.1, "geom1": "link_6", "body1": "link_6", "geom2": "floor", "body2": "world", "force": 80.2, "pos": [0.378, -0.038, 0.0], "duration": 0.0, "peak_force": 80.2}
| Field | Unit | Meaning |
|---|---|---|
time |
s | Simulation time of the message |
geom1, geom2 |
The two touching geoms (see naming below) | |
body1, body2 |
The bodies those geoms belong to; world for the floor |
|
force |
N | Total normal force between the pair at the time of the message |
peak_force |
N | Largest force seen since the pair started touching |
pos |
m | Mean position of the contact points, world frame |
duration |
s | How long the pair has been touching |
Naming
A contact pair is identified by two geom labels. A named geom is labelled with its name. An unnamed geom is labelled with its body's name, so all the unnamed collision geoms of one body aggregate into one pair. An unnamed body is labelled body<id>, and a deformable flex with its name. Name the geoms you want to tell apart, as scene_numpad.xml does with key_*.
When the arm is involved, it is always side 1: geom1/body1 is one of base_link, link_1 to link_6, carriage_left or carriage_right (the gripper fingers). Contacts between two arm parts, and between scene objects that do not involve the arm, are streamed as well, in MuJoCo's order.
Filtering on the client
Everything MuJoCo resolves is streamed, including the two gripper fingers touching each other whenever the gripper closes and objects resting on the floor. The client decides what matters:
- By name:
geom1starting withcarriage_andgeom2starting withkey_is a key press;geom2 == "floor"is a crash; a key touched bylink_2is a crash too, so check both sides. - By force: a press and a smash on the same key differ only in
forceandpeak_force. Pressing a numpad key reads about 1 N; a finger driven into the floor reads hundreds of N.
examples/watch_collisions.py does exactly this: it prints the stream on a background thread, labelling each event PRESS or CRASH (--all prints every pair unfiltered), while the prompt drives the arm through the trossen_arm driver. wave swings the arm clear of everything and prints nothing; crash drives the gripper into the floor and prints CRASH events with the impact force; quit returns home.
python examples/watch_collisions.py
Publish
Bump version in pyproject.toml, then:
pip install build twine
python -m build
twine upload dist/*
Attributions
The arm model and meshes in trossen_arm_sim/assets/ are from trossen_arm_mujoco, Copyright (c) 2025 Trossen Robotics, licensed under BSD-3-Clause.
Metadata
Release files for trossen_arm_sim 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| trossen_arm_sim-0.2.2.tar.gz | 782.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| trossen_arm_sim-0.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.6 MB
Release files / trossen_arm_sim-0.2.2.tar.gz
| Download URL | trossen_arm_sim-0.2.2.tar.gz |
|---|---|
| Size | 782.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d86a228a2de4bd0899ec1578f42ba5aa6947d6fa4d882400a167fd0e8e990810
|
|
BLAKE2b-256 checksum How to use checksums |
2f95fe50e799dbd8ae55b94b8272d6073a8aadac7a44f81d3c71e8f3ea92008b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.8.18
|
Release files / trossen_arm_sim-0.2.2-py3-none-any.whl
| Download URL | trossen_arm_sim-0.2.2-py3-none-any.whl |
|---|---|
| Size | 780.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
31224eaa698eb3b01bc5b930d9607b929e247d9756529a2ce9df967fb4aec91a
|
|
BLAKE2b-256 checksum How to use checksums |
843aea9e73b257d1439db06dea6fed8a8afcd6c995af7bcbbf63109d2d3f60c6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.8.18
|