Mercurial > public > ostc_companion
view 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 source
////////////////////////////////////////////////////////////////////////////// /// \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 }
