tassello 0.6.0 → 0.7.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 +16 -0
- package/README.md +40 -8
- package/dist/server/behavior_tree_playback.js +2 -2
- package/dist/server/behavior_trees.d.ts +5 -3
- package/dist/server/behavior_trees.js +33 -35
- package/dist/server/rosbag.d.ts +15 -11
- package/dist/server/rosbag.js +396 -144
- package/dist/server/rosbag_format.d.ts +28 -0
- package/dist/server/rosbag_format.js +366 -0
- package/dist/server/rosbag_process.d.ts +20 -0
- package/dist/server/rosbag_process.js +99 -0
- package/dist/server/rosbags.d.ts +42 -0
- package/dist/server/rosbags.js +444 -0
- package/dist/server/server.js +5 -2
- package/dist/server/session.d.ts +2 -0
- package/dist/server/session.js +11 -1
- package/dist/shared/protocol.d.ts +24 -2
- package/dist/shared/rosbag.d.ts +104 -0
- package/dist/shared/rosbag.js +35 -0
- package/dist/types/client/connection.d.ts +17 -2
- package/dist/types/client/sdk/index.d.ts +2 -1
- package/dist/types/shared/protocol.d.ts +24 -2
- package/dist/types/shared/rosbag.d.ts +104 -0
- package/dist/www/assets/icons.js +1 -1
- package/dist/www/assets/index.css +1 -1
- package/dist/www/assets/index.js +40 -40
- package/docs/plugins.md +3 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ change the plugin API (`tassello/sdk`, `tassello/server`, `tassello/vite`), as n
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
- Rosbags widget: the robot's rosbags, as `ros2 bag` makes and reads them. A folder's bags, the
|
|
9
|
+
newest first, with their time span, size, storage and compression, and each topic's type,
|
|
10
|
+
count and rate, from their `metadata.yaml` (Humble's and Jazzy's), else from their files (a
|
|
11
|
+
recording killed: MCAP's summary, SQLite's tables). Downloaded (folders as `.tar.gz`), deleted
|
|
12
|
+
(bags only, from the dashboard's own pages), or played on the robot's topics with
|
|
13
|
+
`ros2 bag play`: paused, sought, sped up, looped, from an offset, some topics only, `/clock`
|
|
14
|
+
published. New ones recorded with `ros2 bag record`: topics chosen from the graph, all of them,
|
|
15
|
+
or matching a regular expression, but those matching another; with its advanced options, the
|
|
16
|
+
storage (MCAP, SQLite) and its preset, a new file by size or duration, the compression of each
|
|
17
|
+
file or message and its format, the cache, hidden topics, sim time. The recordings and the
|
|
18
|
+
playback are the robot's: every page sees them, and stops them.
|
|
19
|
+
- Behavior Tree Inspector: its recordings are made by the same rosbags service, with the same
|
|
20
|
+
advanced options, and listed (and stopped) by the Rosbags widgets. Bags compressed by rosbag2
|
|
21
|
+
(zstd, each file or each message) are played back too.
|
|
22
|
+
- Plugins: `connection.subscribeBags`, `bagsAction`, `recordBehaviorTree`'s options, and the
|
|
23
|
+
types of `src/shared/rosbag.ts`.
|
|
8
24
|
- Parameters widget: nodes' parameters, as rqt_reconfigure and `ros2 param` show them. A node's
|
|
9
25
|
parameters grouped by their prefixes, each edited in place (a switch, a slider within its range,
|
|
10
26
|
a field), with what the node refuses and why; changes made elsewhere followed live on
|
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, its nodes' parameters,
|
|
15
|
+
as the `ros2` command line shows them, its nodes' parameters, its rosbags (recorded, listed,
|
|
16
|
+
downloaded and played back), a file manager, a terminal, and its networks: Wi-Fi
|
|
16
17
|
joined, connections switched, the robot as an access point.
|
|
17
18
|
- **Plugins**: widgets of your own, with server-side code if they need it, as npm or ROS packages.
|
|
18
19
|
- **One process**: a ROS 2 node in Node.js. Nothing to install on the robot but Node.js.
|
|
@@ -132,6 +133,7 @@ after the others on the devices already arranged.
|
|
|
132
133
|
| **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 |
|
|
133
134
|
| **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`) |
|
|
134
135
|
| **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`) |
|
|
136
|
+
| **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 |
|
|
135
137
|
| **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 |
|
|
136
138
|
| **Terminal** | A shell on the robot, or a command (`htop`, `tmux attach`), in a full terminal: colours, full screen programs, the mouse. It outlives the page: reloaded, reconnected or shown again, it is as it was. On touch screens, the keys phones lack, as Termux has them: Esc, Tab, Ctrl, Alt, Home, End, the arrows, Page Up and Down, and while typing the terminal fills the screen above the on-screen keyboard |
|
|
137
139
|
| **Networking** | The robot's networks, through NetworkManager, as a phone's settings show them: the Wi-Fi networks in range, joined with their password; the connections saved, switched between, edited (their addresses by DHCP or static), forgotten; the robot as an access point, its network shared by a QR code; the interfaces, their state and addresses. Asks before a change that would cut the page off from the robot |
|
|
@@ -198,12 +200,14 @@ Notes on some of them:
|
|
|
198
200
|
shows how it last ended.
|
|
199
201
|
|
|
200
202
|
Its record button records the log on the robot with `ros2 bag record`, into a new folder
|
|
201
|
-
(`<recordings folder>/behavior_tree_<date>_<time>` by default),
|
|
202
|
-
page showing the topic shows it recording
|
|
203
|
+
(`<recordings folder>/behavior_tree_<date>_<time>` by default), with the Rosbags widget's
|
|
204
|
+
advanced options, until pressed again: every page showing the topic shows it recording, and
|
|
205
|
+
the Rosbags widgets list it (and may stop it). Its playback button opens a rosbag of the robot
|
|
203
206
|
(a bag's folder, or its `.mcap` or `.db3` file, e.g. of a whole run recorded with
|
|
204
207
|
`ros2 bag record -a`) and plays its log in the widget, on the tree of its file, without
|
|
205
208
|
publishing anything: pause, seek and choose the speed (0.1× to 16×). Bags of Humble and of
|
|
206
|
-
Jazzy are read either way
|
|
209
|
+
Jazzy are read either way, compressed by rosbag2 (zstd, each file or each message) or not;
|
|
210
|
+
SQLite bags need Node.js 22.13, zstd compressed ones 22.15.
|
|
207
211
|
- **Behavior Tree Editor** edits one BehaviorTree of the file at a time (its toolbar switches
|
|
208
212
|
between them, adds new ones, and double clicking a subtree opens its tree, even in an included
|
|
209
213
|
file). Add a node from the palette by dragging it where it goes (into a node, or beside one), or
|
|
@@ -320,6 +324,29 @@ Notes on some of them:
|
|
|
320
324
|
node's section, and the `/**` one) and sets its parameters one by one, saying which the node
|
|
321
325
|
refused and why. Parameter files are read and written by Python's yaml, as `ros2 param` does. A
|
|
322
326
|
widget can be *view only*.
|
|
327
|
+
- **Rosbags** lists the bags of its *folder* (`~/bags` by default; the toolbar browses for
|
|
328
|
+
another): its folders holding a bag, and its lone `.mcap` and `.db3` files, the newest first,
|
|
329
|
+
read again every 2 s while shown. What a bag holds is read from its `metadata.yaml` (Humble's
|
|
330
|
+
and Jazzy's); a bag without it (its recording killed) from its files: MCAP's summary, SQLite's
|
|
331
|
+
tables (up to 64 MB), and it says so (`ros2 bag reindex` makes the metadata). A bag's details
|
|
332
|
+
give its path, its time span, its files, and its topics with their types, counts and rates.
|
|
333
|
+
*Download* sends a folder as a `.tar.gz`, a file as it is; *Delete* deletes bags only, a folder
|
|
334
|
+
holding anything else than a bag's files being refused (the File Manager deletes those).
|
|
335
|
+
*Record* runs `ros2 bag record` into a new folder (`<folder>/rosbag2_<date>_<time>`): the
|
|
336
|
+
topics chosen from the graph (the widget's *topics to record* chosen first, others typed in),
|
|
337
|
+
or all of them, and those matching a regular expression, but those matching another. Its
|
|
338
|
+
*Advanced* options are `ros2 bag record`'s: the storage (the distribution's default, MCAP or
|
|
339
|
+
SQLite, as `ros2 bag list storage` lists them) and its preset (MCAP's `fastwrite`, `zstd_fast`,
|
|
340
|
+
`zstd_small`, SQLite's `resilient`), a new file at a size or a duration (`-b`, `-d`), the
|
|
341
|
+
compression of each file or of each message and its format (`ros2 bag list compressor`: zstd;
|
|
342
|
+
MCAP compresses its chunks with its presets instead, each message is refused), the cache, the
|
|
343
|
+
hidden topics and sim time. The page remembers them for the next recording, the behavior tree
|
|
344
|
+
inspector's too. Recordings show above the bags with their time and size, stopped as Ctrl+C
|
|
345
|
+
stops them, which completes the bag; the server stops them too when it stops. *Play* runs
|
|
346
|
+
`ros2 bag play`, one bag at a time: its messages published again on the robot's topics, at a
|
|
347
|
+
rate, looped, from an offset, some topics only, `/clock` published or not, started paused.
|
|
348
|
+
Pause, seek and the rate go through the player's services (`/rosbag2_player/…`); where it is
|
|
349
|
+
is the server's reckoning from them. A widget can be *view only*.
|
|
323
350
|
- **File Manager** opens its *folder* (`~` by default; `/…`, `~/…`, `package://…`) and lists it
|
|
324
351
|
with its entries' sizes and times, sorted by name (naturally: `run_2` before `run_10`), size or
|
|
325
352
|
time, folders first; the chevrons unfold folders in place. Its places are home, the root, the
|
|
@@ -467,16 +494,16 @@ and ship example panels. See **[docs/plugins.md](docs/plugins.md)**.
|
|
|
467
494
|
|
|
468
495
|
Tassello has no login. **Anyone who can open the page can drive the robot, publish on any topic,
|
|
469
496
|
call any service and send or cancel any goal (the ROS Monitor), change any node's parameters
|
|
470
|
-
(Parameters), run any command as
|
|
497
|
+
(Parameters), record, play and delete rosbags (Rosbags), run any command as
|
|
471
498
|
the server's user (the Terminal, a shell), read, write, upload, move and delete any of the
|
|
472
499
|
files that user can (the File Manager), and change the robot's networks and read their passwords
|
|
473
500
|
(Networking)**: keep it on a network you trust, or behind a reverse proxy
|
|
474
501
|
with authentication (it serves everything under `/`, its WebSocket at `/ws`). Changes to the files
|
|
475
502
|
are accepted from the dashboard's own pages only (they send a header that another site's page
|
|
476
503
|
cannot), and the root, the home folder and the system's top folders (`/usr`, `/etc`…) are never
|
|
477
|
-
deleted nor moved. Terminals open,
|
|
478
|
-
those whose origin is the host they reached (the `Host` header, or the
|
|
479
|
-
reverse proxy sets), which another site's page cannot pretend.
|
|
504
|
+
deleted nor moved. Terminals open, networks change and rosbags are deleted from the dashboard's
|
|
505
|
+
own pages only too: those whose origin is the host they reached (the `Host` header, or the
|
|
506
|
+
`X-Forwarded-Host` a reverse proxy sets), which another site's page cannot pretend.
|
|
480
507
|
|
|
481
508
|
## How it works
|
|
482
509
|
|
|
@@ -489,6 +516,7 @@ reverse proxy sets), which another site's page cannot pretend.
|
|
|
489
516
|
├─ bash: the buttons' commands
|
|
490
517
|
├─ python3: the terminals' pseudo-terminals
|
|
491
518
|
├─ nmcli: the networks (NetworkManager)
|
|
519
|
+
├─ ros2 bag: rosbags recorded and played
|
|
492
520
|
└─ the plugins' server sides
|
|
493
521
|
```
|
|
494
522
|
|
|
@@ -522,6 +550,10 @@ Safety and bandwidth are the server's concern:
|
|
|
522
550
|
and every 3 s for the signals of the Wi-Fi networks in range, which are scanned for when a page
|
|
523
551
|
opens the widget (at most every 30 s) or asks. Passwords are on `nmcli`'s command line only for
|
|
524
552
|
the moment it takes to save them, activating a connection being another command.
|
|
553
|
+
- Rosbags are recorded and played by `ros2 bag`, the robot's own, as children of the server:
|
|
554
|
+
one recording per folder, for every page (the behavior tree inspectors' among them), one
|
|
555
|
+
playback. A folder of bags is read once for every page showing it, every 2 s, a bag's metadata
|
|
556
|
+
again only when its files change; the recordings' sizes every second.
|
|
525
557
|
- The File Manager's folders are read again every 3 s, and answered "unchanged" while they are;
|
|
526
558
|
the largest are listed in part (5,000 entries), and only the rows in sight are drawn. Uploads
|
|
527
559
|
and downloads are streamed, folders archived by `tar` as they are sent; saves replace files at
|
|
@@ -7,7 +7,7 @@ import { BT_RATE } from '../shared/protocol.js';
|
|
|
7
7
|
import { readTree } from './behavior_tree_file.js';
|
|
8
8
|
import { decodeLog, logLayout } from './behavior_tree_log.js';
|
|
9
9
|
import { BehaviorTreeState } from './behavior_tree_state.js';
|
|
10
|
-
import {
|
|
10
|
+
import { openBag, readBag } from './rosbag.js';
|
|
11
11
|
import { absoluteTopic, errorMessage } from './util.js';
|
|
12
12
|
const TICK_MS = 50;
|
|
13
13
|
// How often the page is told where the playback is, while playing
|
|
@@ -18,7 +18,7 @@ const MAX_SPEED = 64;
|
|
|
18
18
|
const MAX_TRANSITIONS = 2_000_000;
|
|
19
19
|
// The bag's log: of the topic if it has it, else its first
|
|
20
20
|
export async function readLog(bag, topic) {
|
|
21
|
-
const logs = await readBag(await
|
|
21
|
+
const logs = await readBag(await openBag(bag), (t) => /\/BehaviorTreeLog$/.test(t.type));
|
|
22
22
|
const found = (topic && logs.get(absoluteTopic(topic))) || [...logs.values()][0];
|
|
23
23
|
if (!found) {
|
|
24
24
|
throw new Error(`No behavior tree log in ${bag}: no nav2_msgs/msg/BehaviorTreeLog topic`);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type rclnodejs from 'rclnodejs';
|
|
2
2
|
import { type BehaviorTreeRecording, type BehaviorTreeUpdate } from '../shared/protocol.ts';
|
|
3
|
+
import { type RecordOptions } from '../shared/rosbag.ts';
|
|
3
4
|
import { type TreeInfo } from './behavior_tree_file.ts';
|
|
4
5
|
import { BehaviorTreePlayback, type PlaybackWatcher } from './behavior_tree_playback.ts';
|
|
5
6
|
import type { StreamState } from './frame_stream.ts';
|
|
7
|
+
import type { Rosbags } from './rosbags.ts';
|
|
6
8
|
import { type Logger } from './util.ts';
|
|
7
9
|
export type RecordingInfo = BehaviorTreeRecording['recording'];
|
|
8
10
|
export interface BehaviorTreeWatcher {
|
|
@@ -17,12 +19,12 @@ export declare class BehaviorTrees {
|
|
|
17
19
|
private readonly uploadsDir;
|
|
18
20
|
private readonly topics;
|
|
19
21
|
private readonly monitors;
|
|
22
|
+
private readonly rosbags;
|
|
20
23
|
private readonly recordings;
|
|
21
|
-
constructor(node: rclnodejs.Node, logger: Logger, uploadsDir: string);
|
|
24
|
+
constructor(node: rclnodejs.Node, logger: Logger, uploadsDir: string, rosbags: Rosbags);
|
|
22
25
|
watch(topic: string, tree: string, watcher: BehaviorTreeWatcher): () => void;
|
|
23
|
-
record(topic: string, path: string | null): Promise<string | null>;
|
|
26
|
+
record(topic: string, path: string | null, options?: RecordOptions): Promise<string | null>;
|
|
24
27
|
play(bag: string, topic: string, tree: string, watcher: PlaybackWatcher): BehaviorTreePlayback;
|
|
25
|
-
stopAll(): Promise<void>;
|
|
26
28
|
private recordingOf;
|
|
27
29
|
private announce;
|
|
28
30
|
private forget;
|
|
@@ -9,17 +9,19 @@
|
|
|
9
9
|
// outlives its last page for LINGER_MS: a page reloaded, or a panel shown
|
|
10
10
|
// again, finds the nodes as they are, which the log only says when they change.
|
|
11
11
|
//
|
|
12
|
-
// A topic is recorded into a rosbag
|
|
13
|
-
//
|
|
14
|
-
//
|
|
12
|
+
// A topic is recorded into a rosbag when a page asks, until one asks to stop
|
|
13
|
+
// (or a Rosbags widget stops it): every page following it is told. The
|
|
14
|
+
// recording is the server's rosbags' (rosbags.ts), as the Rosbags widgets'
|
|
15
|
+
// are. A bag is played back for the page that opens it
|
|
16
|
+
// (behavior_tree_playback.ts).
|
|
15
17
|
import { promises as fs } from 'node:fs';
|
|
16
18
|
import { BT_RATE } from '../shared/protocol.js';
|
|
19
|
+
import { DEFAULT_RECORD_OPTIONS } from '../shared/rosbag.js';
|
|
17
20
|
import { readTree } from './behavior_tree_file.js';
|
|
18
21
|
import { logTransitions } from './behavior_tree_log.js';
|
|
19
22
|
import { BehaviorTreePlayback } from './behavior_tree_playback.js';
|
|
20
23
|
import { BehaviorTreeState } from './behavior_tree_state.js';
|
|
21
24
|
import { publishersQos } from './qos.js';
|
|
22
|
-
import { BagRecorder, newBagFolder } from './rosbag.js';
|
|
23
25
|
import { absoluteTopic, errorMessage } from './util.js';
|
|
24
26
|
// How often the graph is checked for the topic's publishers, until it has one
|
|
25
27
|
const RETRY_PERIOD_MS = 1000;
|
|
@@ -252,12 +254,23 @@ export class BehaviorTrees {
|
|
|
252
254
|
topics = new Map();
|
|
253
255
|
// By topic, then tree source
|
|
254
256
|
monitors = new Map();
|
|
255
|
-
|
|
257
|
+
rosbags;
|
|
258
|
+
// By topic: the folder it is recorded into, '' while it starts
|
|
256
259
|
recordings = new Map();
|
|
257
|
-
constructor(node, logger, uploadsDir) {
|
|
260
|
+
constructor(node, logger, uploadsDir, rosbags) {
|
|
258
261
|
this.node = node;
|
|
259
262
|
this.logger = logger;
|
|
260
263
|
this.uploadsDir = uploadsDir;
|
|
264
|
+
this.rosbags = rosbags;
|
|
265
|
+
// A recording stopped elsewhere, or by itself
|
|
266
|
+
rosbags.onChange(() => {
|
|
267
|
+
for (const [topic, folder] of this.recordings) {
|
|
268
|
+
if (folder && !rosbags.recording(folder)) {
|
|
269
|
+
this.recordings.delete(topic);
|
|
270
|
+
this.announce(topic);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
});
|
|
261
274
|
}
|
|
262
275
|
// Until the returned function is called
|
|
263
276
|
watch(topic, tree, watcher) {
|
|
@@ -290,57 +303,42 @@ export class BehaviorTrees {
|
|
|
290
303
|
}
|
|
291
304
|
// Records the topic into a new folder, or stops (path null): resolves with
|
|
292
305
|
// the bag's folder, null if it was not recording
|
|
293
|
-
async record(topic, path) {
|
|
306
|
+
async record(topic, path, options = DEFAULT_RECORD_OPTIONS) {
|
|
294
307
|
const name = absoluteTopic(topic);
|
|
295
308
|
const current = this.recordings.get(name);
|
|
296
309
|
if (path === null) {
|
|
297
|
-
if (!current
|
|
310
|
+
if (!current) {
|
|
298
311
|
return null;
|
|
299
312
|
}
|
|
300
313
|
this.recordings.delete(name);
|
|
301
314
|
this.announce(name);
|
|
302
|
-
|
|
303
|
-
this.logger.info(`Recorded ${name} into ${current.path}`);
|
|
304
|
-
return current.path;
|
|
315
|
+
return this.rosbags.stop(current);
|
|
305
316
|
}
|
|
306
|
-
if (current) {
|
|
307
|
-
throw new Error(`${name} is being recorded already
|
|
317
|
+
if (current !== undefined) {
|
|
318
|
+
throw new Error(`${name} is being recorded already${current ? `, into ${current}` : ''}`);
|
|
308
319
|
}
|
|
309
|
-
|
|
310
|
-
|
|
320
|
+
this.recordings.set(name, '');
|
|
321
|
+
let folder;
|
|
311
322
|
try {
|
|
312
|
-
|
|
313
|
-
|
|
323
|
+
const request = { path, topics: [name], all: false, regex: '', exclude: '', options };
|
|
324
|
+
folder = (await this.rosbags.record(request, 'behavior-tree')).path;
|
|
314
325
|
}
|
|
315
326
|
catch (e) {
|
|
316
327
|
this.recordings.delete(name);
|
|
317
328
|
throw new Error(`Cannot record ${name}: ${errorMessage(e)}`);
|
|
318
329
|
}
|
|
319
|
-
|
|
320
|
-
recording.recorder.onExit = (reason) => {
|
|
321
|
-
if (this.recordings.get(name) === recording) {
|
|
322
|
-
this.recordings.delete(name);
|
|
323
|
-
this.announce(name);
|
|
324
|
-
this.logger.warn(`Recording ${name} stopped: ${reason}`);
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
this.logger.info(`Recording ${name} into ${recording.path}`);
|
|
330
|
+
this.recordings.set(name, folder);
|
|
328
331
|
this.announce(name);
|
|
329
|
-
return
|
|
332
|
+
return folder;
|
|
330
333
|
}
|
|
331
334
|
// A bag played back for a page, until closed
|
|
332
335
|
play(bag, topic, tree, watcher) {
|
|
333
336
|
return new BehaviorTreePlayback({ bag, topic, tree: tree.trim(), uploadsDir: this.uploadsDir, logger: this.logger }, watcher);
|
|
334
337
|
}
|
|
335
|
-
// The recordings completed, as the server stops
|
|
336
|
-
async stopAll() {
|
|
337
|
-
const recorders = [...this.recordings.values()].flatMap((recording) => recording.recorder ?? []);
|
|
338
|
-
this.recordings.clear();
|
|
339
|
-
await Promise.all(recorders.map((recorder) => recorder.stop()));
|
|
340
|
-
}
|
|
341
338
|
recordingOf(topic) {
|
|
342
|
-
const
|
|
343
|
-
|
|
339
|
+
const folder = this.recordings.get(topic);
|
|
340
|
+
const recording = folder ? this.rosbags.recording(folder) : null;
|
|
341
|
+
return recording ? { path: recording.path, since: recording.since } : null;
|
|
344
342
|
}
|
|
345
343
|
announce(topic) {
|
|
346
344
|
for (const monitor of this.monitors.get(topic)?.values() ?? []) {
|
package/dist/server/rosbag.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BagInfo } from '../shared/rosbag.ts';
|
|
1
2
|
export interface BagTopic {
|
|
2
3
|
name: string;
|
|
3
4
|
type: string;
|
|
@@ -10,16 +11,19 @@ export type BagTopics = Map<string, {
|
|
|
10
11
|
topic: BagTopic;
|
|
11
12
|
messages: BagMessage[];
|
|
12
13
|
}>;
|
|
14
|
+
export interface BagSource {
|
|
15
|
+
files: string[];
|
|
16
|
+
messageCompression: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function bagPath(input: string, home?: string): string;
|
|
19
|
+
export declare function openBag(input: string, home?: string): Promise<BagSource>;
|
|
13
20
|
export declare function bagFiles(input: string, home?: string): Promise<string[]>;
|
|
14
|
-
export declare function readBag(
|
|
21
|
+
export declare function readBag(bag: BagSource | string[], accept: (topic: BagTopic) => boolean): Promise<BagTopics>;
|
|
22
|
+
export declare function bagInfo(input: string, home?: string): Promise<BagInfo>;
|
|
23
|
+
export type BagCache = Map<string, {
|
|
24
|
+
version: string;
|
|
25
|
+
info: BagInfo;
|
|
26
|
+
}>;
|
|
27
|
+
export declare function listBags(folder: string, cache?: BagCache, home?: string): Promise<BagInfo[]>;
|
|
15
28
|
export declare function newBagFolder(input: string, home?: string): Promise<string>;
|
|
16
|
-
export declare
|
|
17
|
-
readonly folder: string;
|
|
18
|
-
onExit: ((reason: string) => void) | null;
|
|
19
|
-
private readonly child;
|
|
20
|
-
private output;
|
|
21
|
-
private stopping;
|
|
22
|
-
private constructor();
|
|
23
|
-
static start(folder: string, topics: string[]): Promise<BagRecorder>;
|
|
24
|
-
stop(): Promise<void>;
|
|
25
|
-
}
|
|
29
|
+
export declare function folderSize(folder: string): Promise<number>;
|