mirror of https://github.com/qt/qtgrpc.git
Add example of communication using protobuf messages
Add the protobuf example that emulates the work of dummy sensors that send data to the sensor client. The example uses UDP sockets to send datagrams that contain protobuf messages. Messages consist of two layers: - The Type-Length-Value wrapping message that specifies the the message type and allows to verify the message size. - Sensor message that contains a sensor data. The example intends to show how to generate the code from the protobuf schema and use it in simple UDP signalling protocol on both sender and receiver sides. Both client and emulator have simple UI implemented using QtWidgets. Task-number: QTBUG-109598 Pick-to: 6.5 Change-Id: I13e2c5bcd995b8aa6d873c495a7bd83f6651a061 Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
parent
1a72cce91d
commit
76d94d29ea
|
|
@ -12,7 +12,7 @@ project(QtGrpc
|
|||
)
|
||||
|
||||
find_package(Qt6 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS BuildInternals Core)
|
||||
find_package(Qt6 ${PROJECT_VERSION} CONFIG OPTIONAL_COMPONENTS Network)
|
||||
find_package(Qt6 ${PROJECT_VERSION} CONFIG OPTIONAL_COMPONENTS Network Gui Widgets)
|
||||
|
||||
# Try to find Qt6::qtprotobufgen and Qt6::qtgrpcgen targets from host tools
|
||||
# when cross-compiling.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
# Copyright (C) 2023 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
qt_examples_build_begin(EXTERNAL_BUILD)
|
||||
|
||||
add_subdirectory(protobuf)
|
||||
|
||||
qt_examples_build_end()
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# Copyright (C) 2023 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
if(QT_FEATURE_qtprotobufgen AND TARGET Qt6::Network AND TARGET Qt6::Widgets)
|
||||
qt_internal_add_example(sensors)
|
||||
endif()
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# Copyright (C) 2023 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(protobuf_sensors LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/protobuf/sensors")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Protobuf Widgets Network)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
add_subdirectory(emulator)
|
||||
add_subdirectory(client)
|
||||
|
||||
#! [0]
|
||||
qt_add_protobuf(protobuf_sensors
|
||||
PROTO_FILES
|
||||
sensors.proto
|
||||
tlv.proto
|
||||
)
|
||||
#! [0]
|
||||
|
||||
install(TARGETS protobuf_sensors
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# Copyright (C) 2023 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
qt_add_executable(protobuf_sensors_client
|
||||
main.cpp
|
||||
clientconsole.cpp clientconsole.h
|
||||
sensorclient.cpp sensorclient.h
|
||||
clientconsole.ui
|
||||
)
|
||||
|
||||
target_link_libraries(protobuf_sensors_client PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Protobuf
|
||||
Qt6::Widgets
|
||||
Qt6::Network
|
||||
protobuf_sensors
|
||||
)
|
||||
|
||||
install(TARGETS protobuf_sensors_client
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "clientconsole.h"
|
||||
#include "ui_clientconsole.h"
|
||||
|
||||
#include "sensors.qpb.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
ClientConsole::ClientConsole(QWidget *parent) : QWidget(parent), ui(new Ui::ClientConsole)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
}
|
||||
|
||||
void ClientConsole::onCoordinatesUpdated(const qt::examples::sensors::Coordinates &coord)
|
||||
{
|
||||
//! [0]
|
||||
ui->latitudeValue->setText(QString::number(coord.latitude(), 'f', 7));
|
||||
ui->longitudeValue->setText(QString::number(coord.longitude(), 'f', 7));
|
||||
ui->altitudeValue->setText(QString::number(coord.altitude(), 'f', 7));
|
||||
//! [0]
|
||||
}
|
||||
|
||||
void ClientConsole::onTemperatureUpdated(const qt::examples::sensors::Temperature &temp)
|
||||
{
|
||||
ui->temperature->setText(
|
||||
QString("%1 %2")
|
||||
.arg(QString::number(temp.value()))
|
||||
.arg(temp.units() == qt::examples::sensors::Temperature::Celsius ? 'C' : 'F'));
|
||||
}
|
||||
|
||||
void ClientConsole::onWarning(const qt::examples::sensors::WarningNotification &warn)
|
||||
{
|
||||
if (!warn.text().isEmpty())
|
||||
QMessageBox::information(this, QObject::tr("Important notification"), warn.text());
|
||||
}
|
||||
|
||||
ClientConsole::~ClientConsole()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
#include "moc_clientconsole.cpp"
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef CLIENTCONSOLE_H
|
||||
#define CLIENTCONSOLE_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
namespace qt::examples::sensors {
|
||||
class Coordinates;
|
||||
class Temperature;
|
||||
class WarningNotification;
|
||||
} // namespace qt::examples::sensors
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui {
|
||||
class ClientConsole;
|
||||
}
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class ClientConsole : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ClientConsole(QWidget *parent = nullptr);
|
||||
~ClientConsole();
|
||||
|
||||
void onCoordinatesUpdated(const qt::examples::sensors::Coordinates &coord);
|
||||
void onTemperatureUpdated(const qt::examples::sensors::Temperature &temp);
|
||||
void onWarning(const qt::examples::sensors::WarningNotification &warn);
|
||||
|
||||
private:
|
||||
Ui::ClientConsole *ui;
|
||||
};
|
||||
|
||||
#endif // CLIENTCONSOLE_H
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ClientConsole</class>
|
||||
<widget class="QWidget" name="ClientConsole">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>256</width>
|
||||
<height>244</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Client Console</string>
|
||||
</property>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>241</width>
|
||||
<height>141</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Coordinates</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="layoutWidget1">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>70</y>
|
||||
<width>216</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Longitude</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="longitudeValue">
|
||||
<property name="text">
|
||||
<string>-</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>100</y>
|
||||
<width>216</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Altitude</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="altitudeValue">
|
||||
<property name="text">
|
||||
<string>-</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget3">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>40</y>
|
||||
<width>216</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Latitude</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="latitudeValue">
|
||||
<property name="text">
|
||||
<string>-</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>160</y>
|
||||
<width>241</width>
|
||||
<height>81</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Weather</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="layoutWidget4">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>40</y>
|
||||
<width>216</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Temperature</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="temperature">
|
||||
<property name="text">
|
||||
<string>-</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "clientconsole.h"
|
||||
#include "sensorclient.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
SensorClient client;
|
||||
ClientConsole console;
|
||||
QObject::connect(&client, &SensorClient::coordinatesUpdated, &console,
|
||||
&ClientConsole::onCoordinatesUpdated);
|
||||
QObject::connect(&client, &SensorClient::temperatureUpdated, &console,
|
||||
&ClientConsole::onTemperatureUpdated);
|
||||
QObject::connect(&client, &SensorClient::warning, &console, &ClientConsole::onWarning);
|
||||
console.show();
|
||||
return app.exec();
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "sensorclient.h"
|
||||
|
||||
#include <QNetworkDatagram>
|
||||
#include <QDebug>
|
||||
|
||||
#include "sensors.qpb.h"
|
||||
#include "tlv.qpb.h"
|
||||
|
||||
SensorClient::SensorClient(QObject *parent) : QObject(parent)
|
||||
{
|
||||
Q_ASSERT_X(m_client.bind(QHostAddress::LocalHost, 65500), "SensorClient",
|
||||
"Unable to bind to port 65500");
|
||||
QObject::connect(&m_client, &QUdpSocket::readyRead, this, &SensorClient::receive);
|
||||
}
|
||||
|
||||
void SensorClient::receive()
|
||||
{
|
||||
while (m_client.hasPendingDatagrams()) {
|
||||
//! [0]
|
||||
const auto datagram = m_client.receiveDatagram();
|
||||
qt::examples::sensors::tlv::TlvMessage msg;
|
||||
msg.deserialize(&m_serializer, datagram.data());
|
||||
if (m_serializer.deserializationError()
|
||||
!= QAbstractProtobufSerializer::NoError) {
|
||||
qWarning().nospace() << "Unable to deserialize datagram ("
|
||||
<< m_serializer.deserializationError() << ")"
|
||||
<< m_serializer.deserializationErrorString();
|
||||
continue;
|
||||
}
|
||||
//! [0]
|
||||
|
||||
switch (msg.type()) {
|
||||
case qt::examples::sensors::tlv::MessageTypeGadget::Coordinates: {
|
||||
//! [1]
|
||||
qt::examples::sensors::Coordinates coord;
|
||||
coord.deserialize(&m_serializer, msg.value());
|
||||
emit coordinatesUpdated(coord);
|
||||
break;
|
||||
//! [1]
|
||||
}
|
||||
case qt::examples::sensors::tlv::MessageTypeGadget::Temperature: {
|
||||
qt::examples::sensors::Temperature temp;
|
||||
temp.deserialize(&m_serializer, msg.value());
|
||||
emit temperatureUpdated(temp);
|
||||
break;
|
||||
}
|
||||
case qt::examples::sensors::tlv::MessageTypeGadget::WarningNotification: {
|
||||
qt::examples::sensors::WarningNotification warn;
|
||||
warn.deserialize(&m_serializer, msg.value());
|
||||
emit warning(warn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_serializer.deserializationError()
|
||||
!= QAbstractProtobufSerializer::NoError) {
|
||||
qWarning().nospace() << "Unable to deserialize message ("
|
||||
<< m_serializer.deserializationError() << ")"
|
||||
<< m_serializer.deserializationErrorString();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_sensorclient.cpp"
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef SENSORCLIENT_H
|
||||
#define SENSORCLIENT_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <QProtobufSerializer>
|
||||
#include <QUdpSocket>
|
||||
|
||||
namespace qt::examples::sensors {
|
||||
class Coordinates;
|
||||
class Temperature;
|
||||
class WarningNotification;
|
||||
} // namespace qt::examples::sensors
|
||||
|
||||
class SensorClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SensorClient(QObject *parent = nullptr);
|
||||
void receive();
|
||||
|
||||
signals:
|
||||
void coordinatesUpdated(const qt::examples::sensors::Coordinates &);
|
||||
void temperatureUpdated(const qt::examples::sensors::Temperature &);
|
||||
void warning(const qt::examples::sensors::WarningNotification &);
|
||||
|
||||
private:
|
||||
QUdpSocket m_client;
|
||||
QProtobufSerializer m_serializer;
|
||||
};
|
||||
|
||||
#endif // SENSORCLIENT_H
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example sensors
|
||||
\ingroup qtprotobuf-examples
|
||||
\meta tag {network,protobuf,serialization,udp}
|
||||
\meta category {Networking}
|
||||
\title Sensors
|
||||
|
||||
\brief The Sensors example shows how two applications can communicate by
|
||||
sending protobuf messages using UDP sockets.
|
||||
|
||||
The Sensors example consists of the following components:
|
||||
\list
|
||||
\li \c protobuf_sensors library that you generate from the protobuf schema
|
||||
using the \l qt_add_protobuf call.
|
||||
\li \c protobuf_sensor_emulator application that emulates simple sensor
|
||||
behavior.
|
||||
\li \c protobuf_sensors_client application that displays the sensor data
|
||||
from the UDP socket.
|
||||
\endlist
|
||||
|
||||
The client application binds on the \c localhost UDP port \c 65500 and
|
||||
waits for data from the emulator application.
|
||||
\image client.webp
|
||||
|
||||
Use the emulator application to change the values of sensor data and send
|
||||
the data to the client's UDP port.
|
||||
\image emulator.webp
|
||||
|
||||
The applications use the generated messages from the \c protobuf_sensors
|
||||
library to communicate. The library is generated from the protobuf schema
|
||||
described in .proto files.
|
||||
\snippet sensors/CMakeLists.txt 0
|
||||
The first file describes the Type-Length-Value message, that wraps the
|
||||
sensor data:
|
||||
\snippet sensors/tlv.proto 0
|
||||
The second .proto file contains a description of the sensor messages:
|
||||
\snippet sensors/sensors.proto 0
|
||||
|
||||
Messages are serialized using \l QProtobufSerializer that you instantiate in
|
||||
the client:
|
||||
\code
|
||||
class SensorClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
...
|
||||
private:
|
||||
QUdpSocket m_client;
|
||||
QProtobufSerializer m_serializer;
|
||||
};
|
||||
\endcode
|
||||
And the emulator:
|
||||
\code
|
||||
class SensorEmulator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
...
|
||||
QUdpSocket m_socket;
|
||||
QProtobufSerializer m_serializer;
|
||||
};
|
||||
\endcode
|
||||
|
||||
After you click the \uicontrol Send button in the emulator application, the
|
||||
data from \l QLineEdit fields is converted from string format to the message
|
||||
field-specific format, for example, double for the fields of the Coordinates
|
||||
message:
|
||||
\snippet sensors/emulator/emulatorconsole.cpp 0
|
||||
Then the message with all the fields is serialized and wrapped with the
|
||||
Type-Length-Value message:
|
||||
\snippet sensors/emulator/sensoremulator.cpp 0
|
||||
|
||||
The client applies the reverse operations on the received datagrams. First
|
||||
the Type-Length-Value message is deserialized from the datagram data:
|
||||
\snippet sensors/client/sensorclient.cpp 0
|
||||
|
||||
Then the Type-Length-Value message is deserialized into the sensor message:
|
||||
\snippet sensors/client/sensorclient.cpp 1
|
||||
Finally, it is converted and displayed to the user:
|
||||
\snippet sensors/client/clientconsole.cpp 0
|
||||
|
||||
\note Before running the example, make sure that your operating system
|
||||
allows binding on UDP port \c 65500 and sending the data over UDP.
|
||||
*/
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# Copyright (C) 2023 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
qt_add_executable(protobuf_sensor_emulator
|
||||
main.cpp
|
||||
sensoremulator.h sensoremulator.cpp
|
||||
emulatorconsole.h emulatorconsole.cpp
|
||||
emulatorconsole.ui
|
||||
)
|
||||
|
||||
target_link_libraries(protobuf_sensor_emulator PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Protobuf
|
||||
Qt6::Widgets
|
||||
Qt6::Network
|
||||
protobuf_sensors
|
||||
)
|
||||
|
||||
install(TARGETS protobuf_sensor_emulator
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "emulatorconsole.h"
|
||||
#include "ui_emulatorconsole.h"
|
||||
|
||||
#include <QDoubleValidator>
|
||||
#include <QIntValidator>
|
||||
|
||||
#include "sensors.qpb.h"
|
||||
|
||||
EmulatorConsole::EmulatorConsole(QWidget *parent) : QWidget(parent), ui(new Ui::EmulatorConsole)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
auto validator = new QDoubleValidator(-90, 90, 7, ui->latitudeValue);
|
||||
validator->setLocale(QLocale::c());
|
||||
ui->latitudeValue->setValidator(validator);
|
||||
|
||||
validator = new QDoubleValidator(-180, 180, 7, ui->longitudeValue);
|
||||
validator->setLocale(QLocale::c());
|
||||
ui->longitudeValue->setValidator(validator);
|
||||
|
||||
validator = new QDoubleValidator(-1000, 1000, 7, ui->altitudeValue);
|
||||
validator->setLocale(QLocale::c());
|
||||
ui->altitudeValue->setValidator(validator);
|
||||
ui->temperatureValue->setValidator(new QIntValidator(-50, 50, ui->temperatureValue));
|
||||
// ![0]
|
||||
QObject::connect(ui->sendCoordinates, &QPushButton::clicked, this, [this]() {
|
||||
qt::examples::sensors::Coordinates coord;
|
||||
coord.setLatitude(ui->latitudeValue->text().toDouble());
|
||||
coord.setLongitude(ui->longitudeValue->text().toDouble());
|
||||
coord.setAltitude(ui->altitudeValue->text().toDouble());
|
||||
emit coordinatesUpdated(coord);
|
||||
});
|
||||
// ![0]
|
||||
|
||||
QObject::connect(ui->sendTemperature, &QPushButton::clicked, this, [this]() {
|
||||
qt::examples::sensors::Temperature temp;
|
||||
temp.setValue(ui->temperatureValue->text().toInt());
|
||||
temp.setUnits(ui->celciusRadio->isChecked()
|
||||
? qt::examples::sensors::Temperature::Celsius
|
||||
: qt::examples::sensors::Temperature::Farenheit);
|
||||
emit temperatureUpdated(temp);
|
||||
});
|
||||
|
||||
QObject::connect(ui->sendMessage, &QPushButton::clicked, this, [this]() {
|
||||
qt::examples::sensors::WarningNotification warn;
|
||||
warn.setText(ui->warningText->toPlainText());
|
||||
emit warning(warn);
|
||||
});
|
||||
}
|
||||
|
||||
EmulatorConsole::~EmulatorConsole()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
#include "moc_emulatorconsole.cpp"
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef EMULATORCONSOLE_H
|
||||
#define EMULATORCONSOLE_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
namespace qt::examples::sensors {
|
||||
class Coordinates;
|
||||
class Temperature;
|
||||
class WarningNotification;
|
||||
} // namespace qt::examples::sensors
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui {
|
||||
class EmulatorConsole;
|
||||
}
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class EmulatorConsole : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit EmulatorConsole(QWidget *parent = nullptr);
|
||||
~EmulatorConsole();
|
||||
|
||||
signals:
|
||||
void coordinatesUpdated(const qt::examples::sensors::Coordinates &);
|
||||
void temperatureUpdated(const qt::examples::sensors::Temperature &);
|
||||
void warning(const qt::examples::sensors::WarningNotification &);
|
||||
|
||||
private:
|
||||
Ui::EmulatorConsole *ui;
|
||||
};
|
||||
#endif // EMULATORCONSOLE_H
|
||||
|
|
@ -0,0 +1,326 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>EmulatorConsole</class>
|
||||
<widget class="QWidget" name="EmulatorConsole">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>333</width>
|
||||
<height>456</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Emulator Console</string>
|
||||
</property>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>241</width>
|
||||
<height>141</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Coordinates</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="layoutWidget1">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>70</y>
|
||||
<width>216</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Longitude</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="longitudeValue">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>13.5331485</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>100</y>
|
||||
<width>216</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Altitude</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="altitudeValue">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget3">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>40</y>
|
||||
<width>216</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Latitude</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="latitudeValue">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>52.4317463</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>160</y>
|
||||
<width>241</width>
|
||||
<height>141</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Weather</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="layoutWidget4">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>40</y>
|
||||
<width>216</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Temperature</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="temperatureValue">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>20</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QRadioButton" name="celciusRadio">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>80</y>
|
||||
<width>112</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Celcius</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QRadioButton" name="fahrenheitRadio">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>110</y>
|
||||
<width>112</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Fahrenheit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>310</y>
|
||||
<width>241</width>
|
||||
<height>141</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Warning message</string>
|
||||
</property>
|
||||
<widget class="QTextEdit" name="warningText">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>3</x>
|
||||
<y>24</y>
|
||||
<width>231</width>
|
||||
<height>111</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="html">
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
hr { height: 1px; border-width: 0; }
|
||||
li.unchecked::marker { content: "\2610"; }
|
||||
li.checked::marker { content: "\2612"; }
|
||||
</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">!!!A simple warning message!!!</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="sendCoordinates">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>260</x>
|
||||
<y>120</y>
|
||||
<width>61</width>
|
||||
<height>26</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Send</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="sendTemperature">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>260</x>
|
||||
<y>270</y>
|
||||
<width>61</width>
|
||||
<height>26</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Send</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="sendMessage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>260</x>
|
||||
<y>420</y>
|
||||
<width>61</width>
|
||||
<height>26</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Send</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "emulatorconsole.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include "sensoremulator.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
SensorEmulator emul;
|
||||
EmulatorConsole console;
|
||||
QObject::connect(&console, &EmulatorConsole::coordinatesUpdated, &emul,
|
||||
&SensorEmulator::sendCoordinates);
|
||||
QObject::connect(&console, &EmulatorConsole::temperatureUpdated, &emul,
|
||||
&SensorEmulator::sendTemperature);
|
||||
QObject::connect(&console, &EmulatorConsole::warning, &emul, &SensorEmulator::sendWarning);
|
||||
console.show();
|
||||
return app.exec();
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "sensoremulator.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QNetworkDatagram>
|
||||
|
||||
#include "tlv.qpb.h"
|
||||
#include "sensors.qpb.h"
|
||||
|
||||
namespace {
|
||||
QByteArray makeTlvMessage(QProtobufSerializer *serializer, const QByteArray &data,
|
||||
qt::examples::sensors::tlv::MessageTypeGadget::MessageType type)
|
||||
{
|
||||
//! [0]
|
||||
Q_ASSERT(serializer != nullptr);
|
||||
qt::examples::sensors::tlv::TlvMessage msg;
|
||||
msg.setType(type);
|
||||
msg.setValue(data);
|
||||
return msg.serialize(serializer);
|
||||
//! [0]
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SensorEmulator::SensorEmulator(QObject *parent) : QObject(parent) { }
|
||||
|
||||
void SensorEmulator::send(const QByteArray &data)
|
||||
{
|
||||
if (m_socket.writeDatagram(data, QHostAddress::LocalHost, 65500) == -1)
|
||||
qWarning() << "Unable to send data of size: " << data.size() << "to UDP port 65500";
|
||||
}
|
||||
|
||||
void SensorEmulator::sendCoordinates(const qt::examples::sensors::Coordinates &coords)
|
||||
{
|
||||
send(makeTlvMessage(&m_serializer, coords.serialize(&m_serializer),
|
||||
qt::examples::sensors::tlv::MessageTypeGadget::Coordinates));
|
||||
}
|
||||
|
||||
void SensorEmulator::sendTemperature(const qt::examples::sensors::Temperature &temp)
|
||||
{
|
||||
send(makeTlvMessage(&m_serializer, temp.serialize(&m_serializer),
|
||||
qt::examples::sensors::tlv::MessageTypeGadget::Temperature));
|
||||
}
|
||||
|
||||
void SensorEmulator::sendWarning(const qt::examples::sensors::WarningNotification &warn)
|
||||
{
|
||||
send(makeTlvMessage(&m_serializer, warn.serialize(&m_serializer),
|
||||
qt::examples::sensors::tlv::MessageTypeGadget::WarningNotification));
|
||||
}
|
||||
|
||||
#include "moc_sensoremulator.cpp"
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef SENSOREMULATOR_H
|
||||
#define SENSOREMULATOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QUdpSocket>
|
||||
#include <QProtobufSerializer>
|
||||
|
||||
namespace qt::examples::sensors {
|
||||
class Coordinates;
|
||||
class Temperature;
|
||||
class WarningNotification;
|
||||
} // namespace qt::examples::sensors
|
||||
class SensorEmulator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SensorEmulator(QObject *parent = nullptr);
|
||||
|
||||
void sendCoordinates(const qt::examples::sensors::Coordinates &coords);
|
||||
void sendTemperature(const qt::examples::sensors::Temperature &temp);
|
||||
void sendWarning(const qt::examples::sensors::WarningNotification &warn);
|
||||
|
||||
private:
|
||||
void send(const QByteArray &data);
|
||||
|
||||
QUdpSocket m_socket;
|
||||
QProtobufSerializer m_serializer;
|
||||
};
|
||||
|
||||
#endif // SENSOREMULATOR_H
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
syntax = "proto3";
|
||||
|
||||
//! [0]
|
||||
package qt.examples.sensors;
|
||||
|
||||
message Coordinates
|
||||
{
|
||||
double longitude = 1;
|
||||
double latitude = 2;
|
||||
double altitude = 3;
|
||||
}
|
||||
|
||||
message Temperature
|
||||
{
|
||||
enum Unit {
|
||||
Farenheit = 0;
|
||||
Celsius = 1;
|
||||
}
|
||||
sint32 value = 1;
|
||||
Unit units = 2;
|
||||
}
|
||||
|
||||
message WarningNotification
|
||||
{
|
||||
string text = 1;
|
||||
}
|
||||
//! [0]
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
syntax = "proto3";
|
||||
|
||||
//! [0]
|
||||
package qt.examples.sensors.tlv;
|
||||
|
||||
enum MessageType {
|
||||
Coordinates = 0;
|
||||
Temperature = 1;
|
||||
WarningNotification = 2;
|
||||
}
|
||||
|
||||
// Protobuf messages imply inline data size.
|
||||
message TlvMessage
|
||||
{
|
||||
MessageType type = 1;
|
||||
bytes value = 2;
|
||||
}
|
||||
//! [0]
|
||||
|
|
@ -32,9 +32,12 @@ depends += qtdoc qtcore qtcmake
|
|||
{headerdirs,sourcedirs} += ..
|
||||
sourcedirs += ../../tools/doc/src
|
||||
|
||||
url.examples = "https://code.qt.io/cgit/qt/qtgrpc.git/tree/examples/\1?h=$QT_VER"
|
||||
|
||||
exampledirs += ../../../examples/protobuf
|
||||
|
||||
imagedirs += images
|
||||
imagedirs += images \
|
||||
../../../examples/protobuf/sensors/doc/images
|
||||
|
||||
navigation.landingpage = "Qt Protobuf"
|
||||
navigation.cppclassespage = "Qt Protobuf C++ Classes"
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@
|
|||
\list
|
||||
\l {Qt Protobuf C++ Classes}{C++ Classes}
|
||||
\endlist
|
||||
|
||||
\section1 Examples
|
||||
|
||||
\list
|
||||
\li \l {Qt Protobuf Examples}
|
||||
\endlist
|
||||
*/
|
||||
|
||||
/*!
|
||||
|
|
@ -32,3 +38,14 @@
|
|||
\since 6.5
|
||||
\brief Provides protocol buffers support in Qt.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\group qtprotobuf-examples
|
||||
\ingroup all-examples
|
||||
\keyword Qt Protobuf Examples
|
||||
\title Qt Protobuf Examples
|
||||
\brief A collection of examples for \l {Qt Protobuf}
|
||||
|
||||
These examples demonstrate how to generate code using the protobuf schema
|
||||
and use it in your projects.
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue