Skip to content

Commit eb65e37

Browse files
Merge pull request #731 from dimensionalOS/pip-install
# New stuff * I've copied rxpy_backpressure in since it's so small. * I've tested the release of dimos-lcm to test PyPI. * Replaced our go2-webrtc-connect clone with the original. * I'm currently cloning the `dev` repo in order to get LFS files when using `pip install dimos`. I need to switch to `main` when we release. * Added fresh instructions in README_installation.md . --- # Old stuff Now you can: ```bash pip install dimos dimos-robot --robot-ip 192.168.x.xxx run unitree-go2 ``` or with `uv`: ```bash uv add dimos uv run dimos-robot --robot-ip 192.168.x.xxx run unitree-go2 ``` or even better, no need to install: ```bash uvx --from dimos dimos-robot --robot-ip=192.168.x.xxx run unitree-go2 ``` Or even better, rename `dimos-robot` to `dimos` (#740) and it becomes just: ```bash uvx dimos --robot-ip=192.168.x.xxx run unitree-go2 ``` Former-commit-id: 0802899 [formerly 647b7db] Former-commit-id: d446553
1 parent 0efdfbc commit eb65e37

File tree

20 files changed

+580
-40
lines changed

20 files changed

+580
-40
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repos:
99
- id: remove-crlf
1010
- id: insert-license
1111
files: \.py$
12-
exclude: __init__\.py$
12+
exclude: (__init__\.py$)|(dimos/rxpy_backpressure/)
1313
args:
1414
# use if you want to remove licences from all files
1515
# (for globally changing wording or something)

MANIFEST.in

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
global-exclude *.pyc
2+
global-exclude __pycache__
3+
global-exclude .DS_Store
4+
5+
# Exclude test directories
6+
prune tests
7+
8+
# Exclude web development directories
9+
recursive-exclude dimos/web/command-center-extension *
10+
recursive-exclude dimos/web/websocket_vis/node_modules *
11+
12+
# Exclude development files
13+
exclude .gitignore
14+
exclude .gitattributes
15+
prune .git
16+
prune .github
17+
prune .mypy_cache
18+
prune .pytest_cache
19+
prune .ruff_cache
20+
prune .vscode

README_installation.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# DimOS
2+
3+
## Installation
4+
5+
Clone the repo:
6+
7+
```bash
8+
git clone -b main --single-branch git@github.com:dimensionalOS/dimos.git
9+
cd dimos
10+
```
11+
12+
### System dependencies
13+
14+
Tested on Ubuntu 22.04/24.04.
15+
16+
```bash
17+
sudo apt update
18+
sudo apt install git-lfs python3-venv python3-pyaudio portaudio19-dev libturbojpeg0-dev
19+
```
20+
21+
### Python dependencies
22+
23+
Install `uv` by [following their instructions](https://docs.astral.sh/uv/getting-started/installation/) or just run:
24+
25+
```bash
26+
curl -LsSf https://astral.sh/uv/install.sh | sh
27+
```
28+
29+
Install Python dependencies:
30+
31+
```bash
32+
uv sync
33+
```
34+
35+
Depending on what you want to test you might want to install more optional dependencies as well (recommended):
36+
37+
```bash
38+
uv sync --extra dev --extra cpu --extra sim --extra drone
39+
```
40+
41+
### Install Foxglove Studio (robot visualization and control)
42+
43+
> **Note:** This will be obsolete once we finish our migration to open source [Rerun](https://rerun.io/).
44+
45+
Download and install [Foxglove Studio](https://foxglove.dev/download):
46+
47+
```bash
48+
wget https://get.foxglove.dev/desktop/latest/foxglove-studio-latest-linux-amd64.deb
49+
sudo apt install ./foxglove-studio-*.deb
50+
```
51+
52+
[Register an account](https://app.foxglove.dev/signup) to use it.
53+
54+
Open Foxglove Studio:
55+
56+
```bash
57+
foxglove-studio
58+
```
59+
60+
To connect and load our dashboard:
61+
62+
1. Click on "Open connection"
63+
2. In the popup window, leave the WebSocket URL as `ws://localhost:8765` and click "Open"
64+
3. In the top right, click on the "Default" dropdown, then "Import from file..."
65+
4. Navigate to the `dimos` repo and select `assets/foxglove_dashboards/unitree.json`
66+
67+
### Test the install
68+
69+
Run the Python tests:
70+
71+
```bash
72+
uv run pytest dimos
73+
```
74+
75+
They should all pass in about 3 minutes.
76+
77+
### Test a robot replay
78+
79+
Run the system by playing back recorded data from a robot (the replay data is automatically downloaded via Git LFS):
80+
81+
```bash
82+
uv run dimos --replay run unitree-go2-basic
83+
```
84+
85+
You can visualize the robot data in Foxglove Studio.
86+
87+
### Run a simulation
88+
89+
```bash
90+
uv run dimos --simulation run unitree-go2-basic
91+
```
92+
93+
This will open a MuJoCo simulation window. You can also visualize data in Foxglove.
94+
95+
If you want to also teleoperate the simulated robot run:
96+
97+
```bash
98+
uv run dimos --simulation run unitree-go2-basic --extra-module keyboard_teleop
99+
```
100+
101+
This will also open a Keyboard Teleop window. Focus on the window and use WASD to control the robot.
102+
103+
### Command center
104+
105+
You can also control the robot from the `command-center` extension to Foxglove.
106+
107+
First, pull the LFS file:
108+
109+
```bash
110+
git lfs pull --include="assets/dimensional.command-center-extension-0.0.1.foxe"
111+
```
112+
113+
To install it, drag that file over the Foxglove Studio window. The extension will be installed automatically. Then, click on the "Add panel" icon on the top right and add "command-center".
114+
115+
You can now click on the map to give it a travel goal, or click on "Start Keyboard Control" to teleoperate it.
116+
117+
### Using `dimos` in your code
118+
119+
If you want to use dimos in your own project (not the cloned repo), you can install it as a dependency:
120+
121+
```bash
122+
uv add dimos
123+
```
124+
125+
Note, a few dependencies do not have PyPI packages and need to be installed from their Git repositories. These are only required for specific features:
126+
127+
- **CLIP** and **detectron2**: Required for the Detic open-vocabulary object detector
128+
- **contact_graspnet_pytorch**: Required for robotic grasp prediction
129+
130+
You can install them with:
131+
132+
```bash
133+
uv add git+https://github.com/openai/CLIP.git
134+
uv add git+https://github.com/dimensionalOS/contact_graspnet_pytorch.git
135+
uv add git+https://github.com/facebookresearch/detectron2.git
136+
```

dimos/core/__init__.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,22 +94,21 @@ def deploy( # type: ignore[no-untyped-def]
9494
*args,
9595
**kwargs,
9696
):
97-
console = Console()
98-
with console.status(f"deploying [green]{actor_class.__name__}\n", spinner="arc"):
99-
actor = dask_client.submit( # type: ignore[no-untyped-call]
100-
actor_class,
101-
*args,
102-
**kwargs,
103-
actor=True,
104-
).result()
97+
logger.info("Deploying module.", module=actor_class.__name__)
98+
actor = dask_client.submit( # type: ignore[no-untyped-call]
99+
actor_class,
100+
*args,
101+
**kwargs,
102+
actor=True,
103+
).result()
105104

106-
worker = actor.set_ref(actor).result()
107-
logger.info("Deployed module.", module=actor._cls.__name__, worker_id=worker)
105+
worker = actor.set_ref(actor).result()
106+
logger.info("Deployed module.", module=actor._cls.__name__, worker_id=worker)
108107

109-
# Register actor deployment in shared memory
110-
ActorRegistry.update(str(actor), str(worker))
108+
# Register actor deployment in shared memory
109+
ActorRegistry.update(str(actor), str(worker))
111110

112-
return RPCClient(actor, actor_class)
111+
return RPCClient(actor, actor_class)
113112

114113
def check_worker_memory() -> None:
115114
"""Check memory usage of all workers."""

dimos/protocol/rpc/spec.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ def call(self, name: str, arguments: Args, cb: Callable | None) -> Callable[[],
4646
def call_sync(
4747
self, name: str, arguments: Args, rpc_timeout: float | None = 120.0
4848
) -> tuple[Any, Callable[[], None]]:
49+
if name == "start":
50+
rpc_timeout = 1200.0 # starting modules can take longer
4951
event = threading.Event()
5052

5153
def receive_value(val) -> None: # type: ignore[no-untyped-def]
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019 Mark Haynes
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from dimos.rxpy_backpressure.backpressure import BackPressure
2+
3+
__all__ = [BackPressure]
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright (c) rxpy_backpressure
2+
from dimos.rxpy_backpressure.drop import (
3+
wrap_observer_with_buffer_strategy,
4+
wrap_observer_with_drop_strategy,
5+
)
6+
from dimos.rxpy_backpressure.latest import wrap_observer_with_latest_strategy
7+
8+
9+
class BackPressure:
10+
"""
11+
Latest strategy will remember the next most recent message to process and will call the observer with it when
12+
the observer has finished processing its current message.
13+
"""
14+
15+
LATEST = wrap_observer_with_latest_strategy
16+
17+
"""
18+
Drop strategy accepts a cache size, the strategy will remember the most recent messages and remove older
19+
messages from the cache. The strategy guarantees that the oldest messages in the cache are passed to the
20+
observer first.
21+
:param cache_size: int = 10 is default
22+
"""
23+
DROP = wrap_observer_with_drop_strategy
24+
25+
"""
26+
Buffer strategy has a unbounded cache and will pass all messages to its consumer in the order it received them
27+
beware of Memory leaks due to a build up of messages.
28+
"""
29+
BUFFER = wrap_observer_with_buffer_strategy

dimos/rxpy_backpressure/drop.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright (c) rxpy_backpressure
2+
from typing import Any
3+
4+
from dimos.rxpy_backpressure.function_runner import thread_function_runner
5+
from dimos.rxpy_backpressure.locks import BooleanLock, Lock
6+
from dimos.rxpy_backpressure.observer import Observer
7+
8+
9+
class DropBackPressureStrategy(Observer):
10+
def __init__(self, wrapped_observer: Observer, cache_size: int):
11+
self.wrapped_observer: Observer = wrapped_observer
12+
self.__function_runner = thread_function_runner
13+
self.__lock: Lock = BooleanLock()
14+
self.__cache_size: int | None = cache_size
15+
self.__message_cache: list = []
16+
self.__error_cache: list = []
17+
18+
def on_next(self, message):
19+
if self.__lock.is_locked():
20+
self.__update_cache(self.__message_cache, message)
21+
else:
22+
self.__lock.lock()
23+
self.__function_runner(self, self.__on_next, message)
24+
25+
@staticmethod
26+
def __on_next(self, message: any):
27+
self.wrapped_observer.on_next(message)
28+
if len(self.__message_cache) > 0:
29+
self.__function_runner(self, self.__on_next, self.__message_cache.pop(0))
30+
else:
31+
self.__lock.unlock()
32+
33+
def on_error(self, error: any):
34+
if self.__lock.is_locked():
35+
self.__update_cache(self.__error_cache, error)
36+
else:
37+
self.__lock.lock()
38+
self.__function_runner(self, self.__on_error, error)
39+
40+
@staticmethod
41+
def __on_error(self, error: any):
42+
self.wrapped_observer.on_error(error)
43+
if len(self.__error_cache) > 0:
44+
self.__function_runner(self, self.__on_error, self.__error_cache.pop(0))
45+
else:
46+
self.__lock.unlock()
47+
48+
def __update_cache(self, cache: list, item: Any):
49+
if self.__cache_size is None or len(cache) < self.__cache_size:
50+
cache.append(item)
51+
else:
52+
cache.pop(0)
53+
cache.append(item)
54+
55+
def on_completed(self):
56+
self.wrapped_observer.on_completed()
57+
58+
def is_locked(self):
59+
return self.__lock.is_locked()
60+
61+
62+
def wrap_observer_with_drop_strategy(observer: Observer, cache_size: int = 10) -> Observer:
63+
return DropBackPressureStrategy(observer, cache_size=cache_size)
64+
65+
66+
def wrap_observer_with_buffer_strategy(observer: Observer) -> Observer:
67+
return DropBackPressureStrategy(observer, cache_size=None)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Copyright (c) rxpy_backpressure
2+
from threading import Thread
3+
4+
5+
def thread_function_runner(self, func, message):
6+
Thread(target=func, args=(self, message)).start()

0 commit comments

Comments
 (0)