- Qt 6.9
- Qt GRPC
- Qt GRPC Examples
- Vehicle
- Qt 6.9.2 Reference Documentation

# Vehicle

Manage two threaded connections between a Qt gRPC™ client and a C++ gRPC server.

The example simulates a vehicle dashboard that displays data sent by a gRPC server.

The example code has the following components:

- `vehicle_client` Qt gRPC client application that uses the qt_add_protobuf() and qt_add_grpc() CMake functions for message and service Qt code generation.
- `vehicle_server` application that calls C++ gRPC plugin for generating server code and implementing simple server logic.

Note: you need the C++ gRPC plugin installed. Find details here: Module prerequisites

Both components use generated messages from the protobuf schemas described in the files `vehicleservice.proto` and `navigationservice.proto`.

Vehicle service:

     message SpeedMsg {
         int32 speed = 1;
     }

     message RpmMsg {
         int32 rpm = 1;
     }

     message AutonomyMsg {
         int32 autonomy = 1;
     }

     service VehicleService {
         rpc getSpeedStream(google.protobuf.Empty) returns (stream SpeedMsg) {}
         rpc getRpmStream(google.protobuf.Empty) returns (stream RpmMsg) {}
         rpc getAutonomy(google.protobuf.Empty) returns (AutonomyMsg) {}
     }

Navigation service:

     enum DirectionEnum {
         RIGHT = 0;
         LEFT = 1;
         STRAIGHT = 2;
         BACKWARD = 3;
     }

     message NavigationMsg {
         int32 totalDistance = 1;
         int32 traveledDistance = 2;
         DirectionEnum direction = 3;
         string street = 4;
     }

     service NavigationService {
         rpc getNavigationStream(google.protobuf.Empty) returns (stream NavigationMsg) {}
     }

The `VehicleManager` C++ singleton uses two QThread instances to communicate with the server in parallel. The threads have different gRPC connection types. In this example, there are two types:

- Server streaming RPCs For example, the speed stream of the vehicle thread. It uses two callback functions: QGrpcServerStream::messageReceived and QGrpcOperation::finished

       Empty speedRequest;
       m_streamSpeed = m_client->getSpeedStream(speedRequest);

       connect(m_streamSpeed.get(), &QGrpcServerStream::messageReceived, this, [this]() {
           if (const auto speedResponse = m_streamSpeed->read<SpeedMsg>()) {
               emit speedChanged(speedResponse->speed());
           }
       });

       connect(
           m_streamSpeed.get(), &QGrpcServerStream::finished, this,
           [this](const QGrpcStatus &status) {
               if (!status.isOk()) {
                   auto error = QString("Stream error fetching speed %1 (%2)")
                                    .arg(status.message())
                                    .arg(QVariant::fromValue(status.code()).toString());
                   emit connectionError(error);
                   qWarning() << error;
                   return;
               }
           },
           Qt::SingleShotConnection);

- Unary RPCs The RPC `getAutonomy` operation is a unary RPC. It returns a single response. It is only connected to the QGrpcOperation::finished signal.

       Empty autonomyRequest;
       std::unique_ptr<QGrpcCallReply> autonomyReply = m_client->getAutonomy(autonomyRequest);
       const auto *autonomyReplyPtr = autonomyReply.get();
       connect(
           autonomyReplyPtr, &QGrpcCallReply::finished, this,
           [this, autonomyReply = std::move(autonomyReply)](const QGrpcStatus &status) {
               if (!status.isOk()) {
                   auto error = QString("Call error fetching autonomy %1 (%2)")
                                    .arg(status.message())
                                    .arg(QVariant::fromValue(status.code()).toString());
                   emit connectionError(error);
                   qWarning() << error;
                   return;
               }

               if (const auto autonomyMsg = autonomyReply->read<AutonomyMsg>()) {
                   emit autonomyChanged(autonomyMsg->autonomy());
               }
           },
           Qt::SingleShotConnection);

The client main window interface is defined in the Main.qml file. It uses QML Connections type in order to connect to the signals of the `VehicleManager` C++ singleton to custom slots:

         Connections {
             target: VehicleManager

             // This slot will be executed when the VehicleManager::totalDistanceChanged
             // signal is emitted
             function onTotalDistanceChanged(distance: int): void {
                 root.totalDistance = distance;
             }

             function onSpeedChanged(speed: int): void {
                 root.speed = speed;
             }

             function onRpmChanged(rpm: int): void {
                 root.rpm = rpm;
             }

             function onTraveledDistanceChanged(distance: int): void {
                 root.traveledDistance = distance;
             }

             function onDirectionChanged(direction: int): void {
                 if (direction == VehicleManager.RIGHT) {
                     root.directionImageSource = "qrc:/direction_right.svg";
                 } else if (direction == VehicleManager.LEFT) {
                     root.directionImageSource =  "qrc:/direction_left.svg";
                 } else if (direction == VehicleManager.STRAIGHT) {
                     root.directionImageSource =  "qrc:/direction_straight.svg";
                 } else {
                     root.directionImageSource =  "";
                 }
             }

After receiving a response, the client window updates the UI with the received data. This way, messages can be received in different threads and be sent to the client UI in a thread-safe way thanks to the signals.

Example project @ code.qt.io