@rainbow-robotics/muscat-plugin-kit 1.3.6-dev.7 → 1.3.6-dev.8
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 +3 -8
- package/cli/bin/muscat-pk.mjs +0 -5
- package/cli/bundle-templates.mjs +1 -6
- package/cli/docker/Dockerfile.cpp +6 -28
- package/cli/docker/Dockerfile.dev +0 -32
- package/cli/docker/Dockerfile.python +0 -19
- package/cli/src/bridge.mjs +12 -45
- package/cli/src/build.mjs +79 -48
- package/cli/src/cpp.mjs +5 -11
- package/cli/src/dev.mjs +2 -7
- package/cli/src/devBack.mjs +40 -97
- package/cli/src/host_agent.py +83 -143
- package/cli/src/manifest.mjs +0 -11
- package/cli/src/mcp.mjs +54 -398
- package/cli/src/new.mjs +3 -13
- package/cli/src/ports.mjs +7 -21
- package/cli/src/python.mjs +20 -16
- package/cli/src/ros2.mjs +8 -16
- package/cli/src/sdk.mjs +21 -23
- package/cli/src/templates-embedded.mjs +4 -0
- package/cli/src/type.mjs +10 -44
- package/cli/src/uninstall.mjs +20 -3
- package/cli/src/update.mjs +8 -14
- package/cli/src/util.mjs +9 -70
- package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +1 -13
- package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +1 -98
- package/cli/storybook-dist/assets/{Color-YHDXOIA2-C5ttlJWI.js → Color-YHDXOIA2-Du8dJ-nM.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-CCfiEyzU.js → Docs--Fm8069p.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-B3AV-YvP.js → Docs-Cb72AJnj.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-BN206lKl.js → Docs-LdjYwuG4.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-BqH2prGO.js → Docs-Sy5RDbtH.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-DhncK7Aw.js → Docs-guGflRzZ.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-CGN1_Ua8.js → Docs-pwGqGTV_.js} +1 -1
- package/cli/storybook-dist/assets/{DocsRenderer-CFRXHY34-D-w8Kq64.js → DocsRenderer-CFRXHY34-YGBLQ4BD.js} +1 -1
- package/cli/storybook-dist/assets/{NxAvatar.stories-oCcYtLwq.js → NxAvatar.stories-C1d4n8cq.js} +1 -1
- package/cli/storybook-dist/assets/{NxButton.stories-C5jUScIj.js → NxButton.stories-gCBoZdMq.js} +1 -1
- package/cli/storybook-dist/assets/{NxIcons.stories-DeqrVzVs.js → NxIcons.stories-Dg42riT5.js} +1 -1
- package/cli/storybook-dist/assets/{NxNumberInput.stories-gjsEulIN.js → NxNumberInput.stories-DB-vLM2J.js} +1 -1
- package/cli/storybook-dist/assets/{NxPagination.stories-BK6uSWIa.js → NxPagination.stories-CS8iMIak.js} +1 -1
- package/cli/storybook-dist/assets/{NxPointerHoldButton.stories-BDD5CvSZ.js → NxPointerHoldButton.stories-D66BopeS.js} +1 -1
- package/cli/storybook-dist/assets/{NxRangeInput.stories-C_nW26_x.js → NxRangeInput.stories-CZxddsJ2.js} +3 -3
- package/cli/storybook-dist/assets/{NxSelectBox.stories-DqLfKU9H.js → NxSelectBox.stories-CmezGGrm.js} +1 -1
- package/cli/storybook-dist/assets/{NxSlideRange.stories-CQjHA8yv.js → NxSlideRange.stories-CGAFMITi.js} +1 -1
- package/cli/storybook-dist/assets/{NxSwitch.stories-BMHCiPoL.js → NxSwitch.stories-B3QInkXX.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextArea.stories-k7r1WVQh.js → NxTextArea.stories-BgJcgZNe.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextInput.stories-Cqfe1I32.js → NxTextInput.stories-CwMQwI9L.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordion.stories-CJ3Ttuja.js → RBAccordion.stories-BuBVNzJT.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordionGroup.stories-BThv7tM0.js → RBAccordionGroup.stories-Crdb6kFF.js} +1 -1
- package/cli/storybook-dist/assets/{RBAlert.stories-CSMP8Vun.js → RBAlert.stories-CqXgUvA2.js} +1 -1
- package/cli/storybook-dist/assets/{RBBadge.stories-cBMA2R6F.js → RBBadge.stories-BCw_ksML.js} +1 -1
- package/cli/storybook-dist/assets/{RBBattery.stories-BKVOskqs.js → RBBattery.stories-0id4XYoc.js} +1 -1
- package/cli/storybook-dist/assets/{RBCard.stories-BTOfK0HC.js → RBCard.stories-CgpG-7Lk.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckBox.stories-DyE912wG.js → RBCheckBox.stories-BG02BoGX.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckIconButton.stories-0sLFU0kM.js → RBCheckIconButton.stories-vrjD4GQ4.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckLabelButton.stories-c8VecClE.js → RBCheckLabelButton.stories-POba8lU-.js} +1 -1
- package/cli/storybook-dist/assets/{RBChipTab.stories-CQ9Uav5_.js → RBChipTab.stories-XWJbFrHF.js} +1 -1
- package/cli/storybook-dist/assets/{RBChipsBoxTab.stories-Ap4NlhAZ.js → RBChipsBoxTab.stories-BdU0Dzdp.js} +1 -1
- package/cli/storybook-dist/assets/{RBColorIcons.stories-BPPXjif2.js → RBColorIcons.stories-C_0dCNXG.js} +1 -1
- package/cli/storybook-dist/assets/{RBCommand.stories-PDpYOKlm.js → RBCommand.stories-Dvrko5No.js} +1 -1
- package/cli/storybook-dist/assets/{RBConfirm.stories-BEWvMAYP.js → RBConfirm.stories-CkTn0sHa.js} +1 -1
- package/cli/storybook-dist/assets/{RBDateInput.stories-DLdyNnVF.js → RBDateInput.stories-BH3D08cs.js} +1 -1
- package/cli/storybook-dist/assets/{RBDialog.stories-HsawMNOb.js → RBDialog.stories-C2nNX9fl.js} +1 -1
- package/cli/storybook-dist/assets/{RBDivider.stories-dlWwOxik.js → RBDivider.stories-D-GrJ0QZ.js} +1 -1
- package/cli/storybook-dist/assets/{RBDrawer.stories-C87iqGk0.js → RBDrawer.stories-XLOnLOnh.js} +1 -1
- package/cli/storybook-dist/assets/{RBDropdownMenu.stories-B8EQhA0z.js → RBDropdownMenu.stories-BjBSeoV2.js} +1 -1
- package/cli/storybook-dist/assets/{RBEmptyView.stories-SzpIhNDw.js → RBEmptyView.stories-C6U2zKNE.js} +1 -1
- package/cli/storybook-dist/assets/{RBIconButton.stories-BQ0nM1gb.js → RBIconButton.stories-MCJq5Hta.js} +1 -1
- package/cli/storybook-dist/assets/{RBIcons.stories-C19g5M5z.js → RBIcons.stories-DxOCiCe0.js} +1 -1
- package/cli/storybook-dist/assets/{RBInlineLabelButton.stories-DbgI4-1P.js → RBInlineLabelButton.stories-BVZi1aEs.js} +1 -1
- package/cli/storybook-dist/assets/{RBJoyStick.stories-smF-vpGL.js → RBJoyStick.stories-BDThbEek.js} +1 -1
- package/cli/storybook-dist/assets/{RBLabelButton.stories-s53klwDv.js → RBLabelButton.stories-CaPEKUS6.js} +1 -1
- package/cli/storybook-dist/assets/{RBLight.stories-BnzkoR51.js → RBLight.stories-CihQzyEL.js} +1 -1
- package/cli/storybook-dist/assets/{RBMenubar.stories-CW014klu.js → RBMenubar.stories-J852XVFh.js} +1 -1
- package/cli/storybook-dist/assets/{RBMultiRangeInput.stories-qa4tH_4Y.js → RBMultiRangeInput.stories-DXyX0cOZ.js} +1 -1
- package/cli/storybook-dist/assets/{RBPagination.stories-YoQNUpQH.js → RBPagination.stories-C9_49ymO.js} +1 -1
- package/cli/storybook-dist/assets/{RBPopover.stories-Bhpi0OaH.js → RBPopover.stories-DxLE912-.js} +1 -1
- package/cli/storybook-dist/assets/{RBPrompt.stories-iQM5iAka.js → RBPrompt.stories-DlsfGqWb.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioBox.stories-BGyq4rzx.js → RBRadioBox.stories-hb5cNWW1.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioButton.stories-DF6BcUcJ.js → RBRadioButton.stories-CSuOgeN-.js} +1 -1
- package/cli/storybook-dist/assets/RBRangeDateInput.stories-KRg2glX8.js +15 -0
- package/cli/storybook-dist/assets/{RBResizable.stories-DSVHvwjp.js → RBResizable.stories-BetjF9vG.js} +1 -1
- package/cli/storybook-dist/assets/RBSelectBox.stories-ILLQQ8M8.js +6 -0
- package/cli/storybook-dist/assets/{RBSheet.stories-CAK9hb-p.js → RBSheet.stories-D3QbcMKq.js} +1 -1
- package/cli/storybook-dist/assets/{RBSideNumberInput.stories-DUymnH-9.js → RBSideNumberInput.stories-DcTh0IjM.js} +1 -1
- package/cli/storybook-dist/assets/{RBSlider.stories-BwlDh9--.js → RBSlider.stories-B59We7ei.js} +1 -1
- package/cli/storybook-dist/assets/{RBSpinner.stories-VOlkuP6t.js → RBSpinner.stories-9u3_ETbi.js} +1 -1
- package/cli/storybook-dist/assets/{RBSwitch.stories-BiW2dQnT.js → RBSwitch.stories-DrVm1WGX.js} +1 -1
- package/cli/storybook-dist/assets/{RBTab.stories-DFKVsy0C.js → RBTab.stories-D8Dm7cRW.js} +1 -1
- package/cli/storybook-dist/assets/{RBTable.stories-BWG0ZSYI.js → RBTable.stories-dRdZ8g1j.js} +1 -1
- package/cli/storybook-dist/assets/{RBTableRow.stories-JVTD83Mh.js → RBTableRow.stories-rZeoGwHZ.js} +1 -1
- package/cli/storybook-dist/assets/{RBTabs.stories-D-aN385u.js → RBTabs.stories-STRV2lgO.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextArea.stories-Dx5xkmZS.js → RBTextArea.stories-CfgUbrbJ.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextField.stories-DmVciExd.js → RBTextField.stories-B5t8-Y97.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextInput.stories-CCf0VGEv.js → RBTextInput.stories-Bf1edN6l.js} +1 -1
- package/cli/storybook-dist/assets/{RBToast.stories-DT0DGn05.js → RBToast.stories-Cnm8JwQ6.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggle.stories-Ce_q3EtP.js → RBToggle.stories-B0OCo6zd.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggleGroup.stories-CN40YHsR.js → RBToggleGroup.stories-GdYO2Fxg.js} +1 -1
- package/cli/storybook-dist/assets/{RBTooltip.stories-L7btDEpA.js → RBTooltip.stories-DXJTCScO.js} +1 -1
- package/cli/storybook-dist/assets/SideNumberInput-CrF-a1Nr.js +1114 -0
- package/cli/storybook-dist/assets/browserUtils-CxJ5gea3.js +1 -0
- package/cli/storybook-dist/assets/{entry-preview-D_nGAXUk.js → entry-preview-DSRwIaVJ.js} +1 -1
- package/cli/storybook-dist/assets/{iframe-23VS_ZeI.js → iframe-Cj53Wn3d.js} +2 -2
- package/cli/storybook-dist/assets/{index-ClAc6FBW.js → index-C1OzuK6-.js} +1 -1
- package/cli/storybook-dist/assets/{index-Ckae_HWe.js → index-CkHtLEe8.js} +3 -3
- package/cli/storybook-dist/assets/preview-CEfMaO1t.css +1 -0
- package/cli/storybook-dist/assets/{preview-BVJwlGUF.js → preview-CILRoSnB.js} +1 -1
- package/cli/storybook-dist/assets/{preview-CD0LINug.js → preview-CgOtgtCD.js} +2 -2
- package/cli/storybook-dist/assets/preview-D_2um8Sd.js +3 -0
- package/cli/storybook-dist/assets/react-resizable-panels.browser-CtORL3tG.js +102 -0
- package/cli/storybook-dist/iframe.html +1 -1
- package/cli/storybook-dist/index.html +1 -1
- package/cli/storybook-dist/index.json +1 -1
- package/cli/storybook-dist/project.json +1 -1
- package/cli/templates/common/README.md +7 -211
- package/cli/templates/common/py/main.py +2 -29
- package/cli/templates/common/requirements.txt +3 -3
- package/cli/templates/cpp/cpp/CMakeLists.txt +7 -6
- package/cli/templates/cpp/cpp/main.cpp +10 -57
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +0 -4
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +4 -5
- package/package.json +3 -3
- package/cli/storybook-dist/assets/RBRangeDateInput.stories-COOQ_HNF.js +0 -15
- package/cli/storybook-dist/assets/RBSelectBox.stories-BookV8Sm.js +0 -74
- package/cli/storybook-dist/assets/SideNumberInput-B5_xLK8z.js +0 -1109
- package/cli/storybook-dist/assets/browserUtils-CQM9Za7e.js +0 -1
- package/cli/storybook-dist/assets/preview-P8yWWsME.js +0 -3
- package/cli/storybook-dist/assets/preview-zuAvMksL.css +0 -1
- package/cli/storybook-dist/assets/react-resizable-panels.browser-CYcsrUgg.js +0 -120
- package/cli/templates/readme-cpp/README.md +0 -993
|
@@ -398,103 +398,6 @@ if __name__ == "__main__":
|
|
|
398
398
|
time.sleep(1)
|
|
399
399
|
```
|
|
400
400
|
|
|
401
|
-
#### External endpoints (`plugin.router`)
|
|
402
|
-
|
|
403
|
-
`plugin.serve` / `plugin.subscribe` / `plugin.publish` are **internal only** (plugin
|
|
404
|
-
UI ↔ backend, plugin ↔ plugin). To expose an endpoint to the outside world —
|
|
405
|
-
REST, Socket.IO, TCP, MQTT, WebRTC — use `plugin.router`. Declaring one is a
|
|
406
|
-
single decorator; the running Muscat (`common`) picks it up without a restart,
|
|
407
|
-
and it disappears when the plugin stops or is removed.
|
|
408
|
-
|
|
409
|
-
> Requires a robot running `rainbow-rb-sdk` **0.0.9.dev5 or newer** (the SDK is
|
|
410
|
-
> provided by the plugin container at runtime). On older robots `plugin.router`
|
|
411
|
-
> does not exist and raises `AttributeError`.
|
|
412
|
-
|
|
413
|
-
```python
|
|
414
|
-
from rainbow.rb_sdk import RBPluginCall
|
|
415
|
-
from rainbow.rb_sdk.plugin import PluginRouteError
|
|
416
|
-
|
|
417
|
-
plugin = RBPluginCall() # plugin_id comes from env RB_PLUGIN_ID
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
@plugin.router.on("/hello") # all 5 transports
|
|
421
|
-
def hello(payload: dict, meta: dict):
|
|
422
|
-
return {"msg": f"hi {payload.get('name')}"} # return value = response body
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
@plugin.router.on("/admin", explicit_routes=["api"]) # REST only
|
|
426
|
-
def admin(payload, meta):
|
|
427
|
-
if not (meta.get("principal") or {}).get("is_admin"):
|
|
428
|
-
raise PluginRouteError(403, "admin only") # becomes the external status
|
|
429
|
-
...
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
@plugin.router.subscribe("/cmd", explicit_events=["tcp"]) # receive external TCP publish
|
|
433
|
-
def on_cmd(payload: dict):
|
|
434
|
-
...
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
plugin.router.emit("/state", {"busy": True}) # push to external subscribers
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
| API | Meaning | External address |
|
|
441
|
-
| --- | --- | --- |
|
|
442
|
-
| `router.on(path, explicit_routes=None)` | request/response handler | `common/plugin/{plugin_id}/{path}` (REST is always `POST`) |
|
|
443
|
-
| `router.subscribe(topic, explicit_events=None)` | receive external publishes | external side publishes to `common/plugin/{plugin_id}/{topic}` |
|
|
444
|
-
| `router.emit(topic, payload)` | plugin → external push | external side subscribes to `common/plugin/{plugin_id}/{topic}` |
|
|
445
|
-
|
|
446
|
-
- The `plugin_id` prefix is added by the SDK — write only the path in plugin code.
|
|
447
|
-
- Paths may have several segments (`/tool/echo`). Declaring the same path twice raises `ValueError`.
|
|
448
|
-
- Transport names: `api | socket | tcp | mqtt | webrtc`. `subscribe` accepts only `tcp | mqtt | webrtc`. Unknown names raise `ValueError`.
|
|
449
|
-
- `explicit_routes` / `explicit_events` of `None` means every transport. A request on a transport not listed gets 404.
|
|
450
|
-
|
|
451
|
-
Handler contract:
|
|
452
|
-
|
|
453
|
-
- Signature `(payload, meta)`, `(payload)` or `()` — only the arguments you declare are injected. Sync and async both work.
|
|
454
|
-
- `payload`: what the caller sent (for REST: JSON body merged with query params). The shape is up to the plugin.
|
|
455
|
-
- `meta`: `{"transport": "api"|"socket"|"tcp"|"mqtt"|"webrtc", "principal": dict | None}`. When the robot's login policy is on, `principal` holds the token subject. **Role checks are the plugin's job** — inspect `meta["principal"]` yourself.
|
|
456
|
-
- The return value is the response body (must be JSON-serialisable). `None` → empty response.
|
|
457
|
-
- Status codes: `raise PluginRouteError(code, message)`. Any other exception → 500 with `str(exc)` as the message.
|
|
458
|
-
- `subscribe` handlers take `(payload)` only and return nothing.
|
|
459
|
-
|
|
460
|
-
#### Calling plugin endpoints from outside
|
|
461
|
-
|
|
462
|
-
| Transport | How |
|
|
463
|
-
| --- | --- |
|
|
464
|
-
| REST | `POST /common/plugin/{plugin_id}/{path}` (through the api-gateway), JSON body |
|
|
465
|
-
| Socket.IO | emit event `common/plugin/{plugin_id}/{path}`, response arrives in the ack |
|
|
466
|
-
| TCP | topic `common/plugin/{plugin_id}/{path}` request / publish |
|
|
467
|
-
| MQTT | request: `common/plugin/{plugin_id}/{path}/__req__`, publish: `common/plugin/{plugin_id}/{topic}` |
|
|
468
|
-
| WebRTC | data channel topic `common/plugin/{plugin_id}/{path}` |
|
|
469
|
-
|
|
470
|
-
Auth: when the robot's login policy is on, a token is **required** (any role — GUEST passes). When it is off, calls go straight through. REST uses the same Bearer token as the other Muscat APIs.
|
|
471
|
-
|
|
472
|
-
```bash
|
|
473
|
-
curl -s -X POST http://<robot>/common/plugin/my-plugin/hello \
|
|
474
|
-
-H 'Content-Type: application/json' -d '{"name":"kim"}'
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
| Situation | Status |
|
|
478
|
-
| --- | --- |
|
|
479
|
-
| No plugin declares that path / plugin is down / transport not allowed | 404 |
|
|
480
|
-
| Plugin did not answer within 10 s | 504 |
|
|
481
|
-
| `common` ↔ gateway not connected | 503 |
|
|
482
|
-
| Handler raised | 500 (`PluginRouteError` → its `code`) |
|
|
483
|
-
|
|
484
|
-
Known limits:
|
|
485
|
-
|
|
486
|
-
- A plugin path named `install` cannot be called over REST (`POST /common/plugin/{id}/install` is taken by the registry install route first). Socket/TCP are unaffected.
|
|
487
|
-
- Swagger shows one catch-all route, not per-plugin paths.
|
|
488
|
-
- The MQTT broker echoes to the sender: a plugin that `emit`s and `subscribe`s the same topic receives its own push over `mqtt`. Drop `mqtt` from `explicit_events` or use separate topics. (TCP is filtered by `sender_id`.)
|
|
489
|
-
- `on` and `subscribe` may share a name — they live in separate tables.
|
|
490
|
-
|
|
491
|
-
During `muscat-pk dev` / `dev:back` the local bridge (port `10000`) accepts the
|
|
492
|
-
`router.*` registrations, but **external calls need a running `common`** — it is
|
|
493
|
-
`common` that routes `common/plugin/...` to the gateway. Use a robot (or a local
|
|
494
|
-
`common`) to exercise the external addresses. After restarting the plugin process,
|
|
495
|
-
external calls may return 504 for up to about a minute (until the gateway notices
|
|
496
|
-
the old socket is gone), then 404 — that is expected.
|
|
497
|
-
|
|
498
401
|
#### Program command backend (`program.panel`)
|
|
499
402
|
|
|
500
403
|
A `program.panel` contribution defines a **custom command** your plugin adds to
|
|
@@ -734,8 +637,9 @@ below).
|
|
|
734
637
|
|
|
735
638
|
### C++
|
|
736
639
|
|
|
737
|
-
C++ plugins use `cpp/` and are built with CMake inside Docker
|
|
738
|
-
|
|
640
|
+
C++ plugins use `cpp/` and are built with CMake inside Docker. The current C++
|
|
641
|
+
template is a build/run skeleton; wire it to the plugin gateway when the C++ SDK
|
|
642
|
+
is available.
|
|
739
643
|
|
|
740
644
|
```cpp
|
|
741
645
|
#include <chrono>
|
|
@@ -757,31 +661,10 @@ int main() {
|
|
|
757
661
|
}
|
|
758
662
|
```
|
|
759
663
|
|
|
760
|
-
#### C++ SDK (`rainbow-rb-sdk-dev`)
|
|
761
|
-
|
|
762
|
-
Both C++ runtimes (`cpp`, `ros2` + `rclcpp`) link the Rainbow Robotics C++ SDK
|
|
763
|
-
from the apt package `rainbow-rb-sdk-dev`. The `muscat-pk` dev containers already
|
|
764
|
-
have it, so `muscat-pk dev` / `build` need nothing extra. To build outside the
|
|
765
|
-
container (your own Ubuntu 24.04 machine, or the robot PC):
|
|
766
|
-
|
|
767
|
-
```bash
|
|
768
|
-
curl -fsSL https://rainbowrobotics.github.io/rb-sdk/rb-sdk-apt-repo.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/rb-sdk-apt.gpg
|
|
769
|
-
echo 'deb [signed-by=/usr/share/keyrings/rb-sdk-apt.gpg] https://rainbowrobotics.github.io/rb-sdk stable main' | sudo tee /etc/apt/sources.list.d/rb-sdk.list
|
|
770
|
-
sudo apt update && sudo apt install rainbow-rb-sdk-dev
|
|
771
|
-
```
|
|
772
|
-
|
|
773
|
-
- CMake: `find_package(rb_sdk REQUIRED)` + `target_link_libraries(<target> rb_sdk::rb_sdk)` (C++20). pkg-config: `rb_sdk`.
|
|
774
|
-
- Headers: `<rainbow/rb_sdk/manipulate/rb_manipulate_sdk.hpp>` (`RBManipulateSDK`),
|
|
775
|
-
`<rainbow/rb_sdk/program/plugin_program_context.hpp>` (`rb_plugin::PluginProgramContext`, for `program.panel` commands — the header's top comment has a full `main()` example).
|
|
776
|
-
- The binary is linked **dynamically**; `muscat-pk build` copies the matching `librb_sdk.so` into `dist/<arch>/lib/` (the robot has no SDK installed), so each plugin keeps the SDK version it was built with. Change the version with `MUSCAT_PK_RB_SDK_VERSION=<version> MUSCAT_PK_REBUILD_IMAGE=1 muscat-pk dev`.
|
|
777
|
-
- UI ↔ backend messaging works from C++ too: `rb_plugin::RBPluginCall` in `<rainbow/rb_sdk/plugin/rb_plugin.hpp>` (see the scaffolded `cpp/main.cpp`).
|
|
778
|
-
|
|
779
664
|
### ROS2 (rclpy)
|
|
780
665
|
|
|
781
|
-
ROS2 Python plugins use `ros2_ws/` with `runtime.rosClient: "rclpy"`.
|
|
782
|
-
|
|
783
|
-
and on the robot. Use an absolute name (`/cmd_vel`) for topics shared with other nodes.
|
|
784
|
-
`RBPluginCall` works in the node module too (UI messaging and `plugin.router`).
|
|
666
|
+
ROS2 Python plugins use `ros2_ws/` with `runtime.rosClient: "rclpy"`. The node
|
|
667
|
+
is launched with `--ros-args -r __ns:=/{slug}` automatically by `dev:back`.
|
|
785
668
|
|
|
786
669
|
```python
|
|
787
670
|
# ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py
|
|
@@ -799,29 +682,9 @@ class __PLUGIN_CLASS__Node(Node):
|
|
|
799
682
|
# use sdk alongside ROS2 publishers/subscribers/services as needed
|
|
800
683
|
```
|
|
801
684
|
|
|
802
|
-
|
|
803
|
-
#### Talking to an external PC over ROS 2
|
|
804
|
-
|
|
805
|
-
ROS 2 discovery stays on the robot (`ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST`; `muscat-pk dev`
|
|
806
|
-
runs the same), so robots on one LAN don't mix topics. Allow specific PCs from the plugin UI —
|
|
807
|
-
Muscat keeps the list per plugin, starts your backend with `ROS_STATIC_PEERS` set to it, and
|
|
808
|
-
restarts the plugin when it changes. Your ROS code needs no changes; the PC needs no extra settings.
|
|
809
|
-
|
|
810
|
-
```tsx
|
|
811
|
-
const rb = useRb();
|
|
812
|
-
const peers = await rb.rosPeers.get(); // ["192.168.0.50"]
|
|
813
|
-
await rb.rosPeers.set(["192.168.0.50", "192.168.0.51"]); // saves + restarts the plugin; rejects on a bad IP
|
|
814
|
-
```
|
|
815
|
-
|
|
816
|
-
- Per plugin: plugin B keeps its allowed PCs when plugin A (allowing the same PC) is stopped or removed.
|
|
817
|
-
- It opens communication; it is not access control — a PC that knows the robot's IP can still
|
|
818
|
-
add the robot to its own `ROS_STATIC_PEERS`.
|
|
819
|
-
- In `muscat-pk dev` the list is kept in `.muscat-dev/ros-peers.json` (ROS 2 backends).
|
|
820
|
-
|
|
821
685
|
### ROS2 (rclcpp)
|
|
822
686
|
|
|
823
|
-
ROS2 C++ plugins use `ros2_ws/` with `runtime.rosClient: "rclcpp"`.
|
|
824
|
-
the Rainbow C++ SDK the same way as the `cpp` runtime (see _C++ SDK_ above).
|
|
687
|
+
ROS2 C++ plugins use `ros2_ws/` with `runtime.rosClient: "rclcpp"`.
|
|
825
688
|
|
|
826
689
|
```cpp
|
|
827
690
|
#include "rclcpp/rclcpp.hpp"
|
|
@@ -836,65 +699,6 @@ public:
|
|
|
836
699
|
|
|
837
700
|
---
|
|
838
701
|
|
|
839
|
-
## Video streaming (MediaMTX)
|
|
840
|
-
|
|
841
|
-
Muscat runs a MediaMTX media server. The backend publishes a stream over RTSP and
|
|
842
|
-
the UI plays it over WebRTC. `muscat-pk dev` runs the same server (or uses a local
|
|
843
|
-
Muscat's), so the addresses are identical in dev and on the robot.
|
|
844
|
-
|
|
845
|
-
| | Address |
|
|
846
|
-
| --- | --- |
|
|
847
|
-
| Publish (backend) | `rtsp://127.0.0.1:8554/<plugin_id>/<name>` |
|
|
848
|
-
| Play (UI) | `http://<host>:8889/<plugin_id>/<name>/` |
|
|
849
|
-
|
|
850
|
-
- Prefix the path with your plugin id — paths are shared with robot cameras and other plugins.
|
|
851
|
-
- The robot runtime has no ffmpeg, GStreamer or OpenCV; bring your own encoder (below).
|
|
852
|
-
|
|
853
|
-
**Backend (Python)** — use PyAV: add `av` and `numpy` to `requirements.txt`. Its wheels
|
|
854
|
-
include FFmpeg, so they ship in the ZIP like any other dependency.
|
|
855
|
-
|
|
856
|
-
```python
|
|
857
|
-
import fractions, time
|
|
858
|
-
import av, numpy as np
|
|
859
|
-
|
|
860
|
-
out = av.open("rtsp://127.0.0.1:8554/__PLUGIN_ID__/cam", mode="w", format="rtsp",
|
|
861
|
-
options={"rtsp_transport": "tcp"})
|
|
862
|
-
stream = out.add_stream("libx264", rate=25, options={"preset": "ultrafast", "tune": "zerolatency"})
|
|
863
|
-
stream.width, stream.height, stream.pix_fmt = 640, 360, "yuv420p"
|
|
864
|
-
|
|
865
|
-
i = 0
|
|
866
|
-
while True:
|
|
867
|
-
img = np.zeros((360, 640, 3), dtype=np.uint8) # your RGB frame here
|
|
868
|
-
frame = av.VideoFrame.from_ndarray(img, format="rgb24")
|
|
869
|
-
frame.pts, frame.time_base = i, fractions.Fraction(1, 25)
|
|
870
|
-
for packet in stream.encode(frame):
|
|
871
|
-
out.mux(packet)
|
|
872
|
-
i += 1
|
|
873
|
-
time.sleep(1 / 25)
|
|
874
|
-
```
|
|
875
|
-
|
|
876
|
-
Run the encoder loop in a thread (or `asyncio.to_thread`) so it doesn't block the
|
|
877
|
-
`RBPluginCall` handlers.
|
|
878
|
-
|
|
879
|
-
**UI** — embed MediaMTX's WebRTC player. `location.hostname` is `localhost` in the
|
|
880
|
-
preview and the robot's address on the robot:
|
|
881
|
-
|
|
882
|
-
```tsx
|
|
883
|
-
const STREAM = "__PLUGIN_ID__/cam"; // the path the backend publishes
|
|
884
|
-
|
|
885
|
-
export default function Camera() {
|
|
886
|
-
return (
|
|
887
|
-
<iframe
|
|
888
|
-
src={`${location.protocol}//${location.hostname}:8889/${STREAM}/`}
|
|
889
|
-
allow="autoplay"
|
|
890
|
-
className="aspect-video w-full border-0"
|
|
891
|
-
/>
|
|
892
|
-
);
|
|
893
|
-
}
|
|
894
|
-
```
|
|
895
|
-
|
|
896
|
-
---
|
|
897
|
-
|
|
898
702
|
## manifest.json
|
|
899
703
|
|
|
900
704
|
```jsonc
|
|
@@ -1123,8 +927,6 @@ muscat-pk type change --to python # → Python
|
|
|
1123
927
|
|
|
1124
928
|
> Existing backend dirs (`ros2_ws/`, `cpp/`) are not deleted when switching away. Remove them manually if no longer needed.
|
|
1125
929
|
|
|
1126
|
-
> Switching between a Python and a C++ backend asks whether to replace this README with the matching one (the old file is kept as `README.md.bak`). Add `--readme` to answer yes without the prompt.
|
|
1127
|
-
|
|
1128
930
|
---
|
|
1129
931
|
|
|
1130
932
|
## AI coding assistants (MCP)
|
|
@@ -1141,9 +943,7 @@ letting it guess. `muscat-pk mcp` is an MCP server exposing:
|
|
|
1141
943
|
- `search_rb_sdk` — how to use `useRb()`, `rb.*`, `useRbFormData`/`useRbSave`, and
|
|
1142
944
|
the backend runtimes.
|
|
1143
945
|
- `list_rb_sdk` / `get_rb_sdk_api` — the installed `rainbow-rb-sdk` (Python
|
|
1144
|
-
backend) classes and signatures
|
|
1145
|
-
(`rainbow-rb-sdk-dev`, read out of the `muscat-pk` cpp dev image — build it
|
|
1146
|
-
once with `muscat-pk dev:back`).
|
|
946
|
+
backend) classes and signatures.
|
|
1147
947
|
|
|
1148
948
|
**Browse the components live** with `muscat-pk storybook`.
|
|
1149
949
|
|
|
@@ -1191,10 +991,6 @@ muscat-pk build # build + ZIP
|
|
|
1191
991
|
muscat-pk build --no-zip # build only, no ZIP
|
|
1192
992
|
```
|
|
1193
993
|
|
|
1194
|
-
> Building the other arch (e.g. arm64 on an amd64 PC) runs in an emulated container for
|
|
1195
|
-
> C++ backends. Docker Desktop has it built in; on Linux enable it once with
|
|
1196
|
-
> `docker run --privileged --rm tonistiigi/binfmt --install all`, or pass `--arch` for one arch.
|
|
1197
|
-
|
|
1198
994
|
- Builds `ui/content.tsx` → `dist/content.js` (minified)
|
|
1199
995
|
- Pins `requirements.txt` to latest versions (stable-only on `main` branch)
|
|
1200
996
|
- Copies `assets/`, `manifest.json`, `README*.md`, `py/`, and `requirements.txt` into `dist/`
|
|
@@ -10,13 +10,6 @@ Remote dev (from your laptop):
|
|
|
10
10
|
MUSCAT_ROBOT_HOST=192.168.1.100
|
|
11
11
|
MUSCAT_ROBOT_TOKEN=<service token>
|
|
12
12
|
Then: muscat-pk dev:back
|
|
13
|
-
|
|
14
|
-
Handlers:
|
|
15
|
-
plugin.serve / plugin.subscribe / plugin.publish -> plugin UI <-> backend only
|
|
16
|
-
plugin.router.on / subscribe / emit -> external endpoints
|
|
17
|
-
(REST / Socket.IO / TCP / MQTT / WebRTC at common/plugin/<plugin_id>/<path>).
|
|
18
|
-
Needs a robot running rainbow-rb-sdk >= 0.0.9.dev5; older robots have no
|
|
19
|
-
`plugin.router` (AttributeError). See README "External endpoints".
|
|
20
13
|
"""
|
|
21
14
|
|
|
22
15
|
import asyncio
|
|
@@ -24,7 +17,6 @@ import os
|
|
|
24
17
|
from typing import Any
|
|
25
18
|
|
|
26
19
|
from rainbow.rb_sdk import RBManipulateSDK, RBPluginCall
|
|
27
|
-
from rainbow.rb_sdk.rb_sdk_cpp import ConnectOptions
|
|
28
20
|
|
|
29
21
|
|
|
30
22
|
PLUGIN_NAME = "__PLUGIN_NAME__"
|
|
@@ -37,8 +29,6 @@ def register_handlers():
|
|
|
37
29
|
# Register handlers here — called after connect() so subscriptions survive
|
|
38
30
|
# session reconnection in remote-dev mode.
|
|
39
31
|
|
|
40
|
-
# --- Plugin UI <-> backend (not reachable from outside Muscat) ---
|
|
41
|
-
|
|
42
32
|
@plugin.subscribe("event")
|
|
43
33
|
def on_ui_event(payload: dict[str, Any] | None = None):
|
|
44
34
|
"""Handle messages published from the plugin UI with rb.pub("event", ...)."""
|
|
@@ -57,19 +47,6 @@ def register_handlers():
|
|
|
57
47
|
"""Small query example callable from the UI with rb.action("tool/echo", ...)."""
|
|
58
48
|
return {"echo": payload, "plugin": PLUGIN_NAME}
|
|
59
49
|
|
|
60
|
-
# --- External endpoints (REST / Socket.IO / TCP / MQTT / WebRTC) ---
|
|
61
|
-
# Exposed as common/plugin/<plugin_id>/hello, e.g.
|
|
62
|
-
# curl -X POST http://<robot>/common/plugin/<plugin_id>/hello \
|
|
63
|
-
# -H 'Content-Type: application/json' -d '{"name": "kim"}'
|
|
64
|
-
# Restrict transports with explicit_routes=["api"] (REST only), etc.
|
|
65
|
-
# Raise PluginRouteError(403, "msg") to set the response status.
|
|
66
|
-
|
|
67
|
-
@plugin.router.on("/hello")
|
|
68
|
-
def hello(payload: dict[str, Any] | None = None, meta: dict[str, Any] | None = None):
|
|
69
|
-
"""meta = {"transport": "api"|"socket"|"tcp"|"mqtt"|"webrtc", "principal": dict | None}."""
|
|
70
|
-
name = (payload or {}).get("name", "there")
|
|
71
|
-
return {"msg": f"hi {name}", "via": (meta or {}).get("transport")}
|
|
72
|
-
|
|
73
50
|
|
|
74
51
|
def move_j_example():
|
|
75
52
|
"""
|
|
@@ -96,15 +73,11 @@ async def main():
|
|
|
96
73
|
# Remote dev: connect to the robot before registering any handlers.
|
|
97
74
|
# connect() closes and reopens the Zenoh session, so handlers registered
|
|
98
75
|
# before this call would be lost.
|
|
99
|
-
|
|
100
|
-
opts.token = robot_token
|
|
101
|
-
opts.host = robot_host
|
|
102
|
-
manipulate.connect(opts)
|
|
76
|
+
await manipulate.connect(token=robot_token, host=robot_host)
|
|
103
77
|
|
|
104
78
|
register_handlers()
|
|
105
79
|
|
|
106
|
-
plugin.publish("state", {"ready": True, "plugin": PLUGIN_NAME})
|
|
107
|
-
plugin.router.emit("/state", {"ready": True, "plugin": PLUGIN_NAME}) # -> external subscribers of common/plugin/<plugin_id>/state
|
|
80
|
+
plugin.publish("state", {"ready": True, "plugin": PLUGIN_NAME})
|
|
108
81
|
print(f"[{PLUGIN_NAME}] backend ready")
|
|
109
82
|
|
|
110
83
|
try:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
rainbow-rb-sdk==0.1.
|
|
2
|
-
rainbow-rb-flat-buffers==0.1.
|
|
3
|
-
rainbow-rb-zenoh==0.1.
|
|
1
|
+
rainbow-rb-sdk==0.1.7
|
|
2
|
+
rainbow-rb-flat-buffers==0.1.7
|
|
3
|
+
rainbow-rb-zenoh==0.1.7
|
|
4
4
|
requests==2.34.2
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
cmake_minimum_required(VERSION 3.16)
|
|
2
2
|
project(__PLUGIN_SLUG__ CXX)
|
|
3
3
|
|
|
4
|
-
set(CMAKE_CXX_STANDARD
|
|
4
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
5
5
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
6
6
|
|
|
7
|
-
# Rainbow Robotics C++ SDK — provided by the apt package rainbow-rb-sdk-dev
|
|
8
|
-
# (pre-installed in the muscat-pk dev container).
|
|
9
|
-
find_package(rb_sdk REQUIRED)
|
|
10
|
-
|
|
11
7
|
# The executable name must match runtime.entry in manifest.json.
|
|
12
8
|
add_executable(__PLUGIN_SLUG__ main.cpp)
|
|
13
|
-
|
|
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)
|
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
// __PLUGIN_NAME__ — C++ plugin back end.
|
|
2
2
|
//
|
|
3
|
-
// The
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// (serve/subscribe/publish) and external endpoints (router)
|
|
8
|
-
// - manipulate/rb_manipulate_sdk.hpp RBManipulateSDK — control the robot
|
|
9
|
-
// - program/plugin_program_context.hpp rb_plugin::PluginProgramContext —
|
|
10
|
-
// program.panel commands (done/pause/stop,
|
|
11
|
-
// program variables)
|
|
12
|
-
|
|
13
|
-
#include <rainbow/rb_sdk/manipulate/rb_manipulate_sdk.hpp>
|
|
14
|
-
#include <rainbow/rb_sdk/plugin/rb_plugin.hpp>
|
|
15
|
-
|
|
16
|
-
#include <nlohmann/json.hpp>
|
|
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.
|
|
17
7
|
|
|
18
8
|
#include <chrono>
|
|
19
9
|
#include <cstdlib>
|
|
@@ -21,51 +11,14 @@
|
|
|
21
11
|
#include <string>
|
|
22
12
|
#include <thread>
|
|
23
13
|
|
|
24
|
-
using nlohmann::json;
|
|
25
|
-
using rb_plugin::RBPluginCall;
|
|
26
|
-
|
|
27
14
|
int main() {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const char* host = std::getenv("MUSCAT_ROBOT_HOST");
|
|
33
|
-
const char* token = std::getenv("MUSCAT_ROBOT_TOKEN");
|
|
34
|
-
if (host && token) manipulate.connect({.token = token, .host = host});
|
|
35
|
-
|
|
36
|
-
// plugin id comes from RB_PLUGIN_ID.
|
|
37
|
-
auto plugin = RBPluginCall::get_instance(RBPluginCall::resolve_plugin_id(std::nullopt));
|
|
38
|
-
|
|
39
|
-
// --- Plugin UI <-> backend (not reachable from outside Muscat) ---
|
|
40
|
-
|
|
41
|
-
// UI: await rb.action("grip", { width })
|
|
42
|
-
plugin->serve("grip", [&](const json& payload) {
|
|
43
|
-
const int width = payload.is_object() ? payload.value("width", 20) : 20;
|
|
44
|
-
std::cout << "[" << name << "] grip requested: width=" << width << std::endl;
|
|
45
|
-
plugin->publish("state", {{"ready", true}, {"last_grip_width", width}}); // -> rb.sub("state")
|
|
46
|
-
return json{{"ok", true}, {"width", width}};
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// UI: rb.pub("event", ...)
|
|
50
|
-
auto event_sub = plugin->subscribe("event", [&](const std::string&, const json& payload) {
|
|
51
|
-
std::cout << "[" << name << "] event: " << payload.dump() << std::endl;
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// --- External endpoints (REST / Socket.IO / TCP / MQTT / WebRTC) ---
|
|
55
|
-
// curl -X POST http://<robot>/common/plugin/<plugin_id>/hello -d '{"name":"kim"}'
|
|
56
|
-
plugin->router().on("/hello", [](const json& payload, const json& meta) {
|
|
57
|
-
const std::string who = payload.is_object() ? payload.value("name", std::string("there")) : "there";
|
|
58
|
-
return json{{"msg", "hi " + who}, {"via", meta.value("transport", std::string())}};
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// Example: robot control (not called automatically — add safety checks first).
|
|
62
|
-
// IPC::MoveInput_TargetT t; t.tar_values = {0, 0, 0, 0, 0, 0};
|
|
63
|
-
// manipulate.move.call_move_j("C500920", t);
|
|
64
|
-
|
|
65
|
-
plugin->publish("state", {{"ready", true}, {"plugin", name}});
|
|
66
|
-
std::cout << "[" << name << "] backend ready" << std::endl;
|
|
15
|
+
const char* plugin_id = std::getenv("RB_PLUGIN_ID");
|
|
16
|
+
std::cout << "[__PLUGIN_NAME__] backend ready"
|
|
17
|
+
<< (plugin_id ? std::string(" (id=") + plugin_id + ")" : "")
|
|
18
|
+
<< std::endl;
|
|
67
19
|
|
|
68
|
-
//
|
|
20
|
+
// TODO: connect to the plugin gateway and register handlers once the
|
|
21
|
+
// C++ SDK is available. For now, just keep the process alive.
|
|
69
22
|
while (true) {
|
|
70
23
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
71
24
|
}
|
|
@@ -7,13 +7,9 @@ endif()
|
|
|
7
7
|
|
|
8
8
|
find_package(ament_cmake REQUIRED)
|
|
9
9
|
find_package(rclcpp REQUIRED)
|
|
10
|
-
# Rainbow Robotics C++ SDK (apt: rainbow-rb-sdk-dev, pre-installed in the dev
|
|
11
|
-
# container).
|
|
12
|
-
find_package(rb_sdk REQUIRED)
|
|
13
10
|
|
|
14
11
|
add_executable(__PLUGIN_SLUG___node src/node.cpp)
|
|
15
12
|
ament_target_dependencies(__PLUGIN_SLUG___node rclcpp)
|
|
16
|
-
target_link_libraries(__PLUGIN_SLUG___node rb_sdk::rb_sdk)
|
|
17
13
|
|
|
18
14
|
install(TARGETS __PLUGIN_SLUG___node
|
|
19
15
|
DESTINATION lib/${PROJECT_NAME})
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
// __PLUGIN_NAME__ — ROS2 node (rclcpp).
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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.
|
|
6
7
|
#include "rclcpp/rclcpp.hpp"
|
|
7
|
-
// Rainbow Robotics C++ SDK (linked as rb_sdk::rb_sdk): RBManipulateSDK etc.
|
|
8
|
-
#include <rainbow/rb_sdk/manipulate/rb_manipulate_sdk.hpp>
|
|
9
8
|
|
|
10
9
|
class __PLUGIN_CLASS__Node : public rclcpp::Node {
|
|
11
10
|
public:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rainbow-robotics/muscat-plugin-kit",
|
|
3
|
-
"version": "1.3.6-dev.
|
|
3
|
+
"version": "1.3.6-dev.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20"
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
56
|
-
"@rainbow-robotics/plugin-ui": "1.3.6-dev.
|
|
57
|
-
"@rainbow-robotics/preview-host": "1.3.6-dev.
|
|
56
|
+
"@rainbow-robotics/plugin-ui": "1.3.6-dev.8",
|
|
57
|
+
"@rainbow-robotics/preview-host": "1.3.6-dev.8",
|
|
58
58
|
"postcss": "^8.5.8",
|
|
59
59
|
"socket.io": "^4.8.1",
|
|
60
60
|
"typescript": "^5.8.0",
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import{j as o}from"./jsx-runtime-BTJTZTIL.js";import{n as a}from"./SideNumberInput-B5_xLK8z.js";import"./index-Dmy2JGbm.js";import"./react-resizable-panels.browser-CYcsrUgg.js";import"./index-DKFBREX_.js";import"./index-CT3U1fyk.js";import"./index-ChsGqxH_.js";import"./browserUtils-CQM9Za7e.js";import"./index-BRq5TIsn.js";const h={component:a,tags:["autodocs"],argTypes:{size:{control:{type:"select"},options:["xs2","sm","md","lg"],table:{type:{summary:'"xs2" | "sm" | "md" | "lg"'}}},showPresets:{control:"boolean"},showFooter:{control:"boolean"},min:{control:"date"},max:{control:"date"},maxDays:{control:{type:"number",min:0,step:1},description:"선택 가능한 최대 일수. 0은 제한 없음."}},args:{maxDays:0}},m=e=>o.jsx("div",{style:{height:700},children:o.jsx(a,{...e,min:e.min!=null?new Date(e.min):void 0,max:e.max!=null?new Date(e.max):void 0,maxDays:(e.maxDays??0)>0?e.maxDays:void 0})}),t={parameters:{docs:{description:{story:""}}},args:{size:"md",showPresets:!0,showFooter:!0},render:m};var r,s,n;t.parameters={...t.parameters,docs:{...(r=t.parameters)==null?void 0:r.docs,source:{originalSource:`{
|
|
2
|
-
parameters: {
|
|
3
|
-
docs: {
|
|
4
|
-
description: {
|
|
5
|
-
story: ""
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
},
|
|
9
|
-
args: {
|
|
10
|
-
size: "md",
|
|
11
|
-
showPresets: true,
|
|
12
|
-
showFooter: true
|
|
13
|
-
},
|
|
14
|
-
render: Template
|
|
15
|
-
}`,...(n=(s=t.parameters)==null?void 0:s.docs)==null?void 0:n.source}}};const w=["Default"];export{t as Default,w as __namedExportsOrder,h as default};
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import{j as t}from"./jsx-runtime-BTJTZTIL.js";import{w as d,u as c,a as p,e as n}from"./index-ClAc6FBW.js";import{z as b,B as w,V as g}from"./SideNumberInput-B5_xLK8z.js";import"./index-Dmy2JGbm.js";import"./react-resizable-panels.browser-CYcsrUgg.js";import"./index-DKFBREX_.js";import"./index-CT3U1fyk.js";import"./index-ChsGqxH_.js";import"./browserUtils-CQM9Za7e.js";import"./index-BRq5TIsn.js";const N=Array.from({length:40},(e,o)=>({value:`option-${o+1}`,label:`옵션 ${o+1}`})),H=e=>t.jsx("div",{className:"min-h-80 overflow-visible",children:t.jsx(b,{...e,className:"w-96",children:N.map(({value:o,label:a})=>t.jsx(w,{value:o,children:a},o))})}),O=e=>t.jsx("div",{className:"min-h-80 overflow-visible",children:t.jsxs(b,{...e,className:"w-96",children:[t.jsx(w,{value:"option-1",label:"옵션 1",className:"h-12",children:t.jsxs(g,{children:["옵션 1",t.jsx("span",{className:"typo-compact-xs-normal",children:"Additional Information goes here"})]})}),t.jsx(w,{value:"option-2",label:"옵션 2",className:"h-12",children:t.jsxs(g,{children:["옵션 2",t.jsx("span",{className:"typo-compact-xs-normal",children:"Additional Information goes here"})]})}),t.jsx(w,{value:"option-3",label:"옵션 3",disabled:!0,className:"h-12",children:t.jsxs(g,{children:["옵션 3",t.jsx("span",{className:"typo-compact-xs-normal",children:"Additional Information goes here"})]})})]})}),z={component:b,tags:["autodocs"],parameters:{layout:"padded"},argTypes:{size:{control:{type:"select"},options:["xs2","sm","md","lg"]},maxDropBoxHeight:{control:{type:"number"}},disabled:{control:{type:"boolean"}},readOnly:{control:{type:"boolean"}},search:{control:{type:"boolean"}},keepDropDown:{control:{type:"boolean"}},showCloseBtn:{control:{type:"boolean"}},whenScrollCloseDropBox:{control:{type:"boolean"}},enableModal:{control:{type:"boolean"}},error:{control:{type:"boolean"}},searchPlaceholder:{control:{type:"text"}},mobileLabel:{control:{type:"text"}},placeholder:{control:{type:"text"}},selectArrow:{control:!1,table:{disable:!0}},noDataMsg:{control:!1,table:{disable:!0}},onClick:{action:!1,control:!1,table:{disable:!0}},onChange:{action:"onChange",control:!1},handleChange:{action:"handleChange",control:!1}},args:{size:"md",maxDropBoxHeight:200,disabled:!1,readOnly:!1,placeholder:"placeholder",search:!1,keepDropDown:!1,showCloseBtn:!1,whenScrollCloseDropBox:!1,enableModal:!1,error:!1,searchPlaceholder:"Search",onClick:void 0},render:H},B=()=>new Promise(e=>setTimeout(e,350)),u={play:async({canvasElement:e})=>{const o=d(e),a=e.ownerDocument;await B(),await c.click(o.getByText("placeholder"));const l=await p(()=>{const s=a.querySelector('[data-slot="select-drop-box"]');if(!s)throw new Error("drop box not opened");return s}),y=d(l).getByText("옵션 3");await c.click(y),await p(()=>{var s;n((s=e.querySelector('[data-slot="select-box-value"] span'))==null?void 0:s.textContent).toBe("옵션 3"),n(a.querySelector('[data-slot="select-drop-box"]')).toBeNull()})}},m={args:{value:"option-2"},render:O,play:async({canvasElement:e})=>{const o=d(e),a=e.ownerDocument,l=()=>{var r;return(r=e.querySelector('[data-slot="select-box-value"] span'))==null?void 0:r.textContent};await B(),await c.click(o.getByText("옵션 2"));const y=await p(()=>{const r=a.querySelector('[data-slot="select-drop-box"]');if(!r)throw new Error("drop box not opened");return r}),s=d(y).getAllByRole("button"),x=s.find(r=>{var i;return(i=r.textContent)==null?void 0:i.startsWith("옵션 3")});n(x==null?void 0:x.textContent).toContain("Additional Information goes here"),await c.click(x),n(l()).toBe("옵션 2"),n(a.querySelector('[data-slot="select-drop-box"]')).not.toBeNull();const j=s.find(r=>{var i;return(i=r.textContent)==null?void 0:i.startsWith("옵션 1")});await c.click(j),await p(()=>{n(l()).toBe("옵션 1"),n(a.querySelector('[data-slot="select-drop-box"]')).toBeNull()})}},h={args:{maxDropBoxHeight:120},play:async({canvasElement:e})=>{const o=d(e);await B(),await c.click(o.getByText("placeholder"));const a=e.ownerDocument;await p(()=>{const l=a.querySelector('[data-slot="select-drop-box"]');n(l).toBeTruthy(),n(l.getBoundingClientRect().height).toBeLessThanOrEqual(121)})}};var f,v,T;u.parameters={...u.parameters,docs:{...(f=u.parameters)==null?void 0:f.docs,source:{originalSource:`{
|
|
2
|
-
play: async ({
|
|
3
|
-
canvasElement
|
|
4
|
-
}) => {
|
|
5
|
-
const canvas = within(canvasElement);
|
|
6
|
-
const doc = canvasElement.ownerDocument;
|
|
7
|
-
await waitPastClickGuard();
|
|
8
|
-
await userEvent.click(canvas.getByText("placeholder"));
|
|
9
|
-
const dropBox = await waitFor(() => {
|
|
10
|
-
const el = doc.querySelector('[data-slot="select-drop-box"]');
|
|
11
|
-
if (!el) throw new Error("drop box not opened");
|
|
12
|
-
return el as HTMLElement;
|
|
13
|
-
});
|
|
14
|
-
const option = within(dropBox).getByText("옵션 3");
|
|
15
|
-
await userEvent.click(option);
|
|
16
|
-
await waitFor(() => {
|
|
17
|
-
expect(canvasElement.querySelector('[data-slot="select-box-value"] span')?.textContent).toBe("옵션 3");
|
|
18
|
-
expect(doc.querySelector('[data-slot="select-drop-box"]')).toBeNull();
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
}`,...(T=(v=u.parameters)==null?void 0:v.docs)==null?void 0:T.source}}};var C,E,S;m.parameters={...m.parameters,docs:{...(C=m.parameters)==null?void 0:C.docs,source:{originalSource:`{
|
|
22
|
-
args: {
|
|
23
|
-
value: "option-2"
|
|
24
|
-
},
|
|
25
|
-
render: HasInfoListTemplate,
|
|
26
|
-
play: async ({
|
|
27
|
-
canvasElement
|
|
28
|
-
}) => {
|
|
29
|
-
const canvas = within(canvasElement);
|
|
30
|
-
const doc = canvasElement.ownerDocument;
|
|
31
|
-
// DropBox가 열려있을 때는 옵션 목록에도 같은 라벨 텍스트가 존재해
|
|
32
|
-
// canvas.getByText가 trigger와 옵션을 동시에 매칭해버리므로, trigger는
|
|
33
|
-
// select-box-value 슬롯을 직접 조회해 값이 바뀌었는지 확인한다
|
|
34
|
-
const getTriggerText = () => canvasElement.querySelector('[data-slot="select-box-value"] span')?.textContent;
|
|
35
|
-
await waitPastClickGuard();
|
|
36
|
-
await userEvent.click(canvas.getByText("옵션 2"));
|
|
37
|
-
const dropBox = await waitFor(() => {
|
|
38
|
-
const el = doc.querySelector('[data-slot="select-drop-box"]');
|
|
39
|
-
if (!el) throw new Error("drop box not opened");
|
|
40
|
-
return el as HTMLElement;
|
|
41
|
-
});
|
|
42
|
-
const optionEls = within(dropBox).getAllByRole("button");
|
|
43
|
-
const disabledOption = optionEls.find(el => el.textContent?.startsWith("옵션 3"));
|
|
44
|
-
expect(disabledOption?.textContent).toContain("Additional Information goes here");
|
|
45
|
-
|
|
46
|
-
// disabled 옵션은 클릭해도 선택이 바뀌지 않고 DropBox도 닫히지 않음
|
|
47
|
-
await userEvent.click(disabledOption!);
|
|
48
|
-
expect(getTriggerText()).toBe("옵션 2");
|
|
49
|
-
expect(doc.querySelector('[data-slot="select-drop-box"]')).not.toBeNull();
|
|
50
|
-
const enabledOption = optionEls.find(el => el.textContent?.startsWith("옵션 1"));
|
|
51
|
-
await userEvent.click(enabledOption!);
|
|
52
|
-
await waitFor(() => {
|
|
53
|
-
expect(getTriggerText()).toBe("옵션 1");
|
|
54
|
-
expect(doc.querySelector('[data-slot="select-drop-box"]')).toBeNull();
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}`,...(S=(E=m.parameters)==null?void 0:E.docs)==null?void 0:S.source}}};var D,k,q;h.parameters={...h.parameters,docs:{...(D=h.parameters)==null?void 0:D.docs,source:{originalSource:`{
|
|
58
|
-
args: {
|
|
59
|
-
maxDropBoxHeight: 120
|
|
60
|
-
},
|
|
61
|
-
play: async ({
|
|
62
|
-
canvasElement
|
|
63
|
-
}) => {
|
|
64
|
-
const canvas = within(canvasElement);
|
|
65
|
-
await waitPastClickGuard();
|
|
66
|
-
await userEvent.click(canvas.getByText("placeholder"));
|
|
67
|
-
const doc = canvasElement.ownerDocument;
|
|
68
|
-
await waitFor(() => {
|
|
69
|
-
const dropBox = doc.querySelector('[data-slot="select-drop-box"]') as HTMLElement;
|
|
70
|
-
expect(dropBox).toBeTruthy();
|
|
71
|
-
expect(dropBox.getBoundingClientRect().height).toBeLessThanOrEqual(121);
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}`,...(q=(k=h.parameters)==null?void 0:k.docs)==null?void 0:q.source}}};const V=["Default","HasInfoList","MaxDropBoxHeight"];export{u as Default,m as HasInfoList,h as MaxDropBoxHeight,V as __namedExportsOrder,z as default};
|