tassello 0.0.0-stage → 0.1.1
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 +18 -0
- package/LICENSE +202 -0
- package/README.md +240 -2
- package/dist/server/api.d.ts +27 -0
- package/dist/server/api.js +20 -0
- package/dist/server/arm.d.ts +79 -0
- package/dist/server/arm.js +584 -0
- package/dist/server/arm_motion.d.ts +12 -0
- package/dist/server/arm_motion.js +27 -0
- package/dist/server/cli.d.ts +2 -0
- package/dist/server/cli.js +100 -0
- package/dist/server/commands.d.ts +25 -0
- package/dist/server/commands.js +142 -0
- package/dist/server/create_plugin.d.ts +1 -0
- package/dist/server/create_plugin.js +58 -0
- package/dist/server/files.d.ts +15 -0
- package/dist/server/files.js +99 -0
- package/dist/server/frame_stream.d.ts +45 -0
- package/dist/server/frame_stream.js +132 -0
- package/dist/server/grid_encoding.d.ts +24 -0
- package/dist/server/grid_encoding.js +72 -0
- package/dist/server/grids.d.ts +15 -0
- package/dist/server/grids.js +92 -0
- package/dist/server/http.d.ts +14 -0
- package/dist/server/http.js +260 -0
- package/dist/server/image_encoding.d.ts +24 -0
- package/dist/server/image_encoding.js +192 -0
- package/dist/server/images.d.ts +14 -0
- package/dist/server/images.js +85 -0
- package/dist/server/kinematics.d.ts +10 -0
- package/dist/server/kinematics.js +154 -0
- package/dist/server/panels.d.ts +28 -0
- package/dist/server/panels.js +266 -0
- package/dist/server/path_encoding.d.ts +16 -0
- package/dist/server/path_encoding.js +19 -0
- package/dist/server/paths.d.ts +10 -0
- package/dist/server/paths.js +104 -0
- package/dist/server/plugins.d.ts +43 -0
- package/dist/server/plugins.js +303 -0
- package/dist/server/poses.d.ts +8 -0
- package/dist/server/poses.js +29 -0
- package/dist/server/qos.d.ts +8 -0
- package/dist/server/qos.js +32 -0
- package/dist/server/ros.d.ts +10 -0
- package/dist/server/ros.js +57 -0
- package/dist/server/server.d.ts +9 -0
- package/dist/server/server.js +191 -0
- package/dist/server/session.d.ts +49 -0
- package/dist/server/session.js +316 -0
- package/dist/server/streams.d.ts +16 -0
- package/dist/server/streams.js +53 -0
- package/dist/server/systemd.d.ts +19 -0
- package/dist/server/systemd.js +268 -0
- package/dist/server/systemd_format.d.ts +8 -0
- package/dist/server/systemd_format.js +80 -0
- package/dist/server/teleop.d.ts +13 -0
- package/dist/server/teleop.js +73 -0
- package/dist/server/tf.d.ts +20 -0
- package/dist/server/tf.js +79 -0
- package/dist/server/topics.d.ts +15 -0
- package/dist/server/topics.js +134 -0
- package/dist/server/transforms.d.ts +31 -0
- package/dist/server/transforms.js +109 -0
- package/dist/server/util.d.ts +13 -0
- package/dist/server/util.js +54 -0
- package/dist/shared/panel.d.ts +39 -0
- package/dist/shared/panel.js +11 -0
- package/dist/shared/protocol.d.ts +308 -0
- package/dist/shared/protocol.js +18 -0
- package/dist/shared/urdf.d.ts +13 -0
- package/dist/shared/urdf.js +61 -0
- package/dist/types/client/api.d.ts +22 -0
- package/dist/types/client/connection.d.ts +82 -0
- package/dist/types/client/dialogs.d.ts +27 -0
- package/dist/types/client/icons.d.ts +34 -0
- package/dist/types/client/notifications.d.ts +11 -0
- package/dist/types/client/sdk/index.d.ts +9 -0
- package/dist/types/client/widgets/Joystick.d.ts +7 -0
- package/dist/types/client/widgets/types.d.ts +99 -0
- package/dist/types/shared/panel.d.ts +39 -0
- package/dist/types/shared/protocol.d.ts +308 -0
- package/dist/vite/index.d.ts +7 -0
- package/dist/vite/index.js +41 -0
- package/dist/www/assets/Joystick.js +1 -0
- package/dist/www/assets/RobotModelView.js +4226 -0
- package/dist/www/assets/client.js +9 -0
- package/dist/www/assets/icons.js +1 -0
- package/dist/www/assets/index.css +1 -0
- package/dist/www/assets/index.js +2 -0
- package/dist/www/assets/jsx-runtime.js +1 -0
- package/dist/www/assets/react-dom.js +1 -0
- package/dist/www/assets/react.js +1 -0
- package/dist/www/assets/rolldown-runtime.js +1 -0
- package/dist/www/assets/shared/react/jsx-runtime.js +1 -0
- package/dist/www/assets/shared/react-dom/client.js +1 -0
- package/dist/www/assets/shared/react-dom.js +1 -0
- package/dist/www/assets/shared/react.js +1 -0
- package/dist/www/assets/shared/tassello-sdk.js +1 -0
- package/dist/www/favicon.svg +1 -0
- package/dist/www/index.html +35 -0
- package/docs/plugins.md +288 -0
- package/package.json +99 -4
- package/panels/navigation.json +37 -0
- package/panels/teleoperation.json +51 -0
- package/template/README.md +23 -0
- package/template/_gitignore +2 -0
- package/template/package.json +40 -0
- package/template/src/client/index.tsx +93 -0
- package/template/src/client/style.css +27 -0
- package/template/src/server/index.ts +26 -0
- package/template/tsconfig.json +17 -0
- package/template/tsconfig.server.json +18 -0
- package/template/vite.config.ts +7 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Tassello follows [semantic versioning](https://semver.org): before 1.0, a new minor version may
|
|
4
|
+
change the plugin API (`tassello/sdk`, `tassello/server`, `tassello/vite`), as noted here.
|
|
5
|
+
|
|
6
|
+
## 0.1.0
|
|
7
|
+
|
|
8
|
+
The first release, from the Dieletrikbot's web dashboard.
|
|
9
|
+
|
|
10
|
+
- Panels of widgets on a grid, edited in the browser, with layouts of their own for tablets and
|
|
11
|
+
phones.
|
|
12
|
+
- Widgets: Teleop, Navigation, Robot Model, Arm Teleop, Camera, Image Viewer, Button,
|
|
13
|
+
Application Manager.
|
|
14
|
+
- Plugins: widgets loaded at run time (`tassello/sdk`, built with `tassello/vite`), server sides
|
|
15
|
+
(`tassello/server`), defaults for any widget's properties, example panels. Found on the command
|
|
16
|
+
line, in the ament index (`tassello_plugins`) and as `tassello-plugin-*` npm packages.
|
|
17
|
+
- Generic ROS access from the page: publish, call services, subscribe to topics, TF.
|
|
18
|
+
- `tassello create-plugin`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,241 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Tassello
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**A web dashboard for ROS 2 robots.** Drive the robot, send it navigation goals, see it move in 3D,
|
|
4
|
+
watch its cameras, run commands on it and manage its services from any browser on the network,
|
|
5
|
+
phones included. Arrange it all in the browser, as panels of widgets, and extend it with plugins.
|
|
6
|
+
|
|
7
|
+
*Tassello* is Italian for a mosaic tile, and for a wall plug: the dashboard is a mosaic of widgets,
|
|
8
|
+
and plugins add more.
|
|
9
|
+
|
|
10
|
+
- **Panels of widgets** on a grid: add, move and resize them in the browser, no code or config files.
|
|
11
|
+
- **Every device**: a desktop canvas, and layouts of their own for tablets and phones.
|
|
12
|
+
- **Built in**: teleop pads, a Nav2 map with goals, the robot's URDF in 3D, an arm jogger, cameras,
|
|
13
|
+
images, command buttons and a systemd application manager.
|
|
14
|
+
- **Plugins**: widgets of your own, with server-side code if they need it, as npm or ROS packages.
|
|
15
|
+
- **One process**: a ROS 2 node in Node.js. Nothing to install on the robot but Node.js.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
Tassello runs on the robot (or any machine on its ROS network) and needs:
|
|
20
|
+
|
|
21
|
+
- ROS 2: Humble, Jazzy, Kilted or Lyrical, on x64 or arm64 (e.g. a Raspberry Pi), for which
|
|
22
|
+
[rclnodejs](https://github.com/RobotWebTools/rclnodejs) ships prebuilt binaries;
|
|
23
|
+
- Node.js 22 or newer, and npm.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g tassello
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then, with your ROS 2 setup sourced:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
source /opt/ros/jazzy/setup.bash
|
|
33
|
+
tassello
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
and open `http://<robot>:8080`. The first start creates the panels directory with two example
|
|
37
|
+
panels, Teleoperation and Navigation.
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
tassello [options] [--ros-args ...]
|
|
41
|
+
|
|
42
|
+
-p, --port <port> HTTP port (8080)
|
|
43
|
+
--host <address> the address to listen on (all of them)
|
|
44
|
+
--panels-dir <dir> where the panels are kept (~/.local/share/tassello/panels)
|
|
45
|
+
--plugin <plugin> loads a plugin: a folder, or an installed package's name. Repeatable
|
|
46
|
+
--no-discover loads only those plugins, not the installed ones
|
|
47
|
+
--node-name <name> the ROS node's name (tassello)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
ROS arguments go after `--ros-args`, e.g. `tassello --ros-args -p use_sim_time:=true`. The port
|
|
51
|
+
and the panels directory are also the node's parameters `port` and `panels_dir`.
|
|
52
|
+
|
|
53
|
+
### As a service
|
|
54
|
+
|
|
55
|
+
To start it at boot, a systemd unit like this one (as the user the commands should run as):
|
|
56
|
+
|
|
57
|
+
```ini
|
|
58
|
+
[Unit]
|
|
59
|
+
Description=Tassello robot dashboard
|
|
60
|
+
After=network-online.target
|
|
61
|
+
|
|
62
|
+
[Service]
|
|
63
|
+
User=robot
|
|
64
|
+
ExecStart=/bin/bash -c 'source /opt/ros/jazzy/setup.bash && exec tassello'
|
|
65
|
+
Restart=on-failure
|
|
66
|
+
|
|
67
|
+
[Install]
|
|
68
|
+
WantedBy=multi-user.target
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Using it
|
|
72
|
+
|
|
73
|
+
The page shows one **panel** at a time: a canvas of fixed-size cells on which **widgets** lie,
|
|
74
|
+
each spanning as many cells as needed. A panel is made for a task (teleoperation, navigation,
|
|
75
|
+
manipulation, ...) and the top bar switches between them. It has two modes:
|
|
76
|
+
|
|
77
|
+
- **View**: the widgets work: drive, watch, press.
|
|
78
|
+
- **Edit**: add widgets (**Add widget**), drag them to move them, drag their corner to resize
|
|
79
|
+
them, click one to edit its properties in the side panel, and create, rename, duplicate or
|
|
80
|
+
delete panels (**⋯**). Every change is saved as you make it; **Ctrl+Z** undoes it, the
|
|
81
|
+
arrow keys move the selected widget and **Delete** removes it.
|
|
82
|
+
|
|
83
|
+
The URL names the panel (`#/teleoperation`), so a panel can be bookmarked, e.g. on a phone's
|
|
84
|
+
home screen. Each panel is a JSON file, `<panels dir>/<id>.json`: copy it to copy the panel to
|
|
85
|
+
another robot.
|
|
86
|
+
|
|
87
|
+
### Phones and tablets
|
|
88
|
+
|
|
89
|
+
A panel has a layout per kind of device, chosen by the width of the window: **desktop** (from
|
|
90
|
+
1100 px), **tablet** (from 640 px) and **phone**. The widgets and their properties are the same on
|
|
91
|
+
all of them; only where they are differs.
|
|
92
|
+
|
|
93
|
+
- On a desktop the board is a canvas of fixed cells, which scrolls both ways.
|
|
94
|
+
- Tablets have 12 columns and phones 6, stretched to the width of the screen: a layout looks the
|
|
95
|
+
same on every phone, and the page only scrolls down.
|
|
96
|
+
|
|
97
|
+
Until arranged, a tablet's or phone's layout is made from the desktop's: the widgets in its reading
|
|
98
|
+
order, narrowed to the columns (keeping their shape), side by side where they fit. In edit mode the
|
|
99
|
+
device buttons of the top bar show each device's layout, at its width: moving or resizing a widget
|
|
100
|
+
there arranges the layout for that device, and the eye button hides a widget on it (e.g. the 3D
|
|
101
|
+
model on phones). **Back to automatic** forgets the device's own layout. Widgets added later go
|
|
102
|
+
after the others on the devices already arranged.
|
|
103
|
+
|
|
104
|
+
## Widgets
|
|
105
|
+
|
|
106
|
+
| Widget | What it does |
|
|
107
|
+
|---|---|
|
|
108
|
+
| **Teleop** | Publishes velocity commands (`Twist` or `TwistStamped`) while its pads are held: one pad (x, z), or two in omnidirectional mode (x and y, z). Then one zero, and nothing, so that the lower priorities of a twist_mux, e.g. navigation, take over again |
|
|
109
|
+
| **Navigation** | The map, optionally the global and local costmaps and the planned path, and the robot where TF puts it. Its tools set the pose estimate and send navigation goals, as RViz's do: press where the robot is or should go, drag towards where it faces. Pan, zoom, fit the map or follow the robot |
|
|
110
|
+
| **Robot Model** | The robot in 3D, from its URDF and meshes, posed by the joint states. The links of moving joints turn orange, with an arrow at the joint in the direction of the motion |
|
|
111
|
+
| **Arm Teleop** | Jogs an arm driven by ros2_control `JointTrajectoryController`s: a slider per joint, or pads moving the end effector in a straight line (damped least squares on the URDF's kinematics, collision checked by MoveIt's move_group when it runs) |
|
|
112
|
+
| **Camera** | An image topic, raw (`sensor_msgs/Image`) or compressed (`sensor_msgs/CompressedImage`) |
|
|
113
|
+
| **Image Viewer** | A static image: a file of the robot, typed (`/…`, `~/…`, `package://…`) or picked by browsing its folders, or an image uploaded from the page's device |
|
|
114
|
+
| **Button** | Runs a bash command on the robot, showing its output. Takes its running colour until the command finishes, on every page showing it; pressing it again stops the command |
|
|
115
|
+
| **Application Manager** | The robot's systemd services matching a name: their state, whether they start at boot, buttons to start, stop and restart them, and their journal |
|
|
116
|
+
|
|
117
|
+
Their properties (topics, frames, speeds, colours, ...) are edited in the side panel of edit mode.
|
|
118
|
+
|
|
119
|
+
Notes on some of them:
|
|
120
|
+
|
|
121
|
+
- **Navigation** publishes goals as `geometry_msgs/PoseStamped` (`/goal_pose`, which Nav2's
|
|
122
|
+
bt_navigator follows) and pose estimates as `geometry_msgs/PoseWithCovarianceStamped`
|
|
123
|
+
(`/initialpose`, with RViz's covariance, which AMCL starts from). Layers in another frame, like
|
|
124
|
+
Nav2's local costmap, are placed in the map frame by TF.
|
|
125
|
+
- **Robot Model** loads the meshes of the description from the robot's ROS packages
|
|
126
|
+
(`package://` paths), which the server serves compressed. three.js is only fetched by the pages
|
|
127
|
+
showing one.
|
|
128
|
+
- **Arm Teleop** streams its controllers one-point trajectories just ahead of the arm, 20 times
|
|
129
|
+
a second: when the stream stops, the arm stops at the last point. A joint stays within the
|
|
130
|
+
URDF's limits, or those of an optional *calibration state*: a `std_msgs/String` topic whose
|
|
131
|
+
JSON a robot's hardware interface may publish:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"active": true,
|
|
136
|
+
"calibrating": false,
|
|
137
|
+
"problem": null,
|
|
138
|
+
"joints": [
|
|
139
|
+
{ "name": "joint_1", "has_motor": true, "located": true, "drivable": true,
|
|
140
|
+
"lower": -1.5, "upper": 1.5, "allowed_lower": -1.4, "allowed_upper": 1.4 }
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
A drivable joint moves within `allowed_lower`..`allowed_upper`; others do not move, and the
|
|
146
|
+
widget says why (no motor, not located, no limits).
|
|
147
|
+
- **Button** commands run with `bash -c`, as the server's user and in its environment (the
|
|
148
|
+
sourced ROS setup), from the home directory.
|
|
149
|
+
- **Application Manager** lists the services of the system's and the user's service managers. It
|
|
150
|
+
acts as the server's user: for the system's services, give that user the right to manage them,
|
|
151
|
+
e.g. a polkit rule allowing it to start, stop and restart `myrobot-*` units. To read systemd's
|
|
152
|
+
own journal lines, the user must be in the `systemd-journal` group.
|
|
153
|
+
- **Image Viewer** reads only images (PNG, JPEG, GIF, WebP, BMP, SVG). Uploads are kept next to
|
|
154
|
+
the panels (`~/.local/share/tassello/images`). Images are served sandboxed: an SVG cannot run
|
|
155
|
+
scripts in the dashboard.
|
|
156
|
+
|
|
157
|
+
## Plugins
|
|
158
|
+
|
|
159
|
+
A plugin adds widgets, and server-side code if they need it, to any Tassello: an npm package or
|
|
160
|
+
a folder whose `package.json` says what it has. Robots get their own controls this way without
|
|
161
|
+
forking the dashboard: a gripper, a battery gauge, a calibration procedure, a custom service.
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
tassello create-plugin my-robot # a plugin with an example widget and server side
|
|
165
|
+
cd my-robot && npm install && npm run build
|
|
166
|
+
tassello --plugin .
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Plugins are found:
|
|
170
|
+
|
|
171
|
+
- on the command line: `--plugin <folder or package>`;
|
|
172
|
+
- in ROS packages of the sourced workspaces that register a `tassello_plugins` resource in the
|
|
173
|
+
ament index, so that a robot's ROS package can carry its own;
|
|
174
|
+
- as npm packages named `tassello-plugin-*` (or `@scope/tassello-plugin-*`), installed next to
|
|
175
|
+
Tassello (e.g. both with `npm install -g`) or in the working directory's `node_modules`.
|
|
176
|
+
|
|
177
|
+
A plugin can also give Tassello's widgets new defaults (a robot's topic names and speed limits)
|
|
178
|
+
and ship example panels. See **[docs/plugins.md](docs/plugins.md)**.
|
|
179
|
+
|
|
180
|
+
## Security
|
|
181
|
+
|
|
182
|
+
Tassello has no login. **Anyone who can open the page can drive the robot, run any command as
|
|
183
|
+
the server's user and read its images**: keep it on a network you trust, or behind a reverse
|
|
184
|
+
proxy with authentication (it serves everything under `/`, its WebSocket at `/ws`).
|
|
185
|
+
|
|
186
|
+
## How it works
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
browser ── HTTP: page, /api (panels, topics, files), /plugins ────┐
|
|
190
|
+
└─ WebSocket /ws: twists, poses, images, maps, TF, ───────┤
|
|
191
|
+
topics, services, commands, plugins └─ server (Node.js, one ROS node)
|
|
192
|
+
├─ rclnodejs: ROS
|
|
193
|
+
├─ sharp: raw images to JPEG, maps to PNG
|
|
194
|
+
├─ bash: the buttons' commands
|
|
195
|
+
└─ the plugins' server sides
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Safety and bandwidth are the server's concern:
|
|
199
|
+
|
|
200
|
+
- A teleop widget streams its command while a pad is held. When the stream stops without its
|
|
201
|
+
final zero (page closed, network lost), the server publishes it after 0.5 s. A page that stops
|
|
202
|
+
answering the server's pings is dropped within 20 s. Arm jogs stop the same way.
|
|
203
|
+
- Each camera or map widget gets one frame at a time, the latest, and the next once it has drawn
|
|
204
|
+
it: a slow network or phone skips frames instead of lagging behind. Raw images are scaled down
|
|
205
|
+
and compressed once per frame, however many pages watch them. Maps are coloured and compressed
|
|
206
|
+
on the server and kept up to date with their `<topic>_updates`, subscribed with the durability
|
|
207
|
+
of their publishers, so latched maps arrive.
|
|
208
|
+
- Topics are subscribed once, whatever the number of pages, with the type and QoS of their
|
|
209
|
+
publishers; TF only while a page shows the robot.
|
|
210
|
+
- A button runs the command saved in its panel, never one sent by the page.
|
|
211
|
+
|
|
212
|
+
## Developing
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
git clone https://github.com/AntoBrandi/tassello && cd tassello
|
|
216
|
+
npm install
|
|
217
|
+
npm run build # the page (dist/www), the server and the plugin API (dist/)
|
|
218
|
+
npm run server # the server from its sources, with ROS sourced: http://localhost:8080
|
|
219
|
+
npm run dev # the page, reloaded on every change, at http://localhost:5173
|
|
220
|
+
npm run typecheck
|
|
221
|
+
npm test
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`npm run dev` forwards the API and the WebSocket to `DASHBOARD_SERVER`, `http://localhost:8080`
|
|
225
|
+
by default: point it at a robot to work on the page against the real thing.
|
|
226
|
+
|
|
227
|
+
- `src/client/`: the page, React built by Vite. `widgets/` holds the widgets, `board/` the grid,
|
|
228
|
+
`sdk/` what plugins import as `tassello/sdk`.
|
|
229
|
+
- `src/server/`: the server, TypeScript that Node.js runs as is in development and that the
|
|
230
|
+
build compiles to JavaScript for the package. `api.ts` is what plugins import as
|
|
231
|
+
`tassello/server`.
|
|
232
|
+
- `src/shared/`: what both sides agree on: the panels and the WebSocket protocol (`protocol.ts`).
|
|
233
|
+
- `src/vite/`: the Vite plugin that builds plugins, `tassello/vite`.
|
|
234
|
+
- `template/`: what `tassello create-plugin` copies.
|
|
235
|
+
|
|
236
|
+
To add a widget to Tassello itself, write it in `src/client/widgets/` like `ButtonWidget.tsx` and
|
|
237
|
+
list it in `registry.ts`. Never change a widget's `type`: the panels store it.
|
|
238
|
+
|
|
239
|
+
## License
|
|
240
|
+
|
|
241
|
+
[Apache 2.0](LICENSE)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type rclnodejs from 'rclnodejs';
|
|
2
|
+
import type { Logger } from './util.ts';
|
|
3
|
+
export type { Logger };
|
|
4
|
+
export interface PluginSession {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
}
|
|
7
|
+
export type RequestHandler = (params: unknown, session: PluginSession) => unknown;
|
|
8
|
+
export type StreamHandler = (params: unknown, emit: (data: unknown) => void, session: PluginSession) => (() => void) | void;
|
|
9
|
+
export interface CommandWidget {
|
|
10
|
+
property: string;
|
|
11
|
+
fallback?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface PluginContext {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly dir: string;
|
|
16
|
+
readonly node: rclnodejs.Node;
|
|
17
|
+
readonly rclnodejs: typeof rclnodejs;
|
|
18
|
+
readonly logger: Logger;
|
|
19
|
+
handle(method: string, handler: RequestHandler): void;
|
|
20
|
+
stream(name: string, handler: StreamHandler): void;
|
|
21
|
+
commandWidget(type: string, command: CommandWidget): void;
|
|
22
|
+
onShutdown(callback: () => void | Promise<void>): void;
|
|
23
|
+
}
|
|
24
|
+
export interface TasselloPlugin {
|
|
25
|
+
activate(context: PluginContext): void | Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export declare function definePlugin(plugin: TasselloPlugin): TasselloPlugin;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// The server side of a Tassello plugin, published as `tassello/server`: what a
|
|
2
|
+
// plugin's server module exports, and what it is given. A plugin extends the
|
|
3
|
+
// server with requests and streams its widgets reach through the page's
|
|
4
|
+
// connection (connection.request and connection.subscribePlugin of
|
|
5
|
+
// `tassello/sdk`), and with widgets that run commands.
|
|
6
|
+
//
|
|
7
|
+
// import type { TasselloPlugin } from 'tassello/server';
|
|
8
|
+
//
|
|
9
|
+
// export default {
|
|
10
|
+
// activate(ctx) {
|
|
11
|
+
// ctx.handle('greet', (params) => `Hello ${String(params)} from ${ctx.node.name()}`);
|
|
12
|
+
// },
|
|
13
|
+
// } satisfies TasselloPlugin;
|
|
14
|
+
//
|
|
15
|
+
// Importing only types keeps the module free of `tassello` at run time: the
|
|
16
|
+
// server gives the plugin everything it needs, the ROS node included.
|
|
17
|
+
// For plugins that prefer a function to `satisfies TasselloPlugin`
|
|
18
|
+
export function definePlugin(plugin) {
|
|
19
|
+
return plugin;
|
|
20
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type rclnodejs from 'rclnodejs';
|
|
2
|
+
import { type ArmIk, type ArmUpdate } from '../shared/protocol.ts';
|
|
3
|
+
import { Kinematics } from './kinematics.ts';
|
|
4
|
+
import type { Topics } from './topics.ts';
|
|
5
|
+
import { type Logger } from './util.ts';
|
|
6
|
+
interface Vector {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
z: number;
|
|
10
|
+
}
|
|
11
|
+
interface ServiceClient {
|
|
12
|
+
isServiceServerAvailable(): boolean;
|
|
13
|
+
sendRequestAsync(request: unknown, options?: {
|
|
14
|
+
timeout?: number;
|
|
15
|
+
}): Promise<unknown>;
|
|
16
|
+
}
|
|
17
|
+
export interface ArmOptions {
|
|
18
|
+
controllers: string[];
|
|
19
|
+
jointStatesTopic: string;
|
|
20
|
+
descriptionTopic: string;
|
|
21
|
+
calibrationTopic: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ArmJogInput {
|
|
24
|
+
joints: Record<string, number>;
|
|
25
|
+
cartesian: Vector;
|
|
26
|
+
ik: ArmIk;
|
|
27
|
+
}
|
|
28
|
+
interface Controller {
|
|
29
|
+
name: string;
|
|
30
|
+
joints: string[] | null;
|
|
31
|
+
error: string | null;
|
|
32
|
+
}
|
|
33
|
+
export declare class Arm {
|
|
34
|
+
readonly node: rclnodejs.Node;
|
|
35
|
+
readonly topics: Topics;
|
|
36
|
+
readonly logger: Logger;
|
|
37
|
+
private readonly clients;
|
|
38
|
+
private readonly trajectoryPublishers;
|
|
39
|
+
constructor(node: rclnodejs.Node, topics: Topics, logger: Logger);
|
|
40
|
+
watch(options: ArmOptions, send: (update: Omit<ArmUpdate, 'type' | 'id'>) => void): ArmWatcher;
|
|
41
|
+
client(type: string, name: string): ServiceClient;
|
|
42
|
+
publishTrajectory(controller: string, joints: string[], positions: number[], ahead: number, start: number[] | null): void;
|
|
43
|
+
}
|
|
44
|
+
export declare class ArmWatcher {
|
|
45
|
+
private readonly arm;
|
|
46
|
+
private readonly options;
|
|
47
|
+
private readonly send;
|
|
48
|
+
private readonly unwatch;
|
|
49
|
+
private disposed;
|
|
50
|
+
readonly controllers: Controller[];
|
|
51
|
+
readonly positions: Map<string, number>;
|
|
52
|
+
private urdf;
|
|
53
|
+
private urdfKinematics;
|
|
54
|
+
private calibration;
|
|
55
|
+
private topicProblems;
|
|
56
|
+
private jog;
|
|
57
|
+
jogStatus: ArmUpdate['jog'];
|
|
58
|
+
private lastSent;
|
|
59
|
+
private sendTimer;
|
|
60
|
+
private lastSentAt;
|
|
61
|
+
constructor(arm: Arm, options: ArmOptions, send: (update: Omit<ArmUpdate, 'type' | 'id'>) => void);
|
|
62
|
+
dispose(): void;
|
|
63
|
+
input(input: ArmJogInput): void;
|
|
64
|
+
setJogStatus(state: ArmUpdate['jog']['state'], message?: string): void;
|
|
65
|
+
range(name: string): {
|
|
66
|
+
lower: number | null;
|
|
67
|
+
upper: number | null;
|
|
68
|
+
};
|
|
69
|
+
refusal(name: string): string | null;
|
|
70
|
+
kinematics(): Kinematics | null;
|
|
71
|
+
controllerOf(joint: string): Controller | undefined;
|
|
72
|
+
private calibrationJoint;
|
|
73
|
+
private watchTopic;
|
|
74
|
+
private onJointStates;
|
|
75
|
+
private resolve;
|
|
76
|
+
private changed;
|
|
77
|
+
private update;
|
|
78
|
+
}
|
|
79
|
+
export {};
|