@rainbow-robotics/muscat-plugin-kit 1.10.1-dev.1 → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +282 -282
- package/cli/README.md +274 -274
- package/cli/bin/create-muscat-plugin.mjs +8 -8
- package/cli/bin/muscat-pk.mjs +155 -155
- package/cli/bundle-templates.mjs +31 -31
- package/cli/docker/Dockerfile.cpp +28 -28
- package/cli/docker/Dockerfile.dev +30 -30
- package/cli/docker/Dockerfile.python +21 -21
- package/cli/m5-e2e.mjs +69 -69
- package/cli/prepack.mjs +10 -10
- package/cli/src/ai-rules.mjs +70 -70
- package/cli/src/build.mjs +447 -447
- package/cli/src/check-package.mjs +13 -13
- package/cli/src/completion.mjs +166 -166
- package/cli/src/cpp.mjs +78 -78
- package/cli/src/cppIde.mjs +75 -75
- package/cli/src/dev.mjs +165 -165
- package/cli/src/devBack.mjs +734 -727
- package/cli/src/devUi.mjs +247 -243
- package/cli/src/esbuild.mjs +56 -56
- package/cli/src/host_agent.py +223 -223
- package/cli/src/manifest.mjs +287 -287
- package/cli/src/mcp.mjs +1071 -1071
- package/cli/src/muscatLocal.mjs +209 -209
- package/cli/src/new.mjs +343 -343
- package/cli/src/pluginDevServer.mjs +111 -111
- package/cli/src/ports.mjs +193 -193
- package/cli/src/python.mjs +112 -112
- package/cli/src/ros2.mjs +109 -109
- package/cli/src/sdk.mjs +117 -117
- package/cli/src/storybook.mjs +168 -168
- package/cli/src/type.mjs +141 -141
- package/cli/src/uninstall.mjs +81 -81
- package/cli/src/update.mjs +295 -295
- package/cli/src/util.mjs +441 -441
- package/cli/src/zenohDev.mjs +83 -81
- package/cli/storybook-dist/_design-tokens.css +4957 -4957
- package/cli/storybook-dist/_stories-src/NxAvatar.stories.tsx +27 -27
- package/cli/storybook-dist/_stories-src/NxPagination.stories.tsx +28 -28
- package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordion.stories.tsx +46 -46
- package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordionGroup.stories.tsx +73 -73
- package/cli/storybook-dist/_stories-src/RBBadge/RBBadge.stories.tsx +93 -93
- package/cli/storybook-dist/_stories-src/RBBattery/RBBattery.stories.tsx +74 -74
- package/cli/storybook-dist/_stories-src/RBButton/RBCheckIconButton.stories.tsx +28 -28
- package/cli/storybook-dist/_stories-src/RBButton/RBCheckLabelButton.stories.tsx +84 -84
- package/cli/storybook-dist/_stories-src/RBButton/RBIconButton.stories.tsx +57 -57
- package/cli/storybook-dist/_stories-src/RBButton/RBInlineLabelButton.stories.tsx +41 -41
- package/cli/storybook-dist/_stories-src/RBButton/RBLabelButton.stories.tsx +60 -60
- package/cli/storybook-dist/_stories-src/RBDatePicker/RBDateInput.stories.tsx +44 -44
- package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +56 -56
- package/cli/storybook-dist/_stories-src/RBDialog/RBAlert.stories.tsx +51 -51
- package/cli/storybook-dist/_stories-src/RBDialog/RBConfirm.stories.tsx +55 -55
- package/cli/storybook-dist/_stories-src/RBDialog/RBPrompt.stories.tsx +52 -52
- package/cli/storybook-dist/_stories-src/RBDivider/RBDivider.stories.tsx +93 -93
- package/cli/storybook-dist/_stories-src/RBEmptyView/RBEmptyView.stories.tsx +49 -49
- package/cli/storybook-dist/_stories-src/RBIcons/RBColorIcons.stories.tsx +108 -108
- package/cli/storybook-dist/_stories-src/RBIcons/RBIcons.stories.tsx +106 -106
- package/cli/storybook-dist/_stories-src/RBInput/RBCheckBox.stories.tsx +89 -89
- package/cli/storybook-dist/_stories-src/RBInput/RBMultiRangeInput.stories.tsx +64 -64
- package/cli/storybook-dist/_stories-src/RBInput/RBRadioBox.stories.tsx +65 -65
- package/cli/storybook-dist/_stories-src/RBInput/RBRadioButton.stories.tsx +68 -68
- package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +216 -216
- package/cli/storybook-dist/_stories-src/RBInput/RBSideNumberInput.stories.tsx +90 -90
- package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextArea.stories.tsx +45 -45
- package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextInput.stories.tsx +50 -50
- package/cli/storybook-dist/_stories-src/RBInput/RBTextField.stories.tsx +102 -102
- package/cli/storybook-dist/_stories-src/RBJoyStick/RBJoyStick.stories.tsx +31 -31
- package/cli/storybook-dist/_stories-src/RBLight/RBLight.stories.tsx +36 -36
- package/cli/storybook-dist/_stories-src/RBPagination/RBPagination.stories.tsx +49 -49
- package/cli/storybook-dist/_stories-src/RBShadcn/RBCard/RBCard.stories.tsx +89 -89
- package/cli/storybook-dist/_stories-src/RBShadcn/RBCommand/RBCommand.stories.tsx +116 -116
- package/cli/storybook-dist/_stories-src/RBShadcn/RBDialog/RBDialog.stories.tsx +74 -74
- package/cli/storybook-dist/_stories-src/RBShadcn/RBDrawer/RBDrawer.stories.tsx +99 -99
- package/cli/storybook-dist/_stories-src/RBShadcn/RBDropdownMenu/RBDropdownMenu.stories.tsx +123 -123
- package/cli/storybook-dist/_stories-src/RBShadcn/RBMenubar/RBMenubar.stories.tsx +135 -135
- package/cli/storybook-dist/_stories-src/RBShadcn/RBPopover/RBPopover.stories.tsx +69 -69
- package/cli/storybook-dist/_stories-src/RBShadcn/RBResizable/RBResizable.stories.tsx +49 -49
- package/cli/storybook-dist/_stories-src/RBShadcn/RBSheet/RBSheet.stories.tsx +91 -91
- package/cli/storybook-dist/_stories-src/RBShadcn/RBSlider/RBSlider.stories.tsx +38 -38
- package/cli/storybook-dist/_stories-src/RBShadcn/RBSwitch/RBSwitch.stories.tsx +112 -112
- package/cli/storybook-dist/_stories-src/RBShadcn/RBTabs/RBTabs.stories.tsx +75 -75
- package/cli/storybook-dist/_stories-src/RBShadcn/RBToggle/RBToggle.stories.tsx +51 -51
- package/cli/storybook-dist/_stories-src/RBShadcn/RBToggleGroup/RBToggleGroup.stories.tsx +84 -84
- package/cli/storybook-dist/_stories-src/RBTab/RBChipTab.stories.tsx +102 -102
- package/cli/storybook-dist/_stories-src/RBTab/RBChipsBoxTab.stories.tsx +78 -78
- package/cli/storybook-dist/_stories-src/RBTab/RBTab.stories.tsx +57 -57
- package/cli/storybook-dist/_stories-src/RBTable/RBTable.stories.tsx +177 -177
- package/cli/storybook-dist/_stories-src/RBTable/RBTableRow.stories.tsx +64 -64
- package/cli/storybook-dist/_stories-src/RBToast/RBToast.stories.tsx +117 -117
- package/cli/storybook-dist/_stories-src/RBTooltip/RBTooltip.stories.tsx +50 -50
- package/cli/storybook-dist/_stories-src/loading/RBSpinner.stories.tsx +30 -30
- package/cli/storybook-dist/_stories-src/nx-button/NxButton.stories.tsx +54 -54
- package/cli/storybook-dist/_stories-src/nx-button/NxPointerHoldButton.stories.tsx +57 -57
- package/cli/storybook-dist/_stories-src/nx-icons/NxIcons.stories.tsx +48 -48
- package/cli/storybook-dist/_stories-src/nx-inputs/NxNumberInput.stories.tsx +103 -103
- package/cli/storybook-dist/_stories-src/nx-inputs/NxRangeInput.stories.tsx +27 -27
- package/cli/storybook-dist/_stories-src/nx-inputs/NxSelectBox.stories.tsx +67 -67
- package/cli/storybook-dist/_stories-src/nx-inputs/NxSlideRange.stories.tsx +45 -45
- package/cli/storybook-dist/_stories-src/nx-inputs/NxSwitch.stories.tsx +27 -27
- package/cli/storybook-dist/_stories-src/nx-inputs/NxTextArea.stories.tsx +48 -48
- package/cli/storybook-dist/_stories-src/nx-inputs/NxTextInput.stories.tsx +46 -46
- package/cli/storybook-dist/assets/{Color-YHDXOIA2-D6JqZzDJ.js → Color-YHDXOIA2-B4IER17l.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-ESb7JAHg.js → Docs-C8GCHFtK.js} +3 -3
- package/cli/storybook-dist/assets/{Docs-6gD2kUo1.js → Docs-CgdEMPQP.js} +2 -2
- package/cli/storybook-dist/assets/{Docs-B9wd8Ok7.js → Docs-Ck3WHvk6.js} +3 -3
- package/cli/storybook-dist/assets/{Docs-CgFbWWXM.js → Docs-D9NHsoeZ.js} +2 -2
- package/cli/storybook-dist/assets/{Docs-DBQup8Bu.js → Docs-DNYQC7nl.js} +3 -3
- package/cli/storybook-dist/assets/{Docs-CMjovnSY.js → Docs-DUfeiM4I.js} +4 -4
- package/cli/storybook-dist/assets/{DocsRenderer-CFRXHY34-BOUI1AHi.js → DocsRenderer-CFRXHY34-C0WhyDoR.js} +1 -1
- package/cli/storybook-dist/assets/{NxAvatar.stories-DsYFMUh3.js → NxAvatar.stories-CwnK8AUF.js} +1 -1
- package/cli/storybook-dist/assets/{NxButton.stories-CHfNcqsc.js → NxButton.stories-ueYdosfQ.js} +8 -8
- package/cli/storybook-dist/assets/{NxNumberInput.stories-BFw_4vFE.js → NxNumberInput.stories-M5ZCLbrK.js} +1 -1
- package/cli/storybook-dist/assets/{NxPagination.stories-DVfxyw4M.js → NxPagination.stories-bxxUnHEL.js} +1 -1
- package/cli/storybook-dist/assets/{NxPointerHoldButton.stories-BzyxUWoZ.js → NxPointerHoldButton.stories-BHxB2kr5.js} +1 -1
- package/cli/storybook-dist/assets/{NxRangeInput.stories-DmRdzBc1.js → NxRangeInput.stories-BKfr-fp4.js} +6 -6
- package/cli/storybook-dist/assets/{NxSelectBox.stories-B1KkNUOf.js → NxSelectBox.stories-CG8FuTBq.js} +1 -1
- package/cli/storybook-dist/assets/{NxSlideRange.stories-CyOdvfQ6.js → NxSlideRange.stories-BRTFtMTL.js} +1 -1
- package/cli/storybook-dist/assets/{NxSwitch.stories-DrDBw0qp.js → NxSwitch.stories-Bwuyzn-R.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextArea.stories-DTB2aqNS.js → NxTextArea.stories-COq2sGqu.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextInput.stories-DU0rucmM.js → NxTextInput.stories-CjsBLz05.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordion.stories-DTfOtXtp.js → RBAccordion.stories-BARwuZ0Q.js} +3 -3
- package/cli/storybook-dist/assets/{RBAccordionGroup.stories-9y9BjrUe.js → RBAccordionGroup.stories-COrPxETW.js} +10 -10
- package/cli/storybook-dist/assets/{RBAlert.stories-DLTAv9fT.js → RBAlert.stories-BQrkjZPt.js} +3 -3
- package/cli/storybook-dist/assets/{RBBadge.stories-BNMACewA.js → RBBadge.stories-BTsLL-lK.js} +1 -1
- package/cli/storybook-dist/assets/{RBBattery.stories-B3kXMAt5.js → RBBattery.stories-DAeQdJbc.js} +1 -1
- package/cli/storybook-dist/assets/{RBCard.stories-Y-s8R2n7.js → RBCard.stories-HyhOE80R.js} +39 -39
- package/cli/storybook-dist/assets/{RBCheckBox.stories-DBuT9tL4.js → RBCheckBox.stories-KFk0WqX_.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckIconButton.stories-CWu1TL56.js → RBCheckIconButton.stories-CXbbktF5.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckLabelButton.stories-sCu2MGCX.js → RBCheckLabelButton.stories-DWfYz_9Z.js} +17 -17
- package/cli/storybook-dist/assets/{RBChipTab.stories-BX9qLJkR.js → RBChipTab.stories-JPEgVFAS.js} +5 -5
- package/cli/storybook-dist/assets/{RBChipsBoxTab.stories-35u1aJ-P.js → RBChipsBoxTab.stories-CTPuWXrQ.js} +1 -1
- package/cli/storybook-dist/assets/{RBColorIcons.stories-DAOZ5Ies.js → RBColorIcons.stories-DiFRZ5jk.js} +1 -1
- package/cli/storybook-dist/assets/RBCommand.stories-Pf1HAVIR.js +73 -0
- package/cli/storybook-dist/assets/{RBConfirm.stories-iMXcUh7Z.js → RBConfirm.stories-D1cEPUXk.js} +3 -3
- package/cli/storybook-dist/assets/{RBDateInput.stories-hK5rXECf.js → RBDateInput.stories-RPYpCjig.js} +1 -1
- package/cli/storybook-dist/assets/RBDialog.stories-DJH-0XSR.js +38 -0
- package/cli/storybook-dist/assets/{RBDivider.stories-BAK-iZ1Z.js → RBDivider.stories-uEj1vOgK.js} +1 -1
- package/cli/storybook-dist/assets/{RBDrawer.stories-B2nqf2Nh.js → RBDrawer.stories-BiMzElb2.js} +12 -12
- package/cli/storybook-dist/assets/RBDropdownMenu.stories-Da_TBkla.js +86 -0
- package/cli/storybook-dist/assets/{RBEmptyView.stories-nCenrgBh.js → RBEmptyView.stories-BHeKGfPS.js} +1 -1
- package/cli/storybook-dist/assets/{RBIconButton.stories-DRH7O3sp.js → RBIconButton.stories-NEsRFoYp.js} +1 -1
- package/cli/storybook-dist/assets/{RBIcons.stories-D3PyJXG8.js → RBIcons.stories-CcZ-TTLS.js} +1 -1
- package/cli/storybook-dist/assets/{RBInlineLabelButton.stories-modQbt10.js → RBInlineLabelButton.stories-B0wpdQyR.js} +1 -1
- package/cli/storybook-dist/assets/{RBJoyStick.stories-BY14qVgR.js → RBJoyStick.stories-DEgBou3K.js} +1 -1
- package/cli/storybook-dist/assets/{RBLabelButton.stories-DzF7vGwm.js → RBLabelButton.stories-DT8drIsF.js} +1 -1
- package/cli/storybook-dist/assets/{RBLight.stories-lF4FRmVh.js → RBLight.stories-Dh-aoeyG.js} +1 -1
- package/cli/storybook-dist/assets/{RBMenubar.stories-XhrI7bsV.js → RBMenubar.stories-D-H7UqVh.js} +1 -1
- package/cli/storybook-dist/assets/{RBMultiRangeInput.stories-DprllQDl.js → RBMultiRangeInput.stories-hlclv6jI.js} +1 -1
- package/cli/storybook-dist/assets/{RBPagination.stories-2v2gBJfC.js → RBPagination.stories-DWCeoZ_S.js} +1 -1
- package/cli/storybook-dist/assets/{RBPopover.stories-C8iLIZpv.js → RBPopover.stories-C514M09_.js} +1 -1
- package/cli/storybook-dist/assets/{RBPrompt.stories-Th_HrWHI.js → RBPrompt.stories-BSItvfiT.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioBox.stories-CoHuLaN3.js → RBRadioBox.stories-BBb4YbFU.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioButton.stories-DULxE_ff.js → RBRadioButton.stories-CXyOGJCv.js} +1 -1
- package/cli/storybook-dist/assets/{RBRangeDateInput.stories-D8A9KBSS.js → RBRangeDateInput.stories-qvhubu0e.js} +1 -1
- package/cli/storybook-dist/assets/{RBResizable.stories-C41QfrQ3.js → RBResizable.stories-CZKeyGjH.js} +14 -14
- package/cli/storybook-dist/assets/{RBSelectBox.stories-C1gNmwzw.js → RBSelectBox.stories-CRKP3ybf.js} +1 -1
- package/cli/storybook-dist/assets/RBSheet.stories-sCM3ZJUa.js +39 -0
- package/cli/storybook-dist/assets/{RBSideNumberInput.stories-9LlgQ8nh.js → RBSideNumberInput.stories-Bb7gw8RF.js} +3 -3
- package/cli/storybook-dist/assets/{RBSlider.stories-oUgo5K9d.js → RBSlider.stories-23OAQU5z.js} +3 -3
- package/cli/storybook-dist/assets/{RBSpinner.stories-DDpohbY2.js → RBSpinner.stories-2xcQdcmt.js} +1 -1
- package/cli/storybook-dist/assets/{RBSwitch.stories-gW-GN7QO.js → RBSwitch.stories-BACUaJm_.js} +7 -7
- package/cli/storybook-dist/assets/{RBTab.stories-CN-PV3HH.js → RBTab.stories-CDHE3sVs.js} +1 -1
- package/cli/storybook-dist/assets/{RBTable.stories-BAZLpxtM.js → RBTable.stories-C0Cfhfl7.js} +29 -29
- package/cli/storybook-dist/assets/{RBTableRow.stories-CwjVrszB.js → RBTableRow.stories-BF8kIkpp.js} +1 -1
- package/cli/storybook-dist/assets/{RBTabs.stories-JuEo2sUL.js → RBTabs.stories-BdJWRq1o.js} +7 -7
- package/cli/storybook-dist/assets/{RBTextArea.stories-D0_Pv4qZ.js → RBTextArea.stories-CGy2xeYH.js} +3 -3
- package/cli/storybook-dist/assets/{RBTextField.stories-CCsOvZqV.js → RBTextField.stories-IfUOzxCM.js} +15 -15
- package/cli/storybook-dist/assets/{RBTextInput.stories-Cccn3VNm.js → RBTextInput.stories-VjXNFo4R.js} +3 -3
- package/cli/storybook-dist/assets/{RBToast.stories-I5617t0K.js → RBToast.stories-BzcobblV.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggle.stories-DsgKjGsJ.js → RBToggle.stories-DuLE5mKv.js} +4 -4
- package/cli/storybook-dist/assets/{RBToggleGroup.stories-BZEzSDuz.js → RBToggleGroup.stories-CuR0N0GJ.js} +5 -5
- package/cli/storybook-dist/assets/{RBTooltip.stories-4oSyAWsA.js → RBTooltip.stories--aAgNC8_.js} +1 -1
- package/cli/storybook-dist/assets/{SideNumberInput-DSKbd0bG.js → SideNumberInput-DyWQxYUj.js} +743 -743
- package/cli/storybook-dist/assets/{entry-preview-B35XREGs.js → entry-preview-D7IHsza_.js} +1 -1
- package/cli/storybook-dist/assets/{iframe-BB797lmL.js → iframe-CbaIp5I-.js} +2 -2
- package/cli/storybook-dist/assets/{index-BU2567EP.js → index-C6CBgMuG.js} +3 -3
- package/cli/storybook-dist/assets/{preview-C7DaSWyc.js → preview-B-iYLDuW.js} +2 -2
- package/cli/storybook-dist/assets/{preview-CbuRHit4.js → preview-CRWqc9DH.js} +1 -1
- package/cli/storybook-dist/assets/{react-resizable-panels.browser-ChIvlwo7.js → react-resizable-panels.browser-8hC8dr-6.js} +8 -8
- package/cli/storybook-dist/iframe.html +2 -2
- package/cli/storybook-dist/index.html +1 -1
- package/cli/storybook-dist/project.json +1 -1
- package/cli/templates/common/.mcp.json +8 -8
- package/cli/templates/common/README.md +1231 -1231
- package/cli/templates/common/assets/icon.svg +45 -45
- package/cli/templates/common/manifest.json +26 -26
- package/cli/templates/common/package.json +22 -22
- package/cli/templates/common/py/main.py +125 -125
- package/cli/templates/common/tsconfig.json +13 -13
- package/cli/templates/common/ui/content.tsx +216 -216
- package/cli/templates/common/ui/style.css +6 -6
- package/cli/templates/common/vite.config.ts +10 -10
- package/cli/templates/cpp/cpp/main.cpp +73 -73
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +25 -25
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/package.xml +10 -10
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/setup.py +18 -18
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -11
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +23 -23
- package/cli/vite.d.mts +25 -25
- package/cli/vite.mjs +292 -292
- package/package.json +3 -3
- package/cli/storybook-dist/assets/RBCommand.stories-BSqXDkKw.js +0 -73
- package/cli/storybook-dist/assets/RBDialog.stories-BAktz-ku.js +0 -38
- package/cli/storybook-dist/assets/RBDropdownMenu.stories-DbW2fWAj.js +0 -86
- package/cli/storybook-dist/assets/RBSheet.stories-_6VH-RUm.js +0 -39
package/cli/src/host_agent.py
CHANGED
|
@@ -1,223 +1,223 @@
|
|
|
1
|
-
"""Host-side Zenoh byte-proxy for Docker Desktop containers (Mac/WSL remote dev).
|
|
2
|
-
|
|
3
|
-
Joins the robot's Zenoh mesh with eclipse-zenoh (no rainbow SDK) and relays
|
|
4
|
-
{query, put, subscribe} bytes over TCP to the dev container.
|
|
5
|
-
|
|
6
|
-
Env: MUSCAT_ROBOT_HOST, MUSCAT_ROBOT_TOKEN, MUSCAT_ZENOH_ADVERTISE_HOST,
|
|
7
|
-
MUSCAT_ZENOH_PORT (robot-facing), MUSCAT_PROXY_PORT (container-facing).
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
import contextlib
|
|
13
|
-
import json
|
|
14
|
-
import os
|
|
15
|
-
import signal
|
|
16
|
-
import socket
|
|
17
|
-
import struct
|
|
18
|
-
import sys
|
|
19
|
-
import threading
|
|
20
|
-
import urllib.error
|
|
21
|
-
import urllib.request
|
|
22
|
-
|
|
23
|
-
import zenoh
|
|
24
|
-
|
|
25
|
-
PEER_TTL = 30
|
|
26
|
-
QUERY_TARGETS = {0: zenoh.QueryTarget.BEST_MATCHING, 1: zenoh.QueryTarget.ALL, 2: zenoh.QueryTarget.ALL_COMPLETE}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def _send_frame(conn: socket.socket, lock: threading.Lock, obj: dict) -> None:
|
|
30
|
-
body = json.dumps(obj).encode()
|
|
31
|
-
with lock:
|
|
32
|
-
conn.sendall(struct.pack(">I", len(body)) + body)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _recv_frame(conn: socket.socket) -> dict | None:
|
|
36
|
-
hdr = b""
|
|
37
|
-
while len(hdr) < 4:
|
|
38
|
-
chunk = conn.recv(4 - len(hdr))
|
|
39
|
-
if not chunk:
|
|
40
|
-
return None
|
|
41
|
-
hdr += chunk
|
|
42
|
-
n = struct.unpack(">I", hdr)[0]
|
|
43
|
-
buf = b""
|
|
44
|
-
while len(buf) < n:
|
|
45
|
-
chunk = conn.recv(n - len(buf))
|
|
46
|
-
if not chunk:
|
|
47
|
-
return None
|
|
48
|
-
buf += chunk
|
|
49
|
-
return json.loads(buf.decode())
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def _att(sample) -> str:
|
|
53
|
-
return sample.attachment.to_string() if sample.attachment is not None else ""
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _query(session: zenoh.Session, req: dict) -> list[dict]:
|
|
57
|
-
payload = bytes.fromhex(req["payload_hex"]) if req.get("payload_hex") else None
|
|
58
|
-
replies = session.get(
|
|
59
|
-
req["keyexpr"],
|
|
60
|
-
payload=payload,
|
|
61
|
-
timeout=req.get("timeout_ms", 2000) / 1000,
|
|
62
|
-
target=QUERY_TARGETS.get(req.get("target", 0), zenoh.QueryTarget.BEST_MATCHING),
|
|
63
|
-
)
|
|
64
|
-
out = []
|
|
65
|
-
for reply in replies:
|
|
66
|
-
if reply.ok is not None:
|
|
67
|
-
s = reply.ok
|
|
68
|
-
out.append({"key": str(s.key_expr), "payload_hex": s.payload.to_bytes().hex(),
|
|
69
|
-
"att": _att(s), "encoding": str(s.encoding), "is_error": False})
|
|
70
|
-
else:
|
|
71
|
-
e = reply.err
|
|
72
|
-
out.append({"key": req["keyexpr"], "payload_hex": e.payload.to_bytes().hex(),
|
|
73
|
-
"att": "", "encoding": str(e.encoding), "is_error": True})
|
|
74
|
-
return out
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def _handle_conn(conn: socket.socket, session: zenoh.Session) -> None:
|
|
78
|
-
send_lock = threading.Lock()
|
|
79
|
-
subs: dict[str, zenoh.Subscriber] = {}
|
|
80
|
-
try:
|
|
81
|
-
while True:
|
|
82
|
-
req = _recv_frame(conn)
|
|
83
|
-
if req is None:
|
|
84
|
-
return
|
|
85
|
-
op = req.get("type")
|
|
86
|
-
rid = req.get("id")
|
|
87
|
-
|
|
88
|
-
try:
|
|
89
|
-
if op == "query":
|
|
90
|
-
out = {"type": "query_resp", "id": rid, "ok": True, "replies": _query(session, req)}
|
|
91
|
-
|
|
92
|
-
elif op == "put":
|
|
93
|
-
payload = bytes.fromhex(req["payload_hex"]) if req.get("payload_hex") else b""
|
|
94
|
-
session.put(req["keyexpr"], payload)
|
|
95
|
-
out = {"type": "put_resp", "id": rid, "ok": True}
|
|
96
|
-
|
|
97
|
-
elif op == "subscribe":
|
|
98
|
-
keyexpr = req["keyexpr"]
|
|
99
|
-
|
|
100
|
-
def on_sample(s, _ke: str = keyexpr) -> None:
|
|
101
|
-
_send_frame(conn, send_lock, {
|
|
102
|
-
"type": "sample", "keyexpr": _ke, "key": str(s.key_expr),
|
|
103
|
-
"payload_hex": s.payload.to_bytes().hex(), "att": _att(s),
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
old = subs.pop(keyexpr, None)
|
|
107
|
-
if old is not None:
|
|
108
|
-
old.undeclare()
|
|
109
|
-
subs[keyexpr] = session.declare_subscriber(keyexpr, on_sample)
|
|
110
|
-
out = {"type": "subscribe_resp", "id": rid, "ok": True}
|
|
111
|
-
|
|
112
|
-
elif op == "unsubscribe":
|
|
113
|
-
sub = subs.pop(req["keyexpr"], None)
|
|
114
|
-
if sub is not None:
|
|
115
|
-
with contextlib.suppress(Exception):
|
|
116
|
-
sub.undeclare()
|
|
117
|
-
out = {"type": "unsubscribe_resp", "id": rid, "ok": True}
|
|
118
|
-
|
|
119
|
-
else:
|
|
120
|
-
out = {"type": "error", "error": f"unknown op {op!r}"}
|
|
121
|
-
except Exception as exc:
|
|
122
|
-
out = {"type": f"{op}_resp", "id": rid, "ok": False, "error": f"{type(exc).__name__}: {exc}"}
|
|
123
|
-
_send_frame(conn, send_lock, out)
|
|
124
|
-
except (ConnectionResetError, BrokenPipeError, OSError):
|
|
125
|
-
pass
|
|
126
|
-
finally:
|
|
127
|
-
for sub in subs.values():
|
|
128
|
-
with contextlib.suppress(Exception):
|
|
129
|
-
sub.undeclare()
|
|
130
|
-
conn.close()
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
class Peer:
|
|
134
|
-
"""Registers this host with the robot's common: POST /zenoh/peer, heartbeat, DELETE."""
|
|
135
|
-
|
|
136
|
-
def __init__(self, host: str, token: str, ip: str, port: int) -> None:
|
|
137
|
-
self.base = f"http://{host}:8000"
|
|
138
|
-
self.headers = {"Content-Type": "application/json"}
|
|
139
|
-
if token:
|
|
140
|
-
self.headers["Authorization"] = f"Bearer {token}"
|
|
141
|
-
self.body = {"ip": ip, "port": port, "ttl": PEER_TTL}
|
|
142
|
-
self.peer_id: str | None = None
|
|
143
|
-
self.stop = threading.Event()
|
|
144
|
-
|
|
145
|
-
def _http(self, method: str, path: str, body: dict | None = None) -> dict:
|
|
146
|
-
data = json.dumps(body).encode() if body is not None else None
|
|
147
|
-
req = urllib.request.Request(self.base + path, data=data, headers=self.headers, method=method)
|
|
148
|
-
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
149
|
-
raw = resp.read()
|
|
150
|
-
return json.loads(raw) if raw else {}
|
|
151
|
-
|
|
152
|
-
def register(self) -> None:
|
|
153
|
-
self.peer_id = self._http("POST", "/zenoh/peer", self.body)["peer_id"]
|
|
154
|
-
print(f"[host-agent] registered with {self.base} as zenoh peer {self.peer_id}", flush=True)
|
|
155
|
-
|
|
156
|
-
def maintain(self) -> None:
|
|
157
|
-
while True:
|
|
158
|
-
wait = PEER_TTL / 2 if self.peer_id else 3
|
|
159
|
-
if self.stop.wait(wait):
|
|
160
|
-
return
|
|
161
|
-
try:
|
|
162
|
-
if self.peer_id is None:
|
|
163
|
-
self.register()
|
|
164
|
-
elif not self._http("POST", f"/zenoh/peer/{self.peer_id}/heartbeat", {"ttl": PEER_TTL}).get("ok"):
|
|
165
|
-
print(f"[host-agent] heartbeat rejected for {self.peer_id}, re-registering", flush=True)
|
|
166
|
-
self.peer_id = None
|
|
167
|
-
except Exception as exc:
|
|
168
|
-
print(f"[host-agent] peer maintenance failed ({exc}); retrying", flush=True)
|
|
169
|
-
if self.peer_id is not None and isinstance(exc, urllib.error.HTTPError) and exc.code == 404:
|
|
170
|
-
self.peer_id = None
|
|
171
|
-
|
|
172
|
-
def close(self) -> None:
|
|
173
|
-
self.stop.set()
|
|
174
|
-
if self.peer_id:
|
|
175
|
-
with contextlib.suppress(Exception):
|
|
176
|
-
self._http("DELETE", f"/zenoh/peer/{self.peer_id}")
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def main() -> None:
|
|
180
|
-
# dev stops us with SIGTERM; exit via `finally` to deregister.
|
|
181
|
-
signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))
|
|
182
|
-
host = os.environ["MUSCAT_ROBOT_HOST"]
|
|
183
|
-
token = os.environ.get("MUSCAT_ROBOT_TOKEN", "")
|
|
184
|
-
advertise = os.environ["MUSCAT_ZENOH_ADVERTISE_HOST"]
|
|
185
|
-
zenoh_port = int(os.environ["MUSCAT_ZENOH_PORT"])
|
|
186
|
-
proxy_port = int(os.environ["MUSCAT_PROXY_PORT"])
|
|
187
|
-
|
|
188
|
-
# Listen only; the robot dials in after registration. Router mode so a C++ SDK dev container
|
|
189
|
-
# (native Zenoh session, no byte shim) can connect here and be routed to the robot.
|
|
190
|
-
conf = zenoh.Config()
|
|
191
|
-
conf.insert_json5("mode", '"peer"')
|
|
192
|
-
conf.insert_json5("listen/endpoints", json.dumps([f"tcp/0.0.0.0:{zenoh_port}"]))
|
|
193
|
-
conf.insert_json5("scouting/multicast/enabled", "false")
|
|
194
|
-
session = zenoh.open(conf)
|
|
195
|
-
|
|
196
|
-
peer = Peer(host, token, advertise, zenoh_port)
|
|
197
|
-
try:
|
|
198
|
-
peer.register()
|
|
199
|
-
except Exception as exc:
|
|
200
|
-
print(f"[host-agent] registration with {peer.base} failed, retrying in background: {exc}", flush=True)
|
|
201
|
-
threading.Thread(target=peer.maintain, daemon=True, name="zenoh-peer").start()
|
|
202
|
-
print(f"[host-agent] zenoh peer listening :{zenoh_port} (advertised as {advertise})", flush=True)
|
|
203
|
-
|
|
204
|
-
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
205
|
-
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
206
|
-
srv.bind(("0.0.0.0", proxy_port))
|
|
207
|
-
srv.listen(32)
|
|
208
|
-
print(f"[host-agent] byte-proxy listening :{proxy_port} (container connects via host.docker.internal)", flush=True)
|
|
209
|
-
|
|
210
|
-
try:
|
|
211
|
-
while True:
|
|
212
|
-
conn, _addr = srv.accept()
|
|
213
|
-
threading.Thread(target=_handle_conn, args=(conn, session), daemon=True).start()
|
|
214
|
-
except KeyboardInterrupt:
|
|
215
|
-
pass
|
|
216
|
-
finally:
|
|
217
|
-
peer.close()
|
|
218
|
-
with contextlib.suppress(Exception):
|
|
219
|
-
session.close()
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if __name__ == "__main__":
|
|
223
|
-
main()
|
|
1
|
+
"""Host-side Zenoh byte-proxy for Docker Desktop containers (Mac/WSL remote dev).
|
|
2
|
+
|
|
3
|
+
Joins the robot's Zenoh mesh with eclipse-zenoh (no rainbow SDK) and relays
|
|
4
|
+
{query, put, subscribe} bytes over TCP to the dev container.
|
|
5
|
+
|
|
6
|
+
Env: MUSCAT_ROBOT_HOST, MUSCAT_ROBOT_TOKEN, MUSCAT_ZENOH_ADVERTISE_HOST,
|
|
7
|
+
MUSCAT_ZENOH_PORT (robot-facing), MUSCAT_PROXY_PORT (container-facing).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import contextlib
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import signal
|
|
16
|
+
import socket
|
|
17
|
+
import struct
|
|
18
|
+
import sys
|
|
19
|
+
import threading
|
|
20
|
+
import urllib.error
|
|
21
|
+
import urllib.request
|
|
22
|
+
|
|
23
|
+
import zenoh
|
|
24
|
+
|
|
25
|
+
PEER_TTL = 30
|
|
26
|
+
QUERY_TARGETS = {0: zenoh.QueryTarget.BEST_MATCHING, 1: zenoh.QueryTarget.ALL, 2: zenoh.QueryTarget.ALL_COMPLETE}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _send_frame(conn: socket.socket, lock: threading.Lock, obj: dict) -> None:
|
|
30
|
+
body = json.dumps(obj).encode()
|
|
31
|
+
with lock:
|
|
32
|
+
conn.sendall(struct.pack(">I", len(body)) + body)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _recv_frame(conn: socket.socket) -> dict | None:
|
|
36
|
+
hdr = b""
|
|
37
|
+
while len(hdr) < 4:
|
|
38
|
+
chunk = conn.recv(4 - len(hdr))
|
|
39
|
+
if not chunk:
|
|
40
|
+
return None
|
|
41
|
+
hdr += chunk
|
|
42
|
+
n = struct.unpack(">I", hdr)[0]
|
|
43
|
+
buf = b""
|
|
44
|
+
while len(buf) < n:
|
|
45
|
+
chunk = conn.recv(n - len(buf))
|
|
46
|
+
if not chunk:
|
|
47
|
+
return None
|
|
48
|
+
buf += chunk
|
|
49
|
+
return json.loads(buf.decode())
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _att(sample) -> str:
|
|
53
|
+
return sample.attachment.to_string() if sample.attachment is not None else ""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _query(session: zenoh.Session, req: dict) -> list[dict]:
|
|
57
|
+
payload = bytes.fromhex(req["payload_hex"]) if req.get("payload_hex") else None
|
|
58
|
+
replies = session.get(
|
|
59
|
+
req["keyexpr"],
|
|
60
|
+
payload=payload,
|
|
61
|
+
timeout=req.get("timeout_ms", 2000) / 1000,
|
|
62
|
+
target=QUERY_TARGETS.get(req.get("target", 0), zenoh.QueryTarget.BEST_MATCHING),
|
|
63
|
+
)
|
|
64
|
+
out = []
|
|
65
|
+
for reply in replies:
|
|
66
|
+
if reply.ok is not None:
|
|
67
|
+
s = reply.ok
|
|
68
|
+
out.append({"key": str(s.key_expr), "payload_hex": s.payload.to_bytes().hex(),
|
|
69
|
+
"att": _att(s), "encoding": str(s.encoding), "is_error": False})
|
|
70
|
+
else:
|
|
71
|
+
e = reply.err
|
|
72
|
+
out.append({"key": req["keyexpr"], "payload_hex": e.payload.to_bytes().hex(),
|
|
73
|
+
"att": "", "encoding": str(e.encoding), "is_error": True})
|
|
74
|
+
return out
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _handle_conn(conn: socket.socket, session: zenoh.Session) -> None:
|
|
78
|
+
send_lock = threading.Lock()
|
|
79
|
+
subs: dict[str, zenoh.Subscriber] = {}
|
|
80
|
+
try:
|
|
81
|
+
while True:
|
|
82
|
+
req = _recv_frame(conn)
|
|
83
|
+
if req is None:
|
|
84
|
+
return
|
|
85
|
+
op = req.get("type")
|
|
86
|
+
rid = req.get("id")
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
if op == "query":
|
|
90
|
+
out = {"type": "query_resp", "id": rid, "ok": True, "replies": _query(session, req)}
|
|
91
|
+
|
|
92
|
+
elif op == "put":
|
|
93
|
+
payload = bytes.fromhex(req["payload_hex"]) if req.get("payload_hex") else b""
|
|
94
|
+
session.put(req["keyexpr"], payload)
|
|
95
|
+
out = {"type": "put_resp", "id": rid, "ok": True}
|
|
96
|
+
|
|
97
|
+
elif op == "subscribe":
|
|
98
|
+
keyexpr = req["keyexpr"]
|
|
99
|
+
|
|
100
|
+
def on_sample(s, _ke: str = keyexpr) -> None:
|
|
101
|
+
_send_frame(conn, send_lock, {
|
|
102
|
+
"type": "sample", "keyexpr": _ke, "key": str(s.key_expr),
|
|
103
|
+
"payload_hex": s.payload.to_bytes().hex(), "att": _att(s),
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
old = subs.pop(keyexpr, None)
|
|
107
|
+
if old is not None:
|
|
108
|
+
old.undeclare()
|
|
109
|
+
subs[keyexpr] = session.declare_subscriber(keyexpr, on_sample)
|
|
110
|
+
out = {"type": "subscribe_resp", "id": rid, "ok": True}
|
|
111
|
+
|
|
112
|
+
elif op == "unsubscribe":
|
|
113
|
+
sub = subs.pop(req["keyexpr"], None)
|
|
114
|
+
if sub is not None:
|
|
115
|
+
with contextlib.suppress(Exception):
|
|
116
|
+
sub.undeclare()
|
|
117
|
+
out = {"type": "unsubscribe_resp", "id": rid, "ok": True}
|
|
118
|
+
|
|
119
|
+
else:
|
|
120
|
+
out = {"type": "error", "error": f"unknown op {op!r}"}
|
|
121
|
+
except Exception as exc:
|
|
122
|
+
out = {"type": f"{op}_resp", "id": rid, "ok": False, "error": f"{type(exc).__name__}: {exc}"}
|
|
123
|
+
_send_frame(conn, send_lock, out)
|
|
124
|
+
except (ConnectionResetError, BrokenPipeError, OSError):
|
|
125
|
+
pass
|
|
126
|
+
finally:
|
|
127
|
+
for sub in subs.values():
|
|
128
|
+
with contextlib.suppress(Exception):
|
|
129
|
+
sub.undeclare()
|
|
130
|
+
conn.close()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class Peer:
|
|
134
|
+
"""Registers this host with the robot's common: POST /zenoh/peer, heartbeat, DELETE."""
|
|
135
|
+
|
|
136
|
+
def __init__(self, host: str, token: str, ip: str, port: int) -> None:
|
|
137
|
+
self.base = f"http://{host}:8000"
|
|
138
|
+
self.headers = {"Content-Type": "application/json"}
|
|
139
|
+
if token:
|
|
140
|
+
self.headers["Authorization"] = f"Bearer {token}"
|
|
141
|
+
self.body = {"ip": ip, "port": port, "ttl": PEER_TTL}
|
|
142
|
+
self.peer_id: str | None = None
|
|
143
|
+
self.stop = threading.Event()
|
|
144
|
+
|
|
145
|
+
def _http(self, method: str, path: str, body: dict | None = None) -> dict:
|
|
146
|
+
data = json.dumps(body).encode() if body is not None else None
|
|
147
|
+
req = urllib.request.Request(self.base + path, data=data, headers=self.headers, method=method)
|
|
148
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
149
|
+
raw = resp.read()
|
|
150
|
+
return json.loads(raw) if raw else {}
|
|
151
|
+
|
|
152
|
+
def register(self) -> None:
|
|
153
|
+
self.peer_id = self._http("POST", "/zenoh/peer", self.body)["peer_id"]
|
|
154
|
+
print(f"[host-agent] registered with {self.base} as zenoh peer {self.peer_id}", flush=True)
|
|
155
|
+
|
|
156
|
+
def maintain(self) -> None:
|
|
157
|
+
while True:
|
|
158
|
+
wait = PEER_TTL / 2 if self.peer_id else 3
|
|
159
|
+
if self.stop.wait(wait):
|
|
160
|
+
return
|
|
161
|
+
try:
|
|
162
|
+
if self.peer_id is None:
|
|
163
|
+
self.register()
|
|
164
|
+
elif not self._http("POST", f"/zenoh/peer/{self.peer_id}/heartbeat", {"ttl": PEER_TTL}).get("ok"):
|
|
165
|
+
print(f"[host-agent] heartbeat rejected for {self.peer_id}, re-registering", flush=True)
|
|
166
|
+
self.peer_id = None
|
|
167
|
+
except Exception as exc:
|
|
168
|
+
print(f"[host-agent] peer maintenance failed ({exc}); retrying", flush=True)
|
|
169
|
+
if self.peer_id is not None and isinstance(exc, urllib.error.HTTPError) and exc.code == 404:
|
|
170
|
+
self.peer_id = None
|
|
171
|
+
|
|
172
|
+
def close(self) -> None:
|
|
173
|
+
self.stop.set()
|
|
174
|
+
if self.peer_id:
|
|
175
|
+
with contextlib.suppress(Exception):
|
|
176
|
+
self._http("DELETE", f"/zenoh/peer/{self.peer_id}")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def main() -> None:
|
|
180
|
+
# dev stops us with SIGTERM; exit via `finally` to deregister.
|
|
181
|
+
signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))
|
|
182
|
+
host = os.environ["MUSCAT_ROBOT_HOST"]
|
|
183
|
+
token = os.environ.get("MUSCAT_ROBOT_TOKEN", "")
|
|
184
|
+
advertise = os.environ["MUSCAT_ZENOH_ADVERTISE_HOST"]
|
|
185
|
+
zenoh_port = int(os.environ["MUSCAT_ZENOH_PORT"])
|
|
186
|
+
proxy_port = int(os.environ["MUSCAT_PROXY_PORT"])
|
|
187
|
+
|
|
188
|
+
# Listen only; the robot dials in after registration. Router mode so a C++ SDK dev container
|
|
189
|
+
# (native Zenoh session, no byte shim) can connect here and be routed to the robot.
|
|
190
|
+
conf = zenoh.Config()
|
|
191
|
+
conf.insert_json5("mode", '"peer"')
|
|
192
|
+
conf.insert_json5("listen/endpoints", json.dumps([f"tcp/0.0.0.0:{zenoh_port}"]))
|
|
193
|
+
conf.insert_json5("scouting/multicast/enabled", "false")
|
|
194
|
+
session = zenoh.open(conf)
|
|
195
|
+
|
|
196
|
+
peer = Peer(host, token, advertise, zenoh_port)
|
|
197
|
+
try:
|
|
198
|
+
peer.register()
|
|
199
|
+
except Exception as exc:
|
|
200
|
+
print(f"[host-agent] registration with {peer.base} failed, retrying in background: {exc}", flush=True)
|
|
201
|
+
threading.Thread(target=peer.maintain, daemon=True, name="zenoh-peer").start()
|
|
202
|
+
print(f"[host-agent] zenoh peer listening :{zenoh_port} (advertised as {advertise})", flush=True)
|
|
203
|
+
|
|
204
|
+
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
205
|
+
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
206
|
+
srv.bind(("0.0.0.0", proxy_port))
|
|
207
|
+
srv.listen(32)
|
|
208
|
+
print(f"[host-agent] byte-proxy listening :{proxy_port} (container connects via host.docker.internal)", flush=True)
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
while True:
|
|
212
|
+
conn, _addr = srv.accept()
|
|
213
|
+
threading.Thread(target=_handle_conn, args=(conn, session), daemon=True).start()
|
|
214
|
+
except KeyboardInterrupt:
|
|
215
|
+
pass
|
|
216
|
+
finally:
|
|
217
|
+
peer.close()
|
|
218
|
+
with contextlib.suppress(Exception):
|
|
219
|
+
session.close()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
if __name__ == "__main__":
|
|
223
|
+
main()
|