Skip to content

Commit a1477b3

Browse files
committed
add qt serial protocol
1 parent f29b5c7 commit a1477b3

10 files changed

Lines changed: 489 additions & 15 deletions

File tree

.github/workflows/test.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- name: Install dependencies
2121
run: |
2222
sudo apt update
23-
sudo apt install -y g++ cmake libasio-dev socat
23+
sudo apt install -y g++ cmake libasio-dev socat qt6-base-dev qt6-serialport-dev
2424
2525
- name: Configure CMake
2626
run: cmake -S test -B test/build
@@ -47,7 +47,7 @@ jobs:
4747
- name: Install dependencies
4848
run: |
4949
sudo apt update
50-
sudo apt install -y clang-18 g++ cmake libasio-dev socat
50+
sudo apt install -y clang-18 g++ cmake libasio-dev socat qt6-base-dev qt6-serialport-dev
5151
5252
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-18 100
5353
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-18 100

AsioProtocols.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ namespace ProtoComm
119119
throw std::logic_error("channel event callback must be set before starting the protocol");
120120

121121
if (std::find_if(m_channels.begin(), m_channels.end(), [&portName](const Channel& channel) { return channel.portName == portName; }) != m_channels.end())
122-
return false;
122+
return std::nullopt;
123123

124124
Channel& ch = m_channels.emplace_back(m_ioCtx, portName);
125125
try

ProtoComm.hpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,7 @@ namespace ProtoComm
854854
if (!callback)
855855
throw std::invalid_argument("callback cannot be null");
856856

857-
std::vector<uint8_t> buffer;
857+
auto buffer = std::make_shared<std::vector<uint8_t>>();
858858
std::vector<uint8_t> messagePositions(messages.size());
859859
for (size_t i = 0; i < messages.size(); ++i)
860860
{
@@ -865,13 +865,13 @@ namespace ProtoComm
865865

866866
msg.Pack(frame);
867867
frameHandler.Seal(msg, frame);
868-
buffer.insert(buffer.end(), frame.begin(), frame.end());
868+
buffer->insert(buffer->end(), frame.begin(), frame.end());
869869
if ((i + 1) < messages.size())
870870
messagePositions[i + 1] = messagePositions[i] + frame.size();
871871
}
872872

873-
m_protocol.WriteAsync(ch->id, buffer,
874-
[this, ch, callback, messagePositions](const std::error_code& ec, ICommProtocol::ChannelId channelId, size_t size)
873+
m_protocol.WriteAsync(ch->id, *buffer,
874+
[this, ch, callback, buffer, messagePositions](const std::error_code& ec, ICommProtocol::ChannelId channelId, size_t size)
875875
{
876876
auto it = std::find_if(messagePositions.begin(), messagePositions.end(), [size](size_t p) { return p >= size; });
877877
const size_t writtenMessageCount = static_cast<size_t>(std::distance(messagePositions.begin(), it));
@@ -934,7 +934,7 @@ namespace ProtoComm
934934

935935
auto it = std::find_if(m_channels.begin(), m_channels.end(), [channelId](const auto& ch) { return ch->id == channelId; });
936936
if (it == m_channels.end())
937-
std::invalid_argument("channel not found");
937+
throw std::invalid_argument("channel not found");
938938
(void)m_channels.erase(it);
939939
}
940940
else

QtProtocols.cpp

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
#include "QtProtocols.hpp"
2+
3+
namespace ProtoComm
4+
{
5+
6+
#pragma region Serial
7+
8+
QtSerialProtocol::Channel::Channel(const QString& portName)
9+
: id(0)
10+
{
11+
id = std::hash<QString>()(portName);
12+
}
13+
14+
QtSerialProtocol::QtSerialProtocol()
15+
{
16+
m_ioThread.start();
17+
}
18+
19+
QtSerialProtocol::~QtSerialProtocol()
20+
{
21+
if (this->IsRunning())
22+
this->Stop();
23+
24+
m_ioThread.quit();
25+
(void)m_ioThread.wait();
26+
}
27+
28+
size_t QtSerialProtocol::ChannelCount() const
29+
{
30+
return m_channels.size();
31+
}
32+
33+
size_t QtSerialProtocol::AvailableReadSize(ICommProtocol::ChannelId channelId) const
34+
{
35+
const Channel& ch = this->FindChannel(channelId);
36+
return ch.port.bytesAvailable();
37+
}
38+
39+
bool QtSerialProtocol::IsRunning() const
40+
{
41+
return std::find_if(m_channels.begin(), m_channels.end(), [](const Channel& channel) { return channel.port.isOpen(); }) != m_channels.end();
42+
}
43+
44+
bool QtSerialProtocol::IsRunning(ICommProtocol::ChannelId channelId) const
45+
{
46+
const Channel& ch = this->FindChannel(channelId);
47+
return ch.port.isOpen();
48+
}
49+
50+
void QtSerialProtocol::SetChannelEventCallback(ICommProtocol::ChannelEventCallback callback)
51+
{
52+
if (!callback)
53+
throw std::invalid_argument("channel event callback cannot be null");
54+
m_channelEventCallback = callback;
55+
}
56+
57+
std::optional<ICommProtocol::ChannelId> QtSerialProtocol::Start(
58+
const QString& portName,
59+
qint32 baudRate,
60+
QSerialPort::DataBits dataBits,
61+
QSerialPort::StopBits stopBits,
62+
QSerialPort::Parity parity,
63+
QSerialPort::FlowControl flowControl)
64+
{
65+
if (!m_channelEventCallback)
66+
throw std::logic_error("channel event callback must be set before starting the protocol");
67+
68+
if (std::find_if(m_channels.begin(), m_channels.end(), [&portName](const Channel& channel) { return channel.port.portName() == portName; }) != m_channels.end())
69+
return std::nullopt;
70+
71+
const ICommProtocol::ChannelId channelId = std::hash<QString>()(portName);
72+
QObject obj; // we need this to make invokeMethod work
73+
obj.moveToThread(&m_ioThread);
74+
75+
try
76+
{
77+
std::promise<bool> promise;
78+
(void)QMetaObject::invokeMethod(&obj,
79+
[&]()
80+
{
81+
try
82+
{
83+
Channel& ch = m_channels.emplace_back(portName);
84+
85+
ch.port.setPortName(portName);
86+
87+
if (!ch.port.setBaudRate(baudRate))
88+
throw std::runtime_error("failed to set baud rate");
89+
90+
if (!ch.port.setDataBits(dataBits))
91+
throw std::runtime_error("failed to set data bits");
92+
93+
if (!ch.port.setStopBits(stopBits))
94+
throw std::runtime_error("failed to set stop bits");
95+
96+
if (!ch.port.setParity(parity))
97+
throw std::runtime_error("failed to set parity");
98+
99+
if (!ch.port.setFlowControl(flowControl))
100+
throw std::runtime_error("failed to set flow control");
101+
102+
if (!ch.port.open(QIODeviceBase::ReadWrite) || !ch.port.isOpen())
103+
throw std::runtime_error("port failed to open");
104+
105+
promise.set_value(true);
106+
}
107+
catch (const std::exception& e)
108+
{
109+
try
110+
{
111+
promise.set_exception(std::current_exception());
112+
}
113+
catch (...)
114+
{
115+
promise.set_value(false);
116+
}
117+
}
118+
}, Qt::QueuedConnection);
119+
120+
const bool res = promise.get_future().get();
121+
if (!res)
122+
throw std::runtime_error("something went wrong");
123+
124+
m_channelEventCallback(channelId, ICommProtocol::ChannelEventType::ChannelAdded);
125+
return channelId;
126+
}
127+
catch (const std::exception&)
128+
{
129+
auto it = std::find_if(m_channels.begin(), m_channels.end(), [channelId](const Channel& ch) { return ch.id == channelId; });
130+
if (it != m_channels.end() && it->port.isOpen())
131+
{
132+
std::promise<void> promise;
133+
(void)QMetaObject::invokeMethod(&obj,
134+
[this, it, &promise]()
135+
{
136+
it->port.close();
137+
(void)m_channels.erase(it);
138+
promise.set_value();
139+
}, Qt::QueuedConnection);
140+
promise.get_future().get();
141+
}
142+
return std::nullopt;
143+
}
144+
}
145+
146+
void QtSerialProtocol::Stop()
147+
{
148+
for (size_t i = m_channels.size(); (i--) > 0;)
149+
{
150+
auto it = std::next(m_channels.begin(), i);
151+
if (it->port.isOpen())
152+
{
153+
m_channelEventCallback(it->id, ICommProtocol::ChannelEventType::ChannelRemoved);
154+
155+
std::promise<void> promise;
156+
(void)QMetaObject::invokeMethod(&it->port, [this, it, &promise]()
157+
{
158+
it->port.close();
159+
(void)m_channels.erase(it);
160+
promise.set_value();
161+
}, Qt::QueuedConnection);
162+
promise.get_future().get();
163+
}
164+
}
165+
}
166+
167+
void QtSerialProtocol::Stop(ICommProtocol::ChannelId channelId)
168+
{
169+
auto it = std::find_if(m_channels.begin(), m_channels.end(), [channelId](const Channel& ch) { return ch.id == channelId; });
170+
if (it != m_channels.end() && it->port.isOpen())
171+
{
172+
m_channelEventCallback(it->id, ICommProtocol::ChannelEventType::ChannelRemoved);
173+
174+
std::promise<void> promise;
175+
(void)QMetaObject::invokeMethod(&it->port, [this, it, &promise]()
176+
{
177+
it->port.close();
178+
(void)m_channels.erase(it);
179+
promise.set_value();
180+
}, Qt::QueuedConnection);
181+
promise.get_future().get();
182+
}
183+
}
184+
185+
size_t QtSerialProtocol::Read(ICommProtocol::ChannelId channelId, std::span<uint8_t> buffer)
186+
{
187+
if (buffer.empty())
188+
return 0;
189+
190+
Channel& ch = this->FindChannel(channelId);
191+
if (!ch.port.isOpen())
192+
return 0;
193+
return ch.port.read(reinterpret_cast<char*>(buffer.data()), buffer.size());
194+
}
195+
196+
void QtSerialProtocol::ReadAsync(ICommProtocol::ChannelId channelId, ICommProtocol::ReadCallback callback)
197+
{
198+
if (!callback)
199+
throw std::invalid_argument("callback cannot be null");
200+
201+
Channel& ch = this->FindChannel(channelId);
202+
if (!ch.port.isOpen())
203+
{
204+
callback(std::make_error_code(std::errc::not_connected), ch.id, std::span<const uint8_t>{});
205+
return;
206+
}
207+
208+
auto connection = std::make_shared<QMetaObject::Connection>();
209+
(*connection) = ch.port.connect(&ch.port, &QSerialPort::readyRead,
210+
[this, channelId, callback, connection]()
211+
{
212+
Channel& ch = this->FindChannel(channelId);
213+
QByteArray data = ch.port.readAll();
214+
callback(std::error_code(), channelId, std::span<const uint8_t>(reinterpret_cast<const uint8_t*>(data.data()), data.size()));
215+
(void)ch.port.disconnect(*connection);
216+
});
217+
}
218+
219+
void QtSerialProtocol::Write(ICommProtocol::ChannelId channelId, std::span<const uint8_t> buffer)
220+
{
221+
if (buffer.empty())
222+
return;
223+
224+
Channel& ch = this->FindChannel(channelId);
225+
if (ch.port.isOpen())
226+
{
227+
(void)ch.port.write(reinterpret_cast<const char*>(buffer.data()), buffer.size());
228+
(void)ch.port.waitForBytesWritten(-1);
229+
}
230+
}
231+
232+
void QtSerialProtocol::WriteAsync(ICommProtocol::ChannelId channelId, std::span<const uint8_t> buffer, ICommProtocol::WriteCallback callback)
233+
{
234+
if (!callback)
235+
throw std::invalid_argument("callback cannot be null");
236+
237+
if (buffer.empty())
238+
{
239+
callback(std::error_code(), channelId, 0);
240+
return;
241+
}
242+
243+
Channel& ch = this->FindChannel(channelId);
244+
if (!ch.port.isOpen())
245+
{
246+
callback(std::make_error_code(std::errc::not_connected), ch.id, 0);
247+
return;
248+
}
249+
250+
(void)QMetaObject::invokeMethod(&ch.port,
251+
[this, channelId, buffer, callback]()
252+
{
253+
Channel& ch = this->FindChannel(channelId);
254+
(void)ch.port.write(reinterpret_cast<const char*>(buffer.data()), buffer.size());
255+
}, Qt::QueuedConnection);
256+
257+
auto connection = std::make_shared<QMetaObject::Connection>();
258+
(*connection) = ch.port.connect(&ch.port, &QSerialPort::bytesWritten,
259+
[this, channelId, callback, connection](quint64 size)
260+
{
261+
Channel& ch = this->FindChannel(channelId);
262+
QByteArray data = ch.port.readAll();
263+
callback(std::error_code(), channelId, size);
264+
(void)ch.port.disconnect(*connection);
265+
});
266+
}
267+
268+
QtSerialProtocol::Channel& QtSerialProtocol::FindChannel(ICommProtocol::ChannelId channelId)
269+
{
270+
auto it = std::find_if(m_channels.begin(), m_channels.end(), [channelId](const Channel& ch) { return ch.id == channelId; });
271+
if (it == m_channels.end())
272+
throw std::invalid_argument(std::format("channel with the id '{}' not found.", channelId));
273+
return *it;
274+
}
275+
276+
const QtSerialProtocol::Channel& QtSerialProtocol::FindChannel(ICommProtocol::ChannelId channelId) const
277+
{
278+
auto it = std::find_if(m_channels.begin(), m_channels.end(), [channelId](const Channel& ch) { return ch.id == channelId; });
279+
if (it == m_channels.end())
280+
throw std::invalid_argument(std::format("channel with the id '{}' not found.", channelId));
281+
return *it;
282+
}
283+
284+
#pragma endregion Serial
285+
286+
}

0 commit comments

Comments
 (0)