Mercurial > public > ostc_companion
view Serial.cpp @ 14:e47e0f59101d default tip
Enable OSTC 4/5 Icon option
The button for uploading the icon is now activated based on the first FW
version supporting this function
| author | Ideenmodellierer |
|---|---|
| date | Mon, 12 Jan 2026 18:47:00 +0100 |
| parents | 9a3c1a6f9833 |
| children |
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/Exception.h" #include "Utils/Log.h" #include <QString> #ifdef Q_OS_LINUX #include <termios.h> #endif #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 <sys/ioctl.h> #include <termios.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"); if (isprint(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 }
