comparison Serial.cpp @ 1:0b3630a29ad8

Initial version based on previous repository. Project was ported to QT6 and in now cmake based.
author Ideenmodellierer <tiefenrauscher@web.de>
date Thu, 27 Nov 2025 18:40:28 +0100
parents
children 177f640940f2
comparison
equal deleted inserted replaced
0:76ccd6ce50c0 1:0b3630a29ad8
1 //////////////////////////////////////////////////////////////////////////////
2 /// \file Serial.cpp
3 /// \brief RS232 serial i/o.
4 /// \author JD Gascuel.
5 ///
6 /// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
7 /// $Id$
8 //////////////////////////////////////////////////////////////////////////////
9 //
10 // BSD 2-Clause License:
11 //
12 // Redistribution and use in source and binary forms, with or without
13 // modification, are permitted provided that the following conditions
14 // are met:
15 //
16 // 1. Redistributions of source code must retain the above copyright notice,
17 // this list of conditions and the following disclaimer.
18 //
19 // 2. Redistributions in binary form must reproduce the above copyright notice,
20 // this list of conditions and the following disclaimer in the documentation
21 // and/or other materials provided with the distribution.
22 //
23 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
27 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
33 // THE POSSIBILITY OF SUCH DAMAGE.
34 //
35 //////////////////////////////////////////////////////////////////////////////
36
37 #include "Serial.h"
38
39 #include "Utils/Log.h"
40 #include "Utils/Exception.h"
41
42 #include <QString>
43
44 #ifdef WIN32
45 # define S_WRITE(p, b, li, lo) WriteFile(p, b, li, &lo, NULL)
46 # define S_READ(p, b, li, lo) ReadFile(p, b, li, &lo, NULL)
47 # define S_LEN DWORD
48 # define S_FLUSH(p) FlushFileBuffers(p)
49 # define S_PURGE(p) PurgeComm(p, PURGE_RXCLEAR | PURGE_TXCLEAR)
50 # define S_CLOSE(p) CloseHandle(p)
51 #else
52 # include <fcntl.h>
53 # include <termios.h>
54 # include <sys/ioctl.h>
55 # include <unistd.h>
56
57 # define INVALID_HANDLE_VALUE (-1)
58 # define S_WRITE(p, b, li, lo) (lo = write(p, b, li))
59 # define S_READ(p, b, li, lo) (lo = read (p, b, li))
60 # define S_LEN ssize_t
61 # define S_FLUSH(p) tcdrain(p)
62 # define S_PURGE(p) tcflush(p,TCIOFLUSH)
63 # define S_CLOSE(p) ::close(p)
64 #endif
65
66 ///////////////////////////////////////////////////////////////////////////////
67
68 Serial::~Serial()
69 {}
70
71 ///////////////////////////////////////////////////////////////////////////////
72
73 void Serial::open(const QString& port, const QString &type)
74 {
75 if( _isOpen ) return;
76 LOG_TRACE("Open " << port << " ...");
77
78 _hSerial = INVALID_HANDLE_VALUE;
79
80 //------------------------------------------------------------------------
81 // Sanity checks.
82 if( port.isEmpty() )
83 LOG_THROW( "Port is not defined." );
84 if( type.isEmpty() )
85 LOG_THROW( "Port type is not defined." );
86
87 bool usbMode = type.contains("ostc2c", Qt::CaseInsensitive)
88 || ( type.contains("ostc3", Qt::CaseInsensitive)
89 && !type.contains("ostc3p", Qt::CaseInsensitive))
90 || type.contains("ostc_cr", Qt::CaseInsensitive);
91 LOG_TRACE((usbMode ? "Fast USB" : "Slow Bluetooth") << " connection mode.");
92
93 //------------------------------------------------------------------------
94 #ifdef Q_OS_WIN
95 // BUGFIX: COM ports above COM9 are not automatically recognized,
96 // hence we have to prepend DEVICE NAMESPACE...
97 // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
98 QByteArray com = port.toLatin1() + "\0";
99 if( !com.startsWith("\\\\.\\") )
100 com = "\\\\.\\" + com;
101
102 _hSerial = CreateFileA(com.data(),
103 GENERIC_READ | GENERIC_WRITE,
104 0, // Exclusive access.
105 NULL, // No security
106 OPEN_EXISTING,
107 FILE_ATTRIBUTE_DEVICE | FILE_FLAG_NO_BUFFERING,
108 0);
109 if(_hSerial==INVALID_HANDLE_VALUE)
110 {
111 if(GetLastError()==ERROR_FILE_NOT_FOUND)
112 LOG_THROW( "Unknown port" );
113 LOG_THROW( "Unable to open port" );
114 }
115 S_PURGE(_hSerial);
116
117 DCB dcbSerial = {sizeof(dcbSerial), 0};
118 if( !GetCommState(_hSerial, &dcbSerial) )
119 LOG_THROW( "Unable to get COM port config" );
120
121 dcbSerial.BaudRate = CBR_115200;
122 dcbSerial.ByteSize = 8;
123 dcbSerial.Parity = NOPARITY;
124 dcbSerial.StopBits = ONESTOPBIT;
125 dcbSerial.fOutxCtsFlow = DTR_CONTROL_ENABLE; // NO HARDWARE FLOW CONTROL
126 dcbSerial.fRtsControl = RTS_CONTROL_ENABLE; //RTS_CONTROL_DISABLE; // NO HARDWARE FLOW CONTROL
127
128 if( !SetCommState(_hSerial, &dcbSerial) )
129 LOG_THROW( "Unable to set COM port config" );
130
131 COMMTIMEOUTS timeouts={0};
132 if( usbMode ) {
133 timeouts.ReadTotalTimeoutConstant = 500; // 0.5 sec
134 timeouts.WriteTotalTimeoutConstant = 1000; // 1.0 sec
135 } else {
136 timeouts.ReadTotalTimeoutConstant = 2000; // 2.0 sec timeout.
137 }
138 if( !SetCommTimeouts(_hSerial, &timeouts) )
139 LOG_THROW( "Unable to configure port" );
140
141 LOG_TRACE("Connection:");
142 LOG_TRACE(" " << dcbSerial.BaudRate << " bauds.");
143 LOG_TRACE(" " << (int)dcbSerial.ByteSize << " bits, "
144 << (dcbSerial.Parity ? "+parity, " : "no parity, ")
145 << (dcbSerial.StopBits ? QString(" +%1").arg(dcbSerial.StopBits) : QString("no")) << " stops bits.");
146 LOG_TRACE(" CTS is " << (dcbSerial.fOutxCtsFlow ? "ON." : "OFF."));
147 LOG_TRACE(" RTS is " << ((dcbSerial.fRtsControl == RTS_CONTROL_HANDSHAKE) ? "ON."
148 : (dcbSerial.fRtsControl == RTS_CONTROL_ENABLE) ? "FORCED."
149 : "OFF."));
150 LOG_TRACE(" Read timeout " << timeouts.ReadTotalTimeoutConstant << " msec.");
151 LOG_TRACE(" Write timeout " << timeouts.WriteTotalTimeoutConstant << " msec.");
152 #endif
153
154 //------------------------------------------------------------------------
155 #if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
156 QByteArray p = port.toLatin1();
157 if( ! p.startsWith("/dev/") )
158 p = "/dev/" + p;
159 _hSerial = ::open( p.constData(), O_RDWR | O_NOCTTY | O_NONBLOCK, 0);
160 if( _hSerial<0 )
161 LOG_THROW( "Unable to open port " << p);
162
163 if( ioctl(_hSerial, TIOCEXCL) < 0 )
164 LOG_THROW( "Port in use" );
165
166 // Once opened, clearing the O_NONBLOCK flag.
167 if( fcntl(_hSerial, F_SETFL, 0) < 0 )
168 LOG_THROW( "Can't reset non-blocking I/O" );
169
170 struct termios termios;
171 tcgetattr(_hSerial, &termios);
172
173 cfmakeraw(&termios);
174 if( cfsetspeed(&termios, B115200) < 0 )
175 LOG_THROW( "Bad port speed" );
176
177 termios.c_cflag |= CLOCAL| CS8 | CREAD; // No DTR/DSR/DCD, 8bit.
178 termios.c_cflag &= ~( PARENB | CSTOPB // No parity, one stop bit.
179 | CCTS_OFLOW | CRTS_IFLOW); // No hardware flow control.
180
181 if( usbMode ) {
182 // FAST USB: Fix timeout to 0.5 seconde:
183 termios.c_cc[VMIN] = 0; // Pure timout mode (no char needed).
184 termios.c_cc[VTIME] = 5; // 0.5s timeout after last received byte.
185 } else {
186 // SLOW BLUETOOTH: Fix timeout to 2.0 sec:
187 termios.c_cc[VMIN] = 0; // Pure timout mode (no char needed).
188 termios.c_cc[VTIME] = 20; // 2.0sec timeout after last received byte.
189 }
190
191 if( tcsetattr(_hSerial, TCSANOW, &termios) < 0 )
192 LOG_THROW( "Unable to configure port" );
193 #endif
194
195 if( _hSerial == INVALID_HANDLE_VALUE )
196 LOG_THROW( "Port not open" );
197
198 _isOpen = true;
199 LOG_TRACE("Device open successfull.");
200 }
201
202 ///////////////////////////////////////////////////////////////////////////////
203
204 void Serial::close()
205 {
206 if( !_isOpen ) return;
207 LOG_TRACE("Device close ...");
208
209 #ifdef Q_OS_MAC
210 if( ioctl(_hSerial, TIOCNXCL) < 0 )
211 LOG_THROW( "Port in use" );
212 #endif
213
214 if( _hSerial == INVALID_HANDLE_VALUE )
215 LOG_THROW( "Port not open" );
216 S_CLOSE(_hSerial);
217
218 _hSerial = INVALID_HANDLE_VALUE;
219 _isOpen = false;
220 LOG_TRACE("Device close successfull.");
221 }
222
223 ///////////////////////////////////////////////////////////////////////////////
224
225 unsigned char Serial::readByte() const
226 {
227 if( !_isOpen )
228 LOG_THROW( "Port not open" );
229
230 unsigned char byte = 0;
231 S_LEN len = 0;
232 S_READ(_hSerial, &byte, 1, len);
233 if( len != 1 )
234 // LOG_THROW_E(ReadTimeout, "< timeout" );
235 LOG_THROW("< timeout" );
236
237 if( isprint(byte) )
238 // LOG_DEBUG("< " << QString().sprintf("%02x '%c'", byte, byte) );
239 LOG_DEBUG("< " << QString::asprintf("%02x '%c'", byte, byte));
240 else
241 LOG_DEBUG("< " << QString::asprintf("%02x", byte) );
242
243 return byte;
244 }
245
246 void Serial::writeByte(unsigned char byte) const
247 {
248 if( !_isOpen )
249 LOG_THROW( "Port not open" );
250
251 if( isprint(byte) )
252 LOG_DEBUG("> " << QString::asprintf("%02x '%c'", byte, byte) );
253 else
254 LOG_DEBUG("> " << QString::asprintf("%02x", byte) );
255
256 S_LEN len = 0;
257 S_WRITE(_hSerial, &byte, 1, len);
258 if( len != 1 )
259 // LOG_THROW_E(WriteTimeout, "> timeout");
260 LOG_THROW("> timeout");
261 }
262
263 ///////////////////////////////////////////////////////////////////////////////
264
265 unsigned short Serial::readShort() const
266 {
267 unsigned char lo = readByte();
268 unsigned char hi = readByte();
269 return hi << 8 | lo;
270 }
271
272 void Serial::writeShort(unsigned short word) const
273 {
274 unsigned char lo = word & 0xFF;
275 unsigned char hi = word >> 8;
276 writeByte(lo); writeByte(hi);
277 }
278
279 ///////////////////////////////////////////////////////////////////////////////
280
281 void Serial::writeInt24(unsigned int int24) const
282 {
283 unsigned char lo = int24 & 0xFF;
284 unsigned char hi = (int24 >> 8) & 0xFF;
285 unsigned char up = (int24 >> 16) & 0xFF;
286 writeByte(lo); writeByte(hi); writeByte(up);
287 }
288
289 unsigned int Serial::readInt24() const
290 {
291 unsigned char lo = readByte();
292 unsigned char hi = readByte();
293 unsigned char up = readByte();
294 return up << 16 | hi << 8 | lo;
295 }
296
297 ///////////////////////////////////////////////////////////////////////////////
298
299 unsigned int Serial::readBlock(unsigned char* ptr, unsigned int size) const
300 {
301 if( !_isOpen )
302 LOG_THROW( "Port not open" );
303
304 unsigned int bytes = 0;
305
306 bool timeout = false;
307 while( size > 0 )
308 {
309 // Allow up to 1.0sec for each 4K block.
310 S_LEN todo = (size > 4096) ? 4096 : size;
311 S_LEN done = 0;
312 S_READ(_hSerial, ptr+bytes, todo, done);
313
314 if( done == 0 ) {
315 timeout = true;
316 break;
317 }
318
319 size -= done;
320 bytes += (unsigned int)done;
321 }
322
323 if( Log::minLevel <= Log::LEVEL_DEBUG )
324 {
325 const int DUMP_LINE = 16;
326 const int DUMP_MAX = 3*DUMP_LINE;
327 const unsigned int length = (bytes < DUMP_MAX) ? bytes : DUMP_MAX;
328 for(unsigned int i=0; i<length; i += DUMP_LINE)
329 {
330 LogAction logger(Log::LEVEL_DEBUG, __FILE__ ,__LINE__, "readBlock()");
331 logger << "< ";
332 for(unsigned int j=i; j<bytes && j<(i+DUMP_LINE); ++j)
333 logger << QString::asprintf("%02x ", ptr[j]);
334 for(unsigned int j=i; j<bytes && j<(i+DUMP_LINE); ++j)
335 logger << (isprint( ptr[j] )
336 ? ptr[j]
337 : (unsigned char)'.');
338 }
339 if( length < bytes )
340 LOG_DEBUG("< ... " << (bytes-length) << " more bytes.");
341 }
342
343 if( timeout )
344 LOG_THROW_E(ReadTimeout, "< block timeout (missing " << size << " bytes)");
345
346 return bytes;
347 }
348
349 ///////////////////////////////////////////////////////////////////////////////
350
351 void Serial::writeBlock(const unsigned char* ptr, unsigned int size) const
352 {
353 if( !_isOpen )
354 LOG_THROW( "Port not open" );
355
356 if( Log::minLevel <= Log::LEVEL_DEBUG )
357 {
358 const int DUMP_LINE = 16;
359 const int DUMP_MAX = 3*DUMP_LINE;
360 const unsigned int length = (size < DUMP_MAX) ? size : DUMP_MAX;
361 for(unsigned int i=0; i<length; i += DUMP_LINE)
362 {
363 LogAction logger(Log::LEVEL_DEBUG, __FILE__ ,__LINE__, "writeBlock()");
364 logger << "> ";
365 for(unsigned int j=i; j<size && j<(i+DUMP_LINE); ++j)
366 logger << QString::asprintf("%02x ", ptr[j]);
367 for(unsigned int j=i; j<size && j<(i+DUMP_LINE); ++j)
368 logger << (isprint( ptr[j] )
369 ? ptr[j]
370 : (unsigned char)'.');
371 }
372 if( length < size )
373 LOG_DEBUG("> ... " << (size-length) << " more bytes.");
374 }
375
376 while( size > 0 )
377 {
378 // Allow up to 1.0sec for each 4K block.
379 S_LEN chunck = (size > 4096) ? 4096 : size;
380 S_LEN len = 0;
381 S_WRITE(_hSerial, ptr, chunck, len);
382
383 if( len == 0 )
384 LOG_THROW_E(WriteTimeout, "> block timeout");
385
386 size -= len;
387 ptr += len;
388 }
389
390 // Auto-fluh on each write.
391 flush();
392 }
393
394 ///////////////////////////////////////////////////////////////////////////////
395
396 void Serial::purge()
397 {
398 if( !_isOpen )
399 LOG_THROW( "Port not open" );
400
401 // Empty incomming buffer
402 S_PURGE(_hSerial);
403
404 LOG_TRACE("Device purged.");
405 }
406
407 ///////////////////////////////////////////////////////////////////////////////
408
409 void Serial::flush() const
410 {
411 if( !_isOpen )
412 LOG_THROW( "Port not open" );
413
414 S_FLUSH(_hSerial);
415 }
416
417 ///////////////////////////////////////////////////////////////////////////////
418
419 void Serial::sleep(int msec) const
420 {
421 #ifdef WIN32
422 Sleep(msec);
423 #else
424 usleep(msec*1000);
425 #endif
426 }