@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.23 → 0.1.2-dev.25
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/cli/README.md +4 -5
- package/cli/bin/create-muscat-plugin.mjs +1 -1
- package/cli/bin/muscat-pk.mjs +4 -8
- package/cli/docker/Dockerfile.bridge +16 -0
- package/cli/docker/Dockerfile.cpp +21 -0
- package/cli/docker/Dockerfile.python +18 -0
- package/cli/docker/bridge-package.json +8 -0
- package/cli/src/bridge.mjs +91 -0
- package/cli/src/build.mjs +84 -11
- package/cli/src/cpp.mjs +51 -0
- package/cli/src/dev.mjs +12 -6
- package/cli/src/devBack.mjs +145 -46
- package/cli/src/devUi.mjs +15 -11
- package/cli/src/manifest.mjs +33 -4
- package/cli/src/new.mjs +87 -57
- package/cli/src/python.mjs +31 -0
- package/cli/src/socketBridge.mjs +14 -1
- package/cli/src/type.mjs +51 -19
- package/cli/src/util.mjs +10 -0
- package/cli/templates/common/README.md +49 -25
- package/cli/templates/common/manifest.json +4 -1
- package/cli/templates/common/requirements.txt +3 -3
- package/cli/templates/cpp/cpp/CMakeLists.txt +14 -0
- package/cli/templates/cpp/cpp/main.cpp +25 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +17 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +22 -0
- package/package.json +2 -2
- package/cli/src/pack.mjs +0 -58
package/cli/src/socketBridge.mjs
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
3
4
|
import { Server } from "socket.io";
|
|
4
|
-
|
|
5
|
+
|
|
6
|
+
// Self-contained logger — this module also runs standalone inside the bridge
|
|
7
|
+
// Docker container, where ./util.mjs is not available.
|
|
8
|
+
const log = {
|
|
9
|
+
step: (...m) => console.log("›", ...m),
|
|
10
|
+
warn: (...m) => console.warn("⚠", ...m),
|
|
11
|
+
};
|
|
5
12
|
|
|
6
13
|
const DEFAULT_ACTION_TIMEOUT_MS = 10_000;
|
|
7
14
|
|
|
@@ -114,3 +121,9 @@ export function startSocketBridge({ port = 10000, host = "0.0.0.0" } = {}) {
|
|
|
114
121
|
});
|
|
115
122
|
});
|
|
116
123
|
}
|
|
124
|
+
|
|
125
|
+
// When executed directly (inside the bridge Docker container), start the server.
|
|
126
|
+
if (process.argv[1] && process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
127
|
+
const port = Number(process.env.PORT) || 10000;
|
|
128
|
+
startSocketBridge({ port });
|
|
129
|
+
}
|
package/cli/src/type.mjs
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
// muscat-pk type change — switch an existing plugin's backend type.
|
|
2
|
-
// python
|
|
3
|
-
// the
|
|
2
|
+
// python | cpp | ros2 (rclpy) | ros2-cpp (rclcpp).
|
|
3
|
+
// Adds the matching backend overlay; never deletes existing user code
|
|
4
|
+
// (e.g. ros2_ws/ or cpp/ are left in place when switching away).
|
|
4
5
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
6
|
import { createInterface } from "node:readline/promises";
|
|
6
7
|
import { basename, resolve } from "node:path";
|
|
7
8
|
import { findPluginRoot, log } from "./util.mjs";
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
TYPE_CHOICES,
|
|
11
|
+
applyTokens,
|
|
12
|
+
deriveNames,
|
|
13
|
+
overlayCpp,
|
|
14
|
+
overlayRos2,
|
|
15
|
+
runtimeForChoice,
|
|
16
|
+
} from "./new.mjs";
|
|
9
17
|
|
|
10
|
-
const TYPES =
|
|
18
|
+
const TYPES = Object.keys(TYPE_CHOICES);
|
|
11
19
|
|
|
12
20
|
async function prompt(question, fallback) {
|
|
13
21
|
if (!process.stdin.isTTY) return fallback;
|
|
@@ -23,44 +31,54 @@ function readManifest(root) {
|
|
|
23
31
|
return { path, json: JSON.parse(readFileSync(path, "utf8")) };
|
|
24
32
|
}
|
|
25
33
|
|
|
34
|
+
// Derive the current `--type`-style key from a manifest runtime block.
|
|
35
|
+
function currentTypeKey(runtime) {
|
|
36
|
+
if (runtime?.type === "ros2") return runtime.rosClient === "rclcpp" ? "ros2-cpp" : "ros2";
|
|
37
|
+
return runtime?.type ?? "python";
|
|
38
|
+
}
|
|
39
|
+
|
|
26
40
|
export async function runType(args) {
|
|
27
|
-
// accept `type change`, `type`, `type --to ros2`
|
|
41
|
+
// accept `type change`, `type`, `type --to ros2-cpp`
|
|
28
42
|
const sub = args[0];
|
|
29
43
|
const toFlagIdx = args.indexOf("--to");
|
|
30
44
|
let to = toFlagIdx >= 0 ? args[toFlagIdx + 1] : undefined;
|
|
31
45
|
|
|
32
46
|
const root = findPluginRoot();
|
|
33
47
|
const { path, json } = readManifest(root);
|
|
34
|
-
const current = json.runtime
|
|
48
|
+
const current = currentTypeKey(json.runtime);
|
|
35
49
|
|
|
36
50
|
if (sub && sub !== "change") {
|
|
37
|
-
log.warn(`unknown subcommand '${sub}'. Usage: muscat-pk type change [--to python|ros2]`);
|
|
51
|
+
log.warn(`unknown subcommand '${sub}'. Usage: muscat-pk type change [--to python|cpp|ros2|ros2-cpp]`);
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
if (!to) {
|
|
41
55
|
console.log(`\n현재 타입: ${current}`);
|
|
42
56
|
console.log("변경할 타입을 선택하세요:");
|
|
43
57
|
console.log(" 1) Python");
|
|
44
|
-
console.log(" 2)
|
|
45
|
-
|
|
46
|
-
|
|
58
|
+
console.log(" 2) C++");
|
|
59
|
+
console.log(" 3) ROS2 (rclpy / Python)");
|
|
60
|
+
console.log(" 4) ROS2 (rclcpp / C++)");
|
|
61
|
+
const choice = await prompt("\n선택 [1/2/3/4]: ", "");
|
|
62
|
+
to = { "1": "python", "2": "cpp", "3": "ros2", "4": "ros2-cpp" }[choice];
|
|
47
63
|
}
|
|
48
64
|
|
|
49
65
|
if (!TYPES.includes(to)) {
|
|
50
|
-
throw new Error(`invalid type. choose python
|
|
66
|
+
throw new Error(`invalid type. choose python | cpp | ros2 | ros2-cpp.`);
|
|
51
67
|
}
|
|
52
68
|
if (to === current) {
|
|
53
69
|
log.info(`already '${current}' — nothing to change.`);
|
|
54
70
|
return;
|
|
55
71
|
}
|
|
56
72
|
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
const runtimeChoice = TYPE_CHOICES[to];
|
|
74
|
+
const { slug, pluginName, pluginClass, pluginId } = deriveNames(root);
|
|
75
|
+
|
|
76
|
+
if (runtimeChoice.type === "ros2") {
|
|
59
77
|
if (existsSync(resolve(root, "ros2_ws"))) {
|
|
60
78
|
log.warn("ros2_ws/ already exists — keeping it, only flipping manifest type.");
|
|
61
79
|
} else {
|
|
62
|
-
log.step(
|
|
63
|
-
overlayRos2(root, slug);
|
|
80
|
+
log.step(`adding ros2_ws/ overlay (${runtimeChoice.rosClient})`);
|
|
81
|
+
overlayRos2(root, slug, runtimeChoice.rosClient);
|
|
64
82
|
applyTokens(root, {
|
|
65
83
|
__PLUGIN_ID__: pluginId,
|
|
66
84
|
__PLUGIN_NAME__: pluginName,
|
|
@@ -69,14 +87,28 @@ export async function runType(args) {
|
|
|
69
87
|
__RUNTIME_TYPE__: "ros2",
|
|
70
88
|
});
|
|
71
89
|
}
|
|
90
|
+
} else if (runtimeChoice.type === "cpp") {
|
|
91
|
+
if (existsSync(resolve(root, "cpp"))) {
|
|
92
|
+
log.warn("cpp/ already exists — keeping it, only flipping manifest type.");
|
|
93
|
+
} else {
|
|
94
|
+
log.step("adding cpp/ overlay");
|
|
95
|
+
overlayCpp(root);
|
|
96
|
+
applyTokens(root, {
|
|
97
|
+
__PLUGIN_ID__: pluginId,
|
|
98
|
+
__PLUGIN_NAME__: pluginName,
|
|
99
|
+
__PLUGIN_SLUG__: slug,
|
|
100
|
+
__PLUGIN_CLASS__: pluginClass,
|
|
101
|
+
__RUNTIME_TYPE__: "cpp",
|
|
102
|
+
});
|
|
103
|
+
}
|
|
72
104
|
} else {
|
|
73
|
-
//
|
|
74
|
-
if (existsSync(resolve(root, "ros2_ws"))) {
|
|
75
|
-
log.warn("
|
|
105
|
+
// → python. Leave any ros2_ws/ or cpp/ in place (never delete user code).
|
|
106
|
+
if (existsSync(resolve(root, "ros2_ws")) || existsSync(resolve(root, "cpp"))) {
|
|
107
|
+
log.warn("existing backend dir left in place (not deleted). Remove it manually if unused.");
|
|
76
108
|
}
|
|
77
109
|
}
|
|
78
110
|
|
|
79
|
-
json.runtime =
|
|
111
|
+
json.runtime = runtimeForChoice(runtimeChoice, slug);
|
|
80
112
|
writeFileSync(path, JSON.stringify(json, null, 2) + "\n");
|
|
81
113
|
log.ok(`${basename(root)} type: ${current} → ${to}`);
|
|
82
114
|
}
|
package/cli/src/util.mjs
CHANGED
|
@@ -91,6 +91,16 @@ export const log = {
|
|
|
91
91
|
error: (...m) => console.error(`${RED}✖${RESET}`, ...m.map(s => `${RED}${s}${RESET}`)),
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
+
// dev / dev:back / build run the plugin back end, the dev bridge, and cpp/ros2
|
|
95
|
+
// compilation in Docker. Show a clear install hint when Docker is missing.
|
|
96
|
+
export function ensureDocker() {
|
|
97
|
+
if (hasBin("docker")) return;
|
|
98
|
+
log.warn("Docker not found.");
|
|
99
|
+
log.warn(" muscat-pk runs the plugin back end, the dev bridge, and cpp/ros2 builds in Docker.");
|
|
100
|
+
log.warn(" Install Docker, then retry: https://docs.docker.com/get-docker/");
|
|
101
|
+
throw new Error("Docker is required for dev / dev:back / build.");
|
|
102
|
+
}
|
|
103
|
+
|
|
94
104
|
export function humanSize(bytes) {
|
|
95
105
|
if (bytes < 1024) return `${bytes} B`;
|
|
96
106
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -8,7 +8,7 @@ A Muscat plugin project. Build, develop, and ship with `muscat-pk` CLI.
|
|
|
8
8
|
muscat-pk dev # UI preview + backend together
|
|
9
9
|
muscat-pk dev:ui # UI preview only
|
|
10
10
|
muscat-pk dev:back # backend only (Python watchfiles / ROS2 Docker)
|
|
11
|
-
muscat-pk
|
|
11
|
+
muscat-pk build # build + distributable ZIP
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
---
|
|
@@ -25,7 +25,7 @@ __PLUGIN_DIR__/
|
|
|
25
25
|
│ └─ icon.svg # icon referenced from manifest
|
|
26
26
|
├─ manifest.json # metadata, contribution points, runtime config
|
|
27
27
|
├─ README.md # this file (copied to dist/ on build)
|
|
28
|
-
└─ requirements.txt # Python deps (auto-pinned to latest on
|
|
28
|
+
└─ requirements.txt # Python deps (auto-pinned to latest on build)
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
> ROS2 plugins additionally include a `ros2_ws/` directory.
|
|
@@ -102,8 +102,9 @@ connect(App);
|
|
|
102
102
|
## Backend (`py/main.py`)
|
|
103
103
|
|
|
104
104
|
The backend communicates with the frontend using `rainbow.rb_sdk.RBPluginCall`.
|
|
105
|
-
`muscat-pk dev` starts the
|
|
106
|
-
|
|
105
|
+
`muscat-pk dev` starts the Socket.IO bridge in Docker (published on port 10000) and
|
|
106
|
+
runs the backend container sharing its network, so the SDK connects at localhost:10000.
|
|
107
|
+
`py/main.py` should contain plugin logic only and must not start its own server.
|
|
107
108
|
|
|
108
109
|
### Python plugin
|
|
109
110
|
|
|
@@ -192,6 +193,13 @@ class __PLUGIN_CLASS__Node(Node):
|
|
|
192
193
|
"icons": "assets/icon.svg",
|
|
193
194
|
// "icons": { "16": "assets/icon-16.png", "48": "assets/icon-48.png", "128": "assets/icon-128.png" },
|
|
194
195
|
|
|
196
|
+
// targetModels and/or targetCategorys — at least one is required
|
|
197
|
+
// "all" = every target, or list specific values
|
|
198
|
+
"targetModels": "all",
|
|
199
|
+
// "targetModels": ["C500920", "C500930"],
|
|
200
|
+
"targetCategorys": "all",
|
|
201
|
+
// "targetCategorys": ["manipulate", "amr", "rby1", "vision"],
|
|
202
|
+
|
|
195
203
|
"contributes": [
|
|
196
204
|
// program.command — adds a step node to the program tree
|
|
197
205
|
{
|
|
@@ -223,11 +231,26 @@ class __PLUGIN_CLASS__Node(Node):
|
|
|
223
231
|
}
|
|
224
232
|
],
|
|
225
233
|
|
|
226
|
-
//
|
|
234
|
+
// runtime — one of:
|
|
235
|
+
// { "type": "none" }
|
|
236
|
+
// { "type": "python", "entry": "main.py" }
|
|
237
|
+
// { "type": "cpp", "entry": "<executable name>" }
|
|
238
|
+
// { "type": "ros2", "rosClient": "rclpy" | "rclcpp", "package": "...", "launch": "..._node" }
|
|
227
239
|
"runtime": { "type": "python", "entry": "main.py" }
|
|
228
240
|
}
|
|
229
241
|
```
|
|
230
242
|
|
|
243
|
+
**Backend types:**
|
|
244
|
+
|
|
245
|
+
| type | source dir | build / run | entry |
|
|
246
|
+
|---|---|---|---|
|
|
247
|
+
| `python` | `py/` | pip + run in Docker | `main.py` |
|
|
248
|
+
| `cpp` | `cpp/` | CMake in Docker | built executable name |
|
|
249
|
+
| `ros2` (rclpy) | `ros2_ws/` | colcon (ament_python) in Docker | — |
|
|
250
|
+
| `ros2` (rclcpp) | `ros2_ws/` | colcon (ament_cmake) in Docker | — |
|
|
251
|
+
|
|
252
|
+
> Every back end runs in a Docker container matching the robot (Ubuntu 24.04 + Python 3.12), so dev behaves like production and compiled binaries are ABI-compatible. **Docker is required** for all backend types (only a frontend-only `none` plugin skips it). No local Python/toolchain setup is needed.
|
|
253
|
+
|
|
231
254
|
**Contribution points:**
|
|
232
255
|
|
|
233
256
|
| point | Where it appears | `commandInfo` required |
|
|
@@ -258,9 +281,11 @@ class __PLUGIN_CLASS__Node(Node):
|
|
|
258
281
|
|
|
259
282
|
**Validation rules:**
|
|
260
283
|
- `id`, `name`, `version`, `minMuscatVersion` — required non-empty strings
|
|
261
|
-
- `author` — required
|
|
284
|
+
- `author` — required non-empty string
|
|
262
285
|
- `description` — required (`string` or `{ en (required), ko?, ja?, zh?, vi?, th?, es? }`)
|
|
263
286
|
- `icons` — SVG: single string path ending in `.svg`; PNG/JPG: object with `"16"`, `"48"`, `"128"` all present
|
|
287
|
+
- `targetModels` — `"all"` or a non-empty array of robot model ID strings (at least one of `targetModels` / `targetCategorys` required)
|
|
288
|
+
- `targetCategorys` — `"all"` or a non-empty array of `"manipulate" | "amr" | "rby1" | "vision"` (at least one of `targetModels` / `targetCategorys` required)
|
|
264
289
|
- `contributes` — required array; must contain at least one `toolbar` entry
|
|
265
290
|
- `ui` — each contribution must have a unique value
|
|
266
291
|
- `commandInfo` — required when `point` is `"program.command"`
|
|
@@ -281,7 +306,7 @@ README.ja.md # Japanese
|
|
|
281
306
|
```
|
|
282
307
|
|
|
283
308
|
All `README*.md` files found at the project root are automatically copied to `dist/` on
|
|
284
|
-
`muscat-pk build
|
|
309
|
+
`muscat-pk build`. Muscat displays the file matching the user's locale,
|
|
285
310
|
falling back to `README.md` if none matches.
|
|
286
311
|
|
|
287
312
|
---
|
|
@@ -297,36 +322,36 @@ numpy==1.26.4
|
|
|
297
322
|
opencv-python
|
|
298
323
|
```
|
|
299
324
|
|
|
300
|
-
Run `muscat-pk dev` or `muscat-pk dev:back` — dependencies are installed automatically on every startup.
|
|
301
|
-
|
|
302
|
-
- **Python plugin**: installed into `.venv` via `pip install -r requirements.txt`
|
|
303
|
-
- **ROS2 plugin**: installed inside the Docker container via `pip install -r requirements.txt`
|
|
325
|
+
Run `muscat-pk dev` or `muscat-pk dev:back` — dependencies are installed automatically inside the dev container (`pip install -r requirements.txt`) on every startup. No local Python or venv needed.
|
|
304
326
|
|
|
305
|
-
> `muscat-pk build`
|
|
327
|
+
> `muscat-pk build` only **pins** versions in `requirements.txt` — it does not run `pip install` (the robot installs them at load time).
|
|
306
328
|
|
|
307
329
|
---
|
|
308
330
|
|
|
309
331
|
## Switching backend type
|
|
310
332
|
|
|
311
333
|
```bash
|
|
312
|
-
muscat-pk type change
|
|
313
|
-
muscat-pk type change --to
|
|
314
|
-
muscat-pk type change --to
|
|
334
|
+
muscat-pk type change # interactive
|
|
335
|
+
muscat-pk type change --to cpp # → C++
|
|
336
|
+
muscat-pk type change --to ros2 # → ROS2 (rclpy / Python)
|
|
337
|
+
muscat-pk type change --to ros2-cpp # → ROS2 (rclcpp / C++)
|
|
338
|
+
muscat-pk type change --to python # → Python
|
|
315
339
|
```
|
|
316
340
|
|
|
317
|
-
> `ros2_ws/`
|
|
341
|
+
> Existing backend dirs (`ros2_ws/`, `cpp/`) are not deleted when switching away. Remove them manually if no longer needed.
|
|
318
342
|
|
|
319
343
|
---
|
|
320
344
|
|
|
321
345
|
## Shipping
|
|
322
346
|
|
|
323
347
|
```bash
|
|
324
|
-
muscat-pk
|
|
348
|
+
muscat-pk build # build + ZIP
|
|
349
|
+
muscat-pk build --no-zip # build only, no ZIP
|
|
325
350
|
```
|
|
326
351
|
|
|
327
|
-
- Builds `ui/content.tsx` → `dist/content.js`
|
|
352
|
+
- Builds `ui/content.tsx` → `dist/content.js` (minified)
|
|
328
353
|
- Pins `requirements.txt` to latest versions (stable-only on `main` branch)
|
|
329
|
-
- Copies `assets/`, `manifest.json`,
|
|
354
|
+
- Copies `assets/`, `manifest.json`, `README*.md`, `py/`, and `requirements.txt` into `dist/`
|
|
330
355
|
- ROS2: runs `colcon build` inside the Jazzy container; includes `ros2_ws/install/`
|
|
331
356
|
- Output: `__PLUGIN_DIR__-{version}.zip`
|
|
332
357
|
|
|
@@ -339,10 +364,10 @@ __PLUGIN_DIR__-1.0.0.zip
|
|
|
339
364
|
│ ├─ manifest.json
|
|
340
365
|
│ ├─ assets/
|
|
341
366
|
│ ├─ README.md
|
|
342
|
-
│
|
|
343
|
-
├─ py/
|
|
344
|
-
│ └─
|
|
345
|
-
└─
|
|
367
|
+
│ ├─ README.ko.md # if present
|
|
368
|
+
│ ├─ main.py # from py/
|
|
369
|
+
│ └─ requirements.txt # pinned
|
|
370
|
+
└─ ros2_ws/install/ # ROS2 only
|
|
346
371
|
```
|
|
347
372
|
|
|
348
373
|
---
|
|
@@ -353,8 +378,7 @@ __PLUGIN_DIR__-1.0.0.zip
|
|
|
353
378
|
muscat-pk dev # UI + backend
|
|
354
379
|
muscat-pk dev:ui # UI preview only (preview-host)
|
|
355
380
|
muscat-pk dev:back # backend only (Python: watchfiles / ROS2: Docker)
|
|
356
|
-
muscat-pk build #
|
|
357
|
-
muscat-pk pack # build + ZIP
|
|
381
|
+
muscat-pk build # build → dist/ + distributable ZIP (--no-zip to skip)
|
|
358
382
|
```
|
|
359
383
|
|
|
360
384
|
```bash
|
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
"name": "__PLUGIN_NAME__",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"minMuscatVersion": "1.0.0",
|
|
6
|
-
"author": "",
|
|
6
|
+
"author": "__PLUGIN_AUTHOR__",
|
|
7
|
+
"email": "__PLUGIN_EMAIL__",
|
|
7
8
|
"description": {
|
|
8
9
|
"ko": "",
|
|
9
10
|
"en": ""
|
|
10
11
|
},
|
|
11
12
|
"icons": "assets/icon.svg",
|
|
13
|
+
"targetModels": "all",
|
|
14
|
+
"targetCategorys": "all",
|
|
12
15
|
"contributes": [
|
|
13
16
|
{
|
|
14
17
|
"point": "program.command",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
rainbow-rb-sdk==0.0.9.
|
|
2
|
-
rainbow-rb-flat-buffers==0.0.9.
|
|
3
|
-
rainbow-rb-zenoh==0.0.9.
|
|
1
|
+
rainbow-rb-sdk==0.0.9.dev43
|
|
2
|
+
rainbow-rb-flat-buffers==0.0.9.dev43
|
|
3
|
+
rainbow-rb-zenoh==0.0.9.dev43
|
|
4
4
|
requests==2.34.2
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.16)
|
|
2
|
+
project(__PLUGIN_SLUG__ CXX)
|
|
3
|
+
|
|
4
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
5
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
6
|
+
|
|
7
|
+
# The executable name must match runtime.entry in manifest.json.
|
|
8
|
+
add_executable(__PLUGIN_SLUG__ main.cpp)
|
|
9
|
+
|
|
10
|
+
# TODO(cpp-sdk): link the rainbow cpp SDK dynamically (the robot provides the
|
|
11
|
+
# .so at runtime — do NOT bundle/static-link it). Once the SDK ships an apt
|
|
12
|
+
# package with CMake config, enable:
|
|
13
|
+
# find_package(rainbow_cpp_sdk REQUIRED)
|
|
14
|
+
# target_link_libraries(__PLUGIN_SLUG__ PRIVATE rainbow::cpp_sdk)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// __PLUGIN_NAME__ — C++ plugin back end.
|
|
2
|
+
//
|
|
3
|
+
// The C++ plugin SDK is not available yet, so this is a build/run skeleton.
|
|
4
|
+
// Once the SDK lands, connect to the plugin gateway here (the host exposes it
|
|
5
|
+
// at the RB_PLUGIN_GATEWAY / RB_PLUGIN_ID environment variables) and wire up
|
|
6
|
+
// pub / sub / serve, mirroring the Python rainbow.rb_sdk.RBPluginCall API.
|
|
7
|
+
|
|
8
|
+
#include <chrono>
|
|
9
|
+
#include <cstdlib>
|
|
10
|
+
#include <iostream>
|
|
11
|
+
#include <thread>
|
|
12
|
+
|
|
13
|
+
int main() {
|
|
14
|
+
const char* plugin_id = std::getenv("RB_PLUGIN_ID");
|
|
15
|
+
std::cout << "[__PLUGIN_NAME__] backend ready"
|
|
16
|
+
<< (plugin_id ? std::string(" (id=") + plugin_id + ")" : "")
|
|
17
|
+
<< std::endl;
|
|
18
|
+
|
|
19
|
+
// TODO: connect to the plugin gateway and register handlers once the
|
|
20
|
+
// C++ SDK is available. For now, just keep the process alive.
|
|
21
|
+
while (true) {
|
|
22
|
+
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
23
|
+
}
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.16)
|
|
2
|
+
project(__PLUGIN_SLUG__)
|
|
3
|
+
|
|
4
|
+
if(NOT CMAKE_CXX_STANDARD)
|
|
5
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
6
|
+
endif()
|
|
7
|
+
|
|
8
|
+
find_package(ament_cmake REQUIRED)
|
|
9
|
+
find_package(rclcpp REQUIRED)
|
|
10
|
+
|
|
11
|
+
add_executable(__PLUGIN_SLUG___node src/node.cpp)
|
|
12
|
+
ament_target_dependencies(__PLUGIN_SLUG___node rclcpp)
|
|
13
|
+
|
|
14
|
+
install(TARGETS __PLUGIN_SLUG___node
|
|
15
|
+
DESTINATION lib/${PROJECT_NAME})
|
|
16
|
+
|
|
17
|
+
ament_package()
|
|
@@ -0,0 +1,11 @@
|
|
|
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 (rclcpp)</description>
|
|
6
|
+
<maintainer email="todo@example.com">maintainer</maintainer>
|
|
7
|
+
<license>Proprietary</license>
|
|
8
|
+
<buildtool_depend>ament_cmake</buildtool_depend>
|
|
9
|
+
<depend>rclcpp</depend>
|
|
10
|
+
<export><build_type>ament_cmake</build_type></export>
|
|
11
|
+
</package>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// __PLUGIN_NAME__ — ROS2 node (rclcpp).
|
|
2
|
+
//
|
|
3
|
+
// The node is launched with `--ros-args -r __ns:=/{slug}` automatically by
|
|
4
|
+
// dev:back. ROS2 topics are bridged to the plugin communication layer, so a
|
|
5
|
+
// plain rclcpp node is all you need — relative topic names get the namespace
|
|
6
|
+
// prefix applied automatically.
|
|
7
|
+
#include "rclcpp/rclcpp.hpp"
|
|
8
|
+
|
|
9
|
+
class __PLUGIN_CLASS__Node : public rclcpp::Node {
|
|
10
|
+
public:
|
|
11
|
+
__PLUGIN_CLASS__Node() : Node("__PLUGIN_SLUG___node") {
|
|
12
|
+
RCLCPP_INFO(this->get_logger(), "__PLUGIN_NAME__ node started");
|
|
13
|
+
// create publishers / subscribers / services here
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
int main(int argc, char** argv) {
|
|
18
|
+
rclcpp::init(argc, argv);
|
|
19
|
+
rclcpp::spin(std::make_shared<__PLUGIN_CLASS__Node>());
|
|
20
|
+
rclcpp::shutdown();
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
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.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20"
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@types/react-dom": "^19.0.4"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@rainbow-robotics/preview-host": "0.1.2-dev.
|
|
34
|
+
"@rainbow-robotics/preview-host": "0.1.2-dev.25",
|
|
35
35
|
"socket.io": "^4.8.1"
|
|
36
36
|
},
|
|
37
37
|
"optionalDependencies": {
|
package/cli/src/pack.mjs
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// muscat-pk pack — build, then create a distributable ZIP with a size report.
|
|
2
|
-
import { execSync } from "node:child_process";
|
|
3
|
-
import { existsSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
4
|
-
import { basename, resolve } from "node:path";
|
|
5
|
-
import { buildPlugin, exportRos2Build, pinRequirements } from "./build.mjs";
|
|
6
|
-
import { findPluginRoot, humanSize, log } from "./util.mjs";
|
|
7
|
-
|
|
8
|
-
function dirSize(dir) {
|
|
9
|
-
let total = 0;
|
|
10
|
-
for (const entry of readdirSync(dir)) {
|
|
11
|
-
const full = resolve(dir, entry);
|
|
12
|
-
const st = statSync(full);
|
|
13
|
-
total += st.isDirectory() ? dirSize(full) : st.size;
|
|
14
|
-
}
|
|
15
|
-
return total;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function runPack() {
|
|
19
|
-
const pluginRoot = findPluginRoot();
|
|
20
|
-
log.step("pinning requirements.txt to latest PyPI versions");
|
|
21
|
-
await pinRequirements(pluginRoot);
|
|
22
|
-
await buildPlugin({ root: pluginRoot, minify: true });
|
|
23
|
-
await exportRos2Build(pluginRoot);
|
|
24
|
-
|
|
25
|
-
const manifest = JSON.parse(
|
|
26
|
-
readFileSync(resolve(pluginRoot, "manifest.json"), "utf8"),
|
|
27
|
-
);
|
|
28
|
-
const zipName = `${basename(pluginRoot)}-${manifest.version || "0.0.0"}.zip`;
|
|
29
|
-
const zipPath = resolve(pluginRoot, zipName);
|
|
30
|
-
rmSync(zipPath, { force: true });
|
|
31
|
-
|
|
32
|
-
// dist/ already contains main.py, requirements.txt, manifest.json, assets/.
|
|
33
|
-
const entries = ["dist"];
|
|
34
|
-
if (existsSync(resolve(pluginRoot, "ros2_ws/install"))) {
|
|
35
|
-
entries.push("ros2_ws/install");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
log.step(`packing ${entries.join(", ")} → ${zipName}`);
|
|
39
|
-
try {
|
|
40
|
-
execSync(`zip -r -q "${zipPath}" ${entries.map((e) => `"${e}"`).join(" ")}`, {
|
|
41
|
-
cwd: pluginRoot,
|
|
42
|
-
stdio: "inherit",
|
|
43
|
-
});
|
|
44
|
-
} catch {
|
|
45
|
-
throw new Error("`zip` not found. Install zip or pack manually.");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const zipBytes = statSync(zipPath).size;
|
|
49
|
-
log.ok(`${zipName} ${humanSize(zipBytes)}`);
|
|
50
|
-
for (const e of entries) {
|
|
51
|
-
const full = resolve(pluginRoot, e);
|
|
52
|
-
const size = statSync(full).isDirectory() ? dirSize(full) : statSync(full).size;
|
|
53
|
-
log.info(` ├─ ${e} ${humanSize(size)}`);
|
|
54
|
-
}
|
|
55
|
-
log.warn(
|
|
56
|
-
"robot install checks free space via GET /plugin/capacity (507 if low).",
|
|
57
|
-
);
|
|
58
|
-
}
|