BulletLab 0.1.2__py3-none-any.whl
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.
- bulletlab/__init__.py +48 -0
- bulletlab/core/__init__.py +10 -0
- bulletlab/core/simulation.py +377 -0
- bulletlab/core/world.py +173 -0
- bulletlab/logging/__init__.py +9 -0
- bulletlab/logging/csv_writer.py +54 -0
- bulletlab/logging/json_writer.py +70 -0
- bulletlab/logging/logger.py +258 -0
- bulletlab/plotting/__init__.py +9 -0
- bulletlab/plotting/live_plot.py +364 -0
- bulletlab/robot/__init__.py +12 -0
- bulletlab/robot/joint.py +402 -0
- bulletlab/robot/link.py +401 -0
- bulletlab/robot/robot.py +533 -0
- bulletlab/telemetry/__init__.py +10 -0
- bulletlab/telemetry/channel.py +124 -0
- bulletlab/telemetry/manager.py +227 -0
- bulletlab/ui/__init__.py +31 -0
- bulletlab/ui/app.py +543 -0
- bulletlab/ui/panels/__init__.py +16 -0
- bulletlab/ui/panels/console.py +189 -0
- bulletlab/ui/panels/explorer.py +144 -0
- bulletlab/ui/panels/plots.py +143 -0
- bulletlab/ui/panels/properties.py +265 -0
- bulletlab/ui/panels/telemetry.py +105 -0
- bulletlab/ui/widgets.py +348 -0
- bulletlab/utils/__init__.py +26 -0
- bulletlab/utils/math_utils.py +183 -0
- bulletlab/utils/timer.py +107 -0
- bulletlab/utils/urdf_utils.py +114 -0
- bulletlab-0.1.2.dist-info/METADATA +284 -0
- bulletlab-0.1.2.dist-info/RECORD +35 -0
- bulletlab-0.1.2.dist-info/WHEEL +5 -0
- bulletlab-0.1.2.dist-info/licenses/LICENSE +21 -0
- bulletlab-0.1.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TelemetryManager – watches and aggregates multiple data channels.
|
|
3
|
+
|
|
4
|
+
The TelemetryManager polls all registered channels on each call to
|
|
5
|
+
:meth:`update` and provides a unified snapshot of the current state.
|
|
6
|
+
|
|
7
|
+
Example::
|
|
8
|
+
|
|
9
|
+
from bulletlab.telemetry import TelemetryManager
|
|
10
|
+
|
|
11
|
+
telemetry = TelemetryManager()
|
|
12
|
+
telemetry.watch("Speed", lambda: robot.base_velocity[0], unit="m/s")
|
|
13
|
+
telemetry.watch("Roll", lambda: robot.roll, unit="rad")
|
|
14
|
+
telemetry.watch("Height", lambda: robot.base_position[2], unit="m")
|
|
15
|
+
|
|
16
|
+
for _ in range(1000):
|
|
17
|
+
sim.step()
|
|
18
|
+
telemetry.update(t=sim.elapsed_time)
|
|
19
|
+
|
|
20
|
+
print(telemetry.snapshot())
|
|
21
|
+
print(telemetry.get("Speed"))
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import time
|
|
27
|
+
from typing import Any, Callable
|
|
28
|
+
|
|
29
|
+
from bulletlab.telemetry.channel import TelemetryChannel
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TelemetryManager:
|
|
33
|
+
"""Aggregates multiple :class:`~bulletlab.telemetry.channel.TelemetryChannel` instances.
|
|
34
|
+
|
|
35
|
+
Register channels with :meth:`watch`, then call :meth:`update` every
|
|
36
|
+
simulation step. Retrieve the latest values via :meth:`get` or
|
|
37
|
+
:meth:`snapshot`.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
history_len: Default history buffer length for new channels.
|
|
41
|
+
|
|
42
|
+
Example::
|
|
43
|
+
|
|
44
|
+
telemetry = TelemetryManager()
|
|
45
|
+
telemetry.watch("Roll", lambda: robot.roll, unit="rad")
|
|
46
|
+
telemetry.watch("Speed", lambda: robot.speed, unit="m/s")
|
|
47
|
+
|
|
48
|
+
for _ in range(1000):
|
|
49
|
+
sim.step()
|
|
50
|
+
telemetry.update(t=sim.elapsed_time)
|
|
51
|
+
|
|
52
|
+
print(telemetry.snapshot())
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, history_len: int = 1000) -> None:
|
|
56
|
+
self._channels: dict[str, TelemetryChannel] = {}
|
|
57
|
+
self._history_len = history_len
|
|
58
|
+
self._last_update_time: float = 0.0
|
|
59
|
+
|
|
60
|
+
# ------------------------------------------------------------------
|
|
61
|
+
# Registration
|
|
62
|
+
# ------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
def watch(
|
|
65
|
+
self,
|
|
66
|
+
name: str,
|
|
67
|
+
source: Callable[[], Any],
|
|
68
|
+
unit: str = "",
|
|
69
|
+
history_len: int | None = None,
|
|
70
|
+
) -> TelemetryChannel:
|
|
71
|
+
"""Register a new channel to monitor.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
name: Human-readable channel name.
|
|
75
|
+
source: A callable (lambda, function, or method) that returns
|
|
76
|
+
the current value when called.
|
|
77
|
+
unit: Optional unit string (e.g. ``"m/s"``).
|
|
78
|
+
history_len: History buffer size. Defaults to manager's default.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
The created :class:`~bulletlab.telemetry.channel.TelemetryChannel`.
|
|
82
|
+
|
|
83
|
+
Example::
|
|
84
|
+
|
|
85
|
+
telemetry.watch("Speed", lambda: robot.speed, unit="m/s")
|
|
86
|
+
telemetry.watch("Joint_1", robot.joints["iiwa_joint_1"].position)
|
|
87
|
+
"""
|
|
88
|
+
if callable(source):
|
|
89
|
+
source_fn = source
|
|
90
|
+
else:
|
|
91
|
+
# Allow passing a property value directly as a lambda
|
|
92
|
+
_val = source
|
|
93
|
+
source_fn = lambda: _val # noqa: E731
|
|
94
|
+
|
|
95
|
+
hlen = history_len if history_len is not None else self._history_len
|
|
96
|
+
channel = TelemetryChannel(name=name, source=source_fn, history_len=hlen, unit=unit)
|
|
97
|
+
self._channels[name] = channel
|
|
98
|
+
return channel
|
|
99
|
+
|
|
100
|
+
def unwatch(self, name: str) -> None:
|
|
101
|
+
"""Remove a channel by name.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
name: Channel name to remove.
|
|
105
|
+
"""
|
|
106
|
+
self._channels.pop(name, None)
|
|
107
|
+
|
|
108
|
+
# ------------------------------------------------------------------
|
|
109
|
+
# Update
|
|
110
|
+
# ------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
def update(self, t: float | None = None) -> dict[str, Any]:
|
|
113
|
+
"""Poll all channels and return a snapshot of current values.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
t: Timestamp for this update cycle. If ``None``, uses
|
|
117
|
+
:func:`time.monotonic`.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Dictionary mapping channel names to their current values.
|
|
121
|
+
|
|
122
|
+
Example::
|
|
123
|
+
|
|
124
|
+
values = telemetry.update(t=sim.elapsed_time)
|
|
125
|
+
"""
|
|
126
|
+
timestamp = t if t is not None else time.monotonic()
|
|
127
|
+
self._last_update_time = timestamp
|
|
128
|
+
result = {}
|
|
129
|
+
for name, channel in self._channels.items():
|
|
130
|
+
result[name] = channel.poll(t=timestamp)
|
|
131
|
+
return result
|
|
132
|
+
|
|
133
|
+
# ------------------------------------------------------------------
|
|
134
|
+
# Retrieval
|
|
135
|
+
# ------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
def get(self, name: str, default: Any = None) -> Any:
|
|
138
|
+
"""Return the latest value for a channel.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
name: Channel name.
|
|
142
|
+
default: Value to return if channel does not exist.
|
|
143
|
+
|
|
144
|
+
Example::
|
|
145
|
+
|
|
146
|
+
speed = telemetry.get("Speed")
|
|
147
|
+
"""
|
|
148
|
+
channel = self._channels.get(name)
|
|
149
|
+
return channel.latest if channel is not None else default
|
|
150
|
+
|
|
151
|
+
def snapshot(self) -> dict[str, Any]:
|
|
152
|
+
"""Return a dictionary of all channel names to their latest values.
|
|
153
|
+
|
|
154
|
+
Example::
|
|
155
|
+
|
|
156
|
+
data = telemetry.snapshot()
|
|
157
|
+
print(data["Speed"])
|
|
158
|
+
"""
|
|
159
|
+
return {name: ch.latest for name, ch in self._channels.items()}
|
|
160
|
+
|
|
161
|
+
def history(self, name: str) -> list[tuple[float, Any]]:
|
|
162
|
+
"""Return the full history for a channel.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
name: Channel name.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
List of ``(timestamp, value)`` tuples.
|
|
169
|
+
|
|
170
|
+
Example::
|
|
171
|
+
|
|
172
|
+
times_and_vals = telemetry.history("Speed")
|
|
173
|
+
"""
|
|
174
|
+
channel = self._channels.get(name)
|
|
175
|
+
if channel is None:
|
|
176
|
+
return []
|
|
177
|
+
return list(channel.history)
|
|
178
|
+
|
|
179
|
+
def values_array(self, name: str) -> list[Any]:
|
|
180
|
+
"""Return only the values (no timestamps) for a channel.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
name: Channel name.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
List of values in chronological order.
|
|
187
|
+
"""
|
|
188
|
+
channel = self._channels.get(name)
|
|
189
|
+
if channel is None:
|
|
190
|
+
return []
|
|
191
|
+
return channel.values
|
|
192
|
+
|
|
193
|
+
# ------------------------------------------------------------------
|
|
194
|
+
# Reset
|
|
195
|
+
# ------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
def clear_history(self) -> None:
|
|
198
|
+
"""Clear history buffers for all channels."""
|
|
199
|
+
for channel in self._channels.values():
|
|
200
|
+
channel.clear()
|
|
201
|
+
|
|
202
|
+
def clear_all(self) -> None:
|
|
203
|
+
"""Remove all registered channels."""
|
|
204
|
+
self._channels.clear()
|
|
205
|
+
|
|
206
|
+
# ------------------------------------------------------------------
|
|
207
|
+
# Introspection
|
|
208
|
+
# ------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def channels(self) -> dict[str, TelemetryChannel]:
|
|
212
|
+
"""Dictionary of all registered channels."""
|
|
213
|
+
return dict(self._channels)
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def channel_names(self) -> list[str]:
|
|
217
|
+
"""List of all registered channel names."""
|
|
218
|
+
return list(self._channels.keys())
|
|
219
|
+
|
|
220
|
+
def __len__(self) -> int:
|
|
221
|
+
return len(self._channels)
|
|
222
|
+
|
|
223
|
+
def __contains__(self, name: str) -> bool:
|
|
224
|
+
return name in self._channels
|
|
225
|
+
|
|
226
|
+
def __repr__(self) -> str:
|
|
227
|
+
return f"TelemetryManager(channels={list(self._channels.keys())})"
|
bulletlab/ui/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BulletLab UI subpackage.
|
|
3
|
+
|
|
4
|
+
Provides the ImGui-based control window, all built-in panels, and widget helpers.
|
|
5
|
+
|
|
6
|
+
Example::
|
|
7
|
+
|
|
8
|
+
from bulletlab.ui import BulletLabUI
|
|
9
|
+
from bulletlab.ui import widgets as ui
|
|
10
|
+
|
|
11
|
+
app = BulletLabUI(sim=sim, robots=[robot])
|
|
12
|
+
app.run()
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from bulletlab.ui.app import BulletLabUI
|
|
16
|
+
from bulletlab.ui import widgets
|
|
17
|
+
from bulletlab.ui.panels.explorer import ExplorerPanel
|
|
18
|
+
from bulletlab.ui.panels.properties import PropertiesPanel
|
|
19
|
+
from bulletlab.ui.panels.telemetry import TelemetryPanel
|
|
20
|
+
from bulletlab.ui.panels.console import ConsolePanel
|
|
21
|
+
from bulletlab.ui.panels.plots import PlotsPanel
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"BulletLabUI",
|
|
25
|
+
"widgets",
|
|
26
|
+
"ExplorerPanel",
|
|
27
|
+
"PropertiesPanel",
|
|
28
|
+
"TelemetryPanel",
|
|
29
|
+
"ConsolePanel",
|
|
30
|
+
"PlotsPanel",
|
|
31
|
+
]
|