On this page

Simple Service

Answering std_srvs/SetBool requests from QML with a single, mostly declarative service server.

Simple Service shows the server side of a ROS 2 service in QML: it wraps one SetBoolServiceServer in a Node and switches a "lamp" on or off whenever a std_srvs/SetBool request arrives — from the Simple Service Client example, from ros2 service call, or from any other ROS 2 node. To make failure handling honest, the bulb burns out after ten switching cycles and the service starts reporting success: false until the bulb is replaced.

The Simple Service window: an editable topic, the lamp that requests switch on and off, bulb wear, a deferred-reply switch, and the last request.

Running the example

  1. Source your ROS 2 environment, then launch Qt Creator from the same shell so it can find the generated QML modules.
  2. Open examples/simple_service/CMakeLists.txt in Qt Creator, then build and run.
  3. Call the service from a separate, ROS-sourced terminal:
    ros2 service call /simple_service_lamp std_srvs/srv/SetBool "{data: true}"

Pair this example with Simple Service Client to see both ends of a service in two Qt windows.

A declarative service server

Like publishers and subscribers, a service server is declared as a child of a Node; the node owns the underlying rclcpp service and tears it down automatically. In the simplest style there is no callback at all: the server's response property is a binding, and each incoming request is answered with its current value. The incoming payload is available as the read-only request property:

import QtRos2.StdSrvs

Node {
    id: rosNode
    nodeName: "simple_service_node"

    SetBoolServiceServer {
        id: lampService
        topic: topicField.text

        onRequestReceived: request => ++root.cycles

        response: root.burnedOut
            ? ({ success: false, message: "the bulb is burned out" })
            : ({ success: true, message: `lamp is now ${lampService.request ? "on" : "off"}` })
    }
}

The work splits along QML's usual grain: derived state lives in bindings (the lamp color binds to lampService.request), per-request effects live in the onRequestReceived signal handler (counting switching cycles), and the response — including failure — is a conditional expression. The evaluation order is guaranteed: request updates first, then requestReceived fires, and only then is response read and sent. That is why the very request that burns out the bulb already receives the failure answer.

For std_srvs/SetBool the request payload is a single bool, so request is a plain boolean. Services with multi-field requests receive the generated value type instead, with the same camelCase field access as messages. The response works symmetrically: a plain JavaScript object with the response fields (here success and message) converts to the SetBool response automatically.

Failure is in-band

ROS 2 services have no separate error channel — a server that cannot comply still answers, encoding failure in the response. That is exactly why std_srvs types carry success and message fields, and why failure needs no special API here: it is just another branch of the response binding.

Handlers and deferred replies

When producing the response requires running code per request — or when honoring the request takes time — set the handler property instead; a callable handler takes precedence over the response property. The handler may return the response value directly, or return undefined and call reply.send() later. The example's Delay the response switch demonstrates the precedence rule by binding the handler property itself:

handler: delaySwitch.checked
    ? (request, reply) => {
          replyTimer.pendingReplies.push(reply)
          replyTimer.restart()
      }
    : undefined
Timer {
    id: replyTimer
    interval: 1000
    property var pendingReplies: []
    onTriggered: {
        for (const reply of pendingReplies)
            reply.send(lampService.response)   // same declarative value, later
        pendingReplies = []
    }
}

With the switch on, callers observe the answer arriving one second late — while the server window stays fully responsive and keeps accepting further requests: the handler runs on the GUI thread and the ROS executor is never blocked, even while replies are pending.

If neither a handler nor a response binding is set (or the handler throws), the server sends a default-constructed response so that callers never wait forever.

Files:

Images:

See also Simple Service Client and Qt ROS2 std_srvs QML Types.

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.