rosmac
ROS 2 on your Mac in one command — native rclpy/colcon on macOS, Tier-1 Ubuntu for the heavy stuff, one TCP port between them.
A user's own Franka workspace: colcon build on macOS (RoboStack, no Docker), then
ros2 launch with RViz on the Mac. ▶ full clip ·
▶ Mac↔VM bridge (teleop over one TCP port)
ROS 2 does not really support macOS (Tier 3; Apple Silicon isn't even listed). Existing
workarounds each hit a wall: Docker on macOS has no --network=host, so host↔container DDS
discovery is broken by design; plain VMs give up on Mac-side tooling entirely; and while
RoboStack ships surprisingly many osx-arm64 packages, the heavy stack (MoveIt, Gazebo) is
present but not dependable there — dylib breakage and runtime crashes are field-measured, not
hypothetical.
rosmac splits the problem instead of fighting it:
develop natively on the Mac (RoboStack: rclpy, colcon, ros2 CLI)
↕ zenoh bridge — a single TCP port (7447), no DDS multicast at the boundary
run the heavy stack where it's Tier 1 (Lima VM, Ubuntu 22.04 arm64: MoveIt, Gazebo)
→ visualize on the Mac (Foxglove, ws:8765)
⇢ optional: a real robot on your LAN — one more TCP endpoint, same model
(beta, [docs/robot-setup.md](docs/robot-setup.md))
What makes it more than an install script:
rosmac doctor— 16 checks for known failure modes,--fixauto-repairs the safe ones (hung ros2 daemon, orphan bridges, broken lima port rules). Built from a database of 29 field-measured macOS/ROS pitfalls, not a wiki of hope.rosmac deps— maps your workspace'spackage.xmldependencies to RoboStack conda packages (arosdepsubstitute that actually knows about conda).rosmac push --build— anything Linux-only gets copied to the VM and built there.rosmac report— one tar.gz diagnostic bundle for issue reports (collects only from~/.rosmac).
Support matrix
| Item | Supported | Notes |
|---|---|---|
| Hardware | Apple Silicon (M1+) | Intel Macs unsupported (no way to verify) |
| OS | macOS 14 (Sonoma)+ | measured on macOS 26.x / M3 Pro / 18 GB |
| Python | 3.11+ | measured on 3.12 |
| ROS 2 | Humble (rmw_cyclonedds_cpp pinned on both sides) | VM: Ubuntu 22.04 arm64 |
Versioning: SemVer. While 0.y.z, minor versions may break. See CHANGELOG.md.
Requirements
- Homebrew, ≥ 40 GB free disk
- Foxglove app (optional, for visualization)
Quickstart (~6 min measured; +10 min without download caches)
brew install lima micromamba
git clone https://github.com/PythonToGo/rosmac && cd rosmac
python3.12 -m venv .venv && .venv/bin/pip install -e .
export PATH="$PWD/.venv/bin:$PATH"
rosmac init # conda env + bridge binary + VM provisioning (idempotent)
rosmac up # start VM + both zenoh bridges
rosmac doctor # 16 checks — C8 self-verifies a full topic round-trip
Smoke test:
rosmac shell --vm -c 'nohup ros2 run demo_nodes_cpp talker >/dev/null 2>&1 & echo ok'
rosmac shell -c 'ros2 topic echo /chatter --once' # VM topic received on the Mac
Simulation presets
rosmac sim panda-moveit # MoveIt (Panda arm) — /move_action usable from the Mac
rosmac sim gazebo-diffbot # Gazebo Fortress headless + front camera
rosmac sim nav2-diffbot # Nav2 mobile navigation — /navigate_to_pose from the Mac
rosmac sim list / status / stop / --attach
rosmac viz --layout nav2 # Foxglove connection (+ layout import guide)
nav2-diffbot runs SLAM + Nav2 on a lidar diffbot in a walled arena; drive it
with /cmd_vel to build the map, then send /navigate_to_pose goals from the Mac.
The full Nav2 stack works over the default bridge — rosmac sim resets the bridge
session on start so a new stack gets fresh routes (KI-17).
The native dev loop and a worked example (pick_demo) live in docs/workflow.md.
Bring your own workspace
rosmac deps ~/my_ws --install # package.xml deps → RoboStack packages (rosdep substitute)
rosmac shell # colcon build inside — legacy-CMake pitfalls auto-bypassed
rosmac ps # stuck? Mac+VM processes & publishers on one screen
rosmac push ~/my_ws --build # Linux-only packages (libfranka, …) build in the VM
Commands
| Command | What it does |
|---|---|
rosmac init |
deps / conda env / bridge / VM provisioning (idempotent, skips existing) |
rosmac up / down / status |
start/stop/inspect the stack (--keep-vm, --viz) |
rosmac doctor |
16 checks + remedies (--json, --fix auto-repairs safe items) |
rosmac shell |
subshell with the ROS env injected (--vm, -c) — colcon defaults included |
rosmac deps <ws> |
check/install package.xml dependencies (--install, --json) |
rosmac ps |
Mac+VM ROS processes & core-topic publishers (--json) |
rosmac push <ws> |
copy a workspace into the VM (+--build) — for Linux-only packages |
rosmac sim <preset> |
start a sim preset in the VM (tmux) + health gate |
rosmac viz |
start foxglove_bridge + app deep link |
rosmac report |
diagnostic bundle for issues (never collects outside ~/.rosmac) |
rosmac uninstall |
remove everything rosmac created (conda env, VM, ~/.rosmac) |
Exit codes:
| code | meaning | examples |
|---|---|---|
| 0 | success | |
| 1 | execution failure (environment/state) | VM not running, conda env missing, bridge/build failure |
| 2 | usage/config error (fix your input) | unknown preset/layout, workspace without src/, broken config.yaml |
Errors are shown as a cause + fix panel; only unexpected errors show a traceback
(attach a rosmac report bundle when filing those).
Measured performance (M3 Pro, 2026-07)
- Bridge throughput: 10.3 MB/s (1 MB @ 10 Hz, no drops)
- MoveGroup action round-trip: plan+execute, 3 consecutive goals SUCCEEDED
- Nav2
/navigate_to_posefrom the Mac: 3 consecutive goals SUCCEEDED (default bridge) - Gazebo Fortress headless RTF: physics-only 1.00 / with camera (320×240 @ 15 Hz) 0.99
- Camera stream: VM 14.4 fps → Mac 14.4 fps (lossless)
Bridge capability matrix (measured 2026-07)
What works across the Mac ↔ VM zenoh bridge:
| ROS 2 feature | Status | Measured evidence / notes |
|---|---|---|
| Topics | ✅ | pub/sub both directions; 10.3 MB/s @ 10 Hz no drops. First subscription to a new topic takes a few seconds (bridge route creation) |
| Services | ✅ | requires the pinned CycloneDDS RMW — with Fast DDS, discovery looks fine but every call times out (KI-16; why rosmac pins the RMW) |
| Actions | ✅ | MoveGroup plan+execute, 3/3 goals SUCCEEDED; Nav2 /navigate_to_pose 3/3 SUCCEEDED from the Mac (full stack, default bridge) |
| Parameters | ⚠️ partial | raw parameter services (get/set_parameters, …) work via ros2 service call; the ros2 param CLI does not — the bridge doesn't mirror remote nodes into the node graph, so ros2 node list won't show VM nodes |
| rosbag2 | ✅ | record on Mac of VM topics (no loss), record in VM, play from either side reaches the other. Retrieve VM bags with limactl cp -r rosmac:/path ~/dest (D16) — see docs/workflow.md |
| Robot link (LAN) | 🧪 beta | robot: config → Mac bridge adds a TCP endpoint to a robot-side bridge (D15). Topics/services measured against a surrogate robot (2nd VM): 10 MB/s @ 10 Hz no drops, service RTT < 1 ms, auto-reconnect on robot restart. Surrogate-verified — real-hardware/WiFi numbers pending (E.15 R5). Setup: docs/robot-setup.md. Trusted LAN only — plaintext TCP, no auth/TLS |
Structural limits (by design, not bugs):
- A stale bridge silently breaks a fresh stack (KI-17). Restarting the VM
sim stack while the bridge keeps running leaves stale routes behind, so the new
stack's action sub-services fail to discover from the Mac (measured: 0/6, then
4/4 after a bridge restart).
rosmac simresets the bridge session on start to avoid this — the full Nav2 stack then works over the default bridge, no scoping. - Every Mac↔VM message crosses one bridge hop. Fine for dev, teleop and visualization; high-rate closed control loops belong inside the VM (or on the robot).
- macOS-local DDS discovery can be silently degraded by other lima VMs that lack UDP ignore rules (KI-28). rosmac's own VM ships the rules; see known-issues KI-28 for the remedy.
- The VM is headless (D2) — no RViz2/GUI inside; Foxglove on the Mac is
the visualization path (
rosmac viz).
Architecture & design decisions
- Why not just use Docker / a full VM / RoboStack alone: docs/why-rosmac.md
- Decision log and risk register: PLAN.md
- When stuck: docs/troubleshooting.md (common failure modes + fix commands), backed by the field-measured known-issues database (30 pitfalls)
Contributing
Bug reports (with a rosmac report bundle), pitfall contributions, and PRs
within scope are welcome — see CONTRIBUTING.md and the
Code of Conduct. Security issues: SECURITY.md.
License
MIT. Bundled simulation presets are adapted from Apache-2.0 / BSD-3-Clause upstream ROS and Gazebo examples — see THIRD-PARTY-NOTICES.md.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rosmac-0.1.0.tar.gz.
File metadata
- Download URL: rosmac-0.1.0.tar.gz
- Upload date:
- Size: 6.6 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b18ffcd4cae3617e1f153c3ed0da160cf271712603c0afd43804ea842e5ef688
|
|
| MD5 |
35d62735823c50a590493f233af78d9c
|
|
| BLAKE2b-256 |
0223ed05b5df35b83a2e4874dd4eeb8a7ce9bb2a7d6cc0b153b63f9b3a27c8d8
|
Provenance
The following attestation bundles were made for rosmac-0.1.0.tar.gz:
Publisher:
release.yml on PythonToGo/rosmac
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rosmac-0.1.0.tar.gz -
Subject digest:
b18ffcd4cae3617e1f153c3ed0da160cf271712603c0afd43804ea842e5ef688 - Sigstore transparency entry: 2684926030
- Sigstore integration time:
-
Permalink:
PythonToGo/rosmac@fbd2eeac680e10de3fb3750906c13a46e4c9f320 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/PythonToGo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fbd2eeac680e10de3fb3750906c13a46e4c9f320 -
Trigger Event:
release
-
Statement type:
File details
Details for the file rosmac-0.1.0-py3-none-any.whl.
File metadata
- Download URL: rosmac-0.1.0-py3-none-any.whl
- Upload date:
- Size: 77.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
348d77bf3a9ac228c35026e043237143ca326a3e34edb108d93f9d07a9a30027
|
|
| MD5 |
3e434a4f0985a61369722cee85390cd6
|
|
| BLAKE2b-256 |
8302348733ec016527997c38454edac4a634139ca4f923fc2e18eeff117142ff
|
Provenance
The following attestation bundles were made for rosmac-0.1.0-py3-none-any.whl:
Publisher:
release.yml on PythonToGo/rosmac
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rosmac-0.1.0-py3-none-any.whl -
Subject digest:
348d77bf3a9ac228c35026e043237143ca326a3e34edb108d93f9d07a9a30027 - Sigstore transparency entry: 2684926259
- Sigstore integration time:
-
Permalink:
PythonToGo/rosmac@fbd2eeac680e10de3fb3750906c13a46e4c9f320 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/PythonToGo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fbd2eeac680e10de3fb3750906c13a46e4c9f320 -
Trigger Event:
release
-
Statement type: