diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Serial.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,426 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   Serial.cpp
+/// \brief  RS232 serial i/o.
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "Serial.h"
+
+#include "Utils/Log.h"
+#include "Utils/Exception.h"
+
+#include <QString>
+
+#ifdef WIN32
+#   define  S_WRITE(p, b, li, lo)   WriteFile(p, b, li, &lo, NULL)
+#   define  S_READ(p, b, li, lo)    ReadFile(p, b, li, &lo, NULL)
+#   define  S_LEN                   DWORD
+#   define  S_FLUSH(p)              FlushFileBuffers(p)
+#   define  S_PURGE(p)              PurgeComm(p, PURGE_RXCLEAR | PURGE_TXCLEAR)
+#   define  S_CLOSE(p)              CloseHandle(p)
+#else
+#   include <fcntl.h>
+#   include <termios.h>
+#   include <sys/ioctl.h>
+#   include <unistd.h>
+
+#   define  INVALID_HANDLE_VALUE    (-1)
+#   define  S_WRITE(p, b, li, lo)   (lo = write(p, b, li))
+#   define  S_READ(p, b, li, lo)    (lo = read (p, b, li))
+#   define  S_LEN                   ssize_t
+#   define  S_FLUSH(p)              tcdrain(p)
+#   define  S_PURGE(p)              tcflush(p,TCIOFLUSH)
+#   define  S_CLOSE(p)              ::close(p)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+Serial::~Serial()
+{}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Serial::open(const QString& port, const QString &type)
+{
+    if( _isOpen ) return;
+    LOG_TRACE("Open " << port << " ...");
+
+    _hSerial = INVALID_HANDLE_VALUE;
+
+    //------------------------------------------------------------------------
+    // Sanity checks.
+    if( port.isEmpty() )
+        LOG_THROW( "Port is not defined." );
+    if( type.isEmpty() )
+        LOG_THROW( "Port type is not defined." );
+
+    bool usbMode = type.contains("ostc2c",    Qt::CaseInsensitive)
+                || (   type.contains("ostc3",    Qt::CaseInsensitive)
+                   && !type.contains("ostc3p",   Qt::CaseInsensitive))
+                || type.contains("ostc_cr",  Qt::CaseInsensitive);
+    LOG_TRACE((usbMode ? "Fast USB" : "Slow Bluetooth") << " connection mode.");
+
+    //------------------------------------------------------------------------
+#ifdef Q_OS_WIN
+    // BUGFIX: COM ports above COM9 are not automatically recognized,
+    //         hence we have to prepend DEVICE NAMESPACE...
+    // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
+    QByteArray com = port.toLatin1() + "\0";
+    if( !com.startsWith("\\\\.\\") )
+        com = "\\\\.\\" + com;
+
+    _hSerial = CreateFileA(com.data(),
+                         GENERIC_READ | GENERIC_WRITE,
+                         0,             // Exclusive access.
+                         NULL,          // No security
+                         OPEN_EXISTING,
+                         FILE_ATTRIBUTE_DEVICE | FILE_FLAG_NO_BUFFERING,
+                         0);
+    if(_hSerial==INVALID_HANDLE_VALUE)
+    {
+        if(GetLastError()==ERROR_FILE_NOT_FOUND)
+            LOG_THROW( "Unknown port" );
+        LOG_THROW( "Unable to open port" );
+    }
+    S_PURGE(_hSerial);
+
+    DCB dcbSerial = {sizeof(dcbSerial), 0};
+    if( !GetCommState(_hSerial, &dcbSerial) )
+        LOG_THROW( "Unable to get COM port config" );
+
+    dcbSerial.BaudRate     = CBR_115200;
+    dcbSerial.ByteSize     = 8;
+    dcbSerial.Parity       = NOPARITY;
+    dcbSerial.StopBits     = ONESTOPBIT;
+    dcbSerial.fOutxCtsFlow = DTR_CONTROL_ENABLE;                 // NO HARDWARE FLOW CONTROL
+    dcbSerial.fRtsControl  = RTS_CONTROL_ENABLE; //RTS_CONTROL_DISABLE;   // NO HARDWARE FLOW CONTROL
+
+    if( !SetCommState(_hSerial, &dcbSerial) )
+        LOG_THROW( "Unable to set COM port config" );
+
+    COMMTIMEOUTS timeouts={0};
+    if( usbMode ) {
+        timeouts.ReadTotalTimeoutConstant  =  500;  // 0.5 sec
+        timeouts.WriteTotalTimeoutConstant = 1000;  // 1.0 sec
+    } else {
+        timeouts.ReadTotalTimeoutConstant  = 2000;  // 2.0 sec timeout.
+    }
+    if( !SetCommTimeouts(_hSerial, &timeouts) )
+        LOG_THROW( "Unable to configure port" );
+
+    LOG_TRACE("Connection:");
+    LOG_TRACE("    " << dcbSerial.BaudRate << " bauds.");
+    LOG_TRACE("    " << (int)dcbSerial.ByteSize << " bits, "
+                     << (dcbSerial.Parity ? "+parity, " : "no parity, ")
+                     << (dcbSerial.StopBits ? QString(" +%1").arg(dcbSerial.StopBits) : QString("no")) << " stops bits.");
+    LOG_TRACE("    CTS is " << (dcbSerial.fOutxCtsFlow ? "ON." : "OFF."));
+    LOG_TRACE("    RTS is " << ((dcbSerial.fRtsControl == RTS_CONTROL_HANDSHAKE) ? "ON."
+                              : (dcbSerial.fRtsControl == RTS_CONTROL_ENABLE)    ? "FORCED."
+                              :                                                    "OFF."));
+    LOG_TRACE("    Read  timeout " << timeouts.ReadTotalTimeoutConstant  << " msec.");
+    LOG_TRACE("    Write timeout " << timeouts.WriteTotalTimeoutConstant << " msec.");
+#endif
+
+    //------------------------------------------------------------------------
+#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
+    QByteArray p = port.toLatin1();
+    if( ! p.startsWith("/dev/") )
+        p = "/dev/" + p;
+    _hSerial = ::open( p.constData(), O_RDWR | O_NOCTTY | O_NONBLOCK, 0);
+    if( _hSerial<0 )
+        LOG_THROW( "Unable to open port " << p);
+
+    if( ioctl(_hSerial, TIOCEXCL) < 0 )
+        LOG_THROW( "Port in use" );
+
+    // Once opened, clearing the O_NONBLOCK flag.
+    if( fcntl(_hSerial, F_SETFL, 0) < 0 )
+        LOG_THROW( "Can't reset non-blocking I/O" );
+
+    struct termios termios;
+    tcgetattr(_hSerial, &termios);
+
+    cfmakeraw(&termios);
+    if( cfsetspeed(&termios, B115200) < 0 )
+        LOG_THROW( "Bad port speed" );
+
+    termios.c_cflag |= CLOCAL| CS8 | CREAD;     // No DTR/DSR/DCD, 8bit.
+    termios.c_cflag &= ~( PARENB | CSTOPB       // No parity, one stop bit.
+                    | CCTS_OFLOW | CRTS_IFLOW); // No hardware flow control.
+
+    if( usbMode ) {
+        // FAST USB: Fix timeout to 0.5 seconde:
+        termios.c_cc[VMIN]  =  0;   // Pure timout mode (no char needed).
+        termios.c_cc[VTIME] =  5;   // 0.5s timeout after last received byte.
+    } else {
+        // SLOW BLUETOOTH: Fix timeout to 2.0 sec:
+        termios.c_cc[VMIN]  =  0;   // Pure timout mode (no char needed).
+        termios.c_cc[VTIME] = 20;   // 2.0sec timeout after last received byte.
+    }
+
+    if( tcsetattr(_hSerial, TCSANOW, &termios) < 0 )
+        LOG_THROW( "Unable to configure port" );
+#endif
+
+    if( _hSerial == INVALID_HANDLE_VALUE )
+        LOG_THROW( "Port not open" );
+
+    _isOpen = true;
+    LOG_TRACE("Device open successfull.");
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Serial::close()
+{
+    if( !_isOpen ) return;
+    LOG_TRACE("Device close ...");
+
+#ifdef Q_OS_MAC
+    if( ioctl(_hSerial, TIOCNXCL) < 0 )
+        LOG_THROW( "Port in use" );
+#endif
+
+    if( _hSerial == INVALID_HANDLE_VALUE )
+        LOG_THROW( "Port not open" );
+    S_CLOSE(_hSerial);
+
+    _hSerial = INVALID_HANDLE_VALUE;
+    _isOpen  = false;
+    LOG_TRACE("Device close successfull.");
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+unsigned char Serial::readByte() const
+{
+    if( !_isOpen )
+        LOG_THROW( "Port not open" );
+
+    unsigned char byte = 0;
+    S_LEN len = 0;
+    S_READ(_hSerial, &byte, 1, len);
+    if( len != 1 )
+        // LOG_THROW_E(ReadTimeout, "< timeout" );
+        LOG_THROW("< timeout" );
+
+    if( isprint(byte) )
+   //     LOG_DEBUG("< " << QString().sprintf("%02x '%c'", byte, byte) );
+        LOG_DEBUG("< " << QString::asprintf("%02x '%c'", byte, byte));
+    else
+        LOG_DEBUG("< " << QString::asprintf("%02x", byte) );
+
+    return byte;
+}
+
+void Serial::writeByte(unsigned char byte) const
+{
+    if( !_isOpen )
+        LOG_THROW( "Port not open" );
+
+    if( isprint(byte) )
+        LOG_DEBUG("> " << QString::asprintf("%02x '%c'", byte, byte) );
+    else
+        LOG_DEBUG("> " << QString::asprintf("%02x", byte) );
+
+    S_LEN len = 0;
+    S_WRITE(_hSerial, &byte, 1, len);
+    if( len != 1 )
+   //     LOG_THROW_E(WriteTimeout, "> timeout");
+         LOG_THROW("> timeout");
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+unsigned short Serial::readShort() const
+{
+    unsigned char lo = readByte();
+    unsigned char hi = readByte();
+    return hi << 8 | lo;
+}
+
+void Serial::writeShort(unsigned short word) const
+{
+    unsigned char lo = word & 0xFF;
+    unsigned char hi = word >> 8;
+    writeByte(lo); writeByte(hi);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Serial::writeInt24(unsigned int int24) const
+{
+    unsigned char lo = int24 & 0xFF;
+    unsigned char hi = (int24 >>  8) & 0xFF;
+    unsigned char up = (int24 >> 16) & 0xFF;
+    writeByte(lo); writeByte(hi); writeByte(up);
+}
+
+unsigned int Serial::readInt24() const
+{
+    unsigned char lo = readByte();
+    unsigned char hi = readByte();
+    unsigned char up = readByte();
+    return up << 16 | hi << 8 | lo;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+unsigned int Serial::readBlock(unsigned char* ptr, unsigned int size) const
+{
+    if( !_isOpen )
+        LOG_THROW( "Port not open" );
+
+    unsigned int bytes = 0;
+
+    bool timeout = false;
+    while( size > 0 )
+    {
+        // Allow up to 1.0sec for each 4K block.
+        S_LEN todo = (size > 4096) ? 4096 : size;
+        S_LEN done = 0;
+        S_READ(_hSerial, ptr+bytes, todo, done);
+
+        if( done == 0 ) {
+            timeout = true;
+            break;
+        }
+
+        size -= done;
+        bytes += (unsigned int)done;
+    }
+
+    if( Log::minLevel <= Log::LEVEL_DEBUG )
+    {
+        const int DUMP_LINE = 16;
+        const int DUMP_MAX  = 3*DUMP_LINE;
+        const unsigned int length = (bytes < DUMP_MAX) ? bytes : DUMP_MAX;
+        for(unsigned int i=0; i<length; i += DUMP_LINE)
+        {
+            LogAction logger(Log::LEVEL_DEBUG, __FILE__ ,__LINE__, "readBlock()");
+            logger << "< ";
+            for(unsigned int j=i; j<bytes && j<(i+DUMP_LINE); ++j)
+                logger << QString::asprintf("%02x ", ptr[j]);
+            for(unsigned int j=i; j<bytes && j<(i+DUMP_LINE); ++j)
+                logger << (isprint( ptr[j] )
+                          ? ptr[j]
+                          : (unsigned char)'.');
+        }
+        if( length < bytes )
+            LOG_DEBUG("< ... " << (bytes-length) << " more bytes.");
+    }
+
+    if( timeout )
+        LOG_THROW_E(ReadTimeout, "< block timeout (missing " << size << " bytes)");
+
+    return bytes;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Serial::writeBlock(const unsigned char* ptr, unsigned int size) const
+{
+    if( !_isOpen )
+        LOG_THROW( "Port not open" );
+
+    if( Log::minLevel <= Log::LEVEL_DEBUG )
+    {
+        const int DUMP_LINE = 16;
+        const int DUMP_MAX  = 3*DUMP_LINE;
+        const unsigned int length = (size < DUMP_MAX) ? size : DUMP_MAX;
+        for(unsigned int i=0; i<length; i += DUMP_LINE)
+        {
+            LogAction logger(Log::LEVEL_DEBUG, __FILE__ ,__LINE__, "writeBlock()");
+            logger << "> ";
+            for(unsigned int j=i; j<size && j<(i+DUMP_LINE); ++j)
+                logger << QString::asprintf("%02x ", ptr[j]);
+            for(unsigned int j=i; j<size && j<(i+DUMP_LINE); ++j)
+                logger << (isprint( ptr[j] )
+                          ? ptr[j]
+                          : (unsigned char)'.');
+        }
+        if( length < size )
+            LOG_DEBUG("> ... " << (size-length) << " more bytes.");
+    }
+
+    while( size > 0 )
+    {
+        // Allow up to 1.0sec for each 4K block.
+        S_LEN chunck = (size > 4096) ? 4096 : size;
+        S_LEN len = 0;
+        S_WRITE(_hSerial, ptr, chunck, len);
+
+        if( len == 0  )
+            LOG_THROW_E(WriteTimeout, "> block timeout");
+
+        size -= len;
+        ptr  += len;
+    }
+
+    // Auto-fluh on each write.
+    flush();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Serial::purge()
+{
+    if( !_isOpen )
+        LOG_THROW( "Port not open" );
+
+    // Empty incomming buffer
+    S_PURGE(_hSerial);
+
+    LOG_TRACE("Device purged.");
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Serial::flush() const
+{
+    if( !_isOpen )
+        LOG_THROW( "Port not open" );
+
+    S_FLUSH(_hSerial);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Serial::sleep(int msec) const
+{
+#ifdef WIN32
+    Sleep(msec);
+#else
+    usleep(msec*1000);
+#endif
+}