Connection
If you already have a ROS2-based system and would rather work with native ROS2 services/topics than the Python or TypeScript SDKs, the QTrobot ROS2 gateway bridges the two worlds: it connects to the robot's service hub like any other client, and re-exposes every capability as standard ROS2 interfaces under the /qtrobot/ namespace. Every other tutorial in this section assumes the gateway from this page is already running.
Install the gateway
git clone https://github.com/luxai-qtrobot/qtrobot-service-hub-gateway-ros.git
mkdir -p ~/ros2_ws/src
ln -s ~/qtrobot-service-hub-gateway-ros/qtrobot_interfaces ~/ros2_ws/src/qtrobot_interfaces
cd ~/ros2_ws
source /opt/ros/jazzy/setup.bash
colcon build --packages-select qtrobot_interfaces
Only the qtrobot_interfaces package (the message/service definitions) needs a colcon build — the gateway itself is a plain Python package, run directly:
cd ~/qtrobot-service-hub-gateway-ros
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Launch the gateway
In every new terminal:
source ~/qtrobot-service-hub-gateway-ros/.venv/bin/activate
source /opt/ros/jazzy/setup.bash
source ~/ros2_ws/install/setup.bash
cd ~/qtrobot-service-hub-gateway-ros
Then start the gateway, pointing it at your robot's IP address:
python -m gateway.qtrobot_ros2 --ros-args -p robot_ip:=<ROBOT_IP> -r __ns:=/qtrobot
Confirm it's running by listing the new services:
ros2 service list | grep qtrobot
You should see entries such as /qtrobot/tts/engine/say/text, /qtrobot/face/emotion/show, /qtrobot/motor/list, and many more. The camera/IMU services come from a second, independent gateway process — see Camera.
Calling a service from the command line
Before writing any code, it's worth trying a service directly:
ros2 service call /qtrobot/tts/engine/say/text qtrobot_interfaces/srv/TtsEngineSayText \
"{text: 'Hello, I am QTrobot.'}"
Calling a service from a ROS2 node
Here's a minimal Python node that calls the same service:
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from qtrobot_interfaces.srv import TtsEngineSayText
class TtsClient(Node):
def __init__(self):
super().__init__('tts_client')
self.client = self.create_client(TtsEngineSayText, '/qtrobot/tts/engine/say/text')
self.client.wait_for_service(timeout_sec=5.0)
def say(self, text: str):
request = TtsEngineSayText.Request()
request.text = text
future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
return future.result()
def main():
rclpy.init()
node = TtsClient()
response = node.say('Hello, I am QTrobot.')
node.get_logger().info(f'TTS call status: {response.status}')
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Every QTrobot service follows this same shape: a typed Request, a typed Response with at least a status field telling you whether the call succeeded, and the usual wait_for_service / call_async / spin_until_future_complete rclpy pattern. Subscribing to a topic follows the standard rclpy subscription pattern instead — see Motor or Microphone for examples.
Next steps
Continue with the TTS tutorial, or see the full service/topic/message reference in the ROS2 API Reference.