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
}