Rework vehicle example
Improve QML UI, simplify server logic, add documentation Task-number: QTBUG-129571 Task-number: QTBUG-128468 Pick-to: 6.8 Change-Id: I46d04a033a7301b9c98a3e03861b9212e7a2378d Reviewed-by: Alexey Edelev <alexey.edelev@qt.io> Reviewed-by: Jaime Resano <Jaime.RESANO-AISA@qt.io>
|
@ -2,7 +2,7 @@
|
|||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(vehicle_cluster LANGUAGES CXX)
|
||||
project(vehicle LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
|
@ -20,58 +20,57 @@ find_package(Qt6 REQUIRED COMPONENTS
|
|||
|
||||
qt_standard_project_setup()
|
||||
|
||||
add_subdirectory(grpc_vehicle_server)
|
||||
|
||||
qt_add_executable(vehicle_cluster
|
||||
qt_add_executable(vehicle_client
|
||||
main.cpp
|
||||
clusterdatamanager.h
|
||||
clusterdatamanager.cpp
|
||||
vehiclemanager.h
|
||||
vehiclemanager.cpp
|
||||
vehiclethread.h
|
||||
vehiclethread.cpp
|
||||
navithread.h
|
||||
navithread.cpp
|
||||
navigationthread.h
|
||||
navigationthread.cpp
|
||||
)
|
||||
|
||||
qt_add_protobuf(vehiclelib
|
||||
PROTO_FILES
|
||||
proto/vehicleservice.proto
|
||||
proto/naviservice.proto
|
||||
proto/navigationservice.proto
|
||||
)
|
||||
|
||||
qt_add_grpc(vehiclelib CLIENT
|
||||
PROTO_FILES
|
||||
proto/vehicleservice.proto
|
||||
proto/naviservice.proto
|
||||
proto/navigationservice.proto
|
||||
)
|
||||
|
||||
target_link_libraries(vehiclelib PRIVATE Qt6::ProtobufWellKnownTypes)
|
||||
|
||||
set_target_properties(vehicle_cluster PROPERTIES
|
||||
set_target_properties(vehicle_client PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
qt_add_qml_module(vehicle_cluster
|
||||
qt_add_qml_module(vehicle_client
|
||||
URI qtgrpc.examples.vehicle
|
||||
VERSION 1.0
|
||||
RESOURCE_PREFIX "/qt/qml"
|
||||
QML_FILES
|
||||
"ClusterText.qml"
|
||||
"ClusterProgressBar.qml"
|
||||
"StyledText.qml"
|
||||
"StyledProgressBar.qml"
|
||||
"Main.qml"
|
||||
)
|
||||
|
||||
qt_add_resources(vehicle_cluster
|
||||
"vehicle_cluster"
|
||||
qt_add_resources(vehicle_client
|
||||
"resources"
|
||||
PREFIX "/"
|
||||
BASE "resources"
|
||||
FILES
|
||||
"left.png"
|
||||
"right.png"
|
||||
"forward.png"
|
||||
"fuel_lvl.png"
|
||||
"resources/direction_left.svg"
|
||||
"resources/direction_right.svg"
|
||||
"resources/direction_straight.svg"
|
||||
"resources/fuel_icon.svg"
|
||||
)
|
||||
|
||||
target_link_libraries(vehicle_cluster PRIVATE
|
||||
target_link_libraries(vehicle_client PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Quick
|
||||
Qt6::Protobuf
|
||||
|
@ -80,8 +79,13 @@ target_link_libraries(vehicle_cluster PRIVATE
|
|||
vehiclelib
|
||||
)
|
||||
|
||||
install(TARGETS vehicle_cluster vehiclelib
|
||||
install(TARGETS vehicle_client vehiclelib
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
||||
|
||||
add_subdirectory(server)
|
||||
if(TARGET vehicle_server)
|
||||
add_dependencies(vehicle_client vehicle_server)
|
||||
endif()
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property int currentBarValue: 0
|
||||
property int totalBarValue: 0
|
||||
|
||||
property string activeColor: ""
|
||||
property string bgColor: ""
|
||||
|
||||
width: 300
|
||||
height: 10
|
||||
|
||||
Rectangle {
|
||||
id: totalValue
|
||||
anchors.centerIn: parent
|
||||
width: 300
|
||||
height: 10
|
||||
radius: 80
|
||||
|
||||
color: root.bgColor
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
width: (root.currentBarValue && root.totalBarValue > 0)
|
||||
? totalValue.width * (root.currentBarValue/root.totalBarValue)
|
||||
: 0
|
||||
height: 10
|
||||
radius: 80
|
||||
color: root.activeColor
|
||||
}
|
||||
}
|
|
@ -3,299 +3,286 @@
|
|||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.VectorImage
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
import qtgrpc.examples.vehicle
|
||||
|
||||
ApplicationWindow {
|
||||
id: root
|
||||
width: 1280
|
||||
height: 518
|
||||
|
||||
minimumWidth: width
|
||||
maximumWidth: width
|
||||
minimumHeight: height
|
||||
maximumHeight: height
|
||||
|
||||
property int speed: 0
|
||||
property int rpm: 0
|
||||
property int totalDistance: 0
|
||||
property int remainingDistance: 0
|
||||
property int direction: ClusterDataManager.BACKWARD
|
||||
property int fuelLevel: 0
|
||||
property bool availableBtn: false
|
||||
property int speed: -1
|
||||
property int rpm: -1
|
||||
property int totalDistance: -1
|
||||
property int traveledDistance: -1
|
||||
property int estimatedAutonomy: -1
|
||||
property string directionImageSource: ""
|
||||
property string street: ""
|
||||
property string vehicleErrors: ""
|
||||
property string navigationErrors: ""
|
||||
|
||||
width: 1200
|
||||
height: 500
|
||||
visible: true
|
||||
title: qsTr("Cluster Qt GRPC Example")
|
||||
Material.theme: Material.Light
|
||||
title: qsTr("Vehicle Qt GRPC example")
|
||||
|
||||
//! [Connections]
|
||||
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 = "";
|
||||
}
|
||||
}
|
||||
//! [Connections]
|
||||
function onStreetChanged(street: string): void {
|
||||
root.street = street;
|
||||
}
|
||||
|
||||
function onAutonomyChanged(autonomy: int): void {
|
||||
root.estimatedAutonomy = autonomy;
|
||||
}
|
||||
|
||||
function onVehicleErrorsChanged(vehicleErrors: string): void {
|
||||
root.vehicleErrors = vehicleErrors;
|
||||
}
|
||||
|
||||
function onNavigationErrorsChanged(navigationErrors: string): void {
|
||||
root.navigationErrors = navigationErrors;
|
||||
}
|
||||
}
|
||||
|
||||
// Background
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#040a16"
|
||||
color: "#160404"
|
||||
}
|
||||
|
||||
Item {
|
||||
id: background
|
||||
// Information screen
|
||||
GridLayout {
|
||||
anchors.fill: parent
|
||||
visible: !root.availableBtn
|
||||
|
||||
Row {
|
||||
id: textsBar
|
||||
anchors.horizontalCenter: background.horizontalCenter
|
||||
anchors.bottom: progressBars.top
|
||||
spacing: 130
|
||||
|
||||
width: root.width - 110
|
||||
height: 200
|
||||
anchors.margins: 40
|
||||
visible: !(root.vehicleErrors && root.navigationErrors)
|
||||
columnSpacing: 20
|
||||
rowSpacing: 10
|
||||
columns: 2
|
||||
uniformCellWidths: true
|
||||
|
||||
Item {
|
||||
width: 300
|
||||
height: 200
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
ClusterText {
|
||||
width: 200
|
||||
height: 200
|
||||
anchors.bottom: parent.bottom
|
||||
StyledText {
|
||||
id: speedText
|
||||
anchors.left: parent.left
|
||||
|
||||
verticalAlignment: Text.AlignBottom
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.speed == -1 ? "-" : root.speed
|
||||
font.pointSize: 90
|
||||
text: root.speed
|
||||
}
|
||||
|
||||
ClusterText {
|
||||
width: 100
|
||||
height: 200
|
||||
StyledText {
|
||||
id: speedUnitText
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 27
|
||||
anchors.right: parent.right
|
||||
|
||||
verticalAlignment: Text.AlignBottom
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: "Kmph"
|
||||
text: "Km/h"
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 300
|
||||
height: 200
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
Image {
|
||||
id: arrow
|
||||
VectorImage {
|
||||
id: directionImage
|
||||
source: root.directionImageSource
|
||||
|
||||
width: 100
|
||||
height: 100
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 33
|
||||
source: root.getImage()
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
Timer {
|
||||
interval: 2000
|
||||
running: arrow.source !== ""
|
||||
repeat: true
|
||||
onTriggered: arrow.visible = !arrow.visible
|
||||
}
|
||||
}
|
||||
|
||||
ClusterText {
|
||||
width: 200
|
||||
height: 40
|
||||
anchors.bottom: street.top
|
||||
Rectangle {
|
||||
visible: root.directionImageSource
|
||||
anchors.fill: directionImage
|
||||
color: "#363636"
|
||||
radius: 10
|
||||
z: -1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.right: parent.right
|
||||
|
||||
verticalAlignment: Text.AlignBottom
|
||||
anchors.bottom: streetText.top
|
||||
font.pointSize: 24
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: Number(root.remainingDistance * 0.001).toFixed(2) + " km"
|
||||
text: {
|
||||
if (root.totalDistance == -1 || root.traveledDistance == -1) {
|
||||
return "- km";
|
||||
}
|
||||
|
||||
ClusterText {
|
||||
id: street
|
||||
width: 200
|
||||
height: 40
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 27
|
||||
anchors.right: parent.right
|
||||
let remainingDistance = root.totalDistance - root.traveledDistance;
|
||||
|
||||
verticalAlignment: Text.AlignBottom
|
||||
if (remainingDistance > 1000) {
|
||||
return `${Number(remainingDistance * 0.001).toFixed(2)} km`;
|
||||
}
|
||||
return `${remainingDistance} m`
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: streetText
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
horizontalAlignment: Text.AlignRight
|
||||
color: "#828284"
|
||||
text: (root.getImage() !== "") ? "Erich-Thilo St" : "None"
|
||||
text: root.street
|
||||
}
|
||||
}
|
||||
|
||||
StyledProgressBar {
|
||||
Layout.fillWidth: true
|
||||
value: root.speed
|
||||
to: 200
|
||||
activeColor: "#04e2ed"
|
||||
bgColor: "#023061"
|
||||
}
|
||||
|
||||
StyledProgressBar {
|
||||
Layout.fillWidth: true
|
||||
value: root.traveledDistance
|
||||
to: root.totalDistance != -1 ? root.totalDistance : 0
|
||||
activeColor: "#dde90000"
|
||||
bgColor: "#860000"
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
StyledText {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.rpm == -1 ? "-" : root.rpm
|
||||
font.pointSize: 60
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
text: "rpm"
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 300
|
||||
height: 200
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
ClusterText {
|
||||
id: rightSide
|
||||
width: 200
|
||||
height: 200
|
||||
VectorImage {
|
||||
id: fuelIcon
|
||||
width: 28
|
||||
height: 28
|
||||
source: "qrc:/fuel_icon.svg"
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: autonomyText.left
|
||||
anchors.rightMargin: 12
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: autonomyText
|
||||
text: `${root.estimatedAutonomy == -1 ? "-" : root.estimatedAutonomy} km`
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 27
|
||||
anchors.right: parent.right
|
||||
|
||||
verticalAlignment: Text.AlignBottom
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: root.rpm + " rpm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: progressBars
|
||||
|
||||
anchors.horizontalCenter: background.horizontalCenter
|
||||
anchors.verticalCenter: background.verticalCenter
|
||||
anchors.verticalCenterOffset: 50
|
||||
spacing: 130
|
||||
|
||||
width: root.width - 110
|
||||
height: 20
|
||||
|
||||
ClusterProgressBar {
|
||||
currentBarValue: root.speed;
|
||||
totalBarValue: 200
|
||||
activeColor: "#04e2ed"
|
||||
bgColor: "#023061"
|
||||
}
|
||||
ClusterProgressBar {
|
||||
currentBarValue: root.remainingDistance;
|
||||
totalBarValue: root.totalDistance
|
||||
activeColor: "#04e2ed"
|
||||
bgColor: "#023061"
|
||||
}
|
||||
ClusterProgressBar {
|
||||
currentBarValue: root.rpm;
|
||||
totalBarValue: 9000
|
||||
StyledProgressBar {
|
||||
Layout.fillWidth: true
|
||||
value: root.rpm
|
||||
to: 9000
|
||||
activeColor: "#f8c607"
|
||||
bgColor: "#5f3f04"
|
||||
}
|
||||
}
|
||||
|
||||
ClusterProgressBar {
|
||||
id: fuelLevel
|
||||
anchors.leftMargin: 55
|
||||
anchors.left: parent.left
|
||||
anchors.topMargin: 100
|
||||
anchors.top: progressBars.bottom
|
||||
currentBarValue: root.fuelLevel
|
||||
totalBarValue: 250
|
||||
StyledProgressBar {
|
||||
Layout.fillWidth: true
|
||||
value: root.estimatedAutonomy
|
||||
to: 250
|
||||
activeColor: "#05c848"
|
||||
bgColor: "#03511f"
|
||||
}
|
||||
|
||||
ClusterText {
|
||||
id: miles
|
||||
anchors.bottom: fuelLevel.bottom
|
||||
anchors.bottomMargin: 27
|
||||
anchors.right: fuelLevel.right
|
||||
|
||||
verticalAlignment: Text.AlignBottom
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: root.fuelLevel + " Km"
|
||||
}
|
||||
|
||||
Image {
|
||||
anchors.bottom: fuelLevel.bottom
|
||||
anchors.bottomMargin: 35
|
||||
anchors.left: fuelLevel.left
|
||||
|
||||
source:"qrc:/fuel_lvl.png"
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
visible: root.availableBtn
|
||||
ClusterText {
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 60
|
||||
anchors.right: restartBtn.left
|
||||
anchors.rightMargin: 10
|
||||
|
||||
width: 200
|
||||
height: 80
|
||||
font.pointSize: 14
|
||||
visible: root.availableBtn
|
||||
text: "Please, start server and press run."
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: restartBtn
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 60
|
||||
|
||||
width: 100
|
||||
height: 50
|
||||
radius: 80
|
||||
color: "#040a16"
|
||||
border.color: btn.btnPressed ? "#828284" : "white"
|
||||
border.width: 2
|
||||
visible: root.availableBtn
|
||||
|
||||
ClusterText {
|
||||
// No connection error screen
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
color: btn.btnPressed ? "#828284" : "white"
|
||||
text: "RUN"
|
||||
visible: root.vehicleErrors && root.navigationErrors
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
font.pointSize: 14
|
||||
text: qsTr("Please, start vehicle server. Then, press try again.")
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: btn
|
||||
property bool btnPressed: false
|
||||
anchors.fill: parent
|
||||
Button {
|
||||
id: runButton
|
||||
property string baseColor: "white"
|
||||
property string clickedColor: "#828284"
|
||||
property string hoverColor: "#a9a9a9"
|
||||
|
||||
enabled: root.availableBtn
|
||||
onClicked: ClusterDataManager.restart()
|
||||
onPressed: btnPressed = true
|
||||
onReleased: btnPressed = false
|
||||
}
|
||||
}
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
background: Rectangle {
|
||||
border.color: runButton.down ?
|
||||
runButton.clickedColor
|
||||
: (runButton.hovered ? runButton.hoverColor : runButton.baseColor)
|
||||
border.width: 2
|
||||
radius: 2
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
function getImage() {
|
||||
switch (root.direction) {
|
||||
case ClusterDataManager.RIGHT:
|
||||
return "qrc:/right.png"
|
||||
case ClusterDataManager.LEFT:
|
||||
return "qrc:/left.png"
|
||||
case ClusterDataManager.STRAIGHT:
|
||||
return "qrc:/forward.png"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
contentItem: Text {
|
||||
text: qsTr("Try again")
|
||||
font.pointSize: 16
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: runButton.down ?
|
||||
runButton.clickedColor
|
||||
: (runButton.hovered ? runButton.hoverColor : runButton.baseColor)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ClusterDataManager
|
||||
|
||||
function onTotalDistanceChanged(distance) {
|
||||
root.totalDistance = distance
|
||||
}
|
||||
|
||||
function onSpeedChanged(speed) {
|
||||
root.speed = speed
|
||||
}
|
||||
|
||||
function onRpmChanged(rpm) {
|
||||
root.rpm = rpm
|
||||
}
|
||||
|
||||
function onRemainingDistanceChanged(distance) {
|
||||
root.remainingDistance = distance
|
||||
}
|
||||
|
||||
function onDirectionChanged(direction) {
|
||||
root.direction = direction
|
||||
}
|
||||
|
||||
function onFuelLevelChanged(level) {
|
||||
root.fuelLevel = level
|
||||
}
|
||||
|
||||
function onShowRestartClient(value) {
|
||||
root.availableBtn = value
|
||||
onClicked: {
|
||||
root.vehicleErrors = "";
|
||||
root.navigationErrors = "";
|
||||
VehicleManager.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
ProgressBar {
|
||||
id: root
|
||||
|
||||
property string activeColor: ""
|
||||
property string bgColor: ""
|
||||
|
||||
height: 10
|
||||
|
||||
background: Rectangle {
|
||||
anchors.fill: root
|
||||
radius: 2
|
||||
color: root.bgColor
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
// Progress indicator for determinate state.
|
||||
Rectangle {
|
||||
visible: !root.indeterminate
|
||||
width: root.visualPosition * root.width
|
||||
height: root.height
|
||||
radius: 2
|
||||
color: root.activeColor
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 500
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scrolling animation for indeterminate state.
|
||||
Item {
|
||||
visible: root.indeterminate
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
Row {
|
||||
spacing: 20
|
||||
|
||||
Repeater {
|
||||
model: root.width / 40 + 1
|
||||
|
||||
Rectangle {
|
||||
color: root.activeColor
|
||||
width: 20
|
||||
height: parent.height
|
||||
}
|
||||
}
|
||||
|
||||
XAnimator on x {
|
||||
from: 0
|
||||
to: -40
|
||||
loops: Animation.Infinite
|
||||
running: root.indeterminate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,9 +4,7 @@
|
|||
import QtQuick
|
||||
|
||||
Text {
|
||||
wrapMode: Text.WordWrap
|
||||
font.family: "Helvetica"
|
||||
color: "white"
|
||||
style: Text.Sunken
|
||||
font.pointSize: 18
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "clusterdatamanager.h"
|
||||
#include "navithread.h"
|
||||
#include "vehiclethread.h"
|
||||
|
||||
using namespace qtgrpc::examples;
|
||||
ClusterDataManager::ClusterDataManager(QObject *parent) : QObject(parent)
|
||||
{
|
||||
startVehicleClient();
|
||||
startNaviClient();
|
||||
}
|
||||
|
||||
ClusterDataManager::~ClusterDataManager()
|
||||
{
|
||||
if (m_vehicleThread->isRunning()) {
|
||||
m_vehicleThread->quit();
|
||||
m_vehicleThread->wait();
|
||||
}
|
||||
if (m_naviThread && m_naviThread->isRunning()) {
|
||||
m_naviThread->quit();
|
||||
m_naviThread->wait();
|
||||
}
|
||||
}
|
||||
|
||||
void ClusterDataManager::startNaviClient()
|
||||
{
|
||||
m_naviThread = std::make_unique<NaviThread>();
|
||||
connect(m_naviThread.get(), &NaviThread::totalDistanceChanged, this,
|
||||
&ClusterDataManager::totalDistanceChanged, Qt::QueuedConnection);
|
||||
connect(m_naviThread.get(), &NaviThread::remainingDistanceChanged, this,
|
||||
&ClusterDataManager::remainingDistanceChanged, Qt::QueuedConnection);
|
||||
connect(
|
||||
m_naviThread.get(), &NaviThread::directionChanged, this,
|
||||
[this](qtgrpc::examples::DirectionEnumGadget::DirectionEnum direction) {
|
||||
ClusterDataManager::directionChanged(qToUnderlying(direction));
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
m_naviThread->start();
|
||||
}
|
||||
|
||||
void ClusterDataManager::startVehicleClient()
|
||||
{
|
||||
m_vehicleThread = std::make_unique<VehicleThread>();
|
||||
connect(m_vehicleThread.get(), &VehicleThread::speedChanged, this,
|
||||
&ClusterDataManager::speedChanged, Qt::QueuedConnection);
|
||||
connect(m_vehicleThread.get(), &VehicleThread::fuelLevelChanged, this,
|
||||
&ClusterDataManager::fuelLevelChanged, Qt::QueuedConnection);
|
||||
connect(m_vehicleThread.get(), &VehicleThread::rpmChanged, this,
|
||||
&ClusterDataManager::rpmChanged, Qt::QueuedConnection);
|
||||
connect(m_vehicleThread.get(), &VehicleThread::connectionError, this,
|
||||
&ClusterDataManager::setThreadsAvailable, Qt::QueuedConnection);
|
||||
|
||||
m_vehicleThread->start();
|
||||
}
|
||||
|
||||
void ClusterDataManager::setThreadsAvailable(bool value)
|
||||
{
|
||||
if (m_threadsAvailable != value) {
|
||||
m_threadsAvailable = value;
|
||||
emit showRestartClient(m_threadsAvailable);
|
||||
}
|
||||
}
|
||||
|
||||
void ClusterDataManager::restart()
|
||||
{
|
||||
setThreadsAvailable(false);
|
||||
if (m_vehicleThread->isRunning()) {
|
||||
m_vehicleThread->quit();
|
||||
m_vehicleThread->wait();
|
||||
m_vehicleThread->start();
|
||||
}
|
||||
|
||||
if (m_naviThread->isRunning()) {
|
||||
m_naviThread->quit();
|
||||
m_naviThread->wait();
|
||||
m_naviThread->start();
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef CLUSTERDATAMANAGER_H
|
||||
#define CLUSTERDATAMANAGER_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QtTypeTraits>
|
||||
#include <QtQml/QQmlEngine>
|
||||
#include <naviservice_client.grpc.qpb.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class VehicleThread;
|
||||
class NaviThread;
|
||||
class ClusterDataManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
Q_PROPERTY(bool threadsAvailable READ threadsAvailable WRITE setThreadsAvailable NOTIFY
|
||||
showRestartClient FINAL)
|
||||
public:
|
||||
enum NaviDirection {
|
||||
RIGHT = qToUnderlying(qtgrpc::examples::DirectionEnumGadget::DirectionEnum::RIGHT),
|
||||
LEFT = qToUnderlying(qtgrpc::examples::DirectionEnumGadget::DirectionEnum::LEFT),
|
||||
STRAIGHT = qToUnderlying(qtgrpc::examples::DirectionEnumGadget::DirectionEnum::STRAIGHT),
|
||||
BACKWARD = qToUnderlying(qtgrpc::examples::DirectionEnumGadget::DirectionEnum::BACKWARD)
|
||||
};
|
||||
|
||||
Q_ENUM(NaviDirection);
|
||||
|
||||
explicit ClusterDataManager(QObject *parent = nullptr);
|
||||
~ClusterDataManager() override;
|
||||
|
||||
Q_INVOKABLE void restart();
|
||||
|
||||
bool threadsAvailable() const { return m_threadsAvailable; }
|
||||
void setThreadsAvailable(bool value);
|
||||
|
||||
signals:
|
||||
void speedChanged(int speed);
|
||||
void rpmChanged(int rpm);
|
||||
void fuelLevelChanged(int level);
|
||||
|
||||
void totalDistanceChanged(int distance);
|
||||
void remainingDistanceChanged(int distance);
|
||||
void directionChanged(int direction);
|
||||
|
||||
void showRestartClient(bool value);
|
||||
|
||||
private:
|
||||
void startVehicleClient();
|
||||
void startNaviClient();
|
||||
|
||||
std::unique_ptr<NaviThread> m_naviThread;
|
||||
std::unique_ptr<VehicleThread> m_vehicleThread;
|
||||
|
||||
bool m_threadsAvailable = false;
|
||||
};
|
||||
|
||||
#endif // CLUSTERDATAMANAGER_H
|
After Width: | Height: | Size: 26 KiB |
|
@ -0,0 +1,60 @@
|
|||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example vehicle
|
||||
\ingroup qtgrpc-examples
|
||||
\examplecategory {Networking}
|
||||
\meta tag {network,protobuf,grpc,threading}
|
||||
\title Vehicle
|
||||
|
||||
\brief 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.
|
||||
\image vehicle.webp "Vehicle example dashboard screenshot"
|
||||
|
||||
The example code has the following components:
|
||||
\list
|
||||
\li \c vehicle_client Qt gRPC client application that uses the
|
||||
\l {qt_add_protobuf}{qt_add_protobuf()} and \l {qt_add_grpc}{qt_add_grpc()} CMake functions
|
||||
for message and service Qt code generation.
|
||||
\li \c vehicle_server application that calls C++ gRPC plugin for generating server code and
|
||||
implementing simple server logic.
|
||||
\endlist
|
||||
|
||||
\note you need the C++ gRPC plugin installed.
|
||||
Find details here: \l {Module prerequisites}
|
||||
|
||||
Both components use generated messages from the protobuf schemas described in the files
|
||||
\c {vehicleservice.proto} and \c {navigationservice.proto}.
|
||||
|
||||
Vehicle service:
|
||||
\snippet vehicle/proto/vehicleservice.proto Proto types
|
||||
|
||||
Navigation service:
|
||||
\snippet vehicle/proto/navigationservice.proto Proto types
|
||||
|
||||
The \c VehicleManager \l {Singletons in QML#Defining singletons in C++} {C++ singleton} uses
|
||||
two \l QThread instances to communicate with the server in parallel. The threads have different
|
||||
gRPC connection types. In this example, there are two types:
|
||||
\list
|
||||
\li Server streaming RPCs
|
||||
For example, the speed stream of the vehicle thread. It uses two callback functions:
|
||||
QGrpcServerStream::messageReceived and QGrpcOperation::finished
|
||||
\snippet vehicle/vehiclethread.cpp Speed stream
|
||||
|
||||
\li Unary RPCs
|
||||
The RPC \c getAutonomy operation is a unary RPC. It returns a single response. It is only
|
||||
connected to the QGrpcOperation::finished signal.
|
||||
\snippet vehicle/vehiclethread.cpp Autonomy call
|
||||
\endlist
|
||||
|
||||
The client main window interface is defined in the Main.qml file. It uses QML \l Connections
|
||||
type in order to connect to the signals of the \c VehicleManager
|
||||
\l {Singletons in QML#Defining singletons in C++} {C++ singleton} to custom slots:
|
||||
\snippet vehicle/Main.qml Connections
|
||||
|
||||
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.
|
||||
*/
|
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 939 B |
|
@ -1,13 +0,0 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "serverrunner.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
int main()
|
||||
{
|
||||
auto server = std::make_unique<VehicleServer>();
|
||||
server->run();
|
||||
return 0;
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "serverrunner.h"
|
||||
#include "naviservice.grpc.pb.h"
|
||||
#include "vehicleservice.grpc.pb.h"
|
||||
|
||||
#include <grpc++/grpc++.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
namespace {
|
||||
|
||||
using grpc::Server;
|
||||
using grpc::ServerBuilder;
|
||||
using grpc::ServerContext;
|
||||
using grpc::ServerWriter;
|
||||
using grpc::Status;
|
||||
using qtgrpc::examples::DistanceMsg;
|
||||
using qtgrpc::examples::FuelLevelMsg;
|
||||
using qtgrpc::examples::GearMsg;
|
||||
using qtgrpc::examples::NaviService;
|
||||
using qtgrpc::examples::SpeedMsg;
|
||||
using qtgrpc::examples::VehicleService;
|
||||
|
||||
class VehicleServiceServiceImpl final : public qtgrpc::examples::VehicleService::Service
|
||||
{
|
||||
grpc::Status getSpeedStream(grpc::ServerContext *, const ::google::protobuf::Empty *,
|
||||
ServerWriter<SpeedMsg> *writer) override;
|
||||
grpc::Status getGearStream(grpc::ServerContext *, const ::google::protobuf::Empty *,
|
||||
ServerWriter<GearMsg> *writer) override;
|
||||
grpc::Status getFuelLevel(grpc::ServerContext *, const ::google::protobuf::Empty *,
|
||||
FuelLevelMsg *response) override;
|
||||
|
||||
constexpr static std::chrono::seconds vehicleTimeout = std::chrono::seconds(1);
|
||||
int speed = 0;
|
||||
int rpm = 100;
|
||||
};
|
||||
|
||||
class NaviServiceServiceImpl final : public qtgrpc::examples::NaviService::Service
|
||||
{
|
||||
grpc::Status getNaviStream(grpc::ServerContext *, const ::google::protobuf::Empty *,
|
||||
ServerWriter<DistanceMsg> *writer) override;
|
||||
constexpr static std::chrono::seconds naviTimeout = std::chrono::seconds(2);
|
||||
int totaldistance = 2000;
|
||||
int remainingdistance = 0;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Status NaviServiceServiceImpl::getNaviStream(grpc::ServerContext *,
|
||||
const ::google::protobuf::Empty *,
|
||||
ServerWriter<DistanceMsg> *writer)
|
||||
{
|
||||
volatile bool naviRequired = false;
|
||||
DistanceMsg msg;
|
||||
msg.set_totaldistance(totaldistance);
|
||||
std::this_thread::sleep_for(naviTimeout);
|
||||
naviRequired = writer->Write(msg);
|
||||
|
||||
while (naviRequired) {
|
||||
if (remainingdistance < totaldistance) {
|
||||
msg.set_remainingdistance(remainingdistance += 50);
|
||||
} else {
|
||||
msg.set_remainingdistance(0);
|
||||
remainingdistance = 0;
|
||||
}
|
||||
std::this_thread::sleep_for(naviTimeout);
|
||||
naviRequired = writer->Write(msg);
|
||||
|
||||
switch (remainingdistance) {
|
||||
case 0: {
|
||||
msg.set_direction(qtgrpc::examples::DirectionEnum::STRAIGHT);
|
||||
} break;
|
||||
case 50: {
|
||||
msg.set_direction(qtgrpc::examples::DirectionEnum::RIGHT);
|
||||
} break;
|
||||
case 900: {
|
||||
msg.set_direction(qtgrpc::examples::DirectionEnum::LEFT);
|
||||
} break;
|
||||
}
|
||||
std::this_thread::sleep_for(naviTimeout);
|
||||
naviRequired = writer->Write(msg);
|
||||
}
|
||||
|
||||
return Status();
|
||||
}
|
||||
grpc::Status VehicleServiceServiceImpl::getFuelLevel(grpc::ServerContext *,
|
||||
const ::google::protobuf::Empty *,
|
||||
FuelLevelMsg *response)
|
||||
{
|
||||
response->set_fuellevel(152);
|
||||
return Status();
|
||||
}
|
||||
|
||||
Status VehicleServiceServiceImpl::getSpeedStream(grpc::ServerContext *,
|
||||
const ::google::protobuf::Empty *,
|
||||
ServerWriter<SpeedMsg> *writer)
|
||||
{
|
||||
SpeedMsg msg;
|
||||
volatile bool vehicleRequired = true;
|
||||
while (vehicleRequired) {
|
||||
if (speed < 180) {
|
||||
msg.set_speed(speed += 10);
|
||||
} else {
|
||||
msg.set_speed(speed -= 10);
|
||||
}
|
||||
std::this_thread::sleep_for(vehicleTimeout);
|
||||
vehicleRequired = writer->Write(msg);
|
||||
}
|
||||
return Status();
|
||||
}
|
||||
|
||||
Status VehicleServiceServiceImpl::getGearStream(grpc::ServerContext *,
|
||||
const ::google::protobuf::Empty *,
|
||||
ServerWriter<GearMsg> *writer)
|
||||
{
|
||||
GearMsg msg;
|
||||
volatile bool vehicleRequired = true;
|
||||
while (vehicleRequired) {
|
||||
if (rpm < 3800) {
|
||||
msg.set_rpm(rpm += 200);
|
||||
} else {
|
||||
msg.set_rpm(rpm -= 200);
|
||||
}
|
||||
std::this_thread::sleep_for(vehicleTimeout);
|
||||
vehicleRequired = writer->Write(msg);
|
||||
}
|
||||
|
||||
return Status();
|
||||
}
|
||||
|
||||
void VehicleServer::run()
|
||||
{
|
||||
std::string serverUri("127.0.0.1:50051");
|
||||
VehicleServiceServiceImpl vehicleService;
|
||||
NaviServiceServiceImpl naviService;
|
||||
|
||||
grpc::ServerBuilder builder;
|
||||
builder.AddListeningPort(serverUri, grpc::InsecureServerCredentials());
|
||||
builder.RegisterService(&vehicleService);
|
||||
builder.RegisterService(&naviService);
|
||||
|
||||
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
|
||||
if (!server) {
|
||||
std::cout << "Creating grpc::Server failed." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Server listening on " << serverUri << std::endl;
|
||||
server->Wait();
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef SERVER_RUNNER_H
|
||||
#define SERVER_RUNNER_H
|
||||
|
||||
class VehicleServer
|
||||
{
|
||||
public:
|
||||
void run();
|
||||
};
|
||||
|
||||
#endif // SERVER_RUNNER_H
|
Before Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,68 @@
|
|||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "navigationthread.h"
|
||||
#include <navigationservice_client.grpc.qpb.h>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtGrpc/QGrpcChannelOptions>
|
||||
#include <QtGrpc/QGrpcHttp2Channel>
|
||||
|
||||
using namespace qtgrpc::examples;
|
||||
using namespace google::protobuf;
|
||||
|
||||
NavigationThread::NavigationThread(QObject *parent) : QThread(parent)
|
||||
{
|
||||
}
|
||||
|
||||
NavigationThread::~NavigationThread() = default;
|
||||
|
||||
void NavigationThread::run()
|
||||
{
|
||||
if (!m_client) {
|
||||
auto channel = std::shared_ptr<
|
||||
QAbstractGrpcChannel>(new QGrpcHttp2Channel(QUrl("http://localhost:50051",
|
||||
QUrl::StrictMode)));
|
||||
m_client = std::make_shared<qtgrpc::examples::NavigationService::Client>();
|
||||
m_client->attachChannel(channel);
|
||||
}
|
||||
|
||||
Empty request;
|
||||
m_stream = m_client->getNavigationStream(request);
|
||||
|
||||
connect(m_stream.get(), &QGrpcServerStream::messageReceived, this, [this] {
|
||||
const auto result = m_stream->read<NavigationMsg>();
|
||||
if (!result)
|
||||
return;
|
||||
|
||||
emit totalDistanceChanged(result->totalDistance());
|
||||
emit traveledDistanceChanged(result->traveledDistance());
|
||||
emit directionChanged(result->direction());
|
||||
emit streetChanged(result->street());
|
||||
});
|
||||
|
||||
connect(
|
||||
m_stream.get(), &QGrpcServerStream::finished, this,
|
||||
[this](const QGrpcStatus &status) {
|
||||
if (!status.isOk()) {
|
||||
auto error = QString("Stream error fetching navigation %1 (%2)")
|
||||
.arg(status.message())
|
||||
.arg(QVariant::fromValue(status.code()).toString());
|
||||
emit connectionError(error);
|
||||
qWarning() << error;
|
||||
return;
|
||||
}
|
||||
|
||||
emit totalDistanceChanged(0);
|
||||
emit traveledDistanceChanged(0);
|
||||
emit directionChanged(DirectionEnumGadget::DirectionEnum::BACKWARD);
|
||||
m_stream.reset();
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
|
||||
QThread::run();
|
||||
|
||||
// Delete the NavigationService::Client object to shut down the connection
|
||||
m_client.reset();
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef NAVIGATIONTHREAD_H
|
||||
#define NAVIGATIONTHREAD_H
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QThread>
|
||||
#include <memory>
|
||||
#include <navigationservice_client.grpc.qpb.h>
|
||||
|
||||
namespace qtgrpc::examples {
|
||||
|
||||
class NavigationThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NavigationThread(QObject *parent = nullptr);
|
||||
~NavigationThread() override;
|
||||
|
||||
void run() override;
|
||||
|
||||
signals:
|
||||
void totalDistanceChanged(int distance);
|
||||
void traveledDistanceChanged(int distance);
|
||||
void directionChanged(qtgrpc::examples::DirectionEnumGadget::DirectionEnum direction);
|
||||
void streetChanged(QString street);
|
||||
|
||||
void connectionError(QString error);
|
||||
|
||||
private:
|
||||
std::unique_ptr<QGrpcServerStream> m_stream;
|
||||
std::shared_ptr<qtgrpc::examples::NavigationService::Client> m_client;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // NAVIGATIONTHREAD_H
|
|
@ -1,51 +0,0 @@
|
|||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "navithread.h"
|
||||
#include <naviservice_client.grpc.qpb.h>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtGrpc/QGrpcChannelOptions>
|
||||
#include <QtGrpc/QGrpcHttp2Channel>
|
||||
|
||||
using namespace qtgrpc::examples;
|
||||
using namespace google::protobuf;
|
||||
NaviThread::NaviThread(QObject *parent) : QThread(parent)
|
||||
{
|
||||
}
|
||||
|
||||
NaviThread::~NaviThread() = default;
|
||||
|
||||
void NaviThread::run()
|
||||
{
|
||||
if (!m_client) {
|
||||
auto channel = std::shared_ptr<
|
||||
QAbstractGrpcChannel>(new QGrpcHttp2Channel(QUrl("http://localhost:50051",
|
||||
QUrl::StrictMode)));
|
||||
m_client = std::make_shared<qtgrpc::examples::NaviService::Client>();
|
||||
m_client->attachChannel(channel);
|
||||
}
|
||||
|
||||
Empty request;
|
||||
m_stream = m_client->getNaviStream(request);
|
||||
connect(m_stream.get(), &QGrpcServerStream::messageReceived, this, [this] {
|
||||
const auto result = m_stream->read<DistanceMsg>();
|
||||
if (!result)
|
||||
return;
|
||||
emit totalDistanceChanged(result->totalDistance());
|
||||
emit remainingDistanceChanged(result->remainingDistance());
|
||||
emit directionChanged(result->direction());
|
||||
});
|
||||
|
||||
connect(m_stream.get(), &QGrpcServerStream::finished, this, [this] (const QGrpcStatus &status) {
|
||||
if (status.code() != QtGrpc::StatusCode::Ok)
|
||||
qWarning() << "Stream error(" << status.code() << "):" << status.message();
|
||||
|
||||
emit totalDistanceChanged(0);
|
||||
emit remainingDistanceChanged(0);
|
||||
emit directionChanged(DirectionEnumGadget::DirectionEnum::BACKWARD);
|
||||
});
|
||||
|
||||
QThread::run();
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef NAVITHREAD_H
|
||||
#define NAVITHREAD_H
|
||||
|
||||
#include <QtCore/QThread>
|
||||
#include <memory>
|
||||
#include <naviservice_client.grpc.qpb.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QGrpcServerStream;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class NaviThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NaviThread(QObject *parent = nullptr);
|
||||
~NaviThread() override;
|
||||
|
||||
void run() override;
|
||||
signals:
|
||||
void totalDistanceChanged(int distance);
|
||||
void remainingDistanceChanged(int distance);
|
||||
void directionChanged(qtgrpc::examples::DirectionEnumGadget::DirectionEnum direction);
|
||||
|
||||
private:
|
||||
std::shared_ptr<QGrpcServerStream> m_stream;
|
||||
std::shared_ptr<qtgrpc::examples::NaviService::Client> m_client;
|
||||
};
|
||||
|
||||
#endif // NAVITHREAD_H
|
|
@ -5,12 +5,7 @@ syntax = "proto3";
|
|||
package qtgrpc.examples;
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
message DistanceMsg {
|
||||
int32 totalDistance = 1;
|
||||
int32 remainingDistance = 2;
|
||||
DirectionEnum direction = 3;
|
||||
}
|
||||
|
||||
//! [Proto types]
|
||||
enum DirectionEnum {
|
||||
RIGHT = 0;
|
||||
LEFT = 1;
|
||||
|
@ -18,6 +13,14 @@ enum DirectionEnum {
|
|||
BACKWARD = 3;
|
||||
}
|
||||
|
||||
service NaviService {
|
||||
rpc getNaviStream(google.protobuf.Empty) returns (stream DistanceMsg) {}
|
||||
message NavigationMsg {
|
||||
int32 totalDistance = 1;
|
||||
int32 traveledDistance = 2;
|
||||
DirectionEnum direction = 3;
|
||||
string street = 4;
|
||||
}
|
||||
|
||||
service NavigationService {
|
||||
rpc getNavigationStream(google.protobuf.Empty) returns (stream NavigationMsg) {}
|
||||
}
|
||||
//! [Proto types]
|
|
@ -5,20 +5,23 @@ syntax = "proto3";
|
|||
package qtgrpc.examples;
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
|
||||
//! [Proto types]
|
||||
message SpeedMsg {
|
||||
int32 speed = 1;
|
||||
}
|
||||
|
||||
message GearMsg {
|
||||
message RpmMsg {
|
||||
int32 rpm = 1;
|
||||
}
|
||||
|
||||
message FuelLevelMsg {
|
||||
int32 fuelLevel = 1;
|
||||
message AutonomyMsg {
|
||||
int32 autonomy = 1;
|
||||
}
|
||||
|
||||
service VehicleService {
|
||||
rpc getSpeedStream(google.protobuf.Empty) returns (stream SpeedMsg) {}
|
||||
rpc getGearStream(google.protobuf.Empty) returns (stream GearMsg) {}
|
||||
rpc getFuelLevel(google.protobuf.Empty) returns (FuelLevelMsg) {}
|
||||
rpc getRpmStream(google.protobuf.Empty) returns (stream RpmMsg) {}
|
||||
rpc getAutonomy(google.protobuf.Empty) returns (AutonomyMsg) {}
|
||||
}
|
||||
//! [Proto types]
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed">
|
||||
<path d="M600-160v-360H272l64 64-56 56-160-160 160-160 56 56-64 64h328q33 0 56.5 23.5T680-520v360h-80Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 226 B |
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed">
|
||||
<path d="M280-160v-360q0-33 23.5-56.5T360-600h328l-64-64 56-56 160 160-160 160-56-56 64-64H360v360h-80Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 227 B |
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed">
|
||||
<path d="M440-120v-567l-64 63-56-56 160-160 160 160-56 56-64-63v567h-80Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 196 B |
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed">
|
||||
<path
|
||||
d="M160-120v-640q0-33 23.5-56.5T240-840h240q33 0 56.5 23.5T560-760v280h40q33 0 56.5 23.5T680-400v180q0 17 11.5 28.5T720-180q17 0 28.5-11.5T760-220v-288q-9 5-19 6.5t-21 1.5q-42 0-71-29t-29-71q0-32 17.5-57.5T684-694l-84-84 42-42 148 144q15 15 22.5 35t7.5 41v380q0 42-29 71t-71 29q-42 0-71-29t-29-71v-200h-60v300H160Zm80-440h240v-200H240v200Zm480 0q17 0 28.5-11.5T760-600q0-17-11.5-28.5T720-640q-17 0-28.5 11.5T680-600q0 17 11.5 28.5T720-560ZM240-200h240v-280H240v280Zm240 0H240h240Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 618 B |
Before Width: | Height: | Size: 1.2 KiB |
|
@ -2,18 +2,16 @@
|
|||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(VehicleServerRunner LANGUAGES CXX)
|
||||
project(VehicleServer LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
|
||||
|
||||
# Qt6::Grpc module is not used directly in this project. But this allows to find Qt6::Grpc's
|
||||
# dependencies without setting extra cmake module paths.
|
||||
find_package(protobuf)
|
||||
find_package(gRPC)
|
||||
|
||||
if(NOT TARGET gRPC::grpc_cpp_plugin OR NOT TARGET WrapProtoc::WrapProtoc
|
||||
OR NOT TARGET gRPC::grpc++)
|
||||
message(WARNING "Dependencies of SimpleVehicleServer not found. Skipping.")
|
||||
message(WARNING "Dependencies of VehicleServer not found. Skipping.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
|
@ -23,16 +21,19 @@ if(MINGW)
|
|||
" guaranteed otherwise.")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(proto_files
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../proto/vehicleservice.proto"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../proto/naviservice.proto")
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../proto/navigationservice.proto")
|
||||
set(out_dir ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
set(generated_files
|
||||
"${out_dir}/vehicleservice.pb.h" "${out_dir}/vehicleservice.pb.cc"
|
||||
"${out_dir}/vehicleservice.grpc.pb.h" "${out_dir}/vehicleservice.grpc.pb.cc"
|
||||
"${out_dir}/naviservice.pb.h" "${out_dir}/naviservice.pb.cc"
|
||||
"${out_dir}/naviservice.grpc.pb.h" "${out_dir}/naviservice.grpc.pb.cc")
|
||||
"${out_dir}/navigationservice.pb.h" "${out_dir}/navigationservice.pb.cc"
|
||||
"${out_dir}/navigationservice.grpc.pb.h" "${out_dir}/navigationservice.grpc.pb.cc")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${generated_files}
|
||||
|
@ -53,24 +54,22 @@ add_custom_command(
|
|||
|
||||
set_source_files_properties(${generated_files} PROPERTIES GENERATED TRUE)
|
||||
|
||||
add_executable(SimpleVehicleServer
|
||||
qt_add_executable(vehicle_server
|
||||
${generated_files}
|
||||
${CMAKE_CURRENT_LIST_DIR}/serverrunner.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/serverrunner.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/main.cpp
|
||||
)
|
||||
|
||||
target_include_directories(SimpleVehicleServer
|
||||
target_include_directories(vehicle_server
|
||||
PRIVATE
|
||||
${out_dir}
|
||||
)
|
||||
|
||||
target_link_libraries(SimpleVehicleServer PRIVATE
|
||||
target_link_libraries(vehicle_server PRIVATE
|
||||
protobuf::libprotobuf
|
||||
gRPC::grpc++
|
||||
)
|
||||
|
||||
install(TARGETS SimpleVehicleServer
|
||||
install(TARGETS vehicle_server
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
|
@ -0,0 +1,157 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "navigationservice.grpc.pb.h"
|
||||
#include "vehicleservice.grpc.pb.h"
|
||||
|
||||
#include <grpc++/grpc++.h>
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
using namespace qtgrpc::examples;
|
||||
|
||||
class VehicleServiceImpl final : public VehicleService::Service
|
||||
{
|
||||
constexpr static auto speedStreamPeriod = std::chrono::milliseconds(250);
|
||||
constexpr static auto rpmStreamPeriod = std::chrono::milliseconds(400);
|
||||
|
||||
int speed = 0;
|
||||
int rpm = 900;
|
||||
int autonomy = 162;
|
||||
|
||||
grpc::Status getSpeedStream(grpc::ServerContext *, const ::google::protobuf::Empty *,
|
||||
grpc::ServerWriter<SpeedMsg> *writer) override
|
||||
{
|
||||
SpeedMsg msg;
|
||||
bool speedRequired = true;
|
||||
bool speedIncreasing = true;
|
||||
|
||||
while (speedRequired) {
|
||||
if (speed > 180) {
|
||||
speedIncreasing = false;
|
||||
} else if (speed < 60) {
|
||||
speedIncreasing = true;
|
||||
}
|
||||
|
||||
int deltaSpeed = rand() % 6 + 1; // Random number between 1 and 6
|
||||
if (speedIncreasing) {
|
||||
speed += deltaSpeed;
|
||||
} else {
|
||||
speed -= deltaSpeed;
|
||||
}
|
||||
|
||||
msg.set_speed(speed);
|
||||
std::this_thread::sleep_for(speedStreamPeriod);
|
||||
speedRequired = writer->Write(msg);
|
||||
}
|
||||
return grpc::Status();
|
||||
}
|
||||
|
||||
grpc::Status getRpmStream(grpc::ServerContext *, const ::google::protobuf::Empty *,
|
||||
grpc::ServerWriter<RpmMsg> *writer) override
|
||||
{
|
||||
RpmMsg msg;
|
||||
bool rpmRequired = true;
|
||||
bool rpmIncreasing = true;
|
||||
while (rpmRequired) {
|
||||
if (rpm > 4800) {
|
||||
rpmIncreasing = false;
|
||||
} else if (rpm < 2200) {
|
||||
rpmIncreasing = true;
|
||||
}
|
||||
|
||||
if (rpmIncreasing) {
|
||||
rpm += 100;
|
||||
} else {
|
||||
rpm -= 100;
|
||||
}
|
||||
|
||||
msg.set_rpm(rpm);
|
||||
std::this_thread::sleep_for(rpmStreamPeriod);
|
||||
rpmRequired = writer->Write(msg);
|
||||
}
|
||||
|
||||
return grpc::Status();
|
||||
}
|
||||
|
||||
grpc::Status getAutonomy(grpc::ServerContext *, const ::google::protobuf::Empty *,
|
||||
AutonomyMsg *response) override
|
||||
{
|
||||
response->set_autonomy(autonomy);
|
||||
if (autonomy > 10) {
|
||||
autonomy -= 1;
|
||||
}
|
||||
return grpc::Status();
|
||||
}
|
||||
};
|
||||
|
||||
class NavigationServiceImpl final : public NavigationService::Service
|
||||
{
|
||||
constexpr static std::chrono::seconds navigationPeriod = std::chrono::seconds(1);
|
||||
|
||||
int totaldistance = 1200;
|
||||
inline static std::array<std::string, 3> streets = { "Friedrichstraße", "Kurfürstendamm",
|
||||
"Erich-Thilo Straße" };
|
||||
|
||||
grpc::Status getNavigationStream(grpc::ServerContext *, const ::google::protobuf::Empty *,
|
||||
grpc::ServerWriter<NavigationMsg> *writer) override
|
||||
{
|
||||
volatile bool navigationRequired = true;
|
||||
NavigationMsg msg;
|
||||
msg.set_totaldistance(totaldistance);
|
||||
|
||||
for (auto street : streets) {
|
||||
msg.set_street(street);
|
||||
|
||||
int traveledDistance = 0;
|
||||
msg.set_traveleddistance(traveledDistance);
|
||||
|
||||
while (navigationRequired) {
|
||||
if (traveledDistance < totaldistance) {
|
||||
traveledDistance += 50;
|
||||
} else {
|
||||
traveledDistance = 0;
|
||||
}
|
||||
msg.set_traveleddistance(traveledDistance);
|
||||
|
||||
if (traveledDistance <= 300) {
|
||||
msg.set_direction(qtgrpc::examples::DirectionEnum::STRAIGHT);
|
||||
} else if (traveledDistance <= 600) {
|
||||
msg.set_direction(qtgrpc::examples::DirectionEnum::RIGHT);
|
||||
} else if (traveledDistance <= 900) {
|
||||
msg.set_direction(qtgrpc::examples::DirectionEnum::LEFT);
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(navigationPeriod);
|
||||
navigationRequired = writer->Write(msg);
|
||||
}
|
||||
}
|
||||
|
||||
return grpc::Status();
|
||||
}
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
VehicleServiceImpl vehicleService;
|
||||
NavigationServiceImpl navigationService;
|
||||
|
||||
grpc::ServerBuilder builder;
|
||||
builder.AddListeningPort("127.0.0.1:50051", grpc::InsecureServerCredentials());
|
||||
builder.RegisterService(&vehicleService);
|
||||
builder.RegisterService(&navigationService);
|
||||
|
||||
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
|
||||
if (!server) {
|
||||
std::cerr << "Creating grpc::Server failed.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::cout << "Server listening on port 50051";
|
||||
server->Wait();
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "vehiclemanager.h"
|
||||
#include "navigationthread.h"
|
||||
#include "vehiclethread.h"
|
||||
|
||||
using namespace qtgrpc::examples;
|
||||
|
||||
VehicleManager::VehicleManager(QObject *parent) : QObject(parent)
|
||||
{
|
||||
startVehicleClient();
|
||||
startNavigationClient();
|
||||
}
|
||||
|
||||
VehicleManager::~VehicleManager()
|
||||
{
|
||||
if (m_vehicleThread && m_vehicleThread->isRunning()) {
|
||||
m_vehicleThread->quit();
|
||||
m_vehicleThread->wait();
|
||||
}
|
||||
|
||||
if (m_navigationThread && m_navigationThread->isRunning()) {
|
||||
m_navigationThread->quit();
|
||||
m_navigationThread->wait();
|
||||
}
|
||||
}
|
||||
|
||||
void VehicleManager::startNavigationClient()
|
||||
{
|
||||
m_navigationThread = std::make_unique<NavigationThread>();
|
||||
|
||||
connect(m_navigationThread.get(), &NavigationThread::totalDistanceChanged, this,
|
||||
&VehicleManager::totalDistanceChanged, Qt::QueuedConnection);
|
||||
connect(m_navigationThread.get(), &NavigationThread::traveledDistanceChanged, this,
|
||||
&VehicleManager::traveledDistanceChanged, Qt::QueuedConnection);
|
||||
|
||||
connect(
|
||||
m_navigationThread.get(), &NavigationThread::directionChanged, this,
|
||||
[this](qtgrpc::examples::DirectionEnumGadget::DirectionEnum direction) {
|
||||
VehicleManager::directionChanged(qToUnderlying(direction));
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(m_navigationThread.get(), &NavigationThread::streetChanged, this,
|
||||
&VehicleManager::streetChanged, Qt::QueuedConnection);
|
||||
|
||||
connect(m_navigationThread.get(), &NavigationThread::connectionError, this,
|
||||
&VehicleManager::addNavigationError, Qt::QueuedConnection);
|
||||
|
||||
m_navigationThread->start();
|
||||
}
|
||||
|
||||
void VehicleManager::startVehicleClient()
|
||||
{
|
||||
m_vehicleThread = std::make_unique<VehicleThread>();
|
||||
|
||||
connect(m_vehicleThread.get(), &VehicleThread::speedChanged, this,
|
||||
&VehicleManager::speedChanged, Qt::QueuedConnection);
|
||||
connect(m_vehicleThread.get(), &VehicleThread::autonomyChanged, this,
|
||||
&VehicleManager::autonomyChanged, Qt::QueuedConnection);
|
||||
connect(m_vehicleThread.get(), &VehicleThread::rpmChanged, this, &VehicleManager::rpmChanged,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(m_vehicleThread.get(), &VehicleThread::connectionError, this,
|
||||
&VehicleManager::addVehicleError, Qt::QueuedConnection);
|
||||
|
||||
m_vehicleThread->start();
|
||||
}
|
||||
|
||||
void VehicleManager::addVehicleError(QString error)
|
||||
{
|
||||
m_vehicleErrors = m_vehicleErrors.isEmpty() ? error : m_vehicleErrors + "\n" + error;
|
||||
emit vehicleErrorsChanged(m_vehicleErrors);
|
||||
}
|
||||
|
||||
void VehicleManager::addNavigationError(QString error)
|
||||
{
|
||||
m_navigationErrors = m_navigationErrors.isEmpty() ? error : m_navigationErrors + "\n" + error;
|
||||
emit navigationErrorsChanged(m_navigationErrors);
|
||||
}
|
||||
|
||||
void VehicleManager::restart()
|
||||
{
|
||||
m_vehicleErrors = QString();
|
||||
m_navigationErrors = QString();
|
||||
|
||||
if (m_vehicleThread->isRunning()) {
|
||||
m_vehicleThread->quit();
|
||||
m_vehicleThread->wait();
|
||||
m_vehicleThread->start();
|
||||
}
|
||||
|
||||
if (m_navigationThread->isRunning()) {
|
||||
m_navigationThread->quit();
|
||||
m_navigationThread->wait();
|
||||
m_navigationThread->start();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef VEHICLEMANAGER_H
|
||||
#define VEHICLEMANAGER_H
|
||||
|
||||
#include <QQmlPropertyMap>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QtTypeTraits>
|
||||
#include <QtQml/QQmlEngine>
|
||||
#include <memory>
|
||||
#include "navigationservice_client.grpc.qpb.h"
|
||||
#include "navigationservice.qpb.h"
|
||||
#include "navigationthread.h"
|
||||
#include "vehiclethread.h"
|
||||
|
||||
class VehicleManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
public:
|
||||
enum NavigationDirection {
|
||||
RIGHT = qToUnderlying(qtgrpc::examples::DirectionEnumGadget::DirectionEnum::RIGHT),
|
||||
LEFT = qToUnderlying(qtgrpc::examples::DirectionEnumGadget::DirectionEnum::LEFT),
|
||||
STRAIGHT = qToUnderlying(qtgrpc::examples::DirectionEnumGadget::DirectionEnum::STRAIGHT),
|
||||
BACKWARD = qToUnderlying(qtgrpc::examples::DirectionEnumGadget::DirectionEnum::BACKWARD)
|
||||
};
|
||||
|
||||
Q_ENUM(NavigationDirection);
|
||||
|
||||
explicit VehicleManager(QObject *parent = nullptr);
|
||||
~VehicleManager() override;
|
||||
|
||||
Q_INVOKABLE void restart();
|
||||
|
||||
void addVehicleError(QString error);
|
||||
void addNavigationError(QString error);
|
||||
void removeErrors();
|
||||
|
||||
signals:
|
||||
void speedChanged(int speed);
|
||||
void rpmChanged(int rpm);
|
||||
void autonomyChanged(int level);
|
||||
|
||||
void totalDistanceChanged(int distance);
|
||||
void traveledDistanceChanged(int distance);
|
||||
void directionChanged(int direction);
|
||||
void streetChanged(QString street);
|
||||
|
||||
void vehicleErrorsChanged(QString vehicleErrors);
|
||||
void navigationErrorsChanged(QString navigationErrors);
|
||||
|
||||
private:
|
||||
void clearErrors();
|
||||
void startVehicleClient();
|
||||
void startNavigationClient();
|
||||
|
||||
std::unique_ptr<qtgrpc::examples::NavigationThread> m_navigationThread;
|
||||
std::unique_ptr<qtgrpc::examples::VehicleThread> m_vehicleThread;
|
||||
|
||||
QString m_vehicleErrors;
|
||||
QString m_navigationErrors;
|
||||
};
|
||||
|
||||
#endif // VEHICLEMANAGER_H
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
using namespace qtgrpc::examples;
|
||||
using namespace google::protobuf;
|
||||
|
||||
VehicleThread::VehicleThread(QObject *parent) : QThread(parent)
|
||||
{
|
||||
}
|
||||
|
@ -21,51 +22,83 @@ VehicleThread::~VehicleThread() = default;
|
|||
void VehicleThread::run()
|
||||
{
|
||||
if (!m_client) {
|
||||
auto channel = std::shared_ptr<
|
||||
QAbstractGrpcChannel>(new QGrpcHttp2Channel(QUrl("http://localhost:50051",
|
||||
QUrl::StrictMode)));
|
||||
m_client = std::make_shared<qtgrpc::examples::VehicleService::Client>();
|
||||
m_client->attachChannel(channel);
|
||||
m_client = std::make_unique<qtgrpc::examples::VehicleService::Client>();
|
||||
m_client
|
||||
->attachChannel(std::make_shared<QGrpcHttp2Channel>(QUrl("http://localhost:50051")));
|
||||
}
|
||||
|
||||
Empty fuelLvlRequest;
|
||||
std::shared_ptr<QGrpcCallReply> replyFuel = m_client->getFuelLevel(fuelLvlRequest);
|
||||
|
||||
connect(replyFuel.get(), &QGrpcCallReply::finished, [replyFuel, this] (const QGrpcStatus &status) {
|
||||
if (status.code() == QtGrpc::StatusCode::Ok) {
|
||||
if (const auto fuelLvl = replyFuel->read<FuelLevelMsg>())
|
||||
emit fuelLevelChanged(fuelLvl->fuelLevel());
|
||||
} else {
|
||||
emit connectionError(true);
|
||||
emit fuelLevelChanged(0);
|
||||
}
|
||||
});
|
||||
|
||||
//! [Speed stream]
|
||||
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) {
|
||||
emit speedChanged(0);
|
||||
if (status.code() != QtGrpc::StatusCode::Ok) {
|
||||
emit connectionError(true);
|
||||
emit fuelLevelChanged(0);
|
||||
qWarning() << "Stream error(" << status.code() << "):" << status.message();
|
||||
connect(m_streamSpeed.get(), &QGrpcServerStream::messageReceived, this, [this]() {
|
||||
if (const auto speedResponse = m_streamSpeed->read<SpeedMsg>()) {
|
||||
emit speedChanged(speedResponse->speed());
|
||||
}
|
||||
});
|
||||
|
||||
Empty gearRequest;
|
||||
m_streamGear = m_client->getGearStream(gearRequest);
|
||||
connect(m_streamGear.get(), &QGrpcServerStream::messageReceived, this, [this] {
|
||||
if (const auto gearResponse = m_streamGear->read<GearMsg>())
|
||||
emit rpmChanged(gearResponse->rpm());
|
||||
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);
|
||||
//! [Speed stream]
|
||||
|
||||
Empty rpmRequest;
|
||||
m_streamRpm = m_client->getRpmStream(rpmRequest);
|
||||
connect(m_streamRpm.get(), &QGrpcServerStream::messageReceived, this, [this]() {
|
||||
if (const auto rpmResponse = m_streamRpm->read<RpmMsg>()) {
|
||||
emit rpmChanged(rpmResponse->rpm());
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_streamGear.get(), &QGrpcServerStream::finished, this, [this] { emit rpmChanged(0); });
|
||||
connect(
|
||||
m_streamRpm.get(), &QGrpcServerStream::finished, this,
|
||||
[this](const QGrpcStatus &status) {
|
||||
if (!status.isOk()) {
|
||||
auto error = QString("Stream error fetching RPM %1 (%2)")
|
||||
.arg(status.message())
|
||||
.arg(QVariant::fromValue(status.code()).toString());
|
||||
emit connectionError(error);
|
||||
qWarning() << error;
|
||||
return;
|
||||
}
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
|
||||
//! [Autonomy call]
|
||||
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);
|
||||
//! [Autonomy call]
|
||||
|
||||
QThread::run();
|
||||
|
||||
// Delete the VehicleService::Client object to shut down the connection
|
||||
m_client.reset();
|
||||
}
|
||||
|
|
|
@ -4,20 +4,11 @@
|
|||
#ifndef VEHICLETHREAD_H
|
||||
#define VEHICLETHREAD_H
|
||||
|
||||
#include "vehicleservice_client.grpc.qpb.h"
|
||||
#include <QtCore/QThread>
|
||||
#include <memory>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QGrpcServerStream;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace qtgrpc {
|
||||
namespace examples {
|
||||
namespace VehicleService {
|
||||
class Client;
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace qtgrpc::examples {
|
||||
|
||||
class VehicleThread : public QThread
|
||||
{
|
||||
|
@ -26,17 +17,22 @@ class VehicleThread : public QThread
|
|||
public:
|
||||
explicit VehicleThread(QObject *parent = nullptr);
|
||||
~VehicleThread() override;
|
||||
|
||||
void run() override;
|
||||
|
||||
signals:
|
||||
void speedChanged(int speed);
|
||||
void rpmChanged(int rpm);
|
||||
void fuelLevelChanged(int level);
|
||||
void connectionError(bool value);
|
||||
void autonomyChanged(int level);
|
||||
|
||||
void connectionError(QString error);
|
||||
|
||||
private:
|
||||
std::shared_ptr<qtgrpc::examples::VehicleService::Client> m_client;
|
||||
std::shared_ptr<QGrpcServerStream> m_streamSpeed;
|
||||
std::shared_ptr<QGrpcServerStream> m_streamGear;
|
||||
std::unique_ptr<qtgrpc::examples::VehicleService::Client> m_client;
|
||||
std::unique_ptr<QGrpcServerStream> m_streamSpeed;
|
||||
std::unique_ptr<QGrpcServerStream> m_streamRpm;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // VEHICLETHREAD_H
|
||||
|
|