tassello 0.8.0 → 0.9.0
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.
- package/CHANGELOG.md +13 -0
- package/README.md +27 -1
- package/dist/server/message_format.d.ts +1 -0
- package/dist/server/message_format.js +21 -0
- package/dist/server/session.js +2 -0
- package/dist/server/topic_monitor.d.ts +3 -1
- package/dist/server/topic_monitor.js +51 -6
- package/dist/shared/monitor.d.ts +6 -0
- package/dist/shared/monitor.js +4 -0
- package/dist/shared/protocol.d.ts +7 -2
- package/dist/types/client/connection.d.ts +4 -3
- package/dist/types/client/icons.d.ts +2 -0
- package/dist/types/client/sdk/index.d.ts +1 -1
- package/dist/types/client/widgets/types.d.ts +1 -0
- package/dist/types/shared/monitor.d.ts +6 -0
- package/dist/types/shared/protocol.d.ts +7 -2
- package/dist/www/assets/Joystick.js +1 -1
- package/dist/www/assets/RobotModelView.js +2 -2
- package/dist/www/assets/TerminalView.js +2 -2
- package/dist/www/assets/dialogs.js +1 -1
- package/dist/www/assets/icons.js +1 -1
- package/dist/www/assets/index.css +1 -1
- package/dist/www/assets/index.js +50 -45
- package/dist/www/assets/shared/tassello-sdk.js +1 -1
- package/docs/plugins.md +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ change the plugin API (`tassello/sdk`, `tassello/server`, `tassello/vite`), as n
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
- Plot widget: numbers of any topic over time, as rqt_plot and PlotJuggler plot them
|
|
9
|
+
(`/odom.twist.twist.linear.x`, a battery's voltage, joints' efforts), up to 8 series on a
|
|
10
|
+
chart, from every message. Live over the latest *time shown*; paused, zoomed (drag across it,
|
|
11
|
+
the wheel, a pinch) and scrolled through time (Shift+wheel, a swipe, the arrow keys); the value
|
|
12
|
+
axis fitted to what is shown or fixed; the values at the pointer; series hidden from the
|
|
13
|
+
legend. What is shown exported as CSV, a row a message. The series are picked in the widget,
|
|
14
|
+
from the topics of the graph and the numeric fields of their latest message (a `JointState`'s
|
|
15
|
+
named by joint), and saved with the panel, or listed in its properties.
|
|
16
|
+
- The topic monitor (`topic.monitor`) samples: the numbers of every message at some fields, sent
|
|
17
|
+
ten times a second (`TopicSamples`). Plugins: `subscribeTopicMonitor`'s `samples`; its `stats`
|
|
18
|
+
and `echo` handlers are optional.
|
|
19
|
+
- Plugins: widgets get `updateConfig`, which changes their properties and saves the panel, as the
|
|
20
|
+
side panel does.
|
|
8
21
|
- System Monitor widget: the robot's computer, every second. The CPU's load, of each core (those
|
|
9
22
|
offline too), their frequencies and the load average; the memory, its cache and the swap; the
|
|
10
23
|
temperatures of the thermal zones and hwmon chips, by chip with their hottest, against their
|
package/README.md
CHANGED
|
@@ -12,7 +12,8 @@ and plugins add more.
|
|
|
12
12
|
- **Built in**: teleop pads, a Nav2 map with goals, the robot's URDF in 3D, an arm jogger, cameras,
|
|
13
13
|
images, command buttons, a systemd application manager, a behavior tree inspector and
|
|
14
14
|
editor, the ROS log of every node, the robot's diagnostics, its topics, services and actions
|
|
15
|
-
as the `ros2` command line shows them,
|
|
15
|
+
as the `ros2` command line shows them, plots of their numbers over time, its nodes'
|
|
16
|
+
parameters, its rosbags (recorded, listed,
|
|
16
17
|
downloaded and played back), a file manager, a terminal, its networks (Wi-Fi
|
|
17
18
|
joined, connections switched, the robot as an access point), and its computer: CPU, memory,
|
|
18
19
|
temperatures, a Raspberry Pi's throttling, a Jetson's GPU and power, disks and networks.
|
|
@@ -133,6 +134,7 @@ after the others on the devices already arranged.
|
|
|
133
134
|
| **Logs** | The ROS log (`/rosout`), as rqt_console and swri_console show it: every node's messages coloured by level, the nodes running and those that logged with their counts, filtered by node, by level and by text (terms or a regular expression, to show or to hide). Pause, follow the newest, clear, download, and a message's details: where it was logged |
|
|
134
135
|
| **Diagnostics** | The robot's diagnostics (`/diagnostics`, or the aggregator's `/diagnostics_agg`), as rqt_robot_monitor shows them: each status's level, the groups with the worst under them, those with problems unfolded. As a tree, as a list (the worst first) or as the latest changes of level, filtered by level and by text; a widget can be for some statuses only. A status's details: its message, hardware ID, values and latest changes. Pause, download, and forget the statuses no longer published |
|
|
135
136
|
| **ROS Monitor** | The ROS graph, as the `ros2` command line shows it, on three sheets: its topics, services and actions, filtered by name or type, the hidden ones on demand. A topic's messages, their rate, bandwidth and delay, its publishers and subscriptions with their QoS and those that cannot talk, a message to publish once or at a rate (`ros2 topic echo`, `hz`, `bw`, `delay`, `info -v`, `pub`); a service to call, once or at a rate (`ros2 service call`, `info`, `echo`); every goal of an action, whoever sent it, with its feedback, and a goal to send or cancel (`ros2 action send_goal`, `info`); their types' definitions (`ros2 interface show`) |
|
|
137
|
+
| **Plot** | Numbers of any topic over time, as rqt_plot and PlotJuggler plot them: `/odom.twist.twist.linear.x`, a battery's voltage, joints' efforts (named by their joints), up to 8 series on a chart, from every message. Paused, zoomed and scrolled through time, each series shown or hidden; what is shown exported as CSV. Its series are picked in the widget, from the topics of the graph and the fields of their messages, or listed in its properties |
|
|
136
138
|
| **Parameters** | Nodes' parameters, as rqt_reconfigure and `ros2 param` show them: the nodes of the graph, and the parameters of the one chosen grouped by their prefixes, each edited in place (a switch, a slider within its range, a field), live as they change wherever they are set. Their descriptions, ranges and constraints (`ros2 param describe`); new ones set and undeclared ones deleted (`set`, `delete`); all of them dumped as YAML, downloaded or saved on the robot (`dump`), and loaded from a YAML file of the robot or of the page's device (`load`) |
|
|
137
139
|
| **Rosbags** | The robot's rosbags, as `ros2 bag` makes and reads them: those of a folder, the newest first, with when, how long and how big, their storage and compression, each topic's type, count and rate (`ros2 bag info`); downloaded, deleted, or played on the robot's topics (`ros2 bag play`: paused, sought, sped up, looped, some topics only). New ones recorded (`ros2 bag record`): topics chosen from the graph, all of them, or matching an expression, with rosbag2's options: the storage (MCAP, SQLite) and its preset, a new file by size or duration, compression of each file or message. Every page sees the recordings and the playback, the behavior tree inspectors' too |
|
|
138
140
|
| **File Manager** | The robot's files: folders unfolded in place, places (home, the ROS workspaces, bookmarks) and the disks' free space, an entry's details and permissions, what takes a folder's space. Make, rename, cut, copy, paste, drag onto folders, delete, download (folders as `.tar.gz`) and upload (drop files and folders onto it). Text files edited in tabs as gedit does: highlighting, search and replace, undo; images and PGM maps shown |
|
|
@@ -308,6 +310,26 @@ Notes on some of them:
|
|
|
308
310
|
|
|
309
311
|
Messages, requests and goals are made from rclnodejs's code of their types, which Tassello
|
|
310
312
|
makes when it starts for the workspaces sourced (see Install).
|
|
313
|
+
- **Plot** draws up to 8 series on one value axis: a topic, a dot and its field
|
|
314
|
+
(`/odom.twist.twist.linear.x`, `/joint_states.effort[2]`, list items by their index), then a
|
|
315
|
+
label if any, one a line in its *series* property. *Series…* picks them in the widget, in view
|
|
316
|
+
mode, and saves them with the panel: a topic of the graph, then fields among those of its
|
|
317
|
+
latest message that are numbers, with their values (a `JointState`'s by joint name), or a field
|
|
318
|
+
typed. Numbers, booleans (0 or 1) and times (in seconds, a header's stamp) are plotted; NaN and
|
|
319
|
+
infinities break the line, as does a topic that stopped. The server reads every message of the
|
|
320
|
+
topics plotted, not 10 a second, and sends the numbers of the fields asked for ten times a
|
|
321
|
+
second; each sample is on the server's clock, when its message came. The page keeps the
|
|
322
|
+
latest 10 minutes of each series (200,000 samples at most, 200 s of a 1 kHz topic), from when
|
|
323
|
+
the widget opened.
|
|
324
|
+
|
|
325
|
+
Live, it shows the latest *time shown* (30 s), the value axis fitted to what is shown or
|
|
326
|
+
*fixed*. Drag across it to zoom in on that span, which pauses it; the wheel zooms around the
|
|
327
|
+
pointer (live, a shorter or longer span to now), Shift+wheel or a sideways swipe scrolls
|
|
328
|
+
through time, two fingers pinch; *Live* or a double click goes on. Space pauses, the arrow keys
|
|
329
|
+
scroll, `+` and `-` zoom, Escape goes back live. The pointer shows each series' value at its
|
|
330
|
+
time, the legend the values at the right edge; clicking a series in the legend hides it. The
|
|
331
|
+
download button exports what is shown as CSV: the time (s since the epoch), then a column a
|
|
332
|
+
series, a row a message (those of a topic's series on the same rows).
|
|
311
333
|
- **Parameters** lists the nodes of the graph (but the hidden ones, as `ros2 node list` does), or
|
|
312
334
|
shows one node only, its *node* (a panel to tune `/controller_server`). A node's parameters are
|
|
313
335
|
read through its parameter services (`rcl_interfaces`), grouped by the prefixes of their names
|
|
@@ -572,6 +594,10 @@ Safety and bandwidth are the server's concern:
|
|
|
572
594
|
one recording per folder, for every page (the behavior tree inspectors' among them), one
|
|
573
595
|
playback. A folder of bags is read once for every page showing it, every 2 s, a bag's metadata
|
|
574
596
|
again only when its files change; the recordings' sizes every second.
|
|
597
|
+
- A topic plotted is followed once for every page (the ROS Monitor's subscription, with its
|
|
598
|
+
publishers' QoS): its messages are deserialized only while a page plots it, and each page gets
|
|
599
|
+
the numbers of its own fields, ten batches a second: for a 200 Hz topic, about 3 KB a second
|
|
600
|
+
for when its messages came, and 4 KB for each field.
|
|
575
601
|
- The system monitor's sensors are read once for every page, every second while one watches and
|
|
576
602
|
for 30 s after the last leaves; they are looked for again every 30 s (USB ones come and go).
|
|
577
603
|
Each page gets a sample a second, about 4 KB.
|
|
@@ -21,4 +21,5 @@ export interface RosTime {
|
|
|
21
21
|
}
|
|
22
22
|
export declare function toRosMessage(value: unknown, type: string, schemas: Record<string, MessageSchema>, now: () => RosTime, path?: string): Record<string, unknown>;
|
|
23
23
|
export declare function fieldAt(message: unknown, path: string): unknown;
|
|
24
|
+
export declare function sampleValue(value: unknown): number | null;
|
|
24
25
|
export declare function echoJson(value: unknown, limit: number): unknown;
|
|
@@ -307,6 +307,27 @@ export function fieldAt(message, path) {
|
|
|
307
307
|
}
|
|
308
308
|
return value;
|
|
309
309
|
}
|
|
310
|
+
// A field's value as a plot takes it: numbers, 64 bit integers, booleans (0
|
|
311
|
+
// or 1), times and durations (in seconds); null for anything else, and for
|
|
312
|
+
// NaN and infinities
|
|
313
|
+
export function sampleValue(value) {
|
|
314
|
+
if (typeof value === 'number') {
|
|
315
|
+
return Number.isFinite(value) ? value : null;
|
|
316
|
+
}
|
|
317
|
+
if (typeof value === 'bigint') {
|
|
318
|
+
return Number(value);
|
|
319
|
+
}
|
|
320
|
+
if (typeof value === 'boolean') {
|
|
321
|
+
return value ? 1 : 0;
|
|
322
|
+
}
|
|
323
|
+
if (value !== null && typeof value === 'object' && 'sec' in value && 'nanosec' in value) {
|
|
324
|
+
const { sec, nanosec } = value;
|
|
325
|
+
if (typeof sec === 'number' && typeof nanosec === 'number') {
|
|
326
|
+
return sec + nanosec / 1e9;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
310
331
|
function number(value) {
|
|
311
332
|
if (Number.isFinite(value)) {
|
|
312
333
|
return value;
|
package/dist/server/session.js
CHANGED
|
@@ -404,9 +404,11 @@ export class Session {
|
|
|
404
404
|
echo: echo && typeof echo === 'object'
|
|
405
405
|
? { field: optionalString(echo.field), truncate: number(echo.truncate, 1, MAX_TRUNCATE, 128) }
|
|
406
406
|
: null,
|
|
407
|
+
samples: message.samples === undefined ? null : strings(message.samples, 'samples'),
|
|
407
408
|
onStatus: (state, text) => this.sendStreamStatus(id, state, text),
|
|
408
409
|
onStats: (stats) => this.send({ type: 'topic.stats', id, stats }),
|
|
409
410
|
onEcho: (update) => this.send({ type: 'topic.echo', id, ...update }),
|
|
411
|
+
onSamples: (samples) => this.send({ type: 'topic.samples', id, ...samples }),
|
|
410
412
|
}));
|
|
411
413
|
return;
|
|
412
414
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import rclnodejs from 'rclnodejs';
|
|
2
|
-
import { type TopicEcho, type TopicStats } from '../shared/monitor.ts';
|
|
2
|
+
import { type TopicEcho, type TopicSamples, type TopicStats } from '../shared/monitor.ts';
|
|
3
3
|
import type { StreamState } from './frame_stream.ts';
|
|
4
4
|
import type { Interfaces } from './interfaces.ts';
|
|
5
5
|
import { type Logger } from './util.ts';
|
|
@@ -9,11 +9,13 @@ export interface MonitorOptions {
|
|
|
9
9
|
field: string;
|
|
10
10
|
truncate: number;
|
|
11
11
|
} | null;
|
|
12
|
+
samples: string[] | null;
|
|
12
13
|
}
|
|
13
14
|
export interface MonitorWatcher extends MonitorOptions {
|
|
14
15
|
onStatus: (state: StreamState, message: string) => void;
|
|
15
16
|
onStats: (stats: TopicStats) => void;
|
|
16
17
|
onEcho: (echo: TopicEcho) => void;
|
|
18
|
+
onSamples: (samples: TopicSamples) => void;
|
|
17
19
|
}
|
|
18
20
|
export declare class TopicMonitors {
|
|
19
21
|
private readonly node;
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
// at once. One subscription per topic, with its publishers' QoS, shared by
|
|
3
3
|
// its pages, taking the messages serialized (rclnodejs's raw subscriptions):
|
|
4
4
|
// their size is exact, a header's stamp is read from the bytes, and only the
|
|
5
|
-
// messages echoed are deserialized, at most ECHO_RATE a second for each page
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// messages echoed are deserialized, at most ECHO_RATE a second for each page,
|
|
6
|
+
// but for those of a topic a page plots: all of them, whose numbers at the
|
|
7
|
+
// page's fields it gets every SAMPLES_PERIOD_MS. Each page gets the figures
|
|
8
|
+
// of its own window (ros2 topic hz --window) twice a second while messages
|
|
9
|
+
// come.
|
|
8
10
|
import { performance } from 'node:perf_hooks';
|
|
9
11
|
import rclnodejs from 'rclnodejs';
|
|
10
|
-
import { ECHO_RATE, MAX_TRUNCATE, MAX_WINDOW } from '../shared/monitor.js';
|
|
12
|
+
import { ECHO_RATE, MAX_SAMPLED_FIELDS, MAX_TRUNCATE, MAX_WINDOW, SAMPLES_PERIOD_MS, } from '../shared/monitor.js';
|
|
11
13
|
import { missingCodeHint } from './message_code.js';
|
|
12
|
-
import { echoJson, fieldAt } from './message_format.js';
|
|
14
|
+
import { echoJson, fieldAt, sampleValue } from './message_format.js';
|
|
13
15
|
import { publishersQos } from './qos.js';
|
|
14
16
|
import { headerStamp, TopicStatsWindow } from './topic_stats.js';
|
|
15
17
|
import { absoluteTopic, errorMessage } from './util.js';
|
|
@@ -28,6 +30,7 @@ class MonitoredTopic {
|
|
|
28
30
|
subscription = null;
|
|
29
31
|
retryTimer = null;
|
|
30
32
|
statsTimer;
|
|
33
|
+
samplesTimer = null;
|
|
31
34
|
type = '';
|
|
32
35
|
hasHeader = false;
|
|
33
36
|
latest = null;
|
|
@@ -48,6 +51,8 @@ class MonitoredTopic {
|
|
|
48
51
|
echoCount: this.window.count,
|
|
49
52
|
echoAt: 0,
|
|
50
53
|
timer: null,
|
|
54
|
+
samples: null,
|
|
55
|
+
unreadable: false,
|
|
51
56
|
};
|
|
52
57
|
this.watchers.set(watcher, delivery);
|
|
53
58
|
watcher.onStatus(this.state, this.message);
|
|
@@ -57,6 +62,10 @@ class MonitoredTopic {
|
|
|
57
62
|
delivery.echoCount = this.latest.count - 1;
|
|
58
63
|
this.echo(watcher, delivery);
|
|
59
64
|
}
|
|
65
|
+
// And plotted: a battery's state, that comes once a second or less
|
|
66
|
+
if (this.latest) {
|
|
67
|
+
this.sample(watcher, delivery, this.latest);
|
|
68
|
+
}
|
|
60
69
|
}
|
|
61
70
|
remove(watcher) {
|
|
62
71
|
const delivery = this.watchers.get(watcher);
|
|
@@ -70,6 +79,9 @@ class MonitoredTopic {
|
|
|
70
79
|
if (this.retryTimer) {
|
|
71
80
|
clearTimeout(this.retryTimer);
|
|
72
81
|
}
|
|
82
|
+
if (this.samplesTimer) {
|
|
83
|
+
clearTimeout(this.samplesTimer);
|
|
84
|
+
}
|
|
73
85
|
for (const delivery of this.watchers.values()) {
|
|
74
86
|
if (delivery.timer) {
|
|
75
87
|
clearTimeout(delivery.timer);
|
|
@@ -111,10 +123,42 @@ class MonitoredTopic {
|
|
|
111
123
|
}
|
|
112
124
|
const receivedAt = Date.now();
|
|
113
125
|
this.window.add(performance.now() / 1000, buffer.length, delay, receivedAt);
|
|
114
|
-
|
|
126
|
+
const latest = { count: this.window.count, buffer, receivedAt, message: undefined };
|
|
127
|
+
this.latest = latest;
|
|
115
128
|
this.setState('streaming', this.type);
|
|
116
129
|
for (const [watcher, delivery] of this.watchers) {
|
|
117
130
|
this.echo(watcher, delivery);
|
|
131
|
+
this.sample(watcher, delivery, latest);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// The numbers of a message at the page's fields, sent with those of the
|
|
135
|
+
// next ones
|
|
136
|
+
sample(watcher, delivery, latest) {
|
|
137
|
+
if (!watcher.samples) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
latest.message ??= this.deserialize(latest.buffer);
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
if (!delivery.unreadable) {
|
|
145
|
+
delivery.unreadable = true;
|
|
146
|
+
watcher.onStatus('error', `Cannot read the messages of ${this.topic}: ${errorMessage(e)}`);
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const samples = (delivery.samples ??= { at: [], values: watcher.samples.map(() => []) });
|
|
151
|
+
samples.at.push(latest.receivedAt);
|
|
152
|
+
watcher.samples.forEach((field, index) => samples.values[index].push(sampleValue(fieldAt(latest.message, field))));
|
|
153
|
+
this.samplesTimer ??= setTimeout(() => this.sendSamples(), SAMPLES_PERIOD_MS);
|
|
154
|
+
}
|
|
155
|
+
sendSamples() {
|
|
156
|
+
this.samplesTimer = null;
|
|
157
|
+
for (const [watcher, delivery] of this.watchers) {
|
|
158
|
+
if (delivery.samples) {
|
|
159
|
+
watcher.onSamples(delivery.samples);
|
|
160
|
+
delivery.samples = null;
|
|
161
|
+
}
|
|
118
162
|
}
|
|
119
163
|
}
|
|
120
164
|
// The latest message, now or when the page's rate allows
|
|
@@ -210,6 +254,7 @@ export class TopicMonitors {
|
|
|
210
254
|
field: watcher.echo.field.trim(),
|
|
211
255
|
truncate: Math.round(Math.min(MAX_TRUNCATE, Math.max(1, watcher.echo.truncate))),
|
|
212
256
|
},
|
|
257
|
+
samples: watcher.samples?.length ? watcher.samples.slice(0, MAX_SAMPLED_FIELDS) : null,
|
|
213
258
|
};
|
|
214
259
|
let monitored = this.topics.get(name);
|
|
215
260
|
if (!monitored) {
|
package/dist/shared/monitor.d.ts
CHANGED
|
@@ -73,6 +73,10 @@ export interface BigIntValue {
|
|
|
73
73
|
export interface SpecialNumber {
|
|
74
74
|
$number: 'nan' | 'inf' | '-inf';
|
|
75
75
|
}
|
|
76
|
+
export interface TopicSamples {
|
|
77
|
+
at: number[];
|
|
78
|
+
values: (number | null)[][];
|
|
79
|
+
}
|
|
76
80
|
export interface TopicEcho {
|
|
77
81
|
seq: number;
|
|
78
82
|
receivedAt: number;
|
|
@@ -142,3 +146,5 @@ export declare const GRAPH_PERIOD_MS = 2000;
|
|
|
142
146
|
export declare const ECHO_RATE = 10;
|
|
143
147
|
export declare const MAX_WINDOW = 10000;
|
|
144
148
|
export declare const MAX_TRUNCATE = 10000;
|
|
149
|
+
export declare const MAX_SAMPLED_FIELDS = 64;
|
|
150
|
+
export declare const SAMPLES_PERIOD_MS = 100;
|
package/dist/shared/monitor.js
CHANGED
|
@@ -10,3 +10,7 @@ export const ECHO_RATE = 10;
|
|
|
10
10
|
export const MAX_WINDOW = 10_000;
|
|
11
11
|
// Of ros2 topic echo's --truncate-length: the page asks for less
|
|
12
12
|
export const MAX_TRUNCATE = 10_000;
|
|
13
|
+
// Fields sampled of a topic, at most, for a page
|
|
14
|
+
export const MAX_SAMPLED_FIELDS = 64;
|
|
15
|
+
// How often the samples of the messages since the last are sent
|
|
16
|
+
export const SAMPLES_PERIOD_MS = 100;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BtNodeModel } from './bt_nodes.ts';
|
|
2
2
|
import type { FsDisk } from './file_manager.ts';
|
|
3
|
-
import type { ActionGoal, GraphSnapshot, PublishQos, PublishState, TopicDetails, TopicEcho, TopicStats } from './monitor.ts';
|
|
3
|
+
import type { ActionGoal, GraphSnapshot, PublishQos, PublishState, TopicDetails, TopicEcho, TopicSamples, TopicStats } from './monitor.ts';
|
|
4
4
|
import type { NetworkRequest, NetworkState } from './networking.ts';
|
|
5
5
|
import type { ParamInfo, ParamValue } from './parameters.ts';
|
|
6
6
|
import type { BagCapabilities, BagInfo, BagsRequest, Playback, Recording, RecordOptions } from './rosbag.ts';
|
|
@@ -206,6 +206,7 @@ export interface TopicMonitorSubscribe {
|
|
|
206
206
|
field: string;
|
|
207
207
|
truncate: number;
|
|
208
208
|
} | null;
|
|
209
|
+
samples?: string[];
|
|
209
210
|
}
|
|
210
211
|
export interface TopicPublish {
|
|
211
212
|
type: 'topic.publish';
|
|
@@ -659,6 +660,10 @@ export interface TopicEchoUpdate extends TopicEcho {
|
|
|
659
660
|
type: 'topic.echo';
|
|
660
661
|
id: number;
|
|
661
662
|
}
|
|
663
|
+
export interface TopicSamplesUpdate extends TopicSamples {
|
|
664
|
+
type: 'topic.samples';
|
|
665
|
+
id: number;
|
|
666
|
+
}
|
|
662
667
|
export interface TopicPublished extends PublishState {
|
|
663
668
|
type: 'topic.published';
|
|
664
669
|
id: number;
|
|
@@ -694,7 +699,7 @@ export interface ActionGoals {
|
|
|
694
699
|
id: number;
|
|
695
700
|
goals: ActionGoal[];
|
|
696
701
|
}
|
|
697
|
-
export type ServerMessage = Hello | CommandStates | CommandUpdate | StreamStatus | TfUpdate | PathUpdate | OverlayUpdate | TopicMessage | ServicesUpdate | ServiceResult | LogsUpdate | ArmUpdate | Result | PluginData | BehaviorTreeInfo | BehaviorTreeUpdate | BehaviorTreeRecording | BehaviorTreePlaybackState | BehaviorTreeFile | BehaviorTreeModels | RosoutUpdate | DiagnosticsUpdate | TerminalOutput | TerminalExit | NetworkUpdate | GraphUpdate | TopicInfoUpdate | TopicStatsUpdate | TopicEchoUpdate | TopicPublished | ActionGoals | ParamsUpdate | BagsUpdate | SystemUpdate | PanelsChanged | ErrorMessage;
|
|
702
|
+
export type ServerMessage = Hello | CommandStates | CommandUpdate | StreamStatus | TfUpdate | PathUpdate | OverlayUpdate | TopicMessage | ServicesUpdate | ServiceResult | LogsUpdate | ArmUpdate | Result | PluginData | BehaviorTreeInfo | BehaviorTreeUpdate | BehaviorTreeRecording | BehaviorTreePlaybackState | BehaviorTreeFile | BehaviorTreeModels | RosoutUpdate | DiagnosticsUpdate | TerminalOutput | TerminalExit | NetworkUpdate | GraphUpdate | TopicInfoUpdate | TopicStatsUpdate | TopicEchoUpdate | TopicSamplesUpdate | TopicPublished | ActionGoals | ParamsUpdate | BagsUpdate | SystemUpdate | PanelsChanged | ErrorMessage;
|
|
698
703
|
export declare const FRAME_HEADER_BYTES = 11;
|
|
699
704
|
export declare const IMAGE_FORMATS: readonly ['image/jpeg', 'image/png'];
|
|
700
705
|
export interface GridInfo {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ActionGoal, CancelResult, GraphSnapshot, InterfaceInfo, PublishState, TopicDetails, TopicEcho, TopicStats } from '../shared/monitor.ts';
|
|
1
|
+
import type { ActionGoal, CancelResult, GraphSnapshot, InterfaceInfo, PublishState, TopicDetails, TopicEcho, TopicSamples, TopicStats } from '../shared/monitor.ts';
|
|
2
2
|
import type { NetworkRequest, NetworkResult, NetworkSecrets } from '../shared/networking.ts';
|
|
3
3
|
import type { ParamResult, ParamValue } from '../shared/parameters.ts';
|
|
4
4
|
import type { BagsRequest, Playback, Recording, RecordOptions } from '../shared/rosbag.ts';
|
|
@@ -56,8 +56,9 @@ export interface OverlayHandlers {
|
|
|
56
56
|
}
|
|
57
57
|
export interface TopicMonitorHandlers {
|
|
58
58
|
status: (state: StreamStatus['state'], message: string) => void;
|
|
59
|
-
stats
|
|
60
|
-
echo
|
|
59
|
+
stats?: (stats: TopicStats) => void;
|
|
60
|
+
echo?: (echo: TopicEcho) => void;
|
|
61
|
+
samples?: (samples: TopicSamples) => void;
|
|
61
62
|
}
|
|
62
63
|
export interface ActionHandlers {
|
|
63
64
|
status: (state: StreamStatus['state'], message: string) => void;
|
|
@@ -95,4 +95,6 @@ export declare const ServiceIcon: (props: SVGProps<SVGSVGElement>) => import("re
|
|
|
95
95
|
export declare const ActionIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
|
|
96
96
|
export declare const MonitorIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
|
|
97
97
|
export declare const ChipIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
|
|
98
|
+
export declare const PlotIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
|
|
99
|
+
export declare const CheckIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
|
|
98
100
|
export declare const SendIcon: (props: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
|
|
@@ -10,7 +10,7 @@ export type { BtNodeModel, BtPortModel } from '../../shared/bt_nodes.ts';
|
|
|
10
10
|
export type { ParamDescriptor, ParamInfo, ParamResult, ParamScalar, ParamType, ParamValue } from '../../shared/parameters.ts';
|
|
11
11
|
export type { BagCapabilities, BagFile, BagInfo, BagsRequest, BagTopic, CompressionMode, Playback, PlayRequest, Recording, RecordOptions, RecordRequest, } from '../../shared/rosbag.ts';
|
|
12
12
|
export type { DiskIo, Fan, Gpu, NetworkIo, PowerRail, SystemInfo, SystemSample, Temperature, TrendPoint, } from '../../shared/system.ts';
|
|
13
|
-
export type { ActionGoal, BigIntValue, CancelResult, GoalStatus, GraphEndpoints, GraphSnapshot, GraphTopic, InterfaceInfo, InterfaceKind, MessageConstant, MessageField, MessageSchema, PublishQos, PublishState, QosProblem, QosProfile, SpecialNumber, Spread, TopicDetails, TopicEcho, TopicEndpoint, TopicStats, TruncatedValue, } from '../../shared/monitor.ts';
|
|
13
|
+
export type { ActionGoal, BigIntValue, CancelResult, GoalStatus, GraphEndpoints, GraphSnapshot, GraphTopic, InterfaceInfo, InterfaceKind, MessageConstant, MessageField, MessageSchema, PublishQos, PublishState, QosProblem, QosProfile, SpecialNumber, Spread, TopicDetails, TopicEcho, TopicEndpoint, TopicSamples, TopicStats, TruncatedValue, } from '../../shared/monitor.ts';
|
|
14
14
|
export type { ConnectionKind, NetworkActivity, NetworkConnection, NetworkDevice, NetworkPermissions, NetworkRequest, NetworkResult, NetworkSecrets, NetworkSettings, NetworkState, WifiNetwork, WifiSecurity, } from '../../shared/networking.ts';
|
|
15
15
|
export type { FsAnalysis, FsConflict, FsDetails, FsDisk, FsEntry, FsListing, FsOperation, FsPlaces, FsResult, FsSaved, FsSpace, FsText, } from '../../shared/file_manager.ts';
|
|
16
16
|
export type { Panel, WidgetInstance } from '../../shared/panel.ts';
|
|
@@ -73,6 +73,10 @@ export interface BigIntValue {
|
|
|
73
73
|
export interface SpecialNumber {
|
|
74
74
|
$number: 'nan' | 'inf' | '-inf';
|
|
75
75
|
}
|
|
76
|
+
export interface TopicSamples {
|
|
77
|
+
at: number[];
|
|
78
|
+
values: (number | null)[][];
|
|
79
|
+
}
|
|
76
80
|
export interface TopicEcho {
|
|
77
81
|
seq: number;
|
|
78
82
|
receivedAt: number;
|
|
@@ -142,3 +146,5 @@ export declare const GRAPH_PERIOD_MS = 2000;
|
|
|
142
146
|
export declare const ECHO_RATE = 10;
|
|
143
147
|
export declare const MAX_WINDOW = 10000;
|
|
144
148
|
export declare const MAX_TRUNCATE = 10000;
|
|
149
|
+
export declare const MAX_SAMPLED_FIELDS = 64;
|
|
150
|
+
export declare const SAMPLES_PERIOD_MS = 100;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BtNodeModel } from './bt_nodes.ts';
|
|
2
2
|
import type { FsDisk } from './file_manager.ts';
|
|
3
|
-
import type { ActionGoal, GraphSnapshot, PublishQos, PublishState, TopicDetails, TopicEcho, TopicStats } from './monitor.ts';
|
|
3
|
+
import type { ActionGoal, GraphSnapshot, PublishQos, PublishState, TopicDetails, TopicEcho, TopicSamples, TopicStats } from './monitor.ts';
|
|
4
4
|
import type { NetworkRequest, NetworkState } from './networking.ts';
|
|
5
5
|
import type { ParamInfo, ParamValue } from './parameters.ts';
|
|
6
6
|
import type { BagCapabilities, BagInfo, BagsRequest, Playback, Recording, RecordOptions } from './rosbag.ts';
|
|
@@ -206,6 +206,7 @@ export interface TopicMonitorSubscribe {
|
|
|
206
206
|
field: string;
|
|
207
207
|
truncate: number;
|
|
208
208
|
} | null;
|
|
209
|
+
samples?: string[];
|
|
209
210
|
}
|
|
210
211
|
export interface TopicPublish {
|
|
211
212
|
type: 'topic.publish';
|
|
@@ -659,6 +660,10 @@ export interface TopicEchoUpdate extends TopicEcho {
|
|
|
659
660
|
type: 'topic.echo';
|
|
660
661
|
id: number;
|
|
661
662
|
}
|
|
663
|
+
export interface TopicSamplesUpdate extends TopicSamples {
|
|
664
|
+
type: 'topic.samples';
|
|
665
|
+
id: number;
|
|
666
|
+
}
|
|
662
667
|
export interface TopicPublished extends PublishState {
|
|
663
668
|
type: 'topic.published';
|
|
664
669
|
id: number;
|
|
@@ -694,7 +699,7 @@ export interface ActionGoals {
|
|
|
694
699
|
id: number;
|
|
695
700
|
goals: ActionGoal[];
|
|
696
701
|
}
|
|
697
|
-
export type ServerMessage = Hello | CommandStates | CommandUpdate | StreamStatus | TfUpdate | PathUpdate | OverlayUpdate | TopicMessage | ServicesUpdate | ServiceResult | LogsUpdate | ArmUpdate | Result | PluginData | BehaviorTreeInfo | BehaviorTreeUpdate | BehaviorTreeRecording | BehaviorTreePlaybackState | BehaviorTreeFile | BehaviorTreeModels | RosoutUpdate | DiagnosticsUpdate | TerminalOutput | TerminalExit | NetworkUpdate | GraphUpdate | TopicInfoUpdate | TopicStatsUpdate | TopicEchoUpdate | TopicPublished | ActionGoals | ParamsUpdate | BagsUpdate | SystemUpdate | PanelsChanged | ErrorMessage;
|
|
702
|
+
export type ServerMessage = Hello | CommandStates | CommandUpdate | StreamStatus | TfUpdate | PathUpdate | OverlayUpdate | TopicMessage | ServicesUpdate | ServiceResult | LogsUpdate | ArmUpdate | Result | PluginData | BehaviorTreeInfo | BehaviorTreeUpdate | BehaviorTreeRecording | BehaviorTreePlaybackState | BehaviorTreeFile | BehaviorTreeModels | RosoutUpdate | DiagnosticsUpdate | TerminalOutput | TerminalExit | NetworkUpdate | GraphUpdate | TopicInfoUpdate | TopicStatsUpdate | TopicEchoUpdate | TopicSamplesUpdate | TopicPublished | ActionGoals | ParamsUpdate | BagsUpdate | SystemUpdate | PanelsChanged | ErrorMessage;
|
|
698
703
|
export declare const FRAME_HEADER_BYTES = 11;
|
|
699
704
|
export declare const IMAGE_FORMATS: readonly ['image/jpeg', 'image/png'];
|
|
700
705
|
export interface GridInfo {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./rolldown-runtime.js";import{t}from"./react.js";import{
|
|
1
|
+
import{n as e}from"./rolldown-runtime.js";import{t}from"./react.js";import{Kt as n,Zt as r}from"./icons.js";import{t as i}from"./jsx-runtime.js";var a=class extends Error{status;constructor(e,t){super(t),this.status=e}};async function o(e,t,i){let o=await fetch(t,{method:e,headers:{"Content-Type":`application/json`,[r]:n.clientId??``},body:i===void 0?void 0:JSON.stringify(i)});if(!o.ok){let e=`${o.status} ${o.statusText}`;try{e=(await o.json()).error??e}catch{}throw new a(o.status,e)}return o.status===204?void 0:await o.json()}var s=e=>`/api/panels/${encodeURIComponent(e)}`;async function c(e){let t=await fetch(`/api/uploads`,{method:`POST`,body:e});if(!t.ok){let e=await t.json().catch(()=>null);throw Error(e?.error??`${t.status} ${t.statusText}`)}return await t.json()}function l(e){let t=e.trim();if(t.startsWith(`uploads/`))return`/api/uploads/${encodeURIComponent(t.slice(8))}`;let n=/^package:\/\/([^/]+)\/(.+)$/.exec(t);return n?`/api/packages/${encodeURIComponent(n[1])}/${n[2].split(`/`).map(encodeURIComponent).join(`/`)}`:t.startsWith(`/`)||t.startsWith(`~/`)?`/api/files?path=${encodeURIComponent(t)}`:null}var u={listPanels:()=>o(`GET`,`/api/panels`),getPanel:e=>o(`GET`,s(e)),createPanel:e=>o(`POST`,`/api/panels`,e),savePanel:e=>o(`PUT`,s(e.id),e),deletePanel:e=>o(`DELETE`,s(e)),listTopics:()=>o(`GET`,`/api/topics`),listPlugins:()=>o(`GET`,`/api/plugins`),listFiles:(e,t=`image`)=>o(`GET`,`/api/files/list?dir=${encodeURIComponent(e)}&kind=${t}`),uploadFile:async e=>`uploads/${(await c(e)).name}`};function d(e,t={}){let n=new URLSearchParams;for(let[e,r]of Object.entries(t))for(let t of r==null?[]:[r].flat())n.append(e,t);let r=n.toString();return`/api/fs/${e}${r?`?${r}`:``}`}var f={places:()=>o(`GET`,d(`places`)),list:(e,t)=>o(`GET`,d(`list`,{path:e,known:t})),details:e=>o(`GET`,d(`details`,{path:e})),disks:()=>o(`GET`,d(`disks`)),analyze:e=>o(`GET`,d(`analysis`,{path:e})),readText:(e,t)=>o(`GET`,d(`text`,{path:e,stamp:t})),saveText:(e,t,n,r=!1)=>o(`PUT`,d(`text`),{path:e,text:t,base:n,create:r}),operate:e=>o(`POST`,d(`op`),e),downloadUrl:e=>d(`download`,{path:e}),viewUrl:(e,t)=>d(`view`,{path:e,v:t}),upload(e,t,i,a,o){let s=new XMLHttpRequest;return{result:new Promise((c,l)=>{s.open(`POST`,d(`upload`,{path:e,name:t,conflict:a})),s.setRequestHeader(r,n.clientId??``),s.upload.onprogress=e=>o(e.loaded),s.onload=()=>{let e=null;try{e=JSON.parse(s.responseText)}catch{}s.status>=200&&s.status<300&&e?c(e):l(Error(e?.error??`${s.status} ${s.statusText}`))},s.onerror=()=>l(Error(`Cannot reach the robot`)),s.onabort=()=>l(new DOMException(`Cancelled`,`AbortError`)),s.send(i)}),abort:()=>s.abort()}}},p=e(t(),1),m=e(i(),1),h=5e3,g=[],_=1,v=new Set;function y(e){g=e,v.forEach(e=>e())}function b(e){y(g.filter(t=>t.id!==e))}function x(e,t=`info`,n){let r=_++;y([...g.filter(t=>t.message!==e),{id:r,message:e,kind:t,action:n}].slice(-4)),setTimeout(()=>b(r),h)}function S(e){return v.add(e),()=>v.delete(e)}function C(){let e=(0,p.useSyncExternalStore)(S,()=>g);return(0,m.jsx)(`div`,{className:`notifications`,role:`status`,"aria-live":`polite`,children:e.map(e=>(0,m.jsxs)(`div`,{className:`notification ${e.kind}`,children:[(0,m.jsx)(`span`,{children:e.message}),e.action&&(0,m.jsx)(`button`,{type:`button`,className:`link-button`,onClick:()=>{e.action?.run(),b(e.id)},children:e.action.label}),(0,m.jsx)(`button`,{type:`button`,className:`notification-close`,"aria-label":`Dismiss`,onClick:()=>b(e.id),children:`×`})]},e.id))})}function w(e){return e}function T(e){return e}var E=34,D=.08;function O(e){let t=Math.abs(e);return t<D?0:Math.sign(e)*(t-D)/.92}function k({axes:e,onMove:t,disabled:n,label:r}){let i=(0,p.useRef)(null),a=(0,p.useRef)(null),[o,s]=(0,p.useState)({x:0,y:0}),[c,l]=(0,p.useState)(!1),u=(0,p.useRef)(t);u.current=t;let d=(e,t)=>{s({x:e,y:t}),u.current(O(e),O(t))},f=t=>{let n=i.current.getBoundingClientRect(),r=n.width*E/100,a=e===`y`?0:(t.clientX-(n.left+n.width/2))/r,o=e===`x`?0:-(t.clientY-(n.top+n.height/2))/r,s=Math.hypot(a,o);s>1&&(a/=s,o/=s),d(a,o)},h=e=>{a.current===null||e&&e.pointerId!==a.current||(a.current=null,l(!1),d(0,0))},g=(0,p.useRef)(h);return g.current=h,(0,p.useEffect)(()=>{let e=()=>document.hidden&&g.current();return document.addEventListener(`visibilitychange`,e),()=>document.removeEventListener(`visibilitychange`,e)},[]),(0,p.useEffect)(()=>{n&&g.current()},[n]),(0,m.jsxs)(`div`,{className:`joystick-slot`,children:[(0,m.jsxs)(`div`,{ref:i,className:`joystick${e===`x`?` horizontal`:e===`y`?` vertical`:``}${c?` active`:``}${n?` disabled`:``}`,onPointerDown:e=>{n||a.current!==null||e.button!==0||(a.current=e.pointerId,l(!0),e.currentTarget.setPointerCapture(e.pointerId),f(e))},onPointerMove:e=>e.pointerId===a.current&&f(e),onPointerUp:h,onPointerCancel:h,onLostPointerCapture:h,children:[(0,m.jsx)(`div`,{className:`joystick-track`}),(0,m.jsx)(`div`,{className:`joystick-knob`,style:{left:`${50+o.x*E}%`,top:`${50-o.y*E}%`}})]}),(0,m.jsx)(`span`,{className:`joystick-label`,children:r})]})}export{x as a,f as c,C as i,l,T as n,a as o,w as r,u as s,k as t};
|