agenticros 0.0.1 → 0.1.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/LICENSE +192 -0
- package/README.md +90 -4
- package/dist/commands/config.d.ts +20 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +179 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/doctor.d.ts +33 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +232 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/down.d.ts +15 -0
- package/dist/commands/down.d.ts.map +1 -0
- package/dist/commands/down.js +91 -0
- package/dist/commands/down.js.map +1 -0
- package/dist/commands/init.d.ts +21 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +259 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/logs.d.ts +18 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +67 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/status.d.ts +12 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +56 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/up.d.ts +20 -0
- package/dist/commands/up.d.ts.map +1 -0
- package/dist/commands/up.js +70 -0
- package/dist/commands/up.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +107 -0
- package/dist/index.js.map +1 -0
- package/dist/menu.d.ts +9 -0
- package/dist/menu.d.ts.map +1 -0
- package/dist/menu.js +96 -0
- package/dist/menu.js.map +1 -0
- package/dist/runners/real-robot.d.ts +15 -0
- package/dist/runners/real-robot.d.ts.map +1 -0
- package/dist/runners/real-robot.js +46 -0
- package/dist/runners/real-robot.js.map +1 -0
- package/dist/runners/sim.d.ts +19 -0
- package/dist/runners/sim.d.ts.map +1 -0
- package/dist/runners/sim.js +53 -0
- package/dist/runners/sim.js.map +1 -0
- package/dist/util/env.d.ts +24 -0
- package/dist/util/env.d.ts.map +1 -0
- package/dist/util/env.js +53 -0
- package/dist/util/env.js.map +1 -0
- package/dist/util/logger.d.ts +24 -0
- package/dist/util/logger.d.ts.map +1 -0
- package/dist/util/logger.js +62 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/util/paths.d.ts +57 -0
- package/dist/util/paths.d.ts.map +1 -0
- package/dist/util/paths.js +132 -0
- package/dist/util/paths.js.map +1 -0
- package/dist/util/pidfile.d.ts +16 -0
- package/dist/util/pidfile.d.ts.map +1 -0
- package/dist/util/pidfile.js +63 -0
- package/dist/util/pidfile.js.map +1 -0
- package/dist/util/state.d.ts +26 -0
- package/dist/util/state.d.ts.map +1 -0
- package/dist/util/state.js +55 -0
- package/dist/util/state.js.map +1 -0
- package/package.json +60 -1
- package/runtime/BUNDLE.json +11 -0
- package/runtime/LICENSE +192 -0
- package/runtime/README.md +273 -0
- package/runtime/docs/architecture.md +366 -0
- package/runtime/docs/cli.md +140 -0
- package/runtime/docs/memory.md +292 -0
- package/runtime/docs/robot-setup.md +347 -0
- package/runtime/package.json +28 -0
- package/runtime/packages/agenticros/agenticros-agenticros-0.0.1.tgz +0 -0
- package/runtime/packages/agenticros/openclaw.plugin.json +451 -0
- package/runtime/packages/agenticros/package.json +41 -0
- package/runtime/packages/agenticros/src/camera-snapshot-cache.ts +59 -0
- package/runtime/packages/agenticros/src/camera-snapshot-routes.ts +44 -0
- package/runtime/packages/agenticros/src/commands/estop.ts +41 -0
- package/runtime/packages/agenticros/src/commands/transport.ts +195 -0
- package/runtime/packages/agenticros/src/config-file.ts +136 -0
- package/runtime/packages/agenticros/src/config-page.ts +498 -0
- package/runtime/packages/agenticros/src/context/robot-context.ts +373 -0
- package/runtime/packages/agenticros/src/depth.ts +313 -0
- package/runtime/packages/agenticros/src/describer.ts +157 -0
- package/runtime/packages/agenticros/src/image-binary-trim.ts +16 -0
- package/runtime/packages/agenticros/src/index.ts +85 -0
- package/runtime/packages/agenticros/src/landing-page.ts +38 -0
- package/runtime/packages/agenticros/src/memory.ts +44 -0
- package/runtime/packages/agenticros/src/plugin-api.ts +173 -0
- package/runtime/packages/agenticros/src/plugin-image-base64.ts +69 -0
- package/runtime/packages/agenticros/src/preflight.ts +110 -0
- package/runtime/packages/agenticros/src/routes.ts +328 -0
- package/runtime/packages/agenticros/src/safety/validator.ts +43 -0
- package/runtime/packages/agenticros/src/service.ts +359 -0
- package/runtime/packages/agenticros/src/skill-api.ts +65 -0
- package/runtime/packages/agenticros/src/skill-loader.ts +146 -0
- package/runtime/packages/agenticros/src/teleop/page.ts +498 -0
- package/runtime/packages/agenticros/src/teleop/routes.ts +650 -0
- package/runtime/packages/agenticros/src/tools/index.ts +26 -0
- package/runtime/packages/agenticros/src/tools/ros2-action.ts +50 -0
- package/runtime/packages/agenticros/src/tools/ros2-camera.ts +221 -0
- package/runtime/packages/agenticros/src/tools/ros2-depth-distance.ts +58 -0
- package/runtime/packages/agenticros/src/tools/ros2-introspect.ts +62 -0
- package/runtime/packages/agenticros/src/tools/ros2-memory.ts +158 -0
- package/runtime/packages/agenticros/src/tools/ros2-param.ts +87 -0
- package/runtime/packages/agenticros/src/tools/ros2-publish.ts +52 -0
- package/runtime/packages/agenticros/src/tools/ros2-service.ts +46 -0
- package/runtime/packages/agenticros/src/tools/ros2-subscribe.ts +71 -0
- package/runtime/packages/agenticros/tsconfig.json +9 -0
- package/runtime/packages/agenticros-claude-code/README.md +260 -0
- package/runtime/packages/agenticros-claude-code/config.example.json +9 -0
- package/runtime/packages/agenticros-claude-code/dist/config.d.ts +8 -0
- package/runtime/packages/agenticros-claude-code/dist/config.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/config.js +93 -0
- package/runtime/packages/agenticros-claude-code/dist/config.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/depth.d.ts +20 -0
- package/runtime/packages/agenticros-claude-code/dist/depth.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/depth.js +126 -0
- package/runtime/packages/agenticros-claude-code/dist/depth.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/coco-classes.d.ts +6 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/coco-classes.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/coco-classes.js +36 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/coco-classes.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.d.ts +33 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.js +134 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/controller.d.ts +43 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/controller.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/controller.js +73 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/controller.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/detector.d.ts +58 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/detector.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/detector.js +251 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/detector.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.d.ts +61 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.js +268 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/index.d.ts +3 -0
- package/runtime/packages/agenticros-claude-code/dist/index.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/index.js +111 -0
- package/runtime/packages/agenticros-claude-code/dist/index.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/memory.d.ts +17 -0
- package/runtime/packages/agenticros-claude-code/dist/memory.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/memory.js +44 -0
- package/runtime/packages/agenticros-claude-code/dist/memory.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/safety.d.ts +10 -0
- package/runtime/packages/agenticros-claude-code/dist/safety.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/safety.js +34 -0
- package/runtime/packages/agenticros-claude-code/dist/safety.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/tools.d.ts +36 -0
- package/runtime/packages/agenticros-claude-code/dist/tools.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/tools.js +777 -0
- package/runtime/packages/agenticros-claude-code/dist/tools.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/transport.d.ts +17 -0
- package/runtime/packages/agenticros-claude-code/dist/transport.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/transport.js +46 -0
- package/runtime/packages/agenticros-claude-code/dist/transport.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/zero-shot/detector.d.ts +42 -0
- package/runtime/packages/agenticros-claude-code/dist/zero-shot/detector.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/zero-shot/detector.js +114 -0
- package/runtime/packages/agenticros-claude-code/dist/zero-shot/detector.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/package.json +29 -0
- package/runtime/packages/agenticros-claude-code/src/config.ts +96 -0
- package/runtime/packages/agenticros-claude-code/src/depth.ts +173 -0
- package/runtime/packages/agenticros-claude-code/src/find-object/coco-classes.ts +38 -0
- package/runtime/packages/agenticros-claude-code/src/find-object/find-object.ts +190 -0
- package/runtime/packages/agenticros-claude-code/src/follow-me/controller.ts +109 -0
- package/runtime/packages/agenticros-claude-code/src/follow-me/depth-loop.ts +420 -0
- package/runtime/packages/agenticros-claude-code/src/follow-me/detector.ts +303 -0
- package/runtime/packages/agenticros-claude-code/src/follow-me/loop.ts +330 -0
- package/runtime/packages/agenticros-claude-code/src/index.ts +125 -0
- package/runtime/packages/agenticros-claude-code/src/memory.ts +51 -0
- package/runtime/packages/agenticros-claude-code/src/safety.ts +44 -0
- package/runtime/packages/agenticros-claude-code/src/tools.ts +891 -0
- package/runtime/packages/agenticros-claude-code/src/transport.ts +58 -0
- package/runtime/packages/agenticros-claude-code/src/zero-shot/detector.ts +169 -0
- package/runtime/packages/agenticros-claude-code/tsconfig.json +9 -0
- package/runtime/packages/agenticros-claude-code/yolo-debug.mjs +106 -0
- package/runtime/packages/agenticros-gemini/README.md +139 -0
- package/runtime/packages/agenticros-gemini/package.json +28 -0
- package/runtime/packages/agenticros-gemini/scripts/smoke-api.mjs +42 -0
- package/runtime/packages/agenticros-gemini/src/chat.ts +139 -0
- package/runtime/packages/agenticros-gemini/src/config.ts +92 -0
- package/runtime/packages/agenticros-gemini/src/depth.ts +173 -0
- package/runtime/packages/agenticros-gemini/src/index.ts +58 -0
- package/runtime/packages/agenticros-gemini/src/memory.ts +32 -0
- package/runtime/packages/agenticros-gemini/src/safety.ts +44 -0
- package/runtime/packages/agenticros-gemini/src/tools.ts +516 -0
- package/runtime/packages/agenticros-gemini/src/transport.ts +58 -0
- package/runtime/packages/agenticros-gemini/tsconfig.json +8 -0
- package/runtime/packages/core/package.json +47 -0
- package/runtime/packages/core/src/banner.ts +32 -0
- package/runtime/packages/core/src/cmd-vel-twist.ts +31 -0
- package/runtime/packages/core/src/config.ts +279 -0
- package/runtime/packages/core/src/index.ts +54 -0
- package/runtime/packages/core/src/memory/__tests__/factory.test.ts +70 -0
- package/runtime/packages/core/src/memory/__tests__/local-provider.test.ts +195 -0
- package/runtime/packages/core/src/memory/__tests__/mem0-provider.test.ts +192 -0
- package/runtime/packages/core/src/memory/__tests__/smart-defaults.test.ts +46 -0
- package/runtime/packages/core/src/memory/factory.ts +63 -0
- package/runtime/packages/core/src/memory/index.ts +10 -0
- package/runtime/packages/core/src/memory/local/provider.ts +229 -0
- package/runtime/packages/core/src/memory/mem0/provider.ts +379 -0
- package/runtime/packages/core/src/memory/types.ts +96 -0
- package/runtime/packages/core/src/topic-utils.ts +95 -0
- package/runtime/packages/core/src/transport/factory.ts +47 -0
- package/runtime/packages/core/src/transport/local/conversion.ts +333 -0
- package/runtime/packages/core/src/transport/local/entities.ts +129 -0
- package/runtime/packages/core/src/transport/local/transport.ts +406 -0
- package/runtime/packages/core/src/transport/rosbridge/actions.ts +81 -0
- package/runtime/packages/core/src/transport/rosbridge/adapter.ts +157 -0
- package/runtime/packages/core/src/transport/rosbridge/client.ts +438 -0
- package/runtime/packages/core/src/transport/rosbridge/services.ts +41 -0
- package/runtime/packages/core/src/transport/rosbridge/topics.ts +60 -0
- package/runtime/packages/core/src/transport/rosbridge/types.ts +118 -0
- package/runtime/packages/core/src/transport/transport.ts +77 -0
- package/runtime/packages/core/src/transport/types.ts +137 -0
- package/runtime/packages/core/src/transport/webrtc/signaling-client.ts +196 -0
- package/runtime/packages/core/src/transport/webrtc/signaling-types.ts +130 -0
- package/runtime/packages/core/src/transport/webrtc/transport.ts +516 -0
- package/runtime/packages/core/src/transport/zenoh/adapter.ts +357 -0
- package/runtime/packages/core/src/transport/zenoh/cdr.ts +183 -0
- package/runtime/packages/core/src/transport/zenoh/keys.ts +51 -0
- package/runtime/packages/core/tsconfig.json +9 -0
- package/runtime/packages/ros-camera/package.json +30 -0
- package/runtime/packages/ros-camera/src/index.ts +13 -0
- package/runtime/packages/ros-camera/src/snapshot.ts +372 -0
- package/runtime/packages/ros-camera/tsconfig.json +9 -0
- package/runtime/pnpm-lock.yaml +5260 -0
- package/runtime/pnpm-workspace.yaml +2 -0
- package/runtime/ros2_ws/src/agenticros_agent/agenticros_agent/__init__.py +0 -0
- package/runtime/ros2_ws/src/agenticros_agent/agenticros_agent/agent_node.py +561 -0
- package/runtime/ros2_ws/src/agenticros_agent/package.xml +25 -0
- package/runtime/ros2_ws/src/agenticros_agent/resource/agenticros_agent +0 -0
- package/runtime/ros2_ws/src/agenticros_agent/setup.cfg +4 -0
- package/runtime/ros2_ws/src/agenticros_agent/setup.py +25 -0
- package/runtime/ros2_ws/src/agenticros_bringup/README.md +128 -0
- package/runtime/ros2_ws/src/agenticros_bringup/agenticros_bringup/__init__.py +1 -0
- package/runtime/ros2_ws/src/agenticros_bringup/agenticros_bringup/cmd_vel_relay.py +33 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/cmd_vel_bridge.launch.py +58 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/gazebo_turtlebot3.launch.py +69 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/mode_a_gazebo.launch.py +55 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/mode_a_gazebo_rviz.launch.py +48 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/realsense_rosbridge.launch.py +154 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/rosbridge_gazebo.launch.py +54 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/rviz.launch.py +38 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/turtlebot3_gazebo_rviz.launch.py +42 -0
- package/runtime/ros2_ws/src/agenticros_bringup/package.xml +24 -0
- package/runtime/ros2_ws/src/agenticros_bringup/resource/agenticros_bringup +0 -0
- package/runtime/ros2_ws/src/agenticros_bringup/rviz/turtlebot3_agenticros.rviz +174 -0
- package/runtime/ros2_ws/src/agenticros_bringup/setup.cfg +4 -0
- package/runtime/ros2_ws/src/agenticros_bringup/setup.py +28 -0
- package/runtime/ros2_ws/src/agenticros_discovery/agenticros_discovery/__init__.py +0 -0
- package/runtime/ros2_ws/src/agenticros_discovery/agenticros_discovery/discovery_node.py +172 -0
- package/runtime/ros2_ws/src/agenticros_discovery/package.xml +22 -0
- package/runtime/ros2_ws/src/agenticros_discovery/resource/agenticros_discovery +0 -0
- package/runtime/ros2_ws/src/agenticros_discovery/setup.cfg +5 -0
- package/runtime/ros2_ws/src/agenticros_discovery/setup.py +25 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/README.md +66 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/__init__.py +1 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/__main__.py +5 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/follow_me_node.py +278 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/follower_controller.py +631 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/person_tracker.py +635 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/package.xml +22 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/resource/agenticros_follow_me +0 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/setup.py +25 -0
- package/runtime/ros2_ws/src/agenticros_msgs/CMakeLists.txt +26 -0
- package/runtime/ros2_ws/src/agenticros_msgs/msg/CapabilityManifest.msg +9 -0
- package/runtime/ros2_ws/src/agenticros_msgs/package.xml +22 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeGetStatus.srv +11 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeSetDistance.srv +4 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeSetTarget.srv +6 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeStart.srv +5 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeStop.srv +3 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/GetCapabilities.srv +5 -0
- package/runtime/ros2_ws/src/agenticros_sim/CMakeLists.txt +24 -0
- package/runtime/ros2_ws/src/agenticros_sim/README.md +120 -0
- package/runtime/ros2_ws/src/agenticros_sim/config/agenticros-sim.config.json +28 -0
- package/runtime/ros2_ws/src/agenticros_sim/config/amr_bridge.yaml +111 -0
- package/runtime/ros2_ws/src/agenticros_sim/config/amr_view.rviz +172 -0
- package/runtime/ros2_ws/src/agenticros_sim/env-hooks/gz_resource_path.dsv.in +3 -0
- package/runtime/ros2_ws/src/agenticros_sim/env-hooks/gz_resource_path.sh.in +7 -0
- package/runtime/ros2_ws/src/agenticros_sim/launch/sim_amr.launch.py +159 -0
- package/runtime/ros2_ws/src/agenticros_sim/models/agenticros_amr/model.config +17 -0
- package/runtime/ros2_ws/src/agenticros_sim/models/agenticros_amr/model.sdf +244 -0
- package/runtime/ros2_ws/src/agenticros_sim/package.xml +27 -0
- package/runtime/ros2_ws/src/agenticros_sim/worlds/agenticros_indoor.sdf +183 -0
- package/runtime/scripts/activate_workspace.sh +285 -0
- package/runtime/scripts/agenticros-describer.policy.yaml +96 -0
- package/runtime/scripts/agenticros-proxy.cjs +99 -0
- package/runtime/scripts/agenticros-rosbridge.policy.yaml +62 -0
- package/runtime/scripts/check-cli-tarball-size.mjs +42 -0
- package/runtime/scripts/configure_agenticros.sh +200 -0
- package/runtime/scripts/configure_for_sim.sh +64 -0
- package/runtime/scripts/fix-openclaw-control-ui-path.sh +20 -0
- package/runtime/scripts/install_cli.sh +94 -0
- package/runtime/scripts/install_rosbridge_from_source.sh +67 -0
- package/runtime/scripts/lib/agenticros-banner.sh +28 -0
- package/runtime/scripts/onboard_robot.sh +75 -0
- package/runtime/scripts/openai.policy.yaml +77 -0
- package/runtime/scripts/openclaw-dashboard-url.cjs +49 -0
- package/runtime/scripts/pack-runtime.mjs +245 -0
- package/runtime/scripts/run_demo_native.sh +43 -0
- package/runtime/scripts/run_nemoclaw_host_stack.sh +91 -0
- package/runtime/scripts/run_robot_rosbridge.sh +36 -0
- package/runtime/scripts/sandbox_rosbridge_relay.py +137 -0
- package/runtime/scripts/setup-openclaw-local.cjs +75 -0
- package/runtime/scripts/setup_gateway_plugin.sh +329 -0
- package/runtime/scripts/setup_robot.sh +113 -0
- package/runtime/scripts/setup_workspace.sh +484 -0
- package/runtime/scripts/sim/run_sim.sh +146 -0
- package/runtime/scripts/smoke_test_nemoclaw.sh +123 -0
- package/runtime/scripts/start_demo.sh +55 -0
- package/runtime/scripts/sync-skill-tools.mjs +335 -0
- package/runtime/scripts/test-follow-me-sim.mjs +135 -0
- package/runtime/scripts/test-mcp-e2e.mjs +184 -0
- package/runtime/scripts/test-rclnodejs.mts +129 -0
- package/runtime/scripts/use-openclaw-2026.2.26.sh +19 -0
- package/runtime/scripts/use-openclaw-2026.3.11.sh +19 -0
- package/runtime/scripts/zenoh-bridge-ros2dds-robot.json5 +30 -0
- package/runtime/scripts/zenohd-agenticros.json5 +11 -0
- package/runtime/scripts/zenohd-rosclaw.json5 +11 -0
- package/runtime/tsconfig.base.json +19 -0
- package/index.js +0 -6
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FollowerController - Robot Follow Control Logic
|
|
3
|
+
|
|
4
|
+
Computes Twist commands (linear_x, angular_z) for a differential drive robot
|
|
5
|
+
to follow a target person at a specified distance.
|
|
6
|
+
|
|
7
|
+
Also supports manual teleoperation commands:
|
|
8
|
+
- Move forward/backward for distance or time
|
|
9
|
+
- Turn left/right by angle
|
|
10
|
+
- Raw velocity commands
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import time
|
|
14
|
+
import threading
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from typing import Optional, Callable
|
|
17
|
+
from enum import Enum
|
|
18
|
+
import math
|
|
19
|
+
|
|
20
|
+
from .person_tracker import DetectedPerson
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ControlMode(Enum):
|
|
24
|
+
"""Current control mode."""
|
|
25
|
+
IDLE = "idle"
|
|
26
|
+
FOLLOW = "follow"
|
|
27
|
+
MANUAL = "manual"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ManualCommand:
|
|
32
|
+
"""A manual movement command."""
|
|
33
|
+
command_type: str # "move", "turn", "velocity"
|
|
34
|
+
linear_vel: float = 0.0 # m/s
|
|
35
|
+
angular_vel: float = 0.0 # rad/s
|
|
36
|
+
distance: Optional[float] = None # meters (for move)
|
|
37
|
+
angle: Optional[float] = None # radians (for turn)
|
|
38
|
+
duration: Optional[float] = None # seconds
|
|
39
|
+
start_time: float = 0.0
|
|
40
|
+
completed: bool = False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class Twist:
|
|
45
|
+
"""ROS-compatible Twist message structure."""
|
|
46
|
+
linear_x: float = 0.0 # Forward/backward velocity (m/s)
|
|
47
|
+
linear_y: float = 0.0 # Lateral velocity (m/s) - usually 0 for diff drive
|
|
48
|
+
linear_z: float = 0.0 # Vertical velocity (m/s) - usually 0
|
|
49
|
+
angular_x: float = 0.0 # Roll rate (rad/s) - usually 0
|
|
50
|
+
angular_y: float = 0.0 # Pitch rate (rad/s) - usually 0
|
|
51
|
+
angular_z: float = 0.0 # Yaw rate (rad/s) - turning
|
|
52
|
+
|
|
53
|
+
def __repr__(self):
|
|
54
|
+
return f"Twist(linear_x={self.linear_x:.3f} m/s, angular_z={self.angular_z:.3f} rad/s)"
|
|
55
|
+
|
|
56
|
+
def to_dict(self) -> dict:
|
|
57
|
+
"""Convert to dictionary for JSON serialization."""
|
|
58
|
+
return {
|
|
59
|
+
'linear': {'x': self.linear_x, 'y': self.linear_y, 'z': self.linear_z},
|
|
60
|
+
'angular': {'x': self.angular_x, 'y': self.angular_y, 'z': self.angular_z}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
def is_zero(self) -> bool:
|
|
64
|
+
"""Check if this is a zero/stop command."""
|
|
65
|
+
return abs(self.linear_x) < 0.001 and abs(self.angular_z) < 0.001
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class ControllerConfig:
|
|
70
|
+
"""Configuration for the follower controller."""
|
|
71
|
+
# Target distance
|
|
72
|
+
target_distance: float = 1.0 # meters
|
|
73
|
+
|
|
74
|
+
# Velocity limits
|
|
75
|
+
max_linear_vel: float = 0.5 # m/s
|
|
76
|
+
max_angular_vel: float = 1.0 # rad/s
|
|
77
|
+
|
|
78
|
+
# Proportional gains
|
|
79
|
+
Kp_distance: float = 0.5 # Gain for distance error
|
|
80
|
+
Kp_angular: float = 1.5 # Gain for angular error
|
|
81
|
+
|
|
82
|
+
# Deadzones (ignore small errors)
|
|
83
|
+
distance_deadzone: float = 0.05 # 5cm
|
|
84
|
+
angular_deadzone: float = 0.05 # ~3 degrees
|
|
85
|
+
|
|
86
|
+
# Smoothing (exponential moving average)
|
|
87
|
+
smoothing_factor: float = 0.3 # 0 = no smoothing, 1 = instant response
|
|
88
|
+
|
|
89
|
+
# Watchdog
|
|
90
|
+
watchdog_timeout: float = 0.5 # seconds without detection before stopping
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class FollowerController:
|
|
94
|
+
"""
|
|
95
|
+
Computes velocity commands to follow a target person.
|
|
96
|
+
|
|
97
|
+
Uses proportional control with smoothing, deadzones, and safety limits.
|
|
98
|
+
Also supports manual teleoperation commands.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(self, config: Optional[ControllerConfig] = None):
|
|
102
|
+
self.config = config or ControllerConfig()
|
|
103
|
+
|
|
104
|
+
# State
|
|
105
|
+
self.enabled = False
|
|
106
|
+
self.mode = ControlMode.IDLE
|
|
107
|
+
self.target_person_id: Optional[int] = None
|
|
108
|
+
self.target_description: Optional[str] = None
|
|
109
|
+
|
|
110
|
+
# Manual control state
|
|
111
|
+
self._manual_command: Optional[ManualCommand] = None
|
|
112
|
+
self._manual_start_position = 0.0 # Estimated position for distance tracking
|
|
113
|
+
self._manual_start_angle = 0.0 # Estimated angle for turn tracking
|
|
114
|
+
self._command_queue: list[ManualCommand] = []
|
|
115
|
+
self._queue_lock = threading.Lock()
|
|
116
|
+
|
|
117
|
+
# Smoothing state
|
|
118
|
+
self._smoothed_linear = 0.0
|
|
119
|
+
self._smoothed_angular = 0.0
|
|
120
|
+
|
|
121
|
+
# Watchdog
|
|
122
|
+
self._last_detection_time = 0.0
|
|
123
|
+
|
|
124
|
+
# Last computed twist
|
|
125
|
+
self._last_twist = Twist()
|
|
126
|
+
|
|
127
|
+
# Odometry estimation (simple integration)
|
|
128
|
+
self._estimated_distance = 0.0
|
|
129
|
+
self._estimated_angle = 0.0
|
|
130
|
+
self._last_update_time = time.time()
|
|
131
|
+
|
|
132
|
+
# Statistics
|
|
133
|
+
self._update_count = 0
|
|
134
|
+
self._start_time = time.time()
|
|
135
|
+
|
|
136
|
+
def start(self, target_description: Optional[str] = None):
|
|
137
|
+
"""Start following mode."""
|
|
138
|
+
self.enabled = True
|
|
139
|
+
self.mode = ControlMode.FOLLOW
|
|
140
|
+
self.target_description = target_description
|
|
141
|
+
self._last_detection_time = time.time()
|
|
142
|
+
self._cancel_manual_commands()
|
|
143
|
+
print(f"[CONTROLLER] Started following" +
|
|
144
|
+
(f" target: '{target_description}'" if target_description else ""))
|
|
145
|
+
|
|
146
|
+
def stop(self):
|
|
147
|
+
"""Stop all movement and zero velocities."""
|
|
148
|
+
self.enabled = False
|
|
149
|
+
self.mode = ControlMode.IDLE
|
|
150
|
+
self._smoothed_linear = 0.0
|
|
151
|
+
self._smoothed_angular = 0.0
|
|
152
|
+
self._last_twist = Twist()
|
|
153
|
+
self._cancel_manual_commands()
|
|
154
|
+
print("[CONTROLLER] Stopped")
|
|
155
|
+
|
|
156
|
+
# ==========================================
|
|
157
|
+
# Manual Teleoperation Commands
|
|
158
|
+
# ==========================================
|
|
159
|
+
|
|
160
|
+
def move(self, distance: float, velocity: Optional[float] = None) -> dict:
|
|
161
|
+
"""
|
|
162
|
+
Move forward (positive) or backward (negative) by a distance.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
distance: Distance in meters (positive=forward, negative=backward)
|
|
166
|
+
velocity: Optional velocity in m/s (default: 0.3 m/s)
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Status dict
|
|
170
|
+
"""
|
|
171
|
+
vel = velocity if velocity is not None else 0.3
|
|
172
|
+
vel = min(abs(vel), self.config.max_linear_vel)
|
|
173
|
+
if distance < 0:
|
|
174
|
+
vel = -vel
|
|
175
|
+
|
|
176
|
+
duration = abs(distance / vel) if vel != 0 else 0
|
|
177
|
+
|
|
178
|
+
cmd = ManualCommand(
|
|
179
|
+
command_type="move",
|
|
180
|
+
linear_vel=vel,
|
|
181
|
+
angular_vel=0.0,
|
|
182
|
+
distance=abs(distance),
|
|
183
|
+
duration=duration,
|
|
184
|
+
start_time=time.time()
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
self._start_manual_command(cmd)
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
'status': 'ok',
|
|
191
|
+
'command': 'move',
|
|
192
|
+
'distance': distance,
|
|
193
|
+
'velocity': vel,
|
|
194
|
+
'estimated_duration': duration
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
def turn(self, angle_degrees: float, angular_velocity: Optional[float] = None) -> dict:
|
|
198
|
+
"""
|
|
199
|
+
Turn left (positive) or right (negative) by an angle.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
angle_degrees: Angle in degrees (positive=left/CCW, negative=right/CW)
|
|
203
|
+
angular_velocity: Optional angular velocity in rad/s (default: 0.5 rad/s)
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Status dict
|
|
207
|
+
"""
|
|
208
|
+
angle_rad = math.radians(angle_degrees)
|
|
209
|
+
vel = angular_velocity if angular_velocity is not None else 0.5
|
|
210
|
+
vel = min(abs(vel), self.config.max_angular_vel)
|
|
211
|
+
if angle_degrees < 0:
|
|
212
|
+
vel = -vel
|
|
213
|
+
|
|
214
|
+
duration = abs(angle_rad / vel) if vel != 0 else 0
|
|
215
|
+
|
|
216
|
+
cmd = ManualCommand(
|
|
217
|
+
command_type="turn",
|
|
218
|
+
linear_vel=0.0,
|
|
219
|
+
angular_vel=vel,
|
|
220
|
+
angle=abs(angle_rad),
|
|
221
|
+
duration=duration,
|
|
222
|
+
start_time=time.time()
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
self._start_manual_command(cmd)
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
'status': 'ok',
|
|
229
|
+
'command': 'turn',
|
|
230
|
+
'angle_degrees': angle_degrees,
|
|
231
|
+
'angular_velocity': vel,
|
|
232
|
+
'estimated_duration': duration
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
def move_for_time(self, duration: float, velocity: float = 0.3) -> dict:
|
|
236
|
+
"""
|
|
237
|
+
Move forward/backward for a specified duration.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
duration: Time in seconds
|
|
241
|
+
velocity: Velocity in m/s (positive=forward, negative=backward)
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Status dict
|
|
245
|
+
"""
|
|
246
|
+
vel = max(-self.config.max_linear_vel,
|
|
247
|
+
min(self.config.max_linear_vel, velocity))
|
|
248
|
+
|
|
249
|
+
cmd = ManualCommand(
|
|
250
|
+
command_type="move",
|
|
251
|
+
linear_vel=vel,
|
|
252
|
+
angular_vel=0.0,
|
|
253
|
+
duration=duration,
|
|
254
|
+
start_time=time.time()
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
self._start_manual_command(cmd)
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
'status': 'ok',
|
|
261
|
+
'command': 'move_for_time',
|
|
262
|
+
'duration': duration,
|
|
263
|
+
'velocity': vel
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
def set_velocity(self, linear: float = 0.0, angular: float = 0.0,
|
|
267
|
+
duration: Optional[float] = None) -> dict:
|
|
268
|
+
"""
|
|
269
|
+
Set raw velocity command.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
linear: Linear velocity in m/s
|
|
273
|
+
angular: Angular velocity in rad/s
|
|
274
|
+
duration: Optional duration in seconds (None = until next command)
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
Status dict
|
|
278
|
+
"""
|
|
279
|
+
linear = max(-self.config.max_linear_vel,
|
|
280
|
+
min(self.config.max_linear_vel, linear))
|
|
281
|
+
angular = max(-self.config.max_angular_vel,
|
|
282
|
+
min(self.config.max_angular_vel, angular))
|
|
283
|
+
|
|
284
|
+
cmd = ManualCommand(
|
|
285
|
+
command_type="velocity",
|
|
286
|
+
linear_vel=linear,
|
|
287
|
+
angular_vel=angular,
|
|
288
|
+
duration=duration,
|
|
289
|
+
start_time=time.time()
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
self._start_manual_command(cmd)
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
'status': 'ok',
|
|
296
|
+
'command': 'velocity',
|
|
297
|
+
'linear': linear,
|
|
298
|
+
'angular': angular,
|
|
299
|
+
'duration': duration
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
def queue_command(self, cmd_type: str, **kwargs) -> dict:
|
|
303
|
+
"""Add a command to the queue to execute after current command."""
|
|
304
|
+
with self._queue_lock:
|
|
305
|
+
if cmd_type == "move":
|
|
306
|
+
distance = kwargs.get('distance', 1.0)
|
|
307
|
+
velocity = kwargs.get('velocity', 0.3)
|
|
308
|
+
vel = min(abs(velocity), self.config.max_linear_vel)
|
|
309
|
+
if distance < 0:
|
|
310
|
+
vel = -vel
|
|
311
|
+
duration = abs(distance / vel) if vel != 0 else 0
|
|
312
|
+
cmd = ManualCommand(
|
|
313
|
+
command_type="move",
|
|
314
|
+
linear_vel=vel,
|
|
315
|
+
distance=abs(distance),
|
|
316
|
+
duration=duration
|
|
317
|
+
)
|
|
318
|
+
elif cmd_type == "turn":
|
|
319
|
+
angle = kwargs.get('angle', 90)
|
|
320
|
+
angle_rad = math.radians(angle)
|
|
321
|
+
vel = kwargs.get('angular_velocity', 0.5)
|
|
322
|
+
vel = min(abs(vel), self.config.max_angular_vel)
|
|
323
|
+
if angle < 0:
|
|
324
|
+
vel = -vel
|
|
325
|
+
duration = abs(angle_rad / vel) if vel != 0 else 0
|
|
326
|
+
cmd = ManualCommand(
|
|
327
|
+
command_type="turn",
|
|
328
|
+
angular_vel=vel,
|
|
329
|
+
angle=abs(angle_rad),
|
|
330
|
+
duration=duration
|
|
331
|
+
)
|
|
332
|
+
elif cmd_type == "wait":
|
|
333
|
+
duration = kwargs.get('duration', 1.0)
|
|
334
|
+
cmd = ManualCommand(
|
|
335
|
+
command_type="velocity",
|
|
336
|
+
linear_vel=0,
|
|
337
|
+
angular_vel=0,
|
|
338
|
+
duration=duration
|
|
339
|
+
)
|
|
340
|
+
else:
|
|
341
|
+
return {'status': 'error', 'message': f'Unknown command type: {cmd_type}'}
|
|
342
|
+
|
|
343
|
+
self._command_queue.append(cmd)
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
'status': 'ok',
|
|
347
|
+
'message': f'Command queued (queue length: {len(self._command_queue)})',
|
|
348
|
+
'queue_length': len(self._command_queue)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
def execute_sequence(self, commands: list[dict]) -> dict:
|
|
352
|
+
"""
|
|
353
|
+
Execute a sequence of movement commands.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
commands: List of command dicts, e.g.:
|
|
357
|
+
[
|
|
358
|
+
{"type": "move", "distance": 1.0},
|
|
359
|
+
{"type": "turn", "angle": -90},
|
|
360
|
+
{"type": "move", "distance": 2.0},
|
|
361
|
+
{"type": "wait", "duration": 1.0}
|
|
362
|
+
]
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
Status dict
|
|
366
|
+
"""
|
|
367
|
+
self._cancel_manual_commands()
|
|
368
|
+
|
|
369
|
+
for cmd_dict in commands:
|
|
370
|
+
cmd_type = cmd_dict.get('type', cmd_dict.get('command'))
|
|
371
|
+
self.queue_command(cmd_type, **cmd_dict)
|
|
372
|
+
|
|
373
|
+
# Start first command
|
|
374
|
+
self._start_next_queued_command()
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
'status': 'ok',
|
|
378
|
+
'message': f'Executing sequence of {len(commands)} commands',
|
|
379
|
+
'commands': len(commands)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
def _start_manual_command(self, cmd: ManualCommand):
|
|
383
|
+
"""Start executing a manual command."""
|
|
384
|
+
self.mode = ControlMode.MANUAL
|
|
385
|
+
self.enabled = True
|
|
386
|
+
self._manual_command = cmd
|
|
387
|
+
self._manual_command.start_time = time.time()
|
|
388
|
+
self._manual_start_position = self._estimated_distance
|
|
389
|
+
self._manual_start_angle = self._estimated_angle
|
|
390
|
+
print(f"[CONTROLLER] Manual {cmd.command_type}: "
|
|
391
|
+
f"linear={cmd.linear_vel:.2f}m/s, angular={cmd.angular_vel:.2f}rad/s"
|
|
392
|
+
+ (f", duration={cmd.duration:.1f}s" if cmd.duration else ""))
|
|
393
|
+
|
|
394
|
+
def _cancel_manual_commands(self):
|
|
395
|
+
"""Cancel current manual command and clear queue."""
|
|
396
|
+
self._manual_command = None
|
|
397
|
+
with self._queue_lock:
|
|
398
|
+
self._command_queue.clear()
|
|
399
|
+
|
|
400
|
+
def _start_next_queued_command(self):
|
|
401
|
+
"""Start the next command in the queue."""
|
|
402
|
+
with self._queue_lock:
|
|
403
|
+
if self._command_queue:
|
|
404
|
+
cmd = self._command_queue.pop(0)
|
|
405
|
+
self._start_manual_command(cmd)
|
|
406
|
+
else:
|
|
407
|
+
# Queue empty, stop
|
|
408
|
+
self._manual_command = None
|
|
409
|
+
self.mode = ControlMode.IDLE
|
|
410
|
+
self.enabled = False
|
|
411
|
+
print("[CONTROLLER] Command sequence complete")
|
|
412
|
+
|
|
413
|
+
def _update_manual_command(self) -> Twist:
|
|
414
|
+
"""Update manual command execution and return twist."""
|
|
415
|
+
if not self._manual_command:
|
|
416
|
+
return Twist()
|
|
417
|
+
|
|
418
|
+
cmd = self._manual_command
|
|
419
|
+
elapsed = time.time() - cmd.start_time
|
|
420
|
+
|
|
421
|
+
# Check completion conditions
|
|
422
|
+
completed = False
|
|
423
|
+
|
|
424
|
+
if cmd.duration is not None and elapsed >= cmd.duration:
|
|
425
|
+
completed = True
|
|
426
|
+
|
|
427
|
+
if cmd.command_type == "move" and cmd.distance is not None:
|
|
428
|
+
traveled = abs(self._estimated_distance - self._manual_start_position)
|
|
429
|
+
if traveled >= cmd.distance:
|
|
430
|
+
completed = True
|
|
431
|
+
|
|
432
|
+
if cmd.command_type == "turn" and cmd.angle is not None:
|
|
433
|
+
turned = abs(self._estimated_angle - self._manual_start_angle)
|
|
434
|
+
if turned >= cmd.angle:
|
|
435
|
+
completed = True
|
|
436
|
+
|
|
437
|
+
if completed:
|
|
438
|
+
cmd.completed = True
|
|
439
|
+
print(f"[CONTROLLER] Manual command completed")
|
|
440
|
+
self._start_next_queued_command()
|
|
441
|
+
if self._manual_command is None:
|
|
442
|
+
return Twist()
|
|
443
|
+
cmd = self._manual_command
|
|
444
|
+
|
|
445
|
+
return Twist(linear_x=cmd.linear_vel, angular_z=cmd.angular_vel)
|
|
446
|
+
|
|
447
|
+
def get_manual_status(self) -> dict:
|
|
448
|
+
"""Get status of manual control."""
|
|
449
|
+
cmd = self._manual_command
|
|
450
|
+
if not cmd:
|
|
451
|
+
return {
|
|
452
|
+
'active': False,
|
|
453
|
+
'queue_length': len(self._command_queue)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
elapsed = time.time() - cmd.start_time
|
|
457
|
+
remaining = (cmd.duration - elapsed) if cmd.duration else None
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
'active': True,
|
|
461
|
+
'command_type': cmd.command_type,
|
|
462
|
+
'linear_vel': cmd.linear_vel,
|
|
463
|
+
'angular_vel': cmd.angular_vel,
|
|
464
|
+
'elapsed': elapsed,
|
|
465
|
+
'remaining': remaining,
|
|
466
|
+
'queue_length': len(self._command_queue)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
def set_target_distance(self, distance: float):
|
|
470
|
+
"""Set the target follow distance in meters."""
|
|
471
|
+
self.config.target_distance = max(0.3, min(5.0, distance)) # Clamp 0.3-5m
|
|
472
|
+
print(f"[CONTROLLER] Target distance set to {self.config.target_distance:.2f}m")
|
|
473
|
+
|
|
474
|
+
def set_target_person(self, person_id: int):
|
|
475
|
+
"""Lock onto a specific person ID."""
|
|
476
|
+
self.target_person_id = person_id
|
|
477
|
+
print(f"[CONTROLLER] Locked onto Person #{person_id}")
|
|
478
|
+
|
|
479
|
+
def clear_target_person(self):
|
|
480
|
+
"""Clear target lock, follow closest person."""
|
|
481
|
+
self.target_person_id = None
|
|
482
|
+
self.target_description = None
|
|
483
|
+
print("[CONTROLLER] Target cleared, following closest person")
|
|
484
|
+
|
|
485
|
+
def update(self, target_person: Optional[DetectedPerson]) -> Twist:
|
|
486
|
+
"""
|
|
487
|
+
Compute velocity command based on mode and target.
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
target_person: The person to follow, or None if not detected
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
Twist command
|
|
494
|
+
"""
|
|
495
|
+
self._update_count += 1
|
|
496
|
+
current_time = time.time()
|
|
497
|
+
dt = current_time - self._last_update_time
|
|
498
|
+
self._last_update_time = current_time
|
|
499
|
+
|
|
500
|
+
# Update odometry estimation from last twist
|
|
501
|
+
if dt > 0 and dt < 1.0: # Sanity check
|
|
502
|
+
self._estimated_distance += self._last_twist.linear_x * dt
|
|
503
|
+
self._estimated_angle += self._last_twist.angular_z * dt
|
|
504
|
+
|
|
505
|
+
# If disabled, return zero
|
|
506
|
+
if not self.enabled:
|
|
507
|
+
return Twist()
|
|
508
|
+
|
|
509
|
+
# Handle manual control mode
|
|
510
|
+
if self.mode == ControlMode.MANUAL:
|
|
511
|
+
twist = self._update_manual_command()
|
|
512
|
+
self._last_twist = twist
|
|
513
|
+
|
|
514
|
+
# Print status periodically
|
|
515
|
+
if self._update_count % 30 == 0:
|
|
516
|
+
cmd = self._manual_command
|
|
517
|
+
if cmd:
|
|
518
|
+
print(f"[MANUAL] {cmd.command_type}: "
|
|
519
|
+
f"linear={twist.linear_x:.2f}m/s, angular={twist.angular_z:.2f}rad/s")
|
|
520
|
+
|
|
521
|
+
return twist
|
|
522
|
+
|
|
523
|
+
# Follow mode - need a target person
|
|
524
|
+
if self.mode != ControlMode.FOLLOW:
|
|
525
|
+
return Twist()
|
|
526
|
+
|
|
527
|
+
# Watchdog check
|
|
528
|
+
if target_person is None:
|
|
529
|
+
if current_time - self._last_detection_time > self.config.watchdog_timeout:
|
|
530
|
+
# Watchdog triggered - stop
|
|
531
|
+
if self._update_count % 30 == 0: # Print every ~1 second at 30Hz
|
|
532
|
+
print("[CONTROLLER] Watchdog: No person detected, stopping")
|
|
533
|
+
self._smoothed_linear = 0.0
|
|
534
|
+
self._smoothed_angular = 0.0
|
|
535
|
+
self._last_twist = Twist()
|
|
536
|
+
return Twist()
|
|
537
|
+
else:
|
|
538
|
+
# Brief dropout, maintain last command
|
|
539
|
+
return self._last_twist
|
|
540
|
+
|
|
541
|
+
self._last_detection_time = current_time
|
|
542
|
+
|
|
543
|
+
# Calculate errors
|
|
544
|
+
distance_error = target_person.z - self.config.target_distance
|
|
545
|
+
angular_error = -math.atan2(target_person.x, target_person.z) # Negative because x>0 means person is to the right
|
|
546
|
+
|
|
547
|
+
# Apply deadzones
|
|
548
|
+
if abs(distance_error) < self.config.distance_deadzone:
|
|
549
|
+
distance_error = 0.0
|
|
550
|
+
if abs(angular_error) < self.config.angular_deadzone:
|
|
551
|
+
angular_error = 0.0
|
|
552
|
+
|
|
553
|
+
# Compute raw velocities (proportional control)
|
|
554
|
+
linear_cmd = self.config.Kp_distance * distance_error
|
|
555
|
+
angular_cmd = self.config.Kp_angular * angular_error
|
|
556
|
+
|
|
557
|
+
# Clamp to limits
|
|
558
|
+
linear_cmd = max(-self.config.max_linear_vel,
|
|
559
|
+
min(self.config.max_linear_vel, linear_cmd))
|
|
560
|
+
angular_cmd = max(-self.config.max_angular_vel,
|
|
561
|
+
min(self.config.max_angular_vel, angular_cmd))
|
|
562
|
+
|
|
563
|
+
# Apply smoothing (exponential moving average)
|
|
564
|
+
alpha = self.config.smoothing_factor
|
|
565
|
+
self._smoothed_linear = alpha * linear_cmd + (1 - alpha) * self._smoothed_linear
|
|
566
|
+
self._smoothed_angular = alpha * angular_cmd + (1 - alpha) * self._smoothed_angular
|
|
567
|
+
|
|
568
|
+
# Create twist
|
|
569
|
+
twist = Twist(
|
|
570
|
+
linear_x=self._smoothed_linear,
|
|
571
|
+
angular_z=self._smoothed_angular
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
self._last_twist = twist
|
|
575
|
+
|
|
576
|
+
# Print status periodically
|
|
577
|
+
if self._update_count % 10 == 0: # Every ~0.3s at 30Hz
|
|
578
|
+
self._print_status(target_person, twist, distance_error, angular_error)
|
|
579
|
+
|
|
580
|
+
return twist
|
|
581
|
+
|
|
582
|
+
def _print_status(self, person: DetectedPerson, twist: Twist,
|
|
583
|
+
dist_err: float, ang_err: float):
|
|
584
|
+
"""Print formatted status to console."""
|
|
585
|
+
print(f"[DETECTION] Person #{person.id}: "
|
|
586
|
+
f"distance={person.z:.2f}m, x={person.x:.2f}m "
|
|
587
|
+
f"(conf={person.confidence:.0%})")
|
|
588
|
+
|
|
589
|
+
print(f"[TARGET] Following Person #{person.id} "
|
|
590
|
+
f"(target: {self.config.target_distance:.1f}m, "
|
|
591
|
+
f"error: {dist_err:+.2f}m)")
|
|
592
|
+
|
|
593
|
+
print(f"[TWIST] linear_x={twist.linear_x:+.3f} m/s, "
|
|
594
|
+
f"angular_z={twist.angular_z:+.3f} rad/s")
|
|
595
|
+
print() # Blank line for readability
|
|
596
|
+
|
|
597
|
+
def get_status(self) -> dict:
|
|
598
|
+
"""Get current controller status."""
|
|
599
|
+
status = {
|
|
600
|
+
'enabled': self.enabled,
|
|
601
|
+
'mode': self.mode.value,
|
|
602
|
+
'target_distance': self.config.target_distance,
|
|
603
|
+
'target_person_id': self.target_person_id,
|
|
604
|
+
'target_description': self.target_description,
|
|
605
|
+
'last_twist': self._last_twist.to_dict(),
|
|
606
|
+
'max_linear_vel': self.config.max_linear_vel,
|
|
607
|
+
'max_angular_vel': self.config.max_angular_vel,
|
|
608
|
+
'watchdog_timeout': self.config.watchdog_timeout,
|
|
609
|
+
'update_count': self._update_count,
|
|
610
|
+
'uptime': time.time() - self._start_time
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
# Add manual control status if active
|
|
614
|
+
if self.mode == ControlMode.MANUAL:
|
|
615
|
+
status['manual'] = self.get_manual_status()
|
|
616
|
+
|
|
617
|
+
return status
|
|
618
|
+
|
|
619
|
+
@staticmethod
|
|
620
|
+
def print_twist(twist: Twist):
|
|
621
|
+
"""Print a twist command in ROS-compatible format."""
|
|
622
|
+
print("---")
|
|
623
|
+
print("linear:")
|
|
624
|
+
print(f" x: {twist.linear_x:.6f}")
|
|
625
|
+
print(f" y: {twist.linear_y:.6f}")
|
|
626
|
+
print(f" z: {twist.linear_z:.6f}")
|
|
627
|
+
print("angular:")
|
|
628
|
+
print(f" x: {twist.angular_x:.6f}")
|
|
629
|
+
print(f" y: {twist.angular_y:.6f}")
|
|
630
|
+
print(f" z: {twist.angular_z:.6f}")
|
|
631
|
+
print("---")
|