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