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.
Files changed (53) hide show
  1. rosight-0.1.0/.gitignore +53 -0
  2. rosight-0.1.0/LICENSE +21 -0
  3. rosight-0.1.0/PKG-INFO +196 -0
  4. rosight-0.1.0/README.md +151 -0
  5. rosight-0.1.0/docs/architecture.md +121 -0
  6. rosight-0.1.0/docs/configuration.md +88 -0
  7. rosight-0.1.0/docs/development.md +101 -0
  8. rosight-0.1.0/docs/faq.md +101 -0
  9. rosight-0.1.0/docs/index.md +41 -0
  10. rosight-0.1.0/docs/installation.md +83 -0
  11. rosight-0.1.0/docs/keybindings.md +135 -0
  12. rosight-0.1.0/docs/plotting.md +102 -0
  13. rosight-0.1.0/docs/screenshots/01-messages.svg +230 -0
  14. rosight-0.1.0/docs/screenshots/02-nodes.svg +226 -0
  15. rosight-0.1.0/docs/screenshots/03-services.svg +228 -0
  16. rosight-0.1.0/docs/screenshots/04-actions.svg +228 -0
  17. rosight-0.1.0/docs/screenshots/05-plot.svg +225 -0
  18. rosight-0.1.0/docs/screenshots/06-bags.svg +228 -0
  19. rosight-0.1.0/docs/screenshots/07-interfaces.svg +225 -0
  20. rosight-0.1.0/docs/usage.md +127 -0
  21. rosight-0.1.0/pyproject.toml +133 -0
  22. rosight-0.1.0/src/rosight/__init__.py +5 -0
  23. rosight-0.1.0/src/rosight/__main__.py +6 -0
  24. rosight-0.1.0/src/rosight/app.py +277 -0
  25. rosight-0.1.0/src/rosight/app.tcss +70 -0
  26. rosight-0.1.0/src/rosight/cli.py +87 -0
  27. rosight-0.1.0/src/rosight/config.py +153 -0
  28. rosight-0.1.0/src/rosight/ros/__init__.py +17 -0
  29. rosight-0.1.0/src/rosight/ros/backend.py +600 -0
  30. rosight-0.1.0/src/rosight/ros/introspection.py +199 -0
  31. rosight-0.1.0/src/rosight/ros/qos.py +143 -0
  32. rosight-0.1.0/src/rosight/ros/stats.py +146 -0
  33. rosight-0.1.0/src/rosight/utils/__init__.py +1 -0
  34. rosight-0.1.0/src/rosight/utils/datatable.py +133 -0
  35. rosight-0.1.0/src/rosight/utils/formatting.py +88 -0
  36. rosight-0.1.0/src/rosight/utils/path.py +110 -0
  37. rosight-0.1.0/src/rosight/utils/ringbuffer.py +119 -0
  38. rosight-0.1.0/src/rosight/version.py +3 -0
  39. rosight-0.1.0/src/rosight/widgets/__init__.py +1 -0
  40. rosight-0.1.0/src/rosight/widgets/actions_panel.py +136 -0
  41. rosight-0.1.0/src/rosight/widgets/bags_panel.py +192 -0
  42. rosight-0.1.0/src/rosight/widgets/command_palette.py +61 -0
  43. rosight-0.1.0/src/rosight/widgets/help_screen.py +97 -0
  44. rosight-0.1.0/src/rosight/widgets/interfaces_panel.py +184 -0
  45. rosight-0.1.0/src/rosight/widgets/message_tree.py +145 -0
  46. rosight-0.1.0/src/rosight/widgets/nodes_panel.py +157 -0
  47. rosight-0.1.0/src/rosight/widgets/params_panel.py +133 -0
  48. rosight-0.1.0/src/rosight/widgets/plot_panel.py +195 -0
  49. rosight-0.1.0/src/rosight/widgets/plot_view.py +186 -0
  50. rosight-0.1.0/src/rosight/widgets/services_panel.py +153 -0
  51. rosight-0.1.0/src/rosight/widgets/status_bar.py +58 -0
  52. rosight-0.1.0/src/rosight/widgets/tf_panel.py +128 -0
  53. rosight-0.1.0/src/rosight/widgets/topics_panel.py +317 -0
@@ -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
+ [![CI](https://github.com/deyuf/rosight/actions/workflows/ci.yml/badge.svg)](https://github.com/deyuf/rosight/actions/workflows/ci.yml)
49
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ ![Messages tab](docs/screenshots/01-messages.svg)
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
+ ![Nodes tab](docs/screenshots/02-nodes.svg)
78
+ *Nodes (`2`)*
79
+
80
+ ![Services tab](docs/screenshots/03-services.svg)
81
+ *Services (`3`)*
82
+
83
+ ![Actions tab](docs/screenshots/04-actions.svg)
84
+ *Actions (`4`)*
85
+
86
+ ![Bags tab](docs/screenshots/06-bags.svg)
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).
@@ -0,0 +1,151 @@
1
+ # Rosight
2
+
3
+ [![CI](https://github.com/deyuf/rosight/actions/workflows/ci.yml/badge.svg)](https://github.com/deyuf/rosight/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ ![Messages tab](docs/screenshots/01-messages.svg)
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
+ ![Nodes tab](docs/screenshots/02-nodes.svg)
33
+ *Nodes (`2`)*
34
+
35
+ ![Services tab](docs/screenshots/03-services.svg)
36
+ *Services (`3`)*
37
+
38
+ ![Actions tab](docs/screenshots/04-actions.svg)
39
+ *Actions (`4`)*
40
+
41
+ ![Bags tab](docs/screenshots/06-bags.svg)
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).