Python SDK
Overview
BonicBot Bridge is a Python SDK designed for educational robotics programming. It provides a high-level, intuitive API that abstracts the complexity of ROS 2 robotics into simple commands suitable for STEM education and beginners.
System Requirements:
- Python 3.8 or higher
- BonicBot robot running ROS 2 Humble
- Both the robot and control computer must be on the same network
rosbridge_serverrunning on port 9090 on the robot
Installation: You can easily install the SDK via pip:
pip install bonicbot-bridgeBest Practice: The Context Manager
When connecting to the robot, it’s highly recommended to use the with statement (a context manager). This ensures that the robot safely and automatically disconnects when your program finishes, even if an error occurs.
from bonicbot_bridge import BonicBot
with BonicBot(host='192.168.1.100') as bot:
bot.move_forward(speed=0.3, duration=2.0)Connection & Setup
The BonicBot class handles the connection to your robot.
BonicBot(host='localhost', port=9090, timeout=10)Parameters:
host(str): Robot IP address or hostname. Use'localhost'if running directly on the robot, or the robot’s IP/hostname for remote access over the same network.port(int): rosbridge_server port (default:9090).timeout(int): Connection timeout in seconds.
Examples:
# Local connection (running directly on the robot)
bot = BonicBot()
# Remote connection (running on a computer on the same network)
bot = BonicBot(host='192.168.1.100')bot.connect(timeout): Explicitly connect to the robot (this is called automatically when creating aBonicBotobject).bot.disconnect(): Shuts down controllers and closes the connection. Automatically called if using thewithblock.bot.is_connected(): ReturnsTrueif the robot is currently connected.
from bonicbot_bridge import BonicBot
with BonicBot(host='192.168.1.100') as bot:
if bot.is_connected():
print("Connected successfully!")Basic Movement
You can control the robot’s movement easily with built-in commands. When providing a duration to these methods, they are ⏳ Blocking, meaning your program will wait until the movement finishes before going to the next line. If no duration is provided, the robot will move continuously until stop() is called.
bot.move(linear_x, linear_y, angular_z): Direct continuous velocity control (m/s and rad/s).bot.move_forward(speed, duration): Move forward at a setspeed(m/s) for a setduration(seconds).bot.move_backward(speed, duration): Move backward.bot.turn_left(speed, duration): Turn left (counter-clockwise) at a given rotational speed (rad/s).bot.turn_right(speed, duration): Turn right (clockwise).bot.stop(): Stop all movement immediately.bot.is_moving(): ReturnsTrueif the robot is currently moving towards a navigation goal.
Note: For turn functions, speed is measured in radians per second (rad/s), not degrees!
Example
from bonicbot_bridge import BonicBot
with BonicBot(host='192.168.1.100') as bot:
bot.move_forward(speed=0.3, duration=2.0)
bot.turn_left(speed=0.5, duration=1.0)
bot.move_backward(speed=0.2, duration=1.5)
bot.stop()Precise Motion
Use precise motion when you need high-accuracy movement, utilizing closed-loop odometry or the nav2 engine.
Note: Distance commands have a safety guard (MAX_PRECISE_DISTANCE = 10.0 meters). Exceeding it raises a PreciseMotionError.
Engines:
bot.precise.set_default_engine(engine): Choose'internal'(odometry feedback) or'nav2'(action server).
Single & Compound Commands:
bot.drive_distance(dist, speed, engine, timeout): Drive an exact distance. ⏳ Blocking.bot.precise.rotate_angle(angle, speed, engine, timeout): Rotate an exact angle. ⏳ Blocking.bot.precise.drive_and_rotate(dist, angle, speed, turn_speed): Drive then rotate sequentially. ⏳ Blocking.bot.precise.draw_square(side_m, speed, turn_speed): Drive in a square pattern.
Queue-Based Sequences: Queue a sequence of movements to run autonomously.
bot.precise.enqueue_move(cmd_list): Push motion commands. Example:[{'type': 'drive', 'value': 1.0}, {'type': 'rotate', 'value': 90.0}].bot.precise.run_queue(block): Execute the queued commands. Ifblock=True, it is ⏳ Blocking.bot.precise.clear_queue(): Flush pending commands and abort the current sequence.bot.precise.is_precise_moving(): Check if a precise motion command is currently running.
Example
from bonicbot_bridge import BonicBot
with BonicBot(host='192.168.1.100') as bot:
bot.precise.set_default_engine('internal')
# Queue up a shape
bot.precise.enqueue_move([
{'type': 'drive', 'value': 1.0, 'speed': 0.3},
{'type': 'rotate', 'value': 90.0, 'speed': 45.0},
{'type': 'drive', 'value': 1.0, 'speed': 0.3}
])
print("Executing queued sequence...")
bot.precise.run_queue(block=True)Reading Sensors
Access the robot’s position, battery level, and odometry data.
bot.get_position(): Returns a dictionary with the robot’s current position (x,y, andthetain degrees).bot.get_x(): Returns the X position in meters.bot.get_y(): Returns the Y position in meters.bot.get_heading(): Returns the heading orientation in degrees (0-360).bot.sensors.get_distance_traveled(start_pos): Calculates distance traveled from a provided starting position dictionary ({'x': float, 'y': float}).bot.get_battery(): Returns the battery percentage.Note: Currently returns a placeholder of 85.0 as hardware integration is pending.bot.sensors.wait_for_data(timeout): Wait until sensor data becomes available. This is ⏳ Blocking until data is received or the timeout expires.bot.sensors.get_sensor_info(): Get a summary dictionary of all available sensor data states.
Live Subscriptions (Advanced): You can run functions automatically in the background whenever new data arrives.
bot.sensors.subscribe_to_position(callback): Pass a functioncallback(x, y, theta)that will be called on every position update.
Example
from bonicbot_bridge import BonicBot
with BonicBot(host='192.168.1.100') as bot:
if bot.sensors.wait_for_data(timeout=5.0):
# Save start position
start = bot.get_position()
bot.move_forward(0.3, duration=2.0)
# Calculate distance
dist = bot.sensors.get_distance_traveled(start)
print(f"Traveled {dist:.2f} meters!")Servo & Arm Control
Control the physical arms, grippers, and head of BonicBot. By default, arm movement functions are ⏳ Blocking and will wait for the physical move to finish.
Hardware Joint Limits (in degrees):
- Shoulder pitch: -45° to 180°
- Elbow: 0° to 50°
- Gripper: -28.6° to 60° (or -45° to 60° for single finger)
- Neck yaw: -90° to 90°
Basic Arm & Head Control:
bot.move_left_arm(shoulder, elbow, wait): Set left arm angles.bot.move_right_arm(shoulder, elbow, wait): Set right arm angles.bot.set_neck(yaw): Set neck rotation (-90° is right, 90° is left).bot.look_left()/bot.look_right()/bot.look_center(): Easy neck shortcuts.
Grippers:
bot.open_grippers()/bot.close_grippers(): Quick toggles for both grippers.bot.set_grippers(left, right): Set exact angles for both grippers.bot.servo.set_left_gripper(angle)/bot.servo.set_right_gripper(angle): Control a single gripper individually.
Advanced Joint Operations:
bot.servo.set_single_servo(joint_name, angle): Set any specific servo’s angle by its name.bot.servo.get_single_servo(joint_name): Get a specific servo’s current angle.bot.servo.get_servo_angles(): Retrieve a dictionary of all current joint angles.bot.servo.get_servo_limits(): Retrieve a dictionary of(min, max)tuples for every joint.bot.servo.reset_all_servos(): Reset all servos back to 0°.bot.servo.close_grippers(): Closes both left and right grippers and returns a boolean indicating success.bot.servo.look_right(): Turns the neck to look fully to the right and returns a boolean indicating success.bot.servo.look_center(): Centers the robot’s neck and returns a boolean indicating success.bot.servo.set_servo_angles(angles): Legacy method to set multiple servo angles using a dictionary of joint names and values.
Example
from bonicbot_bridge import BonicBot
with BonicBot(host='192.168.1.100') as bot:
bot.look_left()
bot.open_grippers()
# Advanced: Get current limits to make sure we are safe
limits = bot.servo.get_servo_limits()
print(f"Left shoulder limits: {limits.get('left_shoulder_pitch_joint')}")
bot.servo.reset_all_servos()Mapping & Navigation
BonicBot can map its environment using SLAM and navigate autonomously.
Mapping:
bot.start_mapping(): Begin building a new map using SLAM.bot.save_map(): Save the generated map.bot.stop_mapping(): Turn off mapping.
Navigation & Goals:
bot.start_navigation(force): Start the navigation system.Note: This returnsFalseif mapping is active unlessforce=Trueis provided. If you want to explore and map simultaneously, usesetup_for_exploration()instead.bot.stop_navigation(): Turn off navigation.bot.go_to(x, y, theta): Navigate to anx, ycoordinate withthetaorientation.bot.wait_for_goal(timeout): Wait until the robot reaches its destination. This is ⏳ Blocking.bot.cancel_goal(): Stop navigating to the current goal.bot.set_initial_pose(x, y, theta): Required when using a saved map to tell the robot where it starts.
System Status & Locations:
bot.get_nav_status(): Returns current status (e.g.,'idle','navigating','goal_reached').bot.get_distance_to_goal(): Returns remaining distance to the goal in meters.bot.get_system_status(): Get a complete status dictionary of the robot’s systems.bot.system.get_robot_state(): Get the current robot state string.bot.system.save_location(name): Save the robot’s current pose as a named location.bot.system.goto_location(name): Command the robot to navigate to a saved location name.bot.system.delete_location(name): Remove a saved location.
Map Data Access:
bot.system.has_saved_map(): Check if a map file is saved on the robot’s disk.bot.system.get_map_info(): Get metadata about the map (resolution, width, height).bot.system.get_map_data(): Get the full cached OccupancyGrid data array.bot.motion.subscribe_to_nav_status(callback): Subscribes a callback function to receive real-time updates on the navigation status.bot.motion.subscribe_to_distance_to_goal(callback): Subscribes a callback function to receive real-time updates on the remaining distance to the goal.bot.system.is_mapping(): Checks if the robot is currently mapping the environment and returns a boolean.bot.system.is_navigating(): Checks if the navigation system is active and returns a boolean.bot.system.setup_for_mapping(): Sets up the robot for mapping operations and returns a boolean indicating success.bot.system.setup_for_navigation(): Sets up the robot for autonomous navigation and returns a boolean indicating success.bot.system.delete_all_locations(): Removes all saved named locations from the system.bot.system.subscribe_to_map(callback, throttle_rate): Subscribes a callback function to receive live map updates at a specified throttle rate.bot.system.subscribe_to_odom(callback, throttle_rate): Subscribes a callback function to receive live odometry data at a specified throttle rate.bot.system.subscribe_to_robot_state(callback): Subscribes a callback function to receive changes in the robot’s state string.bot.system.subscribe_to_mapping_active(callback): Subscribes a callback function to receive boolean updates when mapping starts or stops.bot.system.subscribe_to_navigation_active(callback): Subscribes a callback function to receive boolean updates when navigation starts or stops.bot.system.subscribe_to_current_goal(callback, throttle_rate): Subscribes a callback function to receive updates on the robot’s current navigation goal coordinates.bot.system.subscribe_to_locations_list(callback, throttle_rate): Subscribes a callback function to receive the list of saved named locations whenever it changes.bot.system.subscribe_to_map_available(callback): Subscribes a callback function to receive boolean updates when a saved map becomes available.
Live Subscriptions (Advanced Dashboarding):
The bot.system controller includes many background callback subscribers: subscribe_to_map(), subscribe_to_odom(), subscribe_to_robot_state(), subscribe_to_navigation_active(), subscribe_to_current_goal(), and subscribe_to_locations_list().
Example
from bonicbot_bridge import BonicBot
with BonicBot(host='192.168.1.100') as bot:
bot.start_mapping()
bot.start_navigation(force=True)
bot.go_to(x=1.5, y=2.0, theta=90)
# Monitor the goal progress
while bot.get_nav_status() == 'navigating':
dist = bot.get_distance_to_goal()
print(f"Distance remaining: {dist:.2f}m")
bot.system.save_location("kitchen")
bot.save_map()Autonomous Exploration
Let BonicBot map the room all by itself using the explore_lite frontier exploration package.
Note: Exploration bypasses bot.start_navigation() because it requires SLAM and Nav2 to boot concurrently.
Exploration Setup & Control:
bot.setup_for_exploration(progress_callback): Boots SLAM, Nav2, and explore_lite safely. ⏳ Blocking (~90s on first boot).bot.explore.start_explore(): Manually publish a resume signal toexplore_lite.bot.explore.stop_explore(): Pauses exploration and kills the node.bot.explore.is_exploring(): ReturnsTrueif exploration is actively hunting frontiers.bot.explore.wait_for_map_complete(min_area, timeout): Wait until the target area is mapped. ⏳ Blocking.
Monitoring & Intervention:
bot.explore.suspend_for_manual_control(): Pauses exploration so you can take over with joystick or movement commands.bot.explore.resume_from_manual_control(): Restarts exploration without re-running the full 90s setup process.bot.explore.diagnostics(): Returns a dictionary of the exploration stack’s internal state for debugging.bot.explore.set_lifecycle_callback(callback): Attaches a callback function to listen to exploration lifecycle events.
Example
from bonicbot_bridge import BonicBot
with BonicBot(host='192.168.29.52') as bot:
bot.setup_for_exploration()
bot.explore.wait_for_map_complete(timeout=300.0)Camera
Stream video and take pictures using the robot’s camera.
Note: Images fetched via get_latest_image() will be in BGR color format if you have OpenCV installed, and RGB format if you are using Pillow.
bot.start_camera(): Starts the camera hardware and the data stream.bot.camera.get_latest_image(): Grabs the most recent image frame.bot.camera.save_image(filepath): Saves the current frame to a local file.bot.camera.wait_for_image(timeout): Wait for the first image to arrive. This is ⏳ Blocking.bot.camera.get_camera_info(): Get camera metadata dictionary (width,height,distortion_model).bot.camera.is_streaming(): ReturnsTrueif the camera stream is currently active.bot.system.stop_camera(): Stops the camera system hardware and returns a boolean indicating success.bot.system.is_camera_active(): Checks if the camera system hardware is currently active and returns a boolean.bot.camera.start_streaming(callback, throttle_ms): Starts camera streaming with an optional callback function for each frame.bot.camera.stop_streaming(): Stops the camera stream from receiving frames and returns a boolean indicating success.
Example
from bonicbot_bridge import BonicBot
with BonicBot(host='192.168.1.100') as bot:
bot.start_camera()
if bot.camera.wait_for_image(timeout=5.0):
info = bot.camera.get_camera_info()
print(f"Camera Info: {info}")
bot.camera.save_image("my_robot_photo.jpg")
bot.system.stop_camera()Vision & Detection
[!NOTE] Architecture: All vision inference runs on the robot’s onboard
vision_pipeline.pyROS 2 node. The bridge SDK simply sends configuration commands and receives detection results — no local models or GPU required on your machine.
ℹ️ Setup: To use vision features, the physical camera hardware must be turned on first before enabling a detection mode. If you also want to see the live video on your computer, you must start the camera stream.
bot.system.start_camera() # 1. Turn on the physical camera hardware
bot.start_camera() # 2. (Optional) Start receiving image frames to your computer
bot.enable_detection('yolo') # 3. Start the vision pipelineBonicBot has powerful AI computer vision capabilities. The vision pipeline uses string literals to define all available pipeline modes.
| Mode | Detects |
|---|---|
'yolo' | YOLO object detection (80 COCO classes: person, bottle, chair, etc.) |
'face' | Human faces with 5 facial landmarks |
'pose' | 33 MediaPipe body pose landmarks |
'gesture' | Hand gestures (Thumb_Up, Open_Palm, Victory, etc.) |
'aruco' | ArUco fiducial markers with full pose |
Enabling Pipelines:
bot.enable_detection(mode, model='yolov8n'): Turn on a vision mode using one of the string names above.bot.disable_detection(): Turn the vision pipeline off.bot.get_active_mode(): Return the currently active vision mode string.
Accessing Data:
bot.get_detections(class_filter=None): Get the latest list of detected objects.bot.get_faces(): Get the latest face data.bot.get_pose_keypoints(): Get raw MediaPipe 33 body pose landmarks.bot.get_gesture(): Get the current detected hand gesture name.bot.get_gesture_full(): Get full gesture data including hand landmarks.bot.get_aruco_markers(): Get the latest list of detected ArUco marker IDs (integers).bot.get_nearest_person(): Retrieves bounding box data for the person detected nearest to the camera.
Waiting for Objects:
bot.wait_for_detection(target_class, timeout): Wait for a specific object class to appear. ⏳ Blocking.bot.wait_for_marker(marker_id, timeout): Wait for a specific ArUco tag ID to appear. ⏳ Blocking.bot.wait_for_face(timeout): Wait for any face to appear. ⏳ Blocking.bot.wait_for_pose(timeout): Wait for pose keypoints to appear. ⏳ Blocking.bot.wait_for_gesture(gesture_name, timeout): Wait for a specific gesture. ⏳ Blocking.
Status Properties & Low-level Control (bot.vision.*):
Check if specific detectors are currently active on the robot using properties on bot.vision:
bot.vision.vision_active:Trueif the vision pipeline is running.bot.vision.yolo_enabled,bot.vision.pose_enabled,bot.vision.face_enabled,bot.vision.gesture_enabled,bot.vision.aruco_enabledbot.vision.is_any_detection_active:Trueif at least one detector is currently active.bot.vision.is_subscribed:Trueif data-topic subscriptions are open.
These lower-level methods let you control the vision pipeline lifecycle directly:
bot.vision.start_vision()/bot.vision.stop_vision(): Start or stop the entire robot-side vision pipeline.bot.vision.enable_detector(detector)/bot.vision.disable_detector(detector): Enable or disable a single detector by name.bot.vision.toggle_detector(detector, enable): Convenience wrapper to enable or disable a detector.bot.vision.subscribe_to_vision_pipeline()/bot.vision.unsubscribe_from_vision_pipeline(): Manually open or close data-topic subscriptions.bot.vision.get_active_detectors(): Return a list of all currently active detector names.
Basic Example
from bonicbot_bridge import BonicBot
with BonicBot(host='192.168.1.100') as bot:
# 1. Turn on the physical camera!
bot.system.start_camera()
# 2. Enable pose detection
bot.enable_detection('pose')
print("Strike a pose!")
while True:
keypoints = bot.get_pose_keypoints()
# MediaPipe returns 33 landmarks, let's check for the nose (index 0)
if keypoints and len(keypoints) > 0:
print("I can see you!")
break
bot.disable_detection()
bot.system.stop_camera()Error Handling
When writing robust code, it’s good practice to catch errors (exceptions). All exceptions in the BonicBot Bridge SDK inherit from a base BonicBotError.
Here is the complete exception hierarchy:
| Exception | Description |
|---|---|
BonicBotError | The base error for everything. |
ConnectionError | Could not connect to the robot’s WebSocket. |
NavigationError | Nav2 failed, an obstacle blocked the path, or a timeout occurred. |
PreciseMotionError | A precise motion guard limit was violated (like exceeding 2.0 meters) or timed out. |
SystemControlError | Essential ROS services like mapping or navigation failed to boot. |
VisionError | General failure in the vision processing pipeline. |
ServoError | A servo or joint command failed to execute. |
CameraError | A camera operation failed, such as failing to start the stream. |
SensorError | A sensor read or subscription failed. |
ExploreError | General failure in the autonomous exploration stack. |
ExploreTimeoutError | Exploration setup or wait ran out of time before reaching its target. |
Example:
from bonicbot_bridge import BonicBot
from bonicbot_bridge.exceptions import BonicBotError, ConnectionError, NavigationError
try:
with BonicBot(host='192.168.1.100', timeout=5.0) as bot:
bot.start_navigation()
bot.go_to(x=5.0, y=5.0)
bot.wait_for_goal(timeout=30.0)
except ConnectionError:
print("Could not connect. Is the robot turned on and connected to Wi-Fi?")
except NavigationError as e:
print(f"Failed to reach destination: {e}")
except BonicBotError as e:
print(f"A general robot error occurred: {e}")