@rainbow-robotics/muscat-plugin-kit 1.1.5 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -0
- package/cli/README.md +40 -4
- package/cli/bin/muscat-pk.mjs +33 -6
- package/cli/docker/Dockerfile.cpp +11 -6
- package/cli/docker/Dockerfile.dev +12 -0
- package/cli/src/build.mjs +6 -6
- package/cli/src/cpp.mjs +3 -1
- package/cli/src/dev.mjs +2 -1
- package/cli/src/devBack.mjs +8 -7
- package/cli/src/manifest.mjs +56 -0
- package/cli/src/mcp.mjs +382 -43
- package/cli/src/new.mjs +3 -0
- package/cli/src/ports.mjs +3 -3
- package/cli/src/ros2.mjs +3 -1
- package/cli/src/templates-embedded.mjs +3 -0
- package/cli/src/util.mjs +13 -0
- package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +13 -1
- package/cli/storybook-dist/assets/{Color-YHDXOIA2-YYWRJKA2.js → Color-YHDXOIA2-DixbmWSF.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-CKJ2w--X.js → Docs-Blx_41TT.js} +1 -1
- package/cli/storybook-dist/assets/Docs-C0E3mHmn.js +7 -0
- package/cli/storybook-dist/assets/Docs-CMEiBjLz.js +20 -0
- package/cli/storybook-dist/assets/{Docs-UmN7NFo1.js → Docs-D6oFYDre.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-DIJ7cbFD.js → Docs-DcUa4nGr.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-DT9RD87x.js → Docs-Dv-5qjF4.js} +1 -1
- package/cli/storybook-dist/assets/{DocsRenderer-CFRXHY34-CyAy1mnJ.js → DocsRenderer-CFRXHY34-GwWANrWN.js} +1 -1
- package/cli/storybook-dist/assets/NxAvatar.stories-CyZ4AE2b.js +13 -0
- package/cli/storybook-dist/assets/NxButton.stories-Bx8UtGNd.js +20 -0
- package/cli/storybook-dist/assets/{NxIcons.stories-Dg42riT5.js → NxIcons.stories-DeqrVzVs.js} +1 -1
- package/cli/storybook-dist/assets/{NxNumberInput.stories-zlQJuGFP.js → NxNumberInput.stories-skizAdd5.js} +3 -3
- package/cli/storybook-dist/assets/NxPagination.stories-gtIxt2u0.js +11 -0
- package/cli/storybook-dist/assets/NxPointerHoldButton.stories-DSHMk3N5.js +14 -0
- package/cli/storybook-dist/assets/{NxRangeInput.stories-Cu5ZWU0w.js → NxRangeInput.stories-C1YtrFZ1.js} +3 -3
- package/cli/storybook-dist/assets/NxSelectBox.stories-DCySAtBp.js +17 -0
- package/cli/storybook-dist/assets/NxSlideRange.stories-gSaMOWo7.js +14 -0
- package/cli/storybook-dist/assets/NxSwitch.stories-DF0DBiyG.js +11 -0
- package/cli/storybook-dist/assets/NxTextArea.stories-Dill0M6z.js +11 -0
- package/cli/storybook-dist/assets/NxTextInput.stories-Bax14r48.js +11 -0
- package/cli/storybook-dist/assets/RBAccordion.stories-CCwz_pIV.js +17 -0
- package/cli/storybook-dist/assets/RBAccordionGroup.stories-BjDxxdPV.js +33 -0
- package/cli/storybook-dist/assets/RBAlert.stories-5z6GasgP.js +17 -0
- package/cli/storybook-dist/assets/RBBadge.stories-BwcM7i7w.js +1 -0
- package/cli/storybook-dist/assets/RBBattery.stories-DOeQH3rK.js +10 -0
- package/cli/storybook-dist/assets/RBCard.stories-DcmiOXAV.js +45 -0
- package/cli/storybook-dist/assets/RBCheckBox.stories-BYru87fX.js +1 -0
- package/cli/storybook-dist/assets/RBCheckIconButton.stories-CXUjtR1a.js +1 -0
- package/cli/storybook-dist/assets/RBCheckLabelButton.stories-BSOQV7Du.js +24 -0
- package/cli/storybook-dist/assets/RBChipTab.stories-ByBmeukU.js +21 -0
- package/cli/storybook-dist/assets/RBChipsBoxTab.stories-GtJ01RTr.js +1 -0
- package/cli/storybook-dist/assets/RBColorIcons.stories-C9saET1u.js +3 -0
- package/cli/storybook-dist/assets/{RBCommand.stories-C3bwPq2T.js → RBCommand.stories-CcvnWlWZ.js} +3 -3
- package/cli/storybook-dist/assets/RBConfirm.stories-hdBVSdBS.js +20 -0
- package/cli/storybook-dist/assets/RBDateInput.stories-BE9JQl-E.js +15 -0
- package/cli/storybook-dist/assets/RBDialog.stories-BaWuDgl_.js +38 -0
- package/cli/storybook-dist/assets/RBDivider.stories-D3INKm_Z.js +1 -0
- package/cli/storybook-dist/assets/RBDrawer.stories-BZ8V0J6-.js +14 -0
- package/cli/storybook-dist/assets/{RBDropdownMenu.stories-9fBcV2Ct.js → RBDropdownMenu.stories-DxLi8HRw.js} +2 -2
- package/cli/storybook-dist/assets/RBEmptyView.stories-DQcvDt2S.js +12 -0
- package/cli/storybook-dist/assets/RBIconButton.stories-DJfuTpQ0.js +1 -0
- package/cli/storybook-dist/assets/RBIcons.stories-BSsyb0BK.js +3 -0
- package/cli/storybook-dist/assets/RBInlineLabelButton.stories-Di3th--x.js +1 -0
- package/cli/storybook-dist/assets/RBJoyStick.stories-DfGazm4n.js +11 -0
- package/cli/storybook-dist/assets/RBLabelButton.stories-CLbxzW56.js +1 -0
- package/cli/storybook-dist/assets/RBLight.stories-DeiuDm_P.js +1 -0
- package/cli/storybook-dist/assets/RBMenubar.stories-BZQTV9e5.js +3 -0
- package/cli/storybook-dist/assets/RBMultiRangeInput.stories-DDNmdi_n.js +14 -0
- package/cli/storybook-dist/assets/RBPagination.stories-CLBCEyCU.js +17 -0
- package/cli/storybook-dist/assets/RBPopover.stories-DRI5Kfa-.js +11 -0
- package/cli/storybook-dist/assets/RBPrompt.stories-DIAmypoJ.js +20 -0
- package/cli/storybook-dist/assets/RBRadioBox.stories-BS_N4aLf.js +1 -0
- package/cli/storybook-dist/assets/RBRadioButton.stories-BTm8cYvm.js +11 -0
- package/cli/storybook-dist/assets/RBRangeDateInput.stories-D5hBsSyB.js +15 -0
- package/cli/storybook-dist/assets/RBResizable.stories-CxPEkbXQ.js +20 -0
- package/cli/storybook-dist/assets/RBSelectBox.stories-FUQ7AcUi.js +6 -0
- package/cli/storybook-dist/assets/RBSheet.stories-mhVHH_71.js +39 -0
- package/cli/storybook-dist/assets/RBSideNumberInput.stories-5FMkLUim.js +5 -0
- package/cli/storybook-dist/assets/RBSlider.stories-Bnhcp9pz.js +5 -0
- package/cli/storybook-dist/assets/RBSpinner.stories-Bf4416mP.js +14 -0
- package/cli/storybook-dist/assets/{RBSwitch.stories-CBYXvhkd.js → RBSwitch.stories-BNhFjRaV.js} +2 -2
- package/cli/storybook-dist/assets/RBTab.stories-Bsr7jlfb.js +1 -0
- package/cli/storybook-dist/assets/RBTable.stories-BkA6pKzs.js +62 -0
- package/cli/storybook-dist/assets/RBTableRow.stories-D07Z7D5-.js +11 -0
- package/cli/storybook-dist/assets/RBTabs.stories-DHnZiV21.js +21 -0
- package/cli/storybook-dist/assets/RBTextArea.stories-CtLMLuV_.js +5 -0
- package/cli/storybook-dist/assets/RBTextField.stories-Chz0i1-u.js +45 -0
- package/cli/storybook-dist/assets/RBTextInput.stories-Dk5cZfUF.js +5 -0
- package/cli/storybook-dist/assets/RBToast.stories-tZxbjmr8.js +11 -0
- package/cli/storybook-dist/assets/RBToggle.stories-VTe7a4xZ.js +12 -0
- package/cli/storybook-dist/assets/RBToggleGroup.stories-WiFnGGYx.js +17 -0
- package/cli/storybook-dist/assets/RBTooltip.stories-B_L4W7Mn.js +9 -0
- package/cli/storybook-dist/assets/{MapEditor-C6_H9h5t.css → SideNumberInput-SAkhydsn.css} +2 -2
- package/cli/storybook-dist/assets/SideNumberInput-os5wKKf7.js +1109 -0
- package/cli/storybook-dist/assets/browserUtils-CQM9Za7e.js +1 -0
- package/cli/storybook-dist/assets/{entry-preview-N9dpIJQA.js → entry-preview-NpzneD8a.js} +1 -1
- package/cli/storybook-dist/assets/{iframe-BwCxSq5d.js → iframe-DsvRKHwc.js} +2 -2
- package/cli/storybook-dist/assets/{index-CgJh_5EZ.js → index-BzvSJzrT.js} +3 -3
- package/cli/storybook-dist/assets/preview-BXXleNPp.css +1 -0
- package/cli/storybook-dist/assets/preview-BXxjLQ9M.js +3 -0
- package/cli/storybook-dist/assets/{preview-DsYc6ovL.js → preview-yXaYuZ78.js} +2 -2
- package/cli/storybook-dist/assets/react-resizable-panels.browser-CYcsrUgg.js +120 -0
- package/cli/storybook-dist/iframe.html +1 -1
- package/cli/storybook-dist/project.json +1 -1
- package/cli/templates/common/README.md +352 -24
- package/cli/templates/common/manifest.json +1 -0
- package/cli/templates/common/py/main.py +24 -1
- package/cli/templates/common/requirements.txt +3 -3
- package/cli/templates/cpp/cpp/CMakeLists.txt +7 -7
- package/cli/templates/cpp/cpp/main.cpp +13 -6
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +4 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +2 -0
- package/package.json +3 -3
- package/cli/storybook-dist/assets/Docs-DLFwGiks.js +0 -7
- package/cli/storybook-dist/assets/Docs-dOc9cH1E.js +0 -20
- package/cli/storybook-dist/assets/MapEditor-Jj1J2sUz.js +0 -6418
- package/cli/storybook-dist/assets/NxAvatar.stories-B-iQoylp.js +0 -13
- package/cli/storybook-dist/assets/NxButton.stories-CawQZN07.js +0 -20
- package/cli/storybook-dist/assets/NxPagination.stories-VYXLplNl.js +0 -11
- package/cli/storybook-dist/assets/NxPointerHoldButton.stories-BpqIkt0X.js +0 -14
- package/cli/storybook-dist/assets/NxSelectBox.stories-JECi_1PM.js +0 -17
- package/cli/storybook-dist/assets/NxSlideRange.stories-DJe8F2Mv.js +0 -14
- package/cli/storybook-dist/assets/NxSwitch.stories-DX6LxxQL.js +0 -11
- package/cli/storybook-dist/assets/NxTextArea.stories-Cy42nYOI.js +0 -11
- package/cli/storybook-dist/assets/NxTextInput.stories-Bb_q6FuW.js +0 -11
- package/cli/storybook-dist/assets/RBAccordion.stories-ChgPXO0_.js +0 -17
- package/cli/storybook-dist/assets/RBAccordionGroup.stories-nmbqYYSE.js +0 -33
- package/cli/storybook-dist/assets/RBAlert.stories-ChOPuFxI.js +0 -17
- package/cli/storybook-dist/assets/RBBadge.stories-Bas7g4jb.js +0 -1
- package/cli/storybook-dist/assets/RBBattery.stories-C0MywWvB.js +0 -10
- package/cli/storybook-dist/assets/RBCard.stories-Ba7y0u6Q.js +0 -45
- package/cli/storybook-dist/assets/RBCheckBox.stories-CsHFsXCd.js +0 -1
- package/cli/storybook-dist/assets/RBCheckIconButton.stories-GERClL9R.js +0 -1
- package/cli/storybook-dist/assets/RBCheckLabelButton.stories-C-BrmKaW.js +0 -24
- package/cli/storybook-dist/assets/RBChipTab.stories-C8JCa6F9.js +0 -21
- package/cli/storybook-dist/assets/RBChipsBoxTab.stories-DmUaHBJ3.js +0 -1
- package/cli/storybook-dist/assets/RBColorIcons.stories-Bcah6eZe.js +0 -3
- package/cli/storybook-dist/assets/RBConfirm.stories-CT0GuE-J.js +0 -20
- package/cli/storybook-dist/assets/RBDateInput.stories-CMd2xRCF.js +0 -15
- package/cli/storybook-dist/assets/RBDialog.stories-Ui5Ni-P_.js +0 -38
- package/cli/storybook-dist/assets/RBDivider.stories-r8TDiO36.js +0 -1
- package/cli/storybook-dist/assets/RBDrawer.stories-CymmrHjI.js +0 -14
- package/cli/storybook-dist/assets/RBEmptyView.stories-By9vncNC.js +0 -12
- package/cli/storybook-dist/assets/RBIconButton.stories-DC29KMxX.js +0 -1
- package/cli/storybook-dist/assets/RBIcons.stories-Dpd_V9b9.js +0 -3
- package/cli/storybook-dist/assets/RBInlineLabelButton.stories-CfUQdAdt.js +0 -1
- package/cli/storybook-dist/assets/RBJoyStick.stories-Dl-uXk9x.js +0 -11
- package/cli/storybook-dist/assets/RBLabelButton.stories-Dx_Vn6EM.js +0 -1
- package/cli/storybook-dist/assets/RBLight.stories-Cm5UYJ4n.js +0 -1
- package/cli/storybook-dist/assets/RBMenubar.stories-CN5z7F6D.js +0 -3
- package/cli/storybook-dist/assets/RBMultiRangeInput.stories-DrAMI4mQ.js +0 -14
- package/cli/storybook-dist/assets/RBPagination.stories-BVLXuTLw.js +0 -17
- package/cli/storybook-dist/assets/RBPopover.stories-VCws5q80.js +0 -11
- package/cli/storybook-dist/assets/RBPrompt.stories-Cw5-wssh.js +0 -20
- package/cli/storybook-dist/assets/RBRadioBox.stories-ClqGtGA_.js +0 -1
- package/cli/storybook-dist/assets/RBRadioButton.stories-SxcJFioj.js +0 -11
- package/cli/storybook-dist/assets/RBRangeDateInput.stories-L_wCftDv.js +0 -15
- package/cli/storybook-dist/assets/RBResizable.stories-2VytDv6m.js +0 -20
- package/cli/storybook-dist/assets/RBSelectBox.stories-BoOKTSL_.js +0 -6
- package/cli/storybook-dist/assets/RBSheet.stories-CUClMqKK.js +0 -39
- package/cli/storybook-dist/assets/RBSideNumberInput.stories-BTYDDAmk.js +0 -5
- package/cli/storybook-dist/assets/RBSlider.stories-Bu6vD9ym.js +0 -5
- package/cli/storybook-dist/assets/RBSpinner.stories-XmVcaTVS.js +0 -14
- package/cli/storybook-dist/assets/RBTab.stories-DFFYlkCv.js +0 -1
- package/cli/storybook-dist/assets/RBTable.stories-CR2j2EQQ.js +0 -62
- package/cli/storybook-dist/assets/RBTableRow.stories-CpPIP1au.js +0 -11
- package/cli/storybook-dist/assets/RBTabs.stories-Bew5XVDZ.js +0 -21
- package/cli/storybook-dist/assets/RBTextArea.stories-JiRn-tk_.js +0 -5
- package/cli/storybook-dist/assets/RBTextField.stories-8LWyUBm9.js +0 -45
- package/cli/storybook-dist/assets/RBTextInput.stories-PLJ70xm5.js +0 -5
- package/cli/storybook-dist/assets/RBToast.stories-BQf_9x0k.js +0 -11
- package/cli/storybook-dist/assets/RBToggle.stories-C5dZnmX_.js +0 -12
- package/cli/storybook-dist/assets/RBToggleGroup.stories-D7fwXCxA.js +0 -17
- package/cli/storybook-dist/assets/RBTooltip.stories-D-sWRxbA.js +0 -9
- package/cli/storybook-dist/assets/RainbowUiProvider-CsMXuxM1.js +0 -31
- package/cli/storybook-dist/assets/browserUtils-CxJ5gea3.js +0 -1
- package/cli/storybook-dist/assets/keymapTileIndex.worker-BsNYNbqs.js +0 -1
- package/cli/storybook-dist/assets/preview-CzDPTg-k.js +0 -3
- package/cli/storybook-dist/assets/preview-DZWUyVBI.css +0 -1
- package/cli/storybook-dist/assets/react-resizable-panels.browser-CHDWt8fa.js +0 -72
- /package/cli/storybook-dist/assets/{RainbowUiProvider-BaelaOGy.css → react-resizable-panels-BaelaOGy.css} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"generatedAt":
|
|
1
|
+
{"generatedAt":1789632370434,"userSince":1750932757926,"hasCustomBabel":false,"hasCustomWebpack":false,"hasStaticDirs":false,"hasStorybookEslint":true,"refCount":0,"testPackages":{"@vitest/browser":"3.2.4","@vitest/coverage-v8":"3.2.4","playwright":"1.59.1","vitest":"3.2.4"},"hasRouterPackage":false,"monorepo":"Turborepo","packageManager":{"type":"yarn","version":"berry","agent":"yarn@berry"},"preview":{"usesGlobals":false},"framework":{"name":"@storybook/react-vite","options":{}},"builder":"@storybook/builder-vite","renderer":"@storybook/react","portableStoriesFileCount":0,"applicationFileCount":0,"storybookVersion":"8.6.18","storybookVersionSpecifier":"^8.6.4","language":"typescript","storybookPackages":{"@chromatic-com/storybook":{"version":"3.2.7"},"@storybook/addon-essentials":{"version":"8.6.18"},"@storybook/addon-links":{"version":"8.6.18"},"@storybook/addon-onboarding":{"version":"8.6.18"},"@storybook/addon-themes":{"version":"8.6.18"},"@storybook/blocks":{"version":"8.6.18"},"@storybook/experimental-addon-test":{"version":"8.6.18"},"@storybook/react":{"version":"8.6.18"},"@storybook/react-vite":{"version":"8.6.18"},"@storybook/test":{"version":"8.6.18"},"@storybook/theming":{"version":"8.6.18"},"eslint-plugin-storybook":{"version":"0.11.6"},"storybook":{"version":"8.6.18"},"storybook-addon-react-docgen":{"version":"1.2.44"}},"addons":{"$SNIP/node_modules/@storybook/addon-links":{"version":null},"$SNIP/node_modules/@storybook/addon-essentials":{"version":null},"$SNIP/node_modules/@chromatic-com/storybook":{"version":null},"$SNIP/node_modules/@storybook/addon-themes":{"version":null},"$SNIP/node_modules/@storybook/experimental-addon-test":{"version":null}}}
|
|
@@ -398,6 +398,103 @@ 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
|
+
|
|
401
498
|
#### Program command backend (`program.panel`)
|
|
402
499
|
|
|
403
500
|
A `program.panel` contribution defines a **custom command** your plugin adds to
|
|
@@ -439,34 +536,206 @@ ctx = PluginProgramContext(plugin_id="my-plugin")
|
|
|
439
536
|
|
|
440
537
|
@ctx.on_execute
|
|
441
538
|
async def handle(args: dict) -> None:
|
|
442
|
-
|
|
443
|
-
|
|
539
|
+
"""Called once when my Step executes. Variable writes apply to the
|
|
540
|
+
running program immediately + broadcast to other robots' main tasks."""
|
|
541
|
+
count = await ctx.get_variable("counter") # read an existing program variable
|
|
542
|
+
await ctx.set_variable("counter", count + 1) # update an existing variable — allowed
|
|
543
|
+
await ctx.set_variable("RBP_TEST", 1) # NEW variables must start with RBP_
|
|
444
544
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
await
|
|
448
|
-
await asyncio.sleep(0.1)
|
|
545
|
+
for i in range(100):
|
|
546
|
+
await ctx.check_stop() # auto-scoped to this Step's robot model
|
|
547
|
+
await asyncio.sleep(0.5)
|
|
449
548
|
|
|
450
|
-
|
|
451
|
-
counter = await ctx.get_variable("counter")
|
|
452
|
-
print(f"[my-plugin] counter={counter}", flush=True)
|
|
549
|
+
await ctx.done()
|
|
453
550
|
|
|
454
|
-
# update a variable
|
|
455
|
-
await ctx.set_variable("result", f"done: {message}")
|
|
456
551
|
|
|
457
|
-
|
|
458
|
-
|
|
552
|
+
async def background_monitor() -> None:
|
|
553
|
+
"""Resident task outside any Step. If no program is running,
|
|
554
|
+
get returns None and set is silently ignored — always safe to call."""
|
|
555
|
+
while True:
|
|
556
|
+
await asyncio.sleep(1.0)
|
|
459
557
|
|
|
558
|
+
# targets every running main task (no robot_model param — always broadcast)
|
|
559
|
+
await ctx.set_variable("RBP_TEST", 3)
|
|
460
560
|
|
|
461
|
-
|
|
462
|
-
|
|
561
|
+
# get options
|
|
562
|
+
v1 = await ctx.get_variable("counter") # first value found in the whole tree
|
|
563
|
+
v2 = await ctx.get_variable("counter", robot_model="C500920") # a specific robot
|
|
564
|
+
v3 = await ctx.get_variable("RBP_TEST", plugin_id="my-plugin") # the value my-plugin last wrote
|
|
565
|
+
|
|
566
|
+
if ctx.executor_state("C500920") == "paused":
|
|
567
|
+
print("C500920 program is paused")
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
async def main() -> None:
|
|
571
|
+
asyncio.create_task(background_monitor())
|
|
572
|
+
await ctx.start() # stays resident
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
asyncio.run(main())
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
#### Program variables & executor state (`PluginProgramContext`)
|
|
579
|
+
|
|
580
|
+
`rainbow.rb_sdk.PluginProgramContext` lets the plugin process interact with the
|
|
581
|
+
running program (PyFM): read/write program variables and observe execution
|
|
582
|
+
state — inside a Step handler or from a resident task, at any time.
|
|
583
|
+
|
|
584
|
+
- The `on_execute` handler is called **only when PyFM executes this plugin's
|
|
585
|
+
Step**. One Step execution = one handler call, and the Step does not finish
|
|
586
|
+
until you call `ctx.done()` — always call it.
|
|
587
|
+
- The plugin process itself stays resident after `ctx.start()`; executor state
|
|
588
|
+
queries and variable access below keep working whether or not a Step is
|
|
589
|
+
executing.
|
|
590
|
+
|
|
591
|
+
#### Variable naming rules (important)
|
|
592
|
+
|
|
593
|
+
PyFM variables are a **program-wide shared namespace**. One rule:
|
|
594
|
+
|
|
595
|
+
- **Modifying an existing variable** — the name passes as-is. Exchanging
|
|
596
|
+
variables between the program and the plugin is the intended usage.
|
|
597
|
+
- **Creating a variable that doesn't exist** — the `RBP_` prefix is
|
|
598
|
+
**required** (enforced by the SDK/server; a violation raises
|
|
599
|
+
`RuntimeError`). Applies equally inside and outside Step execution.
|
|
600
|
+
|
|
601
|
+
For new variable names, a convention that includes the plugin id is strongly
|
|
602
|
+
recommended:
|
|
603
|
+
|
|
604
|
+
```
|
|
605
|
+
RBP_{plugin_id}_{name}
|
|
606
|
+
|
|
607
|
+
e.g. RBP_vision_detect_count, RBP_gripper_last_error
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
Why:
|
|
611
|
+
|
|
612
|
+
- `RBP_` — instantly identifies plugin-created variables in the program
|
|
613
|
+
editor/UI.
|
|
614
|
+
- `{plugin_id}` — **prevents name collisions with other plugins.** If two
|
|
615
|
+
plugins use the same name they overwrite the same variable (`RBP_` alone is
|
|
616
|
+
not something the SDK guards against).
|
|
617
|
+
|
|
618
|
+
#### Reading / writing variables
|
|
619
|
+
|
|
620
|
+
```python
|
|
621
|
+
value = await ctx.get_variable("counter") # read (no name restriction)
|
|
622
|
+
await ctx.set_variable("counter", value + 1) # modify existing variable — allowed
|
|
623
|
+
await ctx.set_variable("RBP_my-plugin_result", 42) # create new variable — RBP_ required
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
`set_variable` always applies to **every running main task** (a plugin belongs
|
|
627
|
+
to muscat, not to a specific robot — there is no `robot_model` parameter):
|
|
628
|
+
|
|
629
|
+
| When | Own program | Other robots' main tasks |
|
|
630
|
+
|---|---|---|
|
|
631
|
+
| During own Step execution | via the control queryable — **immediate** | broadcast — each at its next Step boundary |
|
|
632
|
+
| Outside a Step (resident task, …) | resident endpoint — next Step boundary | same |
|
|
633
|
+
|
|
634
|
+
`get_variable` has options:
|
|
635
|
+
|
|
636
|
+
```python
|
|
637
|
+
await ctx.get_variable("counter") # search all main tasks (first value found)
|
|
638
|
+
await ctx.get_variable("counter", robot_model="C500920") # a specific robot's value
|
|
639
|
+
await ctx.get_variable("RBP_TEST", plugin_id="A.plugin") # the value that plugin last wrote
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
Caveats:
|
|
643
|
+
|
|
644
|
+
- If no program is running, `set_variable` is **silently ignored** and
|
|
645
|
+
`get_variable` returns `None`. (`RuntimeError` is raised only for real
|
|
646
|
+
errors, like the `RBP_` violation on a new variable — the message is tagged
|
|
647
|
+
`[plugin:{id}]` with the offending plugin.)
|
|
648
|
+
- Rarely, a write can be lost if it races the worker's variable flush
|
|
649
|
+
(lock-free read-modify-write). For important values, re-check and retry on
|
|
650
|
+
the next cycle.
|
|
651
|
+
|
|
652
|
+
#### Per-plugin value storage (meta)
|
|
653
|
+
|
|
654
|
+
Besides the shared value, each variable entry records which plugin last wrote
|
|
655
|
+
what:
|
|
656
|
+
|
|
657
|
+
```python
|
|
658
|
+
# state_dict["variables"] — after A wrote 1 and B wrote 2
|
|
659
|
+
"RBP_TEST": {
|
|
660
|
+
"value": 2, # shared slot = last written value
|
|
661
|
+
"type": "local",
|
|
662
|
+
"meta": {"writers": {"A.plugin": 1, "B.plugin": 2}}, # per-plugin record
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
So the exact value is recoverable even when names collide:
|
|
667
|
+
|
|
668
|
+
- Plugin SDK: `ctx.get_variable("RBP_TEST", plugin_id="A.plugin")` → `1`
|
|
669
|
+
- Program step expression: `COUNT = get_var("A.plugin", "RBP_TEST") + 1` → `2`
|
|
670
|
+
- A plain name (`RBP_TEST += 1`) uses the shared slot (last written value) → `3`
|
|
671
|
+
|
|
672
|
+
#### Executor state
|
|
673
|
+
|
|
674
|
+
Subscribes to the `muscat/executor/state` publish (0.8 s interval) and exposes
|
|
675
|
+
per-robot-model state:
|
|
676
|
+
|
|
677
|
+
```python
|
|
678
|
+
ctx.executor_state("C500920") # "running" | "paused" | "waiting" | ... | None
|
|
679
|
+
ctx.executor_state() # {"C500920": "paused", "amr": "running"}
|
|
680
|
+
ctx.is_paused("C500920") # whether that model is paused
|
|
681
|
+
ctx.is_stopped("C500920") # stop/error transition of a model seen active this run
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
- States: `idle | running | paused | waiting | stopped | post_start | completed | error`
|
|
685
|
+
- A model with no running task is `None` / absent from the dict.
|
|
686
|
+
- The publish interval is 0.8 s, so expect up to ~1 s of latency.
|
|
687
|
+
- With `IS_DEV=true` there is no publish at all — always `None`.
|
|
688
|
+
|
|
689
|
+
To get a callback at the moment the state changes, without polling:
|
|
690
|
+
|
|
691
|
+
```python
|
|
692
|
+
@ctx.on_executor_state_change
|
|
693
|
+
async def on_change(robot_model: str, state: str) -> None:
|
|
694
|
+
if state == "paused":
|
|
695
|
+
print(f"{robot_model} paused")
|
|
463
696
|
```
|
|
464
697
|
|
|
698
|
+
- Not called for repeated publishes of the same state — fires exactly once
|
|
699
|
+
per per-model state change.
|
|
700
|
+
- An exception in the handler is only logged; state tracking keeps running.
|
|
701
|
+
|
|
702
|
+
#### Responding to pause / stop
|
|
703
|
+
|
|
704
|
+
```python
|
|
705
|
+
@ctx.on_execute
|
|
706
|
+
async def handle(args: dict) -> None:
|
|
707
|
+
for i in range(100):
|
|
708
|
+
# stop → CancelledError (propagated to the executor as a stop),
|
|
709
|
+
# pause → waits here until resumed.
|
|
710
|
+
# Inside on_execute this automatically watches only the robot model
|
|
711
|
+
# executing this Step.
|
|
712
|
+
await ctx.check_stop()
|
|
713
|
+
|
|
714
|
+
... # actual work
|
|
715
|
+
await asyncio.sleep(0.5)
|
|
716
|
+
|
|
717
|
+
await ctx.done()
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
Scope priority for `check_stop` / `is_paused` / `is_stopped`:
|
|
721
|
+
|
|
722
|
+
1. `robot_model` given — that model only
|
|
723
|
+
2. Omitted inside `on_execute` — **only the model executing this Step**
|
|
724
|
+
(passed in by the executor)
|
|
725
|
+
3. Omitted elsewhere (resident task, …) — all models
|
|
726
|
+
|
|
727
|
+
A handler doing long-running work must call `check_stop()` every loop
|
|
728
|
+
iteration so it reacts to pause/stop immediately. Without it, the plugin keeps
|
|
729
|
+
running even after the operator presses Stop.
|
|
730
|
+
|
|
731
|
+
Variables your plugin wants the Program page variable picker to offer must be
|
|
732
|
+
pre-declared in `manifest.json` under `"variables"` (see the manifest section
|
|
733
|
+
below).
|
|
734
|
+
|
|
465
735
|
### C++
|
|
466
736
|
|
|
467
|
-
C++ plugins use `cpp/` and are built with CMake inside Docker
|
|
468
|
-
|
|
469
|
-
is available.
|
|
737
|
+
C++ plugins use `cpp/` and are built with CMake inside Docker against the
|
|
738
|
+
Rainbow Robotics C++ SDK (see _C++ SDK_ below).
|
|
470
739
|
|
|
471
740
|
```cpp
|
|
472
741
|
#include <chrono>
|
|
@@ -488,6 +757,25 @@ int main() {
|
|
|
488
757
|
}
|
|
489
758
|
```
|
|
490
759
|
|
|
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 dev main' | sudo tee /etc/apt/sources.list.d/rb-sdk.list
|
|
770
|
+
sudo apt update && sudo apt install rainbow-rb-sdk-dev=0.1.2~dev1
|
|
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** (`librb_sdk.so.<version>` by SONAME) and the robot provides that `.so`. Pin the SDK to the robot's version: the dev image default is `0.1.2~dev1`; change it with `MUSCAT_PK_RB_SDK_VERSION=<version> MUSCAT_PK_REBUILD_IMAGE=1 muscat-pk dev`. Do not bundle or static-link the SDK.
|
|
777
|
+
- UI ↔ backend messaging (`RBPluginCall` pub/sub/serve) is Python-only for now.
|
|
778
|
+
|
|
491
779
|
### ROS2 (rclpy)
|
|
492
780
|
|
|
493
781
|
ROS2 Python plugins use `ros2_ws/` with `runtime.rosClient: "rclpy"`. The node
|
|
@@ -511,7 +799,8 @@ class __PLUGIN_CLASS__Node(Node):
|
|
|
511
799
|
|
|
512
800
|
### ROS2 (rclcpp)
|
|
513
801
|
|
|
514
|
-
ROS2 C++ plugins use `ros2_ws/` with `runtime.rosClient: "rclcpp"`.
|
|
802
|
+
ROS2 C++ plugins use `ros2_ws/` with `runtime.rosClient: "rclcpp"`. The node links
|
|
803
|
+
the Rainbow C++ SDK the same way as the `cpp` runtime (see _C++ SDK_ above).
|
|
515
804
|
|
|
516
805
|
```cpp
|
|
517
806
|
#include "rclcpp/rclcpp.hpp"
|
|
@@ -588,6 +877,15 @@ public:
|
|
|
588
877
|
}
|
|
589
878
|
],
|
|
590
879
|
|
|
880
|
+
// optional: variables this plugin exposes to pyfm programs — the Program
|
|
881
|
+
// page variable picker lists ONLY declared variables. name must start with
|
|
882
|
+
// RBP_; description is optional but without it the UI can't explain the
|
|
883
|
+
// variable, so write one.
|
|
884
|
+
"variables": [
|
|
885
|
+
{ "name": "RBP_grip_force", "type": "number", "init": 0.5, "description": { "ko": "그리퍼 힘", "en": "Gripper force" } },
|
|
886
|
+
{ "name": "RBP_target_pose", "type": "array", "init": [0, 0, 0] }
|
|
887
|
+
],
|
|
888
|
+
|
|
591
889
|
// runtime — one of:
|
|
592
890
|
// { "type": "none" }
|
|
593
891
|
// { "type": "python", "entry": "main.py" }
|
|
@@ -654,15 +952,36 @@ public:
|
|
|
654
952
|
- `commandInfo` — required when `point` is `"program.panel"`
|
|
655
953
|
- `ports` — optional `number[]`; every port must be within `28600-28999`
|
|
656
954
|
and must not be a reserved robot/industrial/internal port
|
|
955
|
+
- `variables` — optional array of `{ name, type, init, description? }`:
|
|
956
|
+
- `name` — must start with `RBP_`, then letters/digits/underscore; unique per plugin
|
|
957
|
+
- `type` — `"number" | "string" | "array"`
|
|
958
|
+
- `init` — required initial value, must match `type`
|
|
959
|
+
- `description` — optional `string` or i18n object (`en` required in object form)
|
|
657
960
|
|
|
658
|
-
`muscat-pk build` throws on any validation error. `muscat-pk dev` prints
|
|
961
|
+
`muscat-pk build` throws on any validation error. `muscat-pk dev` prints
|
|
962
|
+
warnings — except `variables` errors (e.g. a name without the `RBP_` prefix),
|
|
963
|
+
which are hard errors in `dev` / `dev:back` too.
|
|
659
964
|
|
|
660
965
|
**Backend port rules:**
|
|
661
966
|
- Declare a port only if your backend opens a TCP server/listener.
|
|
662
|
-
-
|
|
663
|
-
|
|
967
|
+
- **Every listening TCP port must be declared.** `muscat-pk dev` and
|
|
968
|
+
`muscat-pk dev:back` watch the backend and stop it the moment it opens a port
|
|
969
|
+
that is not in `ports`. A port outside `28600-28999` can't be declared, so it
|
|
970
|
+
must not be opened either — picking e.g. `5500` is not a way around the rule.
|
|
971
|
+
It would escape this check *and* the robot's install-time conflict check, and
|
|
972
|
+
only collide once it's running on a robot.
|
|
664
973
|
- Reserved ports include `502`, `2222`, `4840`, `5170-5179`, `8000-8999`,
|
|
665
974
|
`10000`, `30004`, `34964`, `44818`, `50000-50005`, and `50051`.
|
|
975
|
+
- The robot rejects installation (HTTP 409) if a declared port is reserved,
|
|
976
|
+
out of range, or already claimed by another enabled plugin.
|
|
977
|
+
|
|
978
|
+
**Reaching your backend's ports during development:**
|
|
979
|
+
- Declared ports are published to `127.0.0.1` on your machine, so
|
|
980
|
+
`curl http://127.0.0.1:28600` works from the host on macOS, Windows, and Linux.
|
|
981
|
+
- On native Linux the dev container uses host networking, so the ports are on
|
|
982
|
+
the host already.
|
|
983
|
+
- If a declared port is already taken on your machine, `muscat-pk dev` warns and
|
|
984
|
+
skips publishing that one — the backend still listens inside the container.
|
|
666
985
|
|
|
667
986
|
---
|
|
668
987
|
|
|
@@ -740,7 +1059,9 @@ letting it guess. `muscat-pk mcp` is an MCP server exposing:
|
|
|
740
1059
|
- `search_rb_sdk` — how to use `useRb()`, `rb.*`, `useRbFormData`/`useRbSave`, and
|
|
741
1060
|
the backend runtimes.
|
|
742
1061
|
- `list_rb_sdk` / `get_rb_sdk_api` — the installed `rainbow-rb-sdk` (Python
|
|
743
|
-
backend) classes and signatures
|
|
1062
|
+
backend) classes and signatures; with `lang: "cpp"`, the C++ SDK headers
|
|
1063
|
+
(`rainbow-rb-sdk-dev`, read out of the `muscat-pk` cpp dev image — build it
|
|
1064
|
+
once with `muscat-pk dev:back`).
|
|
744
1065
|
|
|
745
1066
|
**Browse the components live** with `muscat-pk storybook`.
|
|
746
1067
|
|
|
@@ -760,7 +1081,14 @@ it works out of the box:
|
|
|
760
1081
|
}
|
|
761
1082
|
```
|
|
762
1083
|
|
|
763
|
-
**Codex** —
|
|
1084
|
+
**Codex** — register once with:
|
|
1085
|
+
|
|
1086
|
+
```bash
|
|
1087
|
+
muscat-pk mcp --install codex
|
|
1088
|
+
```
|
|
1089
|
+
|
|
1090
|
+
This appends the server to `~/.codex/config.toml` (idempotent — safe to run
|
|
1091
|
+
again). Equivalent manual config:
|
|
764
1092
|
|
|
765
1093
|
```toml
|
|
766
1094
|
[mcp_servers.muscat]
|
|
@@ -10,6 +10,13 @@ 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".
|
|
13
20
|
"""
|
|
14
21
|
|
|
15
22
|
import asyncio
|
|
@@ -29,6 +36,8 @@ def register_handlers():
|
|
|
29
36
|
# Register handlers here — called after connect() so subscriptions survive
|
|
30
37
|
# session reconnection in remote-dev mode.
|
|
31
38
|
|
|
39
|
+
# --- Plugin UI <-> backend (not reachable from outside Muscat) ---
|
|
40
|
+
|
|
32
41
|
@plugin.subscribe("event")
|
|
33
42
|
def on_ui_event(payload: dict[str, Any] | None = None):
|
|
34
43
|
"""Handle messages published from the plugin UI with rb.pub("event", ...)."""
|
|
@@ -47,6 +56,19 @@ def register_handlers():
|
|
|
47
56
|
"""Small query example callable from the UI with rb.action("tool/echo", ...)."""
|
|
48
57
|
return {"echo": payload, "plugin": PLUGIN_NAME}
|
|
49
58
|
|
|
59
|
+
# --- External endpoints (REST / Socket.IO / TCP / MQTT / WebRTC) ---
|
|
60
|
+
# Exposed as common/plugin/<plugin_id>/hello, e.g.
|
|
61
|
+
# curl -X POST http://<robot>/common/plugin/<plugin_id>/hello \
|
|
62
|
+
# -H 'Content-Type: application/json' -d '{"name": "kim"}'
|
|
63
|
+
# Restrict transports with explicit_routes=["api"] (REST only), etc.
|
|
64
|
+
# Raise PluginRouteError(403, "msg") to set the response status.
|
|
65
|
+
|
|
66
|
+
@plugin.router.on("/hello")
|
|
67
|
+
def hello(payload: dict[str, Any] | None = None, meta: dict[str, Any] | None = None):
|
|
68
|
+
"""meta = {"transport": "api"|"socket"|"tcp"|"mqtt"|"webrtc", "principal": dict | None}."""
|
|
69
|
+
name = (payload or {}).get("name", "there")
|
|
70
|
+
return {"msg": f"hi {name}", "via": (meta or {}).get("transport")}
|
|
71
|
+
|
|
50
72
|
|
|
51
73
|
def move_j_example():
|
|
52
74
|
"""
|
|
@@ -77,7 +99,8 @@ async def main():
|
|
|
77
99
|
|
|
78
100
|
register_handlers()
|
|
79
101
|
|
|
80
|
-
plugin.publish("state", {"ready": True, "plugin": PLUGIN_NAME})
|
|
102
|
+
plugin.publish("state", {"ready": True, "plugin": PLUGIN_NAME}) # -> plugin UI
|
|
103
|
+
plugin.router.emit("/state", {"ready": True, "plugin": PLUGIN_NAME}) # -> external subscribers of common/plugin/<plugin_id>/state
|
|
81
104
|
print(f"[{PLUGIN_NAME}] backend ready")
|
|
82
105
|
|
|
83
106
|
try:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
rainbow-rb-sdk==0.
|
|
2
|
-
rainbow-rb-flat-buffers==0.
|
|
3
|
-
rainbow-rb-zenoh==0.
|
|
1
|
+
rainbow-rb-sdk==0.1.1
|
|
2
|
+
rainbow-rb-flat-buffers==0.1.1
|
|
3
|
+
rainbow-rb-zenoh==0.1.1
|
|
4
4
|
requests==2.34.2
|
|
@@ -1,14 +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 20)
|
|
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). Linked dynamically: the robot
|
|
9
|
+
# ships the matching librb_sdk.so, so do NOT bundle or static-link it.
|
|
10
|
+
find_package(rb_sdk REQUIRED)
|
|
11
|
+
|
|
7
12
|
# The executable name must match runtime.entry in manifest.json.
|
|
8
13
|
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)
|
|
14
|
+
target_link_libraries(__PLUGIN_SLUG__ PRIVATE rb_sdk::rb_sdk)
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
// __PLUGIN_NAME__ — C++ plugin back end.
|
|
2
2
|
//
|
|
3
|
-
// The C++
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// The Rainbow Robotics C++ SDK (apt: rainbow-rb-sdk-dev) is installed in the
|
|
4
|
+
// dev container and linked via CMake (rb_sdk::rb_sdk). Headers live under
|
|
5
|
+
// <rainbow/rb_sdk/...>:
|
|
6
|
+
// - manipulate/rb_manipulate_sdk.hpp RBManipulateSDK — control the robot
|
|
7
|
+
// - program/plugin_program_context.hpp rb_plugin::PluginProgramContext —
|
|
8
|
+
// program.panel commands (done/pause/stop,
|
|
9
|
+
// program variables); see the header's
|
|
10
|
+
// top comment for a full main() example
|
|
11
|
+
// UI <-> backend messaging (RBPluginCall pub/sub/serve) is Python-only for now.
|
|
12
|
+
|
|
13
|
+
#include <rainbow/rb_sdk/manipulate/rb_manipulate_sdk.hpp>
|
|
7
14
|
|
|
8
15
|
#include <chrono>
|
|
9
16
|
#include <cstdlib>
|
|
@@ -17,8 +24,8 @@ int main() {
|
|
|
17
24
|
<< (plugin_id ? std::string(" (id=") + plugin_id + ")" : "")
|
|
18
25
|
<< std::endl;
|
|
19
26
|
|
|
20
|
-
//
|
|
21
|
-
//
|
|
27
|
+
// e.g. RBManipulateSDK manipulate; manipulate.move.call_move_j(...);
|
|
28
|
+
// Keep the process alive.
|
|
22
29
|
while (true) {
|
|
23
30
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
24
31
|
}
|
|
@@ -7,9 +7,13 @@ 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). Dynamically linked — the robot provides the matching librb_sdk.so.
|
|
12
|
+
find_package(rb_sdk REQUIRED)
|
|
10
13
|
|
|
11
14
|
add_executable(__PLUGIN_SLUG___node src/node.cpp)
|
|
12
15
|
ament_target_dependencies(__PLUGIN_SLUG___node rclcpp)
|
|
16
|
+
target_link_libraries(__PLUGIN_SLUG___node rb_sdk::rb_sdk)
|
|
13
17
|
|
|
14
18
|
install(TARGETS __PLUGIN_SLUG___node
|
|
15
19
|
DESTINATION lib/${PROJECT_NAME})
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
// plain rclcpp node is all you need — relative topic names get the namespace
|
|
6
6
|
// prefix applied automatically.
|
|
7
7
|
#include "rclcpp/rclcpp.hpp"
|
|
8
|
+
// Rainbow Robotics C++ SDK (linked as rb_sdk::rb_sdk): RBManipulateSDK etc.
|
|
9
|
+
#include <rainbow/rb_sdk/manipulate/rb_manipulate_sdk.hpp>
|
|
8
10
|
|
|
9
11
|
class __PLUGIN_CLASS__Node : public rclcpp::Node {
|
|
10
12
|
public:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rainbow-robotics/muscat-plugin-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
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.
|
|
57
|
-
"@rainbow-robotics/preview-host": "1.
|
|
56
|
+
"@rainbow-robotics/plugin-ui": "1.3.0",
|
|
57
|
+
"@rainbow-robotics/preview-host": "1.3.0",
|
|
58
58
|
"postcss": "^8.5.8",
|
|
59
59
|
"socket.io": "^4.8.1",
|
|
60
60
|
"typescript": "^5.8.0",
|