rosight 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rosight-0.1.0/.gitignore +53 -0
- rosight-0.1.0/LICENSE +21 -0
- rosight-0.1.0/PKG-INFO +196 -0
- rosight-0.1.0/README.md +151 -0
- rosight-0.1.0/docs/architecture.md +121 -0
- rosight-0.1.0/docs/configuration.md +88 -0
- rosight-0.1.0/docs/development.md +101 -0
- rosight-0.1.0/docs/faq.md +101 -0
- rosight-0.1.0/docs/index.md +41 -0
- rosight-0.1.0/docs/installation.md +83 -0
- rosight-0.1.0/docs/keybindings.md +135 -0
- rosight-0.1.0/docs/plotting.md +102 -0
- rosight-0.1.0/docs/screenshots/01-messages.svg +230 -0
- rosight-0.1.0/docs/screenshots/02-nodes.svg +226 -0
- rosight-0.1.0/docs/screenshots/03-services.svg +228 -0
- rosight-0.1.0/docs/screenshots/04-actions.svg +228 -0
- rosight-0.1.0/docs/screenshots/05-plot.svg +225 -0
- rosight-0.1.0/docs/screenshots/06-bags.svg +228 -0
- rosight-0.1.0/docs/screenshots/07-interfaces.svg +225 -0
- rosight-0.1.0/docs/usage.md +127 -0
- rosight-0.1.0/pyproject.toml +133 -0
- rosight-0.1.0/src/rosight/__init__.py +5 -0
- rosight-0.1.0/src/rosight/__main__.py +6 -0
- rosight-0.1.0/src/rosight/app.py +277 -0
- rosight-0.1.0/src/rosight/app.tcss +70 -0
- rosight-0.1.0/src/rosight/cli.py +87 -0
- rosight-0.1.0/src/rosight/config.py +153 -0
- rosight-0.1.0/src/rosight/ros/__init__.py +17 -0
- rosight-0.1.0/src/rosight/ros/backend.py +600 -0
- rosight-0.1.0/src/rosight/ros/introspection.py +199 -0
- rosight-0.1.0/src/rosight/ros/qos.py +143 -0
- rosight-0.1.0/src/rosight/ros/stats.py +146 -0
- rosight-0.1.0/src/rosight/utils/__init__.py +1 -0
- rosight-0.1.0/src/rosight/utils/datatable.py +133 -0
- rosight-0.1.0/src/rosight/utils/formatting.py +88 -0
- rosight-0.1.0/src/rosight/utils/path.py +110 -0
- rosight-0.1.0/src/rosight/utils/ringbuffer.py +119 -0
- rosight-0.1.0/src/rosight/version.py +3 -0
- rosight-0.1.0/src/rosight/widgets/__init__.py +1 -0
- rosight-0.1.0/src/rosight/widgets/actions_panel.py +136 -0
- rosight-0.1.0/src/rosight/widgets/bags_panel.py +192 -0
- rosight-0.1.0/src/rosight/widgets/command_palette.py +61 -0
- rosight-0.1.0/src/rosight/widgets/help_screen.py +97 -0
- rosight-0.1.0/src/rosight/widgets/interfaces_panel.py +184 -0
- rosight-0.1.0/src/rosight/widgets/message_tree.py +145 -0
- rosight-0.1.0/src/rosight/widgets/nodes_panel.py +157 -0
- rosight-0.1.0/src/rosight/widgets/params_panel.py +133 -0
- rosight-0.1.0/src/rosight/widgets/plot_panel.py +195 -0
- rosight-0.1.0/src/rosight/widgets/plot_view.py +186 -0
- rosight-0.1.0/src/rosight/widgets/services_panel.py +153 -0
- rosight-0.1.0/src/rosight/widgets/status_bar.py +58 -0
- rosight-0.1.0/src/rosight/widgets/tf_panel.py +128 -0
- rosight-0.1.0/src/rosight/widgets/topics_panel.py +317 -0
rosight-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Build artifacts
|
|
2
|
+
build/
|
|
3
|
+
dist/
|
|
4
|
+
*.egg-info/
|
|
5
|
+
*.egg
|
|
6
|
+
wheels/
|
|
7
|
+
.eggs/
|
|
8
|
+
pip-wheel-metadata/
|
|
9
|
+
|
|
10
|
+
# Python
|
|
11
|
+
__pycache__/
|
|
12
|
+
*.py[cod]
|
|
13
|
+
*$py.class
|
|
14
|
+
*.so
|
|
15
|
+
.Python
|
|
16
|
+
.python-version
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.coverage
|
|
19
|
+
.coverage.*
|
|
20
|
+
htmlcov/
|
|
21
|
+
coverage.xml
|
|
22
|
+
*.cover
|
|
23
|
+
.cache/
|
|
24
|
+
.mypy_cache/
|
|
25
|
+
.ruff_cache/
|
|
26
|
+
.tox/
|
|
27
|
+
.nox/
|
|
28
|
+
.hypothesis/
|
|
29
|
+
|
|
30
|
+
# Virtualenvs
|
|
31
|
+
.venv/
|
|
32
|
+
venv/
|
|
33
|
+
env/
|
|
34
|
+
|
|
35
|
+
# Editors
|
|
36
|
+
.idea/
|
|
37
|
+
.vscode/
|
|
38
|
+
*.swp
|
|
39
|
+
*~
|
|
40
|
+
.DS_Store
|
|
41
|
+
|
|
42
|
+
# Site
|
|
43
|
+
site/
|
|
44
|
+
|
|
45
|
+
# Logs / runtime
|
|
46
|
+
*.log
|
|
47
|
+
.rosight/
|
|
48
|
+
*.bag
|
|
49
|
+
*.db3
|
|
50
|
+
*.mcap
|
|
51
|
+
|
|
52
|
+
# OS
|
|
53
|
+
Thumbs.db
|
rosight-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Deyu Fu
|
|
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.
|
rosight-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rosight
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lazygit-inspired terminal UI for ROS 2: topics, nodes, services, params, live message inspection and plotting.
|
|
5
|
+
Project-URL: Homepage, https://github.com/deyuf/rosight
|
|
6
|
+
Project-URL: Documentation, https://github.com/deyuf/rosight#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/deyuf/rosight
|
|
8
|
+
Project-URL: Issues, https://github.com/deyuf/rosight/issues
|
|
9
|
+
Author: deyuf
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: monitoring,plotting,robotics,ros2,textual,tui
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering
|
|
23
|
+
Classifier: Topic :: System :: Monitoring
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: platformdirs>=4.0
|
|
26
|
+
Requires-Dist: plotext>=5.2
|
|
27
|
+
Requires-Dist: pyyaml>=6.0
|
|
28
|
+
Requires-Dist: rich>=13.7
|
|
29
|
+
Requires-Dist: textual>=0.79
|
|
30
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
33
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
38
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: types-pyyaml; extra == 'dev'
|
|
40
|
+
Provides-Extra: docs
|
|
41
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
42
|
+
Requires-Dist: mkdocs>=1.6; extra == 'docs'
|
|
43
|
+
Requires-Dist: pymdown-extensions>=10.7; extra == 'docs'
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
|
|
46
|
+
# Rosight
|
|
47
|
+
|
|
48
|
+
[](https://github.com/deyuf/rosight/actions/workflows/ci.yml)
|
|
49
|
+
[](LICENSE)
|
|
50
|
+
|
|
51
|
+
> **Rosight** = ROS + sight. A keyboard-driven terminal cockpit for ROS 2.
|
|
52
|
+
|
|
53
|
+
Browse the live ROS 2 graph — messages, nodes, services, actions, parameters,
|
|
54
|
+
TF, bags, interfaces — and live-plot any numeric field, all from one screen
|
|
55
|
+
without a mouse. Built for SSH, tmux, robot consoles.
|
|
56
|
+
|
|
57
|
+

|
|
58
|
+
|
|
59
|
+
## What's inside
|
|
60
|
+
|
|
61
|
+
Nine tabs, keyboard-driven, all on one screen:
|
|
62
|
+
|
|
63
|
+
| `#` | Tab | What you can do |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `1` | **Messages** | Filter the topic table, `Enter` to subscribe, the right pane shows live hz / bw / jitter and the message tree. Press `i` for static topic info (types, publishers, QoS). |
|
|
66
|
+
| `2` | **Nodes** | Discover every node, drill into its publishers, subscribers, service servers/clients, action servers/clients. |
|
|
67
|
+
| `3` | **Services** | List services with types. `c` opens a call form *(planned)*. |
|
|
68
|
+
| `4` | **Actions** | List action servers with types. Goal monitor *(planned)*. |
|
|
69
|
+
| `5` | **Params** | Browse parameters per node, get / set with type awareness. |
|
|
70
|
+
| `6` | **Plot** | Multi-series live plot of any numeric field — pick a field in the message tree and press `p`. Pause / window-scale / CSV export. |
|
|
71
|
+
| `7` | **TF** | Auto-built frame tree from `/tf` and `/tf_static`. |
|
|
72
|
+
| `8` | **Bags** | `ros2 bag record` / `play` / `info`. The header tells you which key stops the recording while it's running. |
|
|
73
|
+
| `9` | **Interfaces** | Browse `msg` / `srv` / `action` definitions of every interface package on the system. |
|
|
74
|
+
|
|
75
|
+
### Screenshots
|
|
76
|
+
|
|
77
|
+

|
|
78
|
+
*Nodes (`2`)*
|
|
79
|
+
|
|
80
|
+

|
|
81
|
+
*Services (`3`)*
|
|
82
|
+
|
|
83
|
+

|
|
84
|
+
*Actions (`4`)*
|
|
85
|
+
|
|
86
|
+

|
|
87
|
+
*Bags (`8`) — recording state*
|
|
88
|
+
|
|
89
|
+
Cross-cutting features:
|
|
90
|
+
|
|
91
|
+
- **Auto-QoS** — subscriber profile is negotiated to match every publisher of a topic (BEST_EFFORT vs RELIABLE, TRANSIENT_LOCAL, etc.), so sensor topics "just work".
|
|
92
|
+
- **Theme persistence** — pick a theme via `Ctrl+P → Change theme`; it survives restarts (stored next to your config in a small `state.toml`).
|
|
93
|
+
- **Runtime domain switch** — `:domain 5` reconnects the rclpy backend on a new `ROS_DOMAIN_ID` without restarting the app.
|
|
94
|
+
- **Live notifications** — important actions (subscribed, started recording, switched domain) show as toasts; nothing important hides in a covered status bar.
|
|
95
|
+
- **No ROS required for the UI** — the package imports cleanly without `rclpy`, so you can dev / unit-test on cloud machines. Run with `--no-ros` to stay offline.
|
|
96
|
+
|
|
97
|
+
## Install
|
|
98
|
+
|
|
99
|
+
Rosight is not yet on PyPI; install from source:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
git clone https://github.com/deyuf/rosight
|
|
103
|
+
cd rosight
|
|
104
|
+
pip install -e .
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Then, in any terminal where you want to use it:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
source /opt/ros/<distro>/setup.bash
|
|
111
|
+
rosight
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
If you maintain multiple distros, source the one whose `rclpy` should drive
|
|
115
|
+
discovery before launching. See [`docs/installation.md`](docs/installation.md)
|
|
116
|
+
for the recommended `--system-site-packages` venv layout that keeps the system
|
|
117
|
+
`rclpy` reachable while pip manages everything else.
|
|
118
|
+
|
|
119
|
+
## Quick start
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# launch (inherits ROS_DOMAIN_ID from env)
|
|
123
|
+
rosight
|
|
124
|
+
|
|
125
|
+
# pick a domain explicitly + log to file
|
|
126
|
+
rosight --domain-id 5 --log-file /tmp/rosight.log
|
|
127
|
+
|
|
128
|
+
# UI-only mode, no rclpy needed (great for theme / layout dev)
|
|
129
|
+
rosight --no-ros
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Inside the app:
|
|
133
|
+
|
|
134
|
+
- `?` — keyboard reference overlay
|
|
135
|
+
- `:` — command palette (`topic <filter>`, `plot <topic> <path>`, `record`, `domain <N>`, `quit`)
|
|
136
|
+
- `1`–`9` — jump to a tab
|
|
137
|
+
- `q` or `Ctrl+C` — quit
|
|
138
|
+
- `r` — manual refresh
|
|
139
|
+
|
|
140
|
+
Plotting workflow (the question new users ask most):
|
|
141
|
+
|
|
142
|
+
1. **Messages tab**, cursor over a topic, **Enter** to subscribe (focus auto-jumps to the message tree)
|
|
143
|
+
2. Use arrow keys / Space to expand fields in the tree
|
|
144
|
+
3. Cursor on a *numeric* leaf (int / float), press **p** — series is added and the Plot tab opens
|
|
145
|
+
|
|
146
|
+
## Documentation
|
|
147
|
+
|
|
148
|
+
| Topic | File |
|
|
149
|
+
|-------|------|
|
|
150
|
+
| Installation per distro | [docs/installation.md](docs/installation.md) |
|
|
151
|
+
| Daily usage | [docs/usage.md](docs/usage.md) |
|
|
152
|
+
| Keybindings | [docs/keybindings.md](docs/keybindings.md) |
|
|
153
|
+
| Configuration | [docs/configuration.md](docs/configuration.md) |
|
|
154
|
+
| Plotting deep-dive | [docs/plotting.md](docs/plotting.md) |
|
|
155
|
+
| Architecture | [docs/architecture.md](docs/architecture.md) |
|
|
156
|
+
| Contributing | [docs/development.md](docs/development.md) |
|
|
157
|
+
| FAQ | [docs/faq.md](docs/faq.md) |
|
|
158
|
+
|
|
159
|
+
## Project layout
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
src/rosight/
|
|
163
|
+
├── app.py # Textual App, panel registry, global key bindings
|
|
164
|
+
├── cli.py # argparse entry point
|
|
165
|
+
├── config.py # TOML config loader + state.toml persistence
|
|
166
|
+
├── ros/
|
|
167
|
+
│ ├── backend.py # central rclpy facade (lifecycle, sub mgmt, domain switch)
|
|
168
|
+
│ ├── introspection.py # dynamic message-type resolution
|
|
169
|
+
│ ├── qos.py # plain-data QoS + auto-negotiation
|
|
170
|
+
│ └── stats.py # hz / bw monitors
|
|
171
|
+
├── utils/
|
|
172
|
+
│ ├── datatable.py # row-cursor preservation + last-column auto-fit
|
|
173
|
+
│ ├── ringbuffer.py # thread-safe bounded buffers
|
|
174
|
+
│ ├── path.py # message field-path parser
|
|
175
|
+
│ └── formatting.py # human-readable display helpers
|
|
176
|
+
└── widgets/ # one file per panel + status bar / help / palette
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Tech stack & rationale — short version
|
|
180
|
+
|
|
181
|
+
- **Python 3.10+** + **rclpy** (first-class ROS 2 client, dynamic message introspection via `rosidl_runtime_py`)
|
|
182
|
+
- **Textual ≥ 0.79** (modern async TUI, CSS-like styling, headless pilot tests — that's how the 80-test suite stays meaningful for a TUI)
|
|
183
|
+
- **plotext** for ANSI plotting in the Plot tab
|
|
184
|
+
- **hatch + ruff + mypy + pytest-asyncio** for the dev loop
|
|
185
|
+
- **MkDocs Material** for the docs site
|
|
186
|
+
- rclpy runs on its own `MultiThreadedExecutor` daemon thread so DDS spinning never blocks the Textual main loop. See [`DESIGN.md`](DESIGN.md) for the full reasoning.
|
|
187
|
+
|
|
188
|
+
## Status
|
|
189
|
+
|
|
190
|
+
Beta. Discovery, subscription, plotting, TF, bag record/play, theme persistence,
|
|
191
|
+
runtime domain switch — all working. Auto-forms for service call and action
|
|
192
|
+
goal are on the roadmap.
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT. See [`LICENSE`](LICENSE).
|
rosight-0.1.0/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Rosight
|
|
2
|
+
|
|
3
|
+
[](https://github.com/deyuf/rosight/actions/workflows/ci.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
> **Rosight** = ROS + sight. A keyboard-driven terminal cockpit for ROS 2.
|
|
7
|
+
|
|
8
|
+
Browse the live ROS 2 graph — messages, nodes, services, actions, parameters,
|
|
9
|
+
TF, bags, interfaces — and live-plot any numeric field, all from one screen
|
|
10
|
+
without a mouse. Built for SSH, tmux, robot consoles.
|
|
11
|
+
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
## What's inside
|
|
15
|
+
|
|
16
|
+
Nine tabs, keyboard-driven, all on one screen:
|
|
17
|
+
|
|
18
|
+
| `#` | Tab | What you can do |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `1` | **Messages** | Filter the topic table, `Enter` to subscribe, the right pane shows live hz / bw / jitter and the message tree. Press `i` for static topic info (types, publishers, QoS). |
|
|
21
|
+
| `2` | **Nodes** | Discover every node, drill into its publishers, subscribers, service servers/clients, action servers/clients. |
|
|
22
|
+
| `3` | **Services** | List services with types. `c` opens a call form *(planned)*. |
|
|
23
|
+
| `4` | **Actions** | List action servers with types. Goal monitor *(planned)*. |
|
|
24
|
+
| `5` | **Params** | Browse parameters per node, get / set with type awareness. |
|
|
25
|
+
| `6` | **Plot** | Multi-series live plot of any numeric field — pick a field in the message tree and press `p`. Pause / window-scale / CSV export. |
|
|
26
|
+
| `7` | **TF** | Auto-built frame tree from `/tf` and `/tf_static`. |
|
|
27
|
+
| `8` | **Bags** | `ros2 bag record` / `play` / `info`. The header tells you which key stops the recording while it's running. |
|
|
28
|
+
| `9` | **Interfaces** | Browse `msg` / `srv` / `action` definitions of every interface package on the system. |
|
|
29
|
+
|
|
30
|
+
### Screenshots
|
|
31
|
+
|
|
32
|
+

|
|
33
|
+
*Nodes (`2`)*
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
*Services (`3`)*
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
*Actions (`4`)*
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
*Bags (`8`) — recording state*
|
|
43
|
+
|
|
44
|
+
Cross-cutting features:
|
|
45
|
+
|
|
46
|
+
- **Auto-QoS** — subscriber profile is negotiated to match every publisher of a topic (BEST_EFFORT vs RELIABLE, TRANSIENT_LOCAL, etc.), so sensor topics "just work".
|
|
47
|
+
- **Theme persistence** — pick a theme via `Ctrl+P → Change theme`; it survives restarts (stored next to your config in a small `state.toml`).
|
|
48
|
+
- **Runtime domain switch** — `:domain 5` reconnects the rclpy backend on a new `ROS_DOMAIN_ID` without restarting the app.
|
|
49
|
+
- **Live notifications** — important actions (subscribed, started recording, switched domain) show as toasts; nothing important hides in a covered status bar.
|
|
50
|
+
- **No ROS required for the UI** — the package imports cleanly without `rclpy`, so you can dev / unit-test on cloud machines. Run with `--no-ros` to stay offline.
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
Rosight is not yet on PyPI; install from source:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git clone https://github.com/deyuf/rosight
|
|
58
|
+
cd rosight
|
|
59
|
+
pip install -e .
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Then, in any terminal where you want to use it:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
source /opt/ros/<distro>/setup.bash
|
|
66
|
+
rosight
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
If you maintain multiple distros, source the one whose `rclpy` should drive
|
|
70
|
+
discovery before launching. See [`docs/installation.md`](docs/installation.md)
|
|
71
|
+
for the recommended `--system-site-packages` venv layout that keeps the system
|
|
72
|
+
`rclpy` reachable while pip manages everything else.
|
|
73
|
+
|
|
74
|
+
## Quick start
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# launch (inherits ROS_DOMAIN_ID from env)
|
|
78
|
+
rosight
|
|
79
|
+
|
|
80
|
+
# pick a domain explicitly + log to file
|
|
81
|
+
rosight --domain-id 5 --log-file /tmp/rosight.log
|
|
82
|
+
|
|
83
|
+
# UI-only mode, no rclpy needed (great for theme / layout dev)
|
|
84
|
+
rosight --no-ros
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Inside the app:
|
|
88
|
+
|
|
89
|
+
- `?` — keyboard reference overlay
|
|
90
|
+
- `:` — command palette (`topic <filter>`, `plot <topic> <path>`, `record`, `domain <N>`, `quit`)
|
|
91
|
+
- `1`–`9` — jump to a tab
|
|
92
|
+
- `q` or `Ctrl+C` — quit
|
|
93
|
+
- `r` — manual refresh
|
|
94
|
+
|
|
95
|
+
Plotting workflow (the question new users ask most):
|
|
96
|
+
|
|
97
|
+
1. **Messages tab**, cursor over a topic, **Enter** to subscribe (focus auto-jumps to the message tree)
|
|
98
|
+
2. Use arrow keys / Space to expand fields in the tree
|
|
99
|
+
3. Cursor on a *numeric* leaf (int / float), press **p** — series is added and the Plot tab opens
|
|
100
|
+
|
|
101
|
+
## Documentation
|
|
102
|
+
|
|
103
|
+
| Topic | File |
|
|
104
|
+
|-------|------|
|
|
105
|
+
| Installation per distro | [docs/installation.md](docs/installation.md) |
|
|
106
|
+
| Daily usage | [docs/usage.md](docs/usage.md) |
|
|
107
|
+
| Keybindings | [docs/keybindings.md](docs/keybindings.md) |
|
|
108
|
+
| Configuration | [docs/configuration.md](docs/configuration.md) |
|
|
109
|
+
| Plotting deep-dive | [docs/plotting.md](docs/plotting.md) |
|
|
110
|
+
| Architecture | [docs/architecture.md](docs/architecture.md) |
|
|
111
|
+
| Contributing | [docs/development.md](docs/development.md) |
|
|
112
|
+
| FAQ | [docs/faq.md](docs/faq.md) |
|
|
113
|
+
|
|
114
|
+
## Project layout
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
src/rosight/
|
|
118
|
+
├── app.py # Textual App, panel registry, global key bindings
|
|
119
|
+
├── cli.py # argparse entry point
|
|
120
|
+
├── config.py # TOML config loader + state.toml persistence
|
|
121
|
+
├── ros/
|
|
122
|
+
│ ├── backend.py # central rclpy facade (lifecycle, sub mgmt, domain switch)
|
|
123
|
+
│ ├── introspection.py # dynamic message-type resolution
|
|
124
|
+
│ ├── qos.py # plain-data QoS + auto-negotiation
|
|
125
|
+
│ └── stats.py # hz / bw monitors
|
|
126
|
+
├── utils/
|
|
127
|
+
│ ├── datatable.py # row-cursor preservation + last-column auto-fit
|
|
128
|
+
│ ├── ringbuffer.py # thread-safe bounded buffers
|
|
129
|
+
│ ├── path.py # message field-path parser
|
|
130
|
+
│ └── formatting.py # human-readable display helpers
|
|
131
|
+
└── widgets/ # one file per panel + status bar / help / palette
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Tech stack & rationale — short version
|
|
135
|
+
|
|
136
|
+
- **Python 3.10+** + **rclpy** (first-class ROS 2 client, dynamic message introspection via `rosidl_runtime_py`)
|
|
137
|
+
- **Textual ≥ 0.79** (modern async TUI, CSS-like styling, headless pilot tests — that's how the 80-test suite stays meaningful for a TUI)
|
|
138
|
+
- **plotext** for ANSI plotting in the Plot tab
|
|
139
|
+
- **hatch + ruff + mypy + pytest-asyncio** for the dev loop
|
|
140
|
+
- **MkDocs Material** for the docs site
|
|
141
|
+
- rclpy runs on its own `MultiThreadedExecutor` daemon thread so DDS spinning never blocks the Textual main loop. See [`DESIGN.md`](DESIGN.md) for the full reasoning.
|
|
142
|
+
|
|
143
|
+
## Status
|
|
144
|
+
|
|
145
|
+
Beta. Discovery, subscription, plotting, TF, bag record/play, theme persistence,
|
|
146
|
+
runtime domain switch — all working. Auto-forms for service call and action
|
|
147
|
+
goal are on the roadmap.
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT. See [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
This page is a tour of the codebase, in the order you'd encounter the
|
|
4
|
+
layers when tracing a single keystroke from the user to a ROS message.
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
┌───────────────────────────────────────────────────────────────┐
|
|
8
|
+
│ Keyboard │
|
|
9
|
+
│ │ │
|
|
10
|
+
│ ▼ │
|
|
11
|
+
│ Textual main loop (asyncio) │
|
|
12
|
+
│ │ │
|
|
13
|
+
│ ▼ │
|
|
14
|
+
│ Widgets ────► panels (TopicsPanel, PlotPanel, ...) │
|
|
15
|
+
│ │ │
|
|
16
|
+
│ ▼ │
|
|
17
|
+
│ RosBackend (thread-safe facade) │
|
|
18
|
+
│ │ │
|
|
19
|
+
│ ▼ │
|
|
20
|
+
│ rclpy MultiThreadedExecutor (background thread) │
|
|
21
|
+
│ │ │
|
|
22
|
+
│ ▼ │
|
|
23
|
+
│ DDS / wire │
|
|
24
|
+
└───────────────────────────────────────────────────────────────┘
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Why the two-thread split
|
|
28
|
+
|
|
29
|
+
Textual's event loop is single-threaded asyncio. Spinning rclpy from the
|
|
30
|
+
same loop blocks the UI on every callback. `RosBackend` therefore owns a
|
|
31
|
+
`MultiThreadedExecutor` running on a daemon thread; subscriber callbacks
|
|
32
|
+
never touch widgets directly. Instead they:
|
|
33
|
+
|
|
34
|
+
1. Update the `Subscription` record (rate / bandwidth / last_msg)
|
|
35
|
+
2. Notify any registered Python callbacks under a lock
|
|
36
|
+
|
|
37
|
+
The Textual side polls these records on cheap timers (`set_interval`) and
|
|
38
|
+
re-renders.
|
|
39
|
+
|
|
40
|
+
## ROS layer
|
|
41
|
+
|
|
42
|
+
The `rosight.ros` package is intentionally importable without `rclpy`:
|
|
43
|
+
|
|
44
|
+
| Module | Purpose |
|
|
45
|
+
|--------|---------|
|
|
46
|
+
| `qos.py` | Plain-data `QoSSpec` + `negotiate()` to derive a subscriber QoS that matches every publisher. |
|
|
47
|
+
| `stats.py` | `RateMonitor` and `BandwidthMonitor` — sliding-window accumulators with thread-safe `tick`/`sample`. |
|
|
48
|
+
| `introspection.py` | `iter_fields(msg)` walks any ROS message (or duck-typed dict) and yields `FieldEntry(path, value, type, is_numeric)`. |
|
|
49
|
+
| `backend.py` | `RosBackend` — owns the rclpy node and executor; the only place rclpy is imported. |
|
|
50
|
+
|
|
51
|
+
Because the heavy imports are deferred to `RosBackend.start()`, the
|
|
52
|
+
package can be unit-tested on cloud CI runners without ROS 2 installed.
|
|
53
|
+
|
|
54
|
+
## Utilities
|
|
55
|
+
|
|
56
|
+
- `utils/ringbuffer.py` — `RingBuffer` (capacity-bounded) and
|
|
57
|
+
`TimedRingBuffer` (window-bounded) used by stats and plot panel.
|
|
58
|
+
Both lock-protected.
|
|
59
|
+
- `utils/path.py` — parses dotted/bracketed field paths and resolves them
|
|
60
|
+
against any object (message, dict, list).
|
|
61
|
+
- `utils/formatting.py` — display helpers: `format_bytes`, `format_rate`,
|
|
62
|
+
`format_value`, etc.
|
|
63
|
+
|
|
64
|
+
## Widgets
|
|
65
|
+
|
|
66
|
+
Each panel is a self-contained Textual `Vertical` with its own
|
|
67
|
+
`compose()`, `BINDINGS`, and CSS in `DEFAULT_CSS`. Cross-panel
|
|
68
|
+
communication goes through `RosightApp` (e.g. the message tree posts a
|
|
69
|
+
`FieldSelected` event that `TopicsPanel` forwards to
|
|
70
|
+
`app.add_plot_series`).
|
|
71
|
+
|
|
72
|
+
A few widgets are reusable:
|
|
73
|
+
|
|
74
|
+
- `MessageTree` renders any ROS message as a navigable tree and exposes
|
|
75
|
+
`FieldSelected`.
|
|
76
|
+
- `PlotView` wraps `plotext` and turns a dict of `PlotSeries` into a
|
|
77
|
+
rendered chart on a 15 Hz tick.
|
|
78
|
+
|
|
79
|
+
## Configuration
|
|
80
|
+
|
|
81
|
+
`config.py` defines a strict dataclass schema, loaded from TOML. Unknown
|
|
82
|
+
keys are dropped silently to preserve forward compatibility. The CLI can
|
|
83
|
+
override `domain_id` per run.
|
|
84
|
+
|
|
85
|
+
## Lifecycle
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
RosightApp.__init__
|
|
89
|
+
└── on_mount
|
|
90
|
+
├── ros.start() → executor thread starts
|
|
91
|
+
└── set_interval(...) → status bar tickers
|
|
92
|
+
RosightApp.on_unmount
|
|
93
|
+
└── ros.stop() → shuts down node, joins thread
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
If `ros.start()` raises `RosUnavailable`, the app stays up in degraded
|
|
97
|
+
mode and shows a notification — useful for offline UI work.
|
|
98
|
+
|
|
99
|
+
## Testing strategy
|
|
100
|
+
|
|
101
|
+
- **Pure-python tests** cover utilities, QoS logic, stats, introspection,
|
|
102
|
+
config, and parsers. They run without rclpy.
|
|
103
|
+
- **Mocked-backend tests** verify the ROS facade without a real node.
|
|
104
|
+
- **Headless smoke tests** boot the Textual app via `App.run_test`
|
|
105
|
+
(Textual's pilot harness) and exercise key bindings.
|
|
106
|
+
- **ROS integration job** (CI) runs the suite inside official `ros:humble`
|
|
107
|
+
/ `ros:jazzy` containers with a sourced workspace.
|
|
108
|
+
|
|
109
|
+
## Extending Rosight
|
|
110
|
+
|
|
111
|
+
To add a new panel:
|
|
112
|
+
|
|
113
|
+
1. Create `src/rosight/widgets/foo_panel.py` that subclasses `Vertical`
|
|
114
|
+
(or another Textual container).
|
|
115
|
+
2. Register it in `RosightApp.compose` inside the `TabbedContent` block
|
|
116
|
+
and add a number-key binding.
|
|
117
|
+
3. If the panel needs ROS data, prefer adding methods to `RosBackend`
|
|
118
|
+
over reaching into rclpy from the widget.
|
|
119
|
+
4. Add a test under `tests/`.
|
|
120
|
+
|
|
121
|
+
See [development.md](development.md) for the full dev loop.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
`rosight` reads a TOML config from `$XDG_CONFIG_HOME/rosight/config.toml`
|
|
4
|
+
(typically `~/.config/rosight/config.toml`). All keys are optional;
|
|
5
|
+
defaults are listed below.
|
|
6
|
+
|
|
7
|
+
```toml
|
|
8
|
+
[ui]
|
|
9
|
+
theme = "rosight-dark" # ui theme name
|
|
10
|
+
refresh_hz = 10 # how often the status bar updates
|
|
11
|
+
discovery_period = 1.0 # seconds between topic/node list refreshes
|
|
12
|
+
vim_keys = true
|
|
13
|
+
|
|
14
|
+
[plot]
|
|
15
|
+
window_seconds = 30.0 # default sliding-window length
|
|
16
|
+
max_points = 5000 # per-series sample cap
|
|
17
|
+
refresh_hz = 15.0 # plot redraw rate
|
|
18
|
+
show_legend = true
|
|
19
|
+
|
|
20
|
+
[stats]
|
|
21
|
+
window_seconds = 5.0 # hz / bandwidth window
|
|
22
|
+
max_samples = 4096
|
|
23
|
+
|
|
24
|
+
[ros]
|
|
25
|
+
domain_id = 0 # leave unset to inherit ROS_DOMAIN_ID
|
|
26
|
+
qos_profile = "auto" # auto | sensor_data | reliable | best_effort
|
|
27
|
+
queue_depth = 10 # default subscriber queue depth
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Overriding via CLI
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
rosight --config /path/to/alt.toml
|
|
34
|
+
rosight --domain-id 7
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The CLI also accepts `ROSIGHT_CONFIG=path` as an env override.
|
|
38
|
+
|
|
39
|
+
## Where each setting goes
|
|
40
|
+
|
|
41
|
+
- **`ui.discovery_period`** controls how often `list_topics` etc. are
|
|
42
|
+
called. Increase if you have a very busy graph and discovery becomes
|
|
43
|
+
expensive.
|
|
44
|
+
- **`plot.window_seconds`** is the initial window; `+` and `-` scale it
|
|
45
|
+
interactively at runtime.
|
|
46
|
+
- **`stats.window_seconds`** governs the smoothing on hz / bandwidth.
|
|
47
|
+
Smaller values are more responsive but noisier on bursty topics.
|
|
48
|
+
- **`ros.qos_profile`** is a forward-looking knob. The default `auto`
|
|
49
|
+
derives QoS from publisher endpoints (recommended); the others force a
|
|
50
|
+
single profile for every subscription.
|
|
51
|
+
|
|
52
|
+
## Schema
|
|
53
|
+
|
|
54
|
+
Defined as plain dataclasses in `src/rosight/config.py`. Unknown keys are
|
|
55
|
+
silently ignored to keep older configs compatible.
|
|
56
|
+
|
|
57
|
+
## Persisted runtime state
|
|
58
|
+
|
|
59
|
+
Things you change inside the app (currently just the **theme** picked from
|
|
60
|
+
`Ctrl+P → Change theme`) are written to a separate small file next to your
|
|
61
|
+
config so Rosight never rewrites your hand-edited `config.toml`:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
$XDG_CONFIG_HOME/rosight/state.toml
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Example content after picking a theme:
|
|
68
|
+
|
|
69
|
+
```toml
|
|
70
|
+
theme = "textual-light"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Delete the file to fall back to whatever `[ui] theme` is set in your config
|
|
74
|
+
(or the built-in default if neither exists).
|
|
75
|
+
|
|
76
|
+
## Runtime domain switch
|
|
77
|
+
|
|
78
|
+
You can change `ROS_DOMAIN_ID` *while the app is running* without losing the
|
|
79
|
+
session:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
: # open the command palette
|
|
83
|
+
domain 5 # rclpy is reinitialised on domain 5
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The rclpy context, executor, and every active subscription are torn down and
|
|
87
|
+
recreated on the new domain. The status bar's `DOMAIN_ID=` field updates
|
|
88
|
+
immediately to reflect the live value (not the env var).
|