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
|
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
project(vehicle_cluster LANGUAGES CXX)
|
project(vehicle LANGUAGES CXX)
|
||||||
|
|
||||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||||
set(INSTALL_EXAMPLESDIR "examples")
|
set(INSTALL_EXAMPLESDIR "examples")
|
||||||
|
@ -20,58 +20,57 @@ find_package(Qt6 REQUIRED COMPONENTS
|
||||||
|
|
||||||
qt_standard_project_setup()
|
qt_standard_project_setup()
|
||||||
|
|
||||||
add_subdirectory(grpc_vehicle_server)
|
qt_add_executable(vehicle_client
|
||||||
|
|
||||||
qt_add_executable(vehicle_cluster
|
|
||||||
main.cpp
|
main.cpp
|
||||||
clusterdatamanager.h
|
vehiclemanager.h
|
||||||
clusterdatamanager.cpp
|
vehiclemanager.cpp
|
||||||
vehiclethread.h
|
vehiclethread.h
|
||||||
vehiclethread.cpp
|
vehiclethread.cpp
|
||||||
navithread.h
|
navigationthread.h
|
||||||
navithread.cpp
|
navigationthread.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_protobuf(vehiclelib
|
qt_add_protobuf(vehiclelib
|
||||||
PROTO_FILES
|
PROTO_FILES
|
||||||
proto/vehicleservice.proto
|
proto/vehicleservice.proto
|
||||||
proto/naviservice.proto
|
proto/navigationservice.proto
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_grpc(vehiclelib CLIENT
|
qt_add_grpc(vehiclelib CLIENT
|
||||||
PROTO_FILES
|
PROTO_FILES
|
||||||
proto/vehicleservice.proto
|
proto/vehicleservice.proto
|
||||||
proto/naviservice.proto
|
proto/navigationservice.proto
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(vehiclelib PRIVATE Qt6::ProtobufWellKnownTypes)
|
target_link_libraries(vehiclelib PRIVATE Qt6::ProtobufWellKnownTypes)
|
||||||
|
|
||||||
set_target_properties(vehicle_cluster PROPERTIES
|
set_target_properties(vehicle_client PROPERTIES
|
||||||
WIN32_EXECUTABLE TRUE
|
WIN32_EXECUTABLE TRUE
|
||||||
MACOSX_BUNDLE TRUE
|
MACOSX_BUNDLE TRUE
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_qml_module(vehicle_cluster
|
qt_add_qml_module(vehicle_client
|
||||||
URI qtgrpc.examples.vehicle
|
URI qtgrpc.examples.vehicle
|
||||||
VERSION 1.0
|
VERSION 1.0
|
||||||
RESOURCE_PREFIX "/qt/qml"
|
RESOURCE_PREFIX "/qt/qml"
|
||||||
QML_FILES
|
QML_FILES
|
||||||
"ClusterText.qml"
|
"StyledText.qml"
|
||||||
"ClusterProgressBar.qml"
|
"StyledProgressBar.qml"
|
||||||
"Main.qml"
|
"Main.qml"
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_resources(vehicle_cluster
|
qt_add_resources(vehicle_client
|
||||||
"vehicle_cluster"
|
"resources"
|
||||||
PREFIX "/"
|
PREFIX "/"
|
||||||
|
BASE "resources"
|
||||||
FILES
|
FILES
|
||||||
"left.png"
|
"resources/direction_left.svg"
|
||||||
"right.png"
|
"resources/direction_right.svg"
|
||||||
"forward.png"
|
"resources/direction_straight.svg"
|
||||||
"fuel_lvl.png"
|
"resources/fuel_icon.svg"
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(vehicle_cluster PRIVATE
|
target_link_libraries(vehicle_client PRIVATE
|
||||||
Qt6::Core
|
Qt6::Core
|
||||||
Qt6::Quick
|
Qt6::Quick
|
||||||
Qt6::Protobuf
|
Qt6::Protobuf
|
||||||
|
@ -80,8 +79,13 @@ target_link_libraries(vehicle_cluster PRIVATE
|
||||||
vehiclelib
|
vehiclelib
|
||||||
)
|
)
|
||||||
|
|
||||||
install(TARGETS vehicle_cluster vehiclelib
|
install(TARGETS vehicle_client vehiclelib
|
||||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||||
LIBRARY 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
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Controls.Material
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.VectorImage
|
||||||
|
import QtQuick.Controls.Basic
|
||||||
|
|
||||||
import qtgrpc.examples.vehicle
|
import qtgrpc.examples.vehicle
|
||||||
|
|
||||||
ApplicationWindow {
|
ApplicationWindow {
|
||||||
id: root
|
id: root
|
||||||
width: 1280
|
|
||||||
height: 518
|
|
||||||
|
|
||||||
minimumWidth: width
|
property int speed: -1
|
||||||
maximumWidth: width
|
property int rpm: -1
|
||||||
minimumHeight: height
|
property int totalDistance: -1
|
||||||
maximumHeight: height
|
property int traveledDistance: -1
|
||||||
|
property int estimatedAutonomy: -1
|
||||||
property int speed: 0
|
property string directionImageSource: ""
|
||||||
property int rpm: 0
|
property string street: ""
|
||||||
property int totalDistance: 0
|
property string vehicleErrors: ""
|
||||||
property int remainingDistance: 0
|
property string navigationErrors: ""
|
||||||
property int direction: ClusterDataManager.BACKWARD
|
|
||||||
property int fuelLevel: 0
|
|
||||||
property bool availableBtn: false
|
|
||||||
|
|
||||||
|
width: 1200
|
||||||
|
height: 500
|
||||||
visible: true
|
visible: true
|
||||||
title: qsTr("Cluster Qt GRPC Example")
|
title: qsTr("Vehicle Qt GRPC example")
|
||||||
Material.theme: Material.Light
|
|
||||||
|
|
||||||
|
//! [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 {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "#040a16"
|
color: "#160404"
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
// Information screen
|
||||||
id: background
|
GridLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: !root.availableBtn
|
anchors.margins: 40
|
||||||
|
visible: !(root.vehicleErrors && root.navigationErrors)
|
||||||
Row {
|
columnSpacing: 20
|
||||||
id: textsBar
|
rowSpacing: 10
|
||||||
anchors.horizontalCenter: background.horizontalCenter
|
columns: 2
|
||||||
anchors.bottom: progressBars.top
|
uniformCellWidths: true
|
||||||
spacing: 130
|
|
||||||
|
|
||||||
width: root.width - 110
|
|
||||||
height: 200
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: 300
|
Layout.fillHeight: true
|
||||||
height: 200
|
Layout.fillWidth: true
|
||||||
|
|
||||||
ClusterText {
|
StyledText {
|
||||||
width: 200
|
id: speedText
|
||||||
height: 200
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
verticalAlignment: Text.AlignBottom
|
text: root.speed == -1 ? "-" : root.speed
|
||||||
font.pointSize: 90
|
font.pointSize: 90
|
||||||
text: root.speed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ClusterText {
|
StyledText {
|
||||||
width: 100
|
id: speedUnitText
|
||||||
height: 200
|
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.bottomMargin: 27
|
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
text: "Km/h"
|
||||||
verticalAlignment: Text.AlignBottom
|
|
||||||
horizontalAlignment: Text.AlignRight
|
|
||||||
text: "Kmph"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: 300
|
Layout.fillHeight: true
|
||||||
height: 200
|
Layout.fillWidth: true
|
||||||
|
|
||||||
Image {
|
VectorImage {
|
||||||
id: arrow
|
id: directionImage
|
||||||
|
source: root.directionImageSource
|
||||||
|
|
||||||
|
width: 100
|
||||||
|
height: 100
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: parent.left
|
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 {
|
Rectangle {
|
||||||
width: 200
|
visible: root.directionImageSource
|
||||||
height: 40
|
anchors.fill: directionImage
|
||||||
anchors.bottom: street.top
|
color: "#363636"
|
||||||
|
radius: 10
|
||||||
|
z: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: streetText.top
|
||||||
verticalAlignment: Text.AlignBottom
|
font.pointSize: 24
|
||||||
horizontalAlignment: Text.AlignRight
|
horizontalAlignment: Text.AlignRight
|
||||||
text: Number(root.remainingDistance * 0.001).toFixed(2) + " km"
|
text: {
|
||||||
|
if (root.totalDistance == -1 || root.traveledDistance == -1) {
|
||||||
|
return "- km";
|
||||||
}
|
}
|
||||||
|
|
||||||
ClusterText {
|
let remainingDistance = root.totalDistance - root.traveledDistance;
|
||||||
id: street
|
|
||||||
width: 200
|
|
||||||
height: 40
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.bottomMargin: 27
|
|
||||||
anchors.right: parent.right
|
|
||||||
|
|
||||||
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
|
horizontalAlignment: Text.AlignRight
|
||||||
color: "#828284"
|
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 {
|
Item {
|
||||||
width: 300
|
Layout.fillHeight: true
|
||||||
height: 200
|
Layout.fillWidth: true
|
||||||
|
|
||||||
ClusterText {
|
VectorImage {
|
||||||
id: rightSide
|
id: fuelIcon
|
||||||
width: 200
|
width: 28
|
||||||
height: 200
|
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.bottom: parent.bottom
|
||||||
anchors.bottomMargin: 27
|
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
verticalAlignment: Text.AlignBottom
|
|
||||||
horizontalAlignment: Text.AlignRight
|
|
||||||
text: root.rpm + " rpm"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
StyledProgressBar {
|
||||||
id: progressBars
|
Layout.fillWidth: true
|
||||||
|
value: root.rpm
|
||||||
anchors.horizontalCenter: background.horizontalCenter
|
to: 9000
|
||||||
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
|
|
||||||
activeColor: "#f8c607"
|
activeColor: "#f8c607"
|
||||||
bgColor: "#5f3f04"
|
bgColor: "#5f3f04"
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ClusterProgressBar {
|
StyledProgressBar {
|
||||||
id: fuelLevel
|
Layout.fillWidth: true
|
||||||
anchors.leftMargin: 55
|
value: root.estimatedAutonomy
|
||||||
anchors.left: parent.left
|
to: 250
|
||||||
anchors.topMargin: 100
|
|
||||||
anchors.top: progressBars.bottom
|
|
||||||
currentBarValue: root.fuelLevel
|
|
||||||
totalBarValue: 250
|
|
||||||
activeColor: "#05c848"
|
activeColor: "#05c848"
|
||||||
bgColor: "#03511f"
|
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 {
|
// No connection error screen
|
||||||
anchors.bottom: fuelLevel.bottom
|
ColumnLayout {
|
||||||
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 {
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: btn.btnPressed ? "#828284" : "white"
|
visible: root.vehicleErrors && root.navigationErrors
|
||||||
text: "RUN"
|
|
||||||
|
StyledText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
font.pointSize: 14
|
||||||
|
text: qsTr("Please, start vehicle server. Then, press try again.")
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
Button {
|
||||||
id: btn
|
id: runButton
|
||||||
property bool btnPressed: false
|
property string baseColor: "white"
|
||||||
anchors.fill: parent
|
property string clickedColor: "#828284"
|
||||||
|
property string hoverColor: "#a9a9a9"
|
||||||
|
|
||||||
enabled: root.availableBtn
|
Layout.alignment: Qt.AlignHCenter
|
||||||
onClicked: ClusterDataManager.restart()
|
|
||||||
onPressed: btnPressed = true
|
background: Rectangle {
|
||||||
onReleased: btnPressed = false
|
border.color: runButton.down ?
|
||||||
}
|
runButton.clickedColor
|
||||||
}
|
: (runButton.hovered ? runButton.hoverColor : runButton.baseColor)
|
||||||
|
border.width: 2
|
||||||
|
radius: 2
|
||||||
|
color: "transparent"
|
||||||
}
|
}
|
||||||
|
|
||||||
function getImage() {
|
contentItem: Text {
|
||||||
switch (root.direction) {
|
text: qsTr("Try again")
|
||||||
case ClusterDataManager.RIGHT:
|
font.pointSize: 16
|
||||||
return "qrc:/right.png"
|
horizontalAlignment: Text.AlignHCenter
|
||||||
case ClusterDataManager.LEFT:
|
verticalAlignment: Text.AlignVCenter
|
||||||
return "qrc:/left.png"
|
color: runButton.down ?
|
||||||
case ClusterDataManager.STRAIGHT:
|
runButton.clickedColor
|
||||||
return "qrc:/forward.png"
|
: (runButton.hovered ? runButton.hoverColor : runButton.baseColor)
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
onClicked: {
|
||||||
target: ClusterDataManager
|
root.vehicleErrors = "";
|
||||||
|
root.navigationErrors = "";
|
||||||
function onTotalDistanceChanged(distance) {
|
VehicleManager.restart();
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
import QtQuick
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
font.family: "Helvetica"
|
font.family: "Helvetica"
|
||||||
color: "white"
|
color: "white"
|
||||||
style: Text.Sunken
|
|
||||||
font.pointSize: 18
|
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;
|
package qtgrpc.examples;
|
||||||
import "google/protobuf/empty.proto";
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
message DistanceMsg {
|
//! [Proto types]
|
||||||
int32 totalDistance = 1;
|
|
||||||
int32 remainingDistance = 2;
|
|
||||||
DirectionEnum direction = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum DirectionEnum {
|
enum DirectionEnum {
|
||||||
RIGHT = 0;
|
RIGHT = 0;
|
||||||
LEFT = 1;
|
LEFT = 1;
|
||||||
|
@ -18,6 +13,14 @@ enum DirectionEnum {
|
||||||
BACKWARD = 3;
|
BACKWARD = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
service NaviService {
|
message NavigationMsg {
|
||||||
rpc getNaviStream(google.protobuf.Empty) returns (stream DistanceMsg) {}
|
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;
|
package qtgrpc.examples;
|
||||||
import "google/protobuf/empty.proto";
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
|
|
||||||
|
//! [Proto types]
|
||||||
message SpeedMsg {
|
message SpeedMsg {
|
||||||
int32 speed = 1;
|
int32 speed = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GearMsg {
|
message RpmMsg {
|
||||||
int32 rpm = 1;
|
int32 rpm = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message FuelLevelMsg {
|
message AutonomyMsg {
|
||||||
int32 fuelLevel = 1;
|
int32 autonomy = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
service VehicleService {
|
service VehicleService {
|
||||||
rpc getSpeedStream(google.protobuf.Empty) returns (stream SpeedMsg) {}
|
rpc getSpeedStream(google.protobuf.Empty) returns (stream SpeedMsg) {}
|
||||||
rpc getGearStream(google.protobuf.Empty) returns (stream GearMsg) {}
|
rpc getRpmStream(google.protobuf.Empty) returns (stream RpmMsg) {}
|
||||||
rpc getFuelLevel(google.protobuf.Empty) returns (FuelLevelMsg) {}
|
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
|
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
project(VehicleServerRunner LANGUAGES CXX)
|
project(VehicleServer LANGUAGES CXX)
|
||||||
|
|
||||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
|
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(protobuf)
|
||||||
find_package(gRPC)
|
find_package(gRPC)
|
||||||
|
|
||||||
if(NOT TARGET gRPC::grpc_cpp_plugin OR NOT TARGET WrapProtoc::WrapProtoc
|
if(NOT TARGET gRPC::grpc_cpp_plugin OR NOT TARGET WrapProtoc::WrapProtoc
|
||||||
OR NOT TARGET gRPC::grpc++)
|
OR NOT TARGET gRPC::grpc++)
|
||||||
message(WARNING "Dependencies of SimpleVehicleServer not found. Skipping.")
|
message(WARNING "Dependencies of VehicleServer not found. Skipping.")
|
||||||
return()
|
return()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -23,16 +21,19 @@ if(MINGW)
|
||||||
" guaranteed otherwise.")
|
" guaranteed otherwise.")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
set(proto_files
|
set(proto_files
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/../proto/vehicleservice.proto"
|
"${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(out_dir ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
set(generated_files
|
set(generated_files
|
||||||
"${out_dir}/vehicleservice.pb.h" "${out_dir}/vehicleservice.pb.cc"
|
"${out_dir}/vehicleservice.pb.h" "${out_dir}/vehicleservice.pb.cc"
|
||||||
"${out_dir}/vehicleservice.grpc.pb.h" "${out_dir}/vehicleservice.grpc.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}/navigationservice.pb.h" "${out_dir}/navigationservice.pb.cc"
|
||||||
"${out_dir}/naviservice.grpc.pb.h" "${out_dir}/naviservice.grpc.pb.cc")
|
"${out_dir}/navigationservice.grpc.pb.h" "${out_dir}/navigationservice.grpc.pb.cc")
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT ${generated_files}
|
OUTPUT ${generated_files}
|
||||||
|
@ -53,24 +54,22 @@ add_custom_command(
|
||||||
|
|
||||||
set_source_files_properties(${generated_files} PROPERTIES GENERATED TRUE)
|
set_source_files_properties(${generated_files} PROPERTIES GENERATED TRUE)
|
||||||
|
|
||||||
add_executable(SimpleVehicleServer
|
qt_add_executable(vehicle_server
|
||||||
${generated_files}
|
${generated_files}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/serverrunner.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/serverrunner.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/main.cpp
|
${CMAKE_CURRENT_LIST_DIR}/main.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(SimpleVehicleServer
|
target_include_directories(vehicle_server
|
||||||
PRIVATE
|
PRIVATE
|
||||||
${out_dir}
|
${out_dir}
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(SimpleVehicleServer PRIVATE
|
target_link_libraries(vehicle_server PRIVATE
|
||||||
protobuf::libprotobuf
|
protobuf::libprotobuf
|
||||||
gRPC::grpc++
|
gRPC::grpc++
|
||||||
)
|
)
|
||||||
|
|
||||||
install(TARGETS SimpleVehicleServer
|
install(TARGETS vehicle_server
|
||||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||||
LIBRARY 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 qtgrpc::examples;
|
||||||
using namespace google::protobuf;
|
using namespace google::protobuf;
|
||||||
|
|
||||||
VehicleThread::VehicleThread(QObject *parent) : QThread(parent)
|
VehicleThread::VehicleThread(QObject *parent) : QThread(parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -21,51 +22,83 @@ VehicleThread::~VehicleThread() = default;
|
||||||
void VehicleThread::run()
|
void VehicleThread::run()
|
||||||
{
|
{
|
||||||
if (!m_client) {
|
if (!m_client) {
|
||||||
auto channel = std::shared_ptr<
|
m_client = std::make_unique<qtgrpc::examples::VehicleService::Client>();
|
||||||
QAbstractGrpcChannel>(new QGrpcHttp2Channel(QUrl("http://localhost:50051",
|
m_client
|
||||||
QUrl::StrictMode)));
|
->attachChannel(std::make_shared<QGrpcHttp2Channel>(QUrl("http://localhost:50051")));
|
||||||
m_client = std::make_shared<qtgrpc::examples::VehicleService::Client>();
|
|
||||||
m_client->attachChannel(channel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Empty fuelLvlRequest;
|
//! [Speed stream]
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Empty speedRequest;
|
Empty speedRequest;
|
||||||
m_streamSpeed = m_client->getSpeedStream(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,
|
connect(m_streamSpeed.get(), &QGrpcServerStream::messageReceived, this, [this]() {
|
||||||
[this](const QGrpcStatus &status) {
|
if (const auto speedResponse = m_streamSpeed->read<SpeedMsg>()) {
|
||||||
emit speedChanged(0);
|
emit speedChanged(speedResponse->speed());
|
||||||
if (status.code() != QtGrpc::StatusCode::Ok) {
|
|
||||||
emit connectionError(true);
|
|
||||||
emit fuelLevelChanged(0);
|
|
||||||
qWarning() << "Stream error(" << status.code() << "):" << status.message();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Empty gearRequest;
|
connect(
|
||||||
m_streamGear = m_client->getGearStream(gearRequest);
|
m_streamSpeed.get(), &QGrpcServerStream::finished, this,
|
||||||
connect(m_streamGear.get(), &QGrpcServerStream::messageReceived, this, [this] {
|
[this](const QGrpcStatus &status) {
|
||||||
if (const auto gearResponse = m_streamGear->read<GearMsg>())
|
if (!status.isOk()) {
|
||||||
emit rpmChanged(gearResponse->rpm());
|
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();
|
QThread::run();
|
||||||
|
|
||||||
|
// Delete the VehicleService::Client object to shut down the connection
|
||||||
|
m_client.reset();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,20 +4,11 @@
|
||||||
#ifndef VEHICLETHREAD_H
|
#ifndef VEHICLETHREAD_H
|
||||||
#define VEHICLETHREAD_H
|
#define VEHICLETHREAD_H
|
||||||
|
|
||||||
|
#include "vehicleservice_client.grpc.qpb.h"
|
||||||
#include <QtCore/QThread>
|
#include <QtCore/QThread>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
namespace qtgrpc::examples {
|
||||||
class QGrpcServerStream;
|
|
||||||
QT_END_NAMESPACE
|
|
||||||
|
|
||||||
namespace qtgrpc {
|
|
||||||
namespace examples {
|
|
||||||
namespace VehicleService {
|
|
||||||
class Client;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class VehicleThread : public QThread
|
class VehicleThread : public QThread
|
||||||
{
|
{
|
||||||
|
@ -26,17 +17,22 @@ class VehicleThread : public QThread
|
||||||
public:
|
public:
|
||||||
explicit VehicleThread(QObject *parent = nullptr);
|
explicit VehicleThread(QObject *parent = nullptr);
|
||||||
~VehicleThread() override;
|
~VehicleThread() override;
|
||||||
|
|
||||||
void run() override;
|
void run() override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void speedChanged(int speed);
|
void speedChanged(int speed);
|
||||||
void rpmChanged(int rpm);
|
void rpmChanged(int rpm);
|
||||||
void fuelLevelChanged(int level);
|
void autonomyChanged(int level);
|
||||||
void connectionError(bool value);
|
|
||||||
|
void connectionError(QString error);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<qtgrpc::examples::VehicleService::Client> m_client;
|
std::unique_ptr<qtgrpc::examples::VehicleService::Client> m_client;
|
||||||
std::shared_ptr<QGrpcServerStream> m_streamSpeed;
|
std::unique_ptr<QGrpcServerStream> m_streamSpeed;
|
||||||
std::shared_ptr<QGrpcServerStream> m_streamGear;
|
std::unique_ptr<QGrpcServerStream> m_streamRpm;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#endif // VEHICLETHREAD_H
|
#endif // VEHICLETHREAD_H
|
||||||
|
|