Skip to content

Booster#2493

Open
AlexWUrobot wants to merge 94 commits intomainfrom
booster
Open

Booster#2493
AlexWUrobot wants to merge 94 commits intomainfrom
booster

Conversation

@AlexWUrobot
Copy link
Copy Markdown

Overview

Solve the issue (speaking out the expression, such as smiling eyes smiling face) in kokoro_tts_provider.py
Solve the issue (Function Call)
Add avahi-daemon for mDNS

Type of change

  • [V] Bug fix
  • [V] New feature

Changes

Add booster k1_sdk.py
Add booster k1_odom_provider.py

Checklist

  • [V] Code complies with style guidelines
  • [V] Self-review completed
  • [V] Documentation updated (if applicable)
  • [V] Local testing completed
  • [V] Tests added/updated (if applicable)

Additional Information

Currently, Booster runs locomotion + balance policy on the same Jetson Orin (via systemctl), which takes ~30–50% CPU.

This is different from Unitree’s architecture:

  • Dedicated a 8-core CPU (Cerebellum) for low-level control
  • Separate compute (Cerebrum / brain pack- Jetson Thor) for high-level tasks

Potential improvements:

  • Add an external brain pack to offload computation
  • Require microphone to improve the voice input quality, or a remote microphone for presenter
  • Improve communication with extended Wifi antennas
  • Current joystick control range (~3–4m) is limited → I used joystick antenna for our autonomous boat remote control

AlexWUrobot and others added 30 commits February 4, 2026 12:49
Introduce UnitreeGo2- and TurtleBot4-specific odom/rplidar inputs and backgrounds and switch configs to use them. Many config JSON5 files updated to replace generic "Odom"/"RPLidar" types with UnitreeGo2 or TurtleBot4 variants (including renaming fabric_gps -> unitree_go2_fabric_gps and GPS reader to UnitreeGo2GPSOdomReader). Action connectors and background code imports were adjusted to use providers.unitree_go2_* and providers.turtlebot4_* implementations; RPLidar and odom background/input plugins were renamed or added (unitree_go2_rplidar, unitree_go2_odom, turtlebot4_odom, turtlebot4_rplidar). Several legacy test config files and the old generic odom background were removed. Tests and provider references were updated/renamed accordingly to align with the new provider/plugin names.
Refactor test patches to match renamed plugin/provider modules and classes with the unitree_go2_* prefix. Updated patch targets for IOProvider, OdomProvider/UnitreeGo2OdomProvider, RPLidarProvider/UnitreeGo2RPLidarProvider and related time/asyncio imports, adjusted assertion expectations (e.g. add_input source name and provider init args). These changes keep tests aligned with the refactored module and class names.
Rename mock and update references to match the UnitreeGo2RPLidar plugin. Renamed tests/integration/mock_inputs/mock_rplidar.py -> mock_unitree_go2_rplidar.py and class MockRPLidar -> MockUnitreeGo2RPLidar; updated tests/integration/mock_inputs/input_registry.py to import the new mock, register the mock under the "UnitreeGo2RPLidar" key, adjust mock module mappings, and update the unregister list; updated tests/integration/data/test_cases/rplidar_test.json5 to use type "UnitreeGo2RPLidar". This keeps mock names consistent with the real plugin identifier.
Remove Zenoh-related settings from the mock Unitree Go2 RPLidar input: drop the `use_zenoh` flag from the lidar config and remove the conditional handling that added `URID` and logging. This simplifies the test mock by eliminating Zenoh-specific configuration and behavior.
Remove the deprecated simple_paths option from RPLidar configs and providers (TurtleBot4 and Unitree Go2). Simplified path selection logic to always use the single default path and removed related config fields (use_zenoh, URID, machine_type where applicable). Update MoveZenoh connector to import and instantiate the TurtleBot4 RPLidar provider. Rename/cleanup the turtlebot4_odom background and add comprehensive unit tests for TurtleBot4 and Unitree odom and RPLidar backgrounds and inputs.
Add a comprehensive test suite for the TurtleBot4 RPLidar plugin. Tests cover RPLidarConfig defaults and custom values, TurtleBot4RPLidar initialization and provider parameter passing, async polling (_poll) with and without data, _raw_to_text and raw_to_text buffering behavior, formatted_latest_buffer (including buffer clearing and IOProvider interaction), and _extract_lidar_config. Uses unittest.mock and pytest (including async tests).
Add comprehensive unit tests for OdomProviderBase, TurtleBot4OdomProvider, and TurtleBot4RPLidarProvider. New test modules mock multiprocessing/threading and external dependencies (Zenoh, D435, sensor messages) and cover initialization, singleton behavior, start/stop, odometry processing (including quaternion->euler/yaw conversions), file logging, zenoh scan handling, path processing, and RPLidar config. Also remove an unused debug logging line from turtlebot4_rplidar_provider.py to tidy the implementation.
Fix incorrect tuple unpacking in tests/providers/test_turtlebot4_odom_provider.py so mock_process is assigned from the correct position in the mock_multiprocessing fixture. This ensures the test uses the intended mock process object when verifying that logging config is passed to the processor.
Clean up tests/providers/test_turtlebot4_rplidar_provider.py by removing redundant inline comments that described obvious assertions (subscriber declaration and path/angle checks). No functional changes—only test file comment cleanup to reduce noise.
Introduce a new UnitreeG1OdomProvider implementing OdomProviderBase to retrieve odometry/pose from Unitree G1 robots via CycloneDDS. Adds g1_odom_processor (runs in a separate process) which initializes the CycloneDDS channel, subscribes to rt/utlidar/robot_pose (PoseStamped_) and pushes messages into a multiprocessing queue; logging is configurable via existing logging helpers. The provider is a singleton that starts a multiprocessing subscriber and a local thread to process queue data, and includes _update_body_state to derive body height (cm) and RobotState (STANDING/SITTING). Optional imports are guarded with a warning if the Unitree SDK or CycloneDDS are not available. Requires unitree_sdk2py/CycloneDDS to function at runtime.
- Updated unitree_g1_autonomy.json5 to change the robot name from "Bits" to "Iris" and modified the system prompt accordingly.
- Introduced UnitreeG1Odom background plugin for odometry data handling.
- Added TurtleBot4Battery plugin for battery status monitoring with Zenoh integration.
- Implemented UnitreeGo2Battery plugin for battery status with error handling for SDK absence.
- Enhanced unit tests for TurtleBot4Battery and UnitreeGo2Battery plugins to ensure proper functionality and error handling.
@AlexWUrobot AlexWUrobot requested review from a team as code owners March 30, 2026 21:07
Copilot AI review requested due to automatic review settings March 30, 2026 21:07
@github-actions github-actions bot added robotics Robotics code changes python Python code infrastructure Docker/Infrastructure tests Test files config Configuration files labels Mar 30, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 30, 2026

Codecov Report

❌ Patch coverage is 31.28295% with 391 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/actions/move_k1_autonomy/connector/k1_sdk.py 12.67% 248 Missing ⚠️
src/providers/k1_odom_provider.py 20.43% 74 Missing ⚠️
src/inputs/plugins/booster_odom.py 32.14% 38 Missing ⚠️
src/llm/plugins/qwen_llm.py 44.44% 30 Missing ⚠️
src/providers/kokoro_tts_provider.py 91.66% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds “Booster” (K1) support across messaging, autonomy movement, and configs while also hardening local Qwen tool-calling and preventing Kokoro TTS from speaking emoji descriptions.

Changes:

  • Add Booster/K1 Zenoh message IDL + new odom provider and input plugin.
  • Add Booster autonomy move action connector (Zenoh RPC) plus multiple hw-test utilities/bridges.
  • Improve Kokoro TTS text sanitization and QwenLLM request/tool-call reliability; update docker/configs for local/mDNS setups.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
system_hw_test/zenoh_echo.py Generic Zenoh “topic echo” tool with basic deserialization for new Booster messages.
system_hw_test/test_booster_zenoh_odom_sub.py Minimal subscriber for validating odometer_state transport.
system_hw_test/test_booster_move.py Sends RemoteControllerState commands over Zenoh for movement testing.
system_hw_test/test_booster_move_zenoh_service.py Tests movement via Zenoh query → ROS2 service bridge (RPC-style).
system_hw_test/booster_zenoh_ros2_bridge.py ROS2 service client + Zenoh queryable bridge; also bridges paths/odom topics.
system_hw_test/booster_zenoh_mock.py Mock Zenoh RPC responder for local testing without ROS2.
src/zenoh_msgs/idl/booster_interface.py New Booster/K1 IDL structs (Odometer, RPC request/response, controller state).
src/zenoh_msgs/idl/init.py Exposes booster_interface types from zenoh_msgs.idl.
src/zenoh_msgs/init.py Re-exports Booster/K1 types at top-level zenoh_msgs.
src/providers/kokoro_tts_provider.py Sanitizes text (emoji/shortcodes/emoticons) before sending to Kokoro.
src/providers/k1_odom_provider.py New Zenoh-based OdomProviderBase implementation using the Booster Odometer IDL.
src/llm/plugins/qwen_llm.py Adds config-driven request overrides allowlist + tool-call fallback to text.
src/inputs/plugins/booster_odom.py New input plugin that surfaces odom/moving posture status to the agent.
src/actions/speak/connector/kokoro_tts.py Makes Kokoro base_url configurable via config (instead of hardcoded).
src/actions/move_k1_autonomy/interface.py Defines K1 autonomy movement action interface (turn/move/stop).
src/actions/move_k1_autonomy/connector/k1_sdk.py Zenoh RPC movement connector with lidar/odom gating + tick-based execution.
docker-compose.yml Updates runtime defaults and adds Avahi socket mount / host networking related settings.
config/greeting_local.json5 Removes unused Unitree ethernet config entry.
config/greeting_local_omr2.json5 Adds a local greeting config targeting omr2 endpoints.
config/greeting_local_omr1.json5 Adds a local greeting config tuned for deterministic tool-calling.
config/greeting_llm_gemini.json5 Adds Gemini-based greeting config.
config/booster_autonomy.json5 Adds a Booster autonomy mode config (odom + paths + move + speak).
.env.example Documents optional QWEN/KOKORO base URL overrides.

Comment on lines +150 to +169
# Only forward a small, known-safe subset of OpenAI request parameters.
# Runtime config injects meta keys like `mode`, `URID`, etc. which MUST NOT
# be forwarded to the OpenAI client.
allowlist = {
"temperature",
"top_p",
"max_tokens",
"presence_penalty",
"frequency_penalty",
"stop",
"seed",
"n",
"stream",
"response_format",
"parallel_tool_calls",
# We set tool_choice internally when tools are present; allow opt-in only
# when no tools are configured.
"tool_choice",
"extra_body",
}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_get_request_overrides() allowlists the OpenAI stream parameter, but ask() treats the response as a non-streaming ChatCompletion (e.g., accesses response.choices). If config sets stream=true, openai.AsyncClient.chat.completions.create() returns a stream object and this code path will break at runtime. Either remove stream from the allowlist or add explicit streaming handling before accessing choices.

Copilot uses AI. Check for mistakes.
Comment on lines +108 to +116
def _run_move_robot(self, vx: float, vy: float, vyaw: float) -> None:
# Called from both sync code (tick thread) and async code (connect).
try:
loop = asyncio.get_running_loop()
except RuntimeError:
asyncio.run(self._move_robot(vx, vy, vyaw))
else:
loop.create_task(self._move_robot(vx, vy, vyaw))

Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_run_move_robot() calls asyncio.run(...) when no event loop is running. Since tick() can invoke this repeatedly, this will create/destroy an event loop per movement command and block the tick thread until the RPC completes, adding significant overhead/latency. Consider making _move_robot synchronous (like src/actions/move_go2_autonomy/connector/unitree_om_path_sdk.py:213+) or running a dedicated background loop/thread for RPC dispatch.

Copilot uses AI. Check for mistakes.
version: "v1.0.0",
hertz: 1,
name: "booster",
api_key: "openmind_free",
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This config hardcodes api_key: "openmind_free" instead of using the ${OM_API_KEY:-...} interpolation used by other configs. This prevents deployments from picking up the real API key from the environment and can lead to confusing auth failures. Use the same env-based pattern here for consistency and easier ops.

Suggested change
api_key: "openmind_free",
api_key: "${OM_API_KEY:-openmind_free}",

Copilot uses AI. Check for mistakes.
Comment on lines +244 to +245
except Exception as e:
print(f"Error handling query: {e}")
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In zenoh_query_handler(), the exception handler logs the error but does not send any reply to the query. This can leave the Zenoh client waiting until timeout without any diagnostic payload. Consider replying with a serialized RpcServiceResponse containing an error BoosterApiRespMsg (or query.reply_err(...) if supported) in the except path.

Suggested change
except Exception as e:
print(f"Error handling query: {e}")
except Exception as e:
# Log the error and send an error response back to the Zenoh client
print(f"Error handling query: {e}")
error_body = json.dumps(
{
"status": "error",
"message": "Error handling Zenoh query",
"detail": str(e),
}
)
api_resp = BoosterApiRespMsg(status=-1, body=error_body)
zenoh_resp = RpcServiceResponse(msg=api_resp)
try:
query.reply(self.zenoh_key, zenoh_resp.serialize())
except Exception as reply_err:
# If sending the error response itself fails, log and give up
print(f"Failed to send error reply over Zenoh: {reply_err}")

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

config Configuration files infrastructure Docker/Infrastructure python Python code robotics Robotics code changes tests Test files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants