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.
@@ -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())})"
@@ -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
+ ]