@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.0 → 0.1.2-dev.13
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/README.md +95 -16
- package/cli/bin/muscat-pk.mjs +10 -2
- package/cli/docker/Dockerfile.dev +13 -0
- package/cli/src/build.mjs +105 -2
- package/cli/src/check-package.mjs +2 -1
- package/cli/src/dev.mjs +9 -2
- package/cli/src/devBack.mjs +131 -43
- package/cli/src/devUi.mjs +47 -13
- package/cli/src/esbuild.mjs +50 -10
- package/cli/src/new.mjs +110 -27
- package/cli/src/pack.mjs +4 -1
- package/cli/src/preview.mjs +91 -0
- package/cli/src/ros2.mjs +65 -0
- package/cli/src/socketBridge.mjs +122 -0
- package/cli/src/type.mjs +82 -0
- package/cli/src/util.mjs +39 -2
- package/cli/templates/common/.vscode/settings.json +3 -0
- package/cli/templates/common/README.md +268 -9
- package/cli/templates/common/main.py +88 -0
- package/cli/templates/common/package.json +3 -10
- package/cli/templates/common/requirements.txt +3 -0
- package/cli/templates/common/src/content.tsx +3 -2
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/__init__.py +0 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +25 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/package.xml +10 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/resource/__PLUGIN_SLUG__ +0 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/setup.py +18 -0
- package/package.json +5 -3
- package/cli/templates/sdk/main.py +0 -21
- package/cli/templates/sdk/requirements.txt +0 -1
package/cli/src/util.mjs
CHANGED
|
@@ -1,8 +1,42 @@
|
|
|
1
1
|
// Shared helpers for muscat-pk.
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
2
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
4
|
import { dirname, resolve } from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
|
|
7
|
+
/** True if an executable is on PATH (cross-platform). */
|
|
8
|
+
export function hasBin(bin) {
|
|
9
|
+
const probe = process.platform === "win32" ? "where" : "which";
|
|
10
|
+
return spawnSync(probe, [bin]).status === 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Find a Python 3.12 interpreter (robot targets 3.12), cross-platform.
|
|
15
|
+
* Returns the command as an argv array, e.g. ["py","-3.12"] or ["python3.12"].
|
|
16
|
+
* Falls back to a generic python3/python with a warning flag.
|
|
17
|
+
*/
|
|
18
|
+
export function detectPythonCmd() {
|
|
19
|
+
const candidates =
|
|
20
|
+
process.platform === "win32"
|
|
21
|
+
? [["py", "-3.12"], ["python3.12"], ["py", "-3"], ["python"]]
|
|
22
|
+
: [["python3.12"], ["python3"], ["python"]];
|
|
23
|
+
for (const cmd of candidates) {
|
|
24
|
+
const r = spawnSync(cmd[0], [...cmd.slice(1), "--version"]);
|
|
25
|
+
if (r.status === 0) {
|
|
26
|
+
const ver = String(r.stdout || r.stderr || "");
|
|
27
|
+
return { cmd, is312: ver.includes("3.12") };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { cmd: process.platform === "win32" ? ["python"] : ["python3"], is312: false };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Path to the venv's python interpreter (cross-platform). */
|
|
34
|
+
export function venvPython(venvDir) {
|
|
35
|
+
return process.platform === "win32"
|
|
36
|
+
? resolve(venvDir, "Scripts", "python.exe")
|
|
37
|
+
: resolve(venvDir, "bin", "python");
|
|
38
|
+
}
|
|
39
|
+
|
|
6
40
|
/** Published install/CLI package name. Author imports use @rainbow-robotics/plugin-ui separately. */
|
|
7
41
|
export const KIT_PUBLISHED_NAME = "@rainbow-robotics/muscat-plugin-kit";
|
|
8
42
|
|
|
@@ -45,11 +79,14 @@ export function findPluginRoot(start = process.cwd()) {
|
|
|
45
79
|
);
|
|
46
80
|
}
|
|
47
81
|
|
|
82
|
+
const ORANGE = "\x1b[33m";
|
|
83
|
+
const RESET = "\x1b[0m";
|
|
84
|
+
|
|
48
85
|
export const log = {
|
|
49
86
|
info: (...m) => console.log(...m),
|
|
50
87
|
step: (...m) => console.log("›", ...m),
|
|
51
|
-
ok:
|
|
52
|
-
warn: (...m) => console.warn(
|
|
88
|
+
ok: (...m) => console.log("✓", ...m),
|
|
89
|
+
warn: (...m) => console.warn(`${ORANGE}⚠${RESET}`, ...m.map(s => `${ORANGE}${s}${RESET}`)),
|
|
53
90
|
};
|
|
54
91
|
|
|
55
92
|
export function humanSize(bytes) {
|
|
@@ -1,16 +1,275 @@
|
|
|
1
1
|
# __PLUGIN_NAME__
|
|
2
2
|
|
|
3
|
-
A Muscat plugin
|
|
3
|
+
A Muscat plugin project. Build, develop, and ship with `muscat-pk` CLI.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
muscat-pk dev # UI preview + backend together
|
|
9
|
+
muscat-pk dev:ui # UI preview only
|
|
10
|
+
muscat-pk dev:back # backend only (Python watchfiles / ROS2 Docker)
|
|
11
|
+
muscat-pk pack # build a distributable ZIP
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Project structure
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
__PLUGIN_DIR__/
|
|
20
|
+
├─ src/
|
|
21
|
+
│ └─ content.tsx # plugin UI entry point
|
|
22
|
+
├─ assets/
|
|
23
|
+
│ └─ icon.svg # icon referenced from manifest
|
|
24
|
+
├─ manifest.json # metadata, contribution points, runtime config
|
|
25
|
+
├─ main.py # backend (Python or ROS2)
|
|
26
|
+
└─ requirements.txt # Python deps (auto-pinned to latest on pack)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
> ROS2 plugins additionally include a `ros2_ws/` directory.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Frontend (`src/content.tsx`)
|
|
34
|
+
|
|
35
|
+
### useRb() hook
|
|
36
|
+
|
|
37
|
+
Access the communication API between the plugin module and UI from any plugin component.
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { useRb } from "@rainbow-robotics/plugin-ui/sdk";
|
|
41
|
+
|
|
42
|
+
function App() {
|
|
43
|
+
const rb = useRb();
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### rb API
|
|
48
|
+
|
|
49
|
+
| Method | Description |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `rb.pub(topic, payload)` | Publish a message to a topic |
|
|
52
|
+
| `rb.sub(topic, handler)` | Subscribe to a topic — returns `{ undeclare() }` |
|
|
53
|
+
| `rb.action(topic, payload?)` | Send a request and await a reply |
|
|
54
|
+
| `rb.serve(topic, handler)` | Handle incoming requests from the backend |
|
|
55
|
+
| `rb.toast(message, type?)` | Host toast notification (`"info" · "success" · "warning" · "error"`) |
|
|
56
|
+
|
|
57
|
+
All methods support Generic Types:
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
const status = await rb.action<{ ok: boolean }, { id: number }>(
|
|
61
|
+
"get-status",
|
|
62
|
+
{ id: 1 },
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const sub = await rb.sub<{ value: number }>("sensor/data", (data) => {
|
|
66
|
+
console.log(data.value);
|
|
67
|
+
});
|
|
68
|
+
sub.undeclare(); // cleanup
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Topic namespace
|
|
72
|
+
|
|
73
|
+
Topics are automatically namespaced by plugin ID to prevent collisions across plugins.
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
"sensor/data" → plugin/{pluginId}/sensor/data (auto-namespaced)
|
|
77
|
+
"/broadcast/x" → broadcast/x (leading / = absolute)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### UI components
|
|
81
|
+
|
|
82
|
+
Import Muscat components from `@rainbow-robotics/plugin-ui/react`. The plugin emits
|
|
83
|
+
`rb-*` tags; the host renders the actual components.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { connect } from "@rainbow-robotics/plugin-ui/connect";
|
|
87
|
+
import { Button, Slider, Text, Switch } from "@rainbow-robotics/plugin-ui/react";
|
|
88
|
+
import { useRb } from "@rainbow-robotics/plugin-ui/sdk";
|
|
89
|
+
|
|
90
|
+
function App() {
|
|
91
|
+
const rb = useRb();
|
|
92
|
+
return <Button label="Run" onPress={() => rb.action("run")} />;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
connect(App);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Backend (`main.py`)
|
|
101
|
+
|
|
102
|
+
The backend communicates with the frontend using `rainbow.rb_sdk.RBPluginCall`.
|
|
103
|
+
Relative topics and plugin context are handled by the SDK.
|
|
104
|
+
`muscat-pk dev` starts the local Socket.IO bridge separately; `main.py`
|
|
105
|
+
should contain plugin logic only and should not start its own server.
|
|
106
|
+
|
|
107
|
+
### Python plugin
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
import time
|
|
111
|
+
from rainbow.rb_sdk import RBAmrSDK, RBManipulateSDK, RBPluginCall
|
|
112
|
+
|
|
113
|
+
plugin = RBPluginCall()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
plugin.publish("state", {"ready": True})
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@plugin.subscribe("event")
|
|
120
|
+
def on_ui_event(payload=None):
|
|
121
|
+
print(payload)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@plugin.serve("tool/echo")
|
|
125
|
+
def echo(payload=None):
|
|
126
|
+
return {"echo": payload}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def call_frontend():
|
|
130
|
+
res = plugin.call("tool/ping", payload={"hello": "plugin"})
|
|
131
|
+
print(res)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def move_j_example():
|
|
135
|
+
amr = RBAmrSDK()
|
|
136
|
+
manipulate = RBManipulateSDK()
|
|
137
|
+
return manipulate.move.call_move_j(
|
|
138
|
+
robot_model="C500920",
|
|
139
|
+
target={"tar_values": [0, 0, 0, 0, 0, 0], "tar_frame": 0, "tar_unit": 0},
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
while True:
|
|
145
|
+
time.sleep(1)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### ROS2 plugin
|
|
149
|
+
|
|
150
|
+
The node is launched with `--ros-args -r __ns:=/{slug}` automatically by `dev:back`.
|
|
151
|
+
ROS2 topics are bridged to the plugin communication layer — no extra code needed.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
# ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py
|
|
155
|
+
import rclpy
|
|
156
|
+
from rclpy.node import Node
|
|
157
|
+
from std_msgs.msg import Float32
|
|
158
|
+
|
|
159
|
+
class __PLUGIN_CLASS__Node(Node):
|
|
160
|
+
def __init__(self):
|
|
161
|
+
super().__init__("__PLUGIN_SLUG___node")
|
|
162
|
+
# relative topic name — __ns prefix applied automatically
|
|
163
|
+
self.pub = self.create_publisher(Float32, "sensor_data", 10)
|
|
164
|
+
self.create_timer(1.0, self.timer_cb)
|
|
165
|
+
|
|
166
|
+
def timer_cb(self):
|
|
167
|
+
self.pub.publish(Float32(data=42.0))
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## manifest.json
|
|
173
|
+
|
|
174
|
+
```jsonc
|
|
175
|
+
{
|
|
176
|
+
"id": "com.example.my-plugin", // unique plugin ID (reverse-domain recommended)
|
|
177
|
+
"name": "My Plugin",
|
|
178
|
+
"version": "1.0.0",
|
|
179
|
+
"minAppVersion": "2.0.0",
|
|
180
|
+
"icon": "assets/icon.svg",
|
|
181
|
+
|
|
182
|
+
"contributes": [
|
|
183
|
+
{ "point": "program.panel", "label": "My Plugin", "icon": "assets/icon.svg" },
|
|
184
|
+
{ "point": "lnb", "label": "My Plugin", "icon": "assets/icon.svg" },
|
|
185
|
+
{ "point": "header.toolbar","label": "My Plugin", "icon": "assets/icon.svg", "opens": "popover" }
|
|
186
|
+
],
|
|
187
|
+
|
|
188
|
+
// "python" | "ros2"
|
|
189
|
+
"runtime": { "type": "python", "entry": "main.py" }
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Contribution points:**
|
|
194
|
+
|
|
195
|
+
| point | Where it appears |
|
|
196
|
+
|---|---|
|
|
197
|
+
| `program.panel` | Program panel tab |
|
|
198
|
+
| `lnb` | Left navigation bar |
|
|
199
|
+
| `header.toolbar` | Header toolbar button |
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Installing Python dependencies
|
|
204
|
+
|
|
205
|
+
Add any package with an optional version pin to `requirements.txt`:
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
rainbow-rb-sdk
|
|
209
|
+
rainbow-rb-flat-buffers
|
|
210
|
+
numpy==1.26.4
|
|
211
|
+
opencv-python
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Then just run `muscat-pk dev` or `muscat-pk dev:back` — dependencies are installed automatically on every startup:
|
|
215
|
+
|
|
216
|
+
- **Python plugin**: installed into `.venv` via `pip install -r requirements.txt`
|
|
217
|
+
- **ROS2 plugin**: installed inside the Docker container via `pip install -r requirements.txt`
|
|
218
|
+
|
|
219
|
+
No extra steps needed.
|
|
220
|
+
|
|
221
|
+
> `muscat-pk build` / `muscat-pk pack` only **pins** versions in `requirements.txt` — it does not run `pip install`.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Switching backend type
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
muscat-pk type change # interactive
|
|
229
|
+
muscat-pk type change --to ros2 # python → ros2
|
|
230
|
+
muscat-pk type change --to python # ros2 → python
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
> `ros2_ws/` is not deleted when switching back to python. Remove it manually if no longer needed.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Shipping
|
|
4
238
|
|
|
5
239
|
```bash
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
240
|
+
muscat-pk pack
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
- Builds `src/content.tsx` → `dist/content.js`
|
|
244
|
+
- Pins `requirements.txt` to latest versions (stable-only on `main` branch)
|
|
245
|
+
- ROS2: runs `colcon build` inside the Jazzy container; includes `ros2_ws/install/`
|
|
246
|
+
- Output: `__PLUGIN_DIR__-{version}.zip`
|
|
247
|
+
|
|
248
|
+
ZIP layout:
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
__PLUGIN_DIR__-1.0.0.zip
|
|
252
|
+
├─ dist/ # built UI
|
|
253
|
+
├─ main.py
|
|
254
|
+
├─ requirements.txt # pinned
|
|
255
|
+
└─ ros2_ws/install/ # ROS2 only
|
|
9
256
|
```
|
|
10
257
|
|
|
11
|
-
|
|
12
|
-
- `manifest.json` — id, contributions, actions, runtime.
|
|
13
|
-
- `assets/` — static assets (icons referenced from the manifest).
|
|
14
|
-
- `main.py` — (sdk runtime) back-end handler using `rainbow-rb-sdk`.
|
|
258
|
+
---
|
|
15
259
|
|
|
16
|
-
|
|
260
|
+
## All commands
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
muscat-pk dev # UI + backend
|
|
264
|
+
muscat-pk dev:ui # UI preview only (preview-host)
|
|
265
|
+
muscat-pk dev:back # backend only (Python: watchfiles / ROS2: Docker)
|
|
266
|
+
muscat-pk build # UI build only → dist/
|
|
267
|
+
muscat-pk pack # build + ZIP
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
muscat-pk type change # switch backend type
|
|
272
|
+
muscat-pk sdk list # show current Python SDK version
|
|
273
|
+
muscat-pk sdk update # update Python SDK to latest
|
|
274
|
+
muscat-pk update # update muscat-plugin-kit itself
|
|
275
|
+
```
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Muscat plugin backend — __PLUGIN_NAME__
|
|
3
|
+
|
|
4
|
+
Run with:
|
|
5
|
+
muscat-pk dev:back
|
|
6
|
+
python main.py
|
|
7
|
+
|
|
8
|
+
This template uses the high-level Rainbow Python SDK instead of wiring the
|
|
9
|
+
transport layer directly.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import time
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from rainbow.rb_sdk import RBManipulateSDK, RBPluginCall
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
PLUGIN_NAME = "__PLUGIN_NAME__"
|
|
19
|
+
|
|
20
|
+
plugin = RBPluginCall()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@plugin.subscribe("event")
|
|
24
|
+
def on_ui_event(payload: dict[str, Any] | None = None):
|
|
25
|
+
"""Handle messages published from the plugin UI with rb.pub("event", ...)."""
|
|
26
|
+
print(f"[{PLUGIN_NAME}] event:", payload)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@plugin.serve("grip")
|
|
30
|
+
def grip(payload: dict[str, Any] | None = None):
|
|
31
|
+
"""Handle the default UI button: rb.action("grip", { width })."""
|
|
32
|
+
width = (payload or {}).get("width", 20)
|
|
33
|
+
print(f"[{PLUGIN_NAME}] grip requested: width={width}")
|
|
34
|
+
|
|
35
|
+
plugin.publish("state", {"ready": True, "last_grip_width": width})
|
|
36
|
+
return {"ok": True, "width": width}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@plugin.serve("tool/echo")
|
|
40
|
+
def echo(payload: dict[str, Any] | None = None):
|
|
41
|
+
"""Small query example callable from the UI with rb.action("tool/echo", ...)."""
|
|
42
|
+
return {"echo": payload, "plugin": PLUGIN_NAME}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def ping_ui_example():
|
|
46
|
+
"""
|
|
47
|
+
Example: call a queryable served by the UI.
|
|
48
|
+
|
|
49
|
+
In the frontend, register:
|
|
50
|
+
rb.serve("tool/ping", async (payload) => ({ pong: payload }))
|
|
51
|
+
"""
|
|
52
|
+
res = plugin.call("tool/ping", payload={"hello": "plugin"})
|
|
53
|
+
print(f"[{PLUGIN_NAME}] ping response:", res)
|
|
54
|
+
return res
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def move_j_example():
|
|
58
|
+
"""
|
|
59
|
+
Example: use other robot SDKs from the same backend.
|
|
60
|
+
|
|
61
|
+
This function is not called automatically. Add safety checks and call it
|
|
62
|
+
from your own handler when the robot is ready.
|
|
63
|
+
"""
|
|
64
|
+
manipulate = RBManipulateSDK()
|
|
65
|
+
|
|
66
|
+
return manipulate.move.call_move_j(
|
|
67
|
+
robot_model="C500920",
|
|
68
|
+
target={
|
|
69
|
+
"tar_values": [0, 0, 0, 0, 0, 0],
|
|
70
|
+
"tar_frame": 0,
|
|
71
|
+
"tar_unit": 0,
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def main():
|
|
77
|
+
plugin.publish("state", {"ready": True, "plugin": PLUGIN_NAME})
|
|
78
|
+
print(f"[{PLUGIN_NAME}] backend ready")
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
while True:
|
|
82
|
+
time.sleep(1)
|
|
83
|
+
except KeyboardInterrupt:
|
|
84
|
+
print(f"[{PLUGIN_NAME}] stopped")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
main()
|
|
@@ -3,19 +3,12 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "muscat-pk dev",
|
|
8
|
-
"dev:ui": "muscat-pk dev:ui",
|
|
9
|
-
"dev:back": "muscat-pk dev:back",
|
|
10
|
-
"build": "muscat-pk build",
|
|
11
|
-
"pack": "muscat-pk pack"
|
|
12
|
-
},
|
|
13
6
|
"dependencies": {
|
|
14
|
-
"@rainbow-robotics/plugin-ui": "
|
|
7
|
+
"@rainbow-robotics/plugin-ui": "__RB_VERSION__"
|
|
15
8
|
},
|
|
16
9
|
"devDependencies": {
|
|
17
|
-
"@rainbow-robotics/muscat-plugin-kit": "
|
|
18
|
-
"@rainbow-robotics/preview-host": "
|
|
10
|
+
"@rainbow-robotics/muscat-plugin-kit": "__RB_VERSION__",
|
|
11
|
+
"@rainbow-robotics/preview-host": "__RB_VERSION__",
|
|
19
12
|
"typescript": "^5.8.0",
|
|
20
13
|
"vite": "^6.4.1"
|
|
21
14
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import { connect } from "@rainbow-robotics/plugin-ui/connect";
|
|
3
|
-
import { Slider, Button, Text, Switch
|
|
3
|
+
import { Slider, Button, Text, Switch } from "@rainbow-robotics/plugin-ui/react";
|
|
4
|
+
import { useRb } from "@rainbow-robotics/plugin-ui/sdk";
|
|
4
5
|
|
|
5
6
|
function App() {
|
|
6
7
|
const rb = useRb();
|
|
@@ -22,7 +23,7 @@ function App() {
|
|
|
22
23
|
onChange={(event: { detail?: boolean }) => {
|
|
23
24
|
const next = event.detail ?? false;
|
|
24
25
|
setOpen(next);
|
|
25
|
-
rb.
|
|
26
|
+
rb.toast(next ? "opened" : "closed", "info");
|
|
26
27
|
}}
|
|
27
28
|
/>
|
|
28
29
|
</>
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import rclpy
|
|
2
|
+
from rclpy.node import Node
|
|
3
|
+
from rainbow.rb_sdk import RBManipulateSDK # PyPI: rainbow-rb-sdk
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
sdk = RBManipulateSDK()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class __PLUGIN_CLASS__Node(Node):
|
|
10
|
+
def __init__(self):
|
|
11
|
+
super().__init__("__PLUGIN_SLUG___node")
|
|
12
|
+
self.get_logger().info("__PLUGIN_NAME__ node started")
|
|
13
|
+
# sdk is available here — use it alongside ROS2 topics as needed
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main(args=None):
|
|
17
|
+
rclpy.init(args=args)
|
|
18
|
+
node = __PLUGIN_CLASS__Node()
|
|
19
|
+
rclpy.spin(node)
|
|
20
|
+
node.destroy_node()
|
|
21
|
+
rclpy.shutdown()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
main()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<package format="3">
|
|
3
|
+
<name>__PLUGIN_SLUG__</name>
|
|
4
|
+
<version>0.0.1</version>
|
|
5
|
+
<description>__PLUGIN_NAME__ ROS2 node</description>
|
|
6
|
+
<maintainer email="todo@example.com">maintainer</maintainer>
|
|
7
|
+
<license>Proprietary</license>
|
|
8
|
+
<depend>rclpy</depend>
|
|
9
|
+
<export><build_type>ament_python</build_type></export>
|
|
10
|
+
</package>
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from setuptools import setup
|
|
2
|
+
|
|
3
|
+
package_name = "__PLUGIN_SLUG__"
|
|
4
|
+
|
|
5
|
+
setup(
|
|
6
|
+
name=package_name,
|
|
7
|
+
version="0.0.1",
|
|
8
|
+
packages=[package_name],
|
|
9
|
+
data_files=[
|
|
10
|
+
("share/ament_index/resource_index/packages", ["resource/" + package_name]),
|
|
11
|
+
("share/" + package_name, ["package.xml"]),
|
|
12
|
+
],
|
|
13
|
+
install_requires=["setuptools"],
|
|
14
|
+
zip_safe=True,
|
|
15
|
+
entry_points={
|
|
16
|
+
"console_scripts": ["__PLUGIN_SLUG___node = __PLUGIN_SLUG__.node:main"],
|
|
17
|
+
},
|
|
18
|
+
)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rainbow-robotics/muscat-plugin-kit",
|
|
3
|
-
"version": "0.1.2-dev.
|
|
3
|
+
"version": "0.1.2-dev.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20"
|
|
@@ -31,8 +31,10 @@
|
|
|
31
31
|
"@types/react-dom": "^19.0.4"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@rainbow-robotics/preview-host": "
|
|
35
|
-
"
|
|
34
|
+
"@rainbow-robotics/preview-host": "0.1.2-dev.13",
|
|
35
|
+
"socket.io": "^4.8.1"
|
|
36
|
+
},
|
|
37
|
+
"optionalDependencies": {
|
|
36
38
|
"esbuild-wasm": "^0.25.0"
|
|
37
39
|
}
|
|
38
40
|
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
|
|
3
|
-
from rb_sdk import RBManipulateSDK # PyPI: rainbow-rb-sdk
|
|
4
|
-
|
|
5
|
-
sdk = RBManipulateSDK()
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
async def on_action(payload: dict):
|
|
9
|
-
# Matches manifest.actions[].topic — handle the "grip" action here.
|
|
10
|
-
width = payload.get("width")
|
|
11
|
-
print(f"[__PLUGIN_NAME__] grip width={width}")
|
|
12
|
-
# TODO: drive the robot via the SDK.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
async def main():
|
|
16
|
-
await sdk.subscribe("rby2/plugin/__PLUGIN_ID__/grip", on_action)
|
|
17
|
-
await sdk.spin()
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if __name__ == "__main__":
|
|
21
|
-
asyncio.run(main())
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
rainbow-rb-sdk==__SDK_VERSION__
|