Mercurial > public > ostc_companion
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 +}
