// SPDX-License-Identifier: GPL-2.0-only /* * Digital Voice Modem - Bridge * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2025 Lorenzo L. Romero, K2LLR */ /** * @file CtsCorController.cpp * @ingroup bridge */ #include "Defines.h" #include "CtsCorController.h" #if !defined(_WIN32) #include #endif // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- /* Initializes a new instance of the CtsCorController class. */ CtsCorController::CtsCorController(const std::string& port) : m_port(port), m_isOpen(false), m_ownsFd(true) #if defined(_WIN32) , m_fd(INVALID_HANDLE_VALUE) #else , m_fd(-1) #endif // defined(_WIN32) { } /* Finalizes a instance of the RtsPttController class. */ CtsCorController::~CtsCorController() { close(); } /* Opens the serial port for CTS control. */ bool CtsCorController::open(int reuseFd) { if (m_isOpen) return true; #if defined(_WIN32) std::string deviceName = m_port; if (deviceName.find("\\\\.\\") == std::string::npos) { deviceName = "\\\\." + m_port; } m_fd = ::CreateFileA(deviceName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (m_fd == INVALID_HANDLE_VALUE) { ::LogError(LOG_HOST, "Cannot open CTS COR device - %s, err=%04lx", m_port.c_str(), ::GetLastError()); return false; } DCB dcb; if (::GetCommState(m_fd, &dcb) == 0) { ::LogError(LOG_HOST, "Cannot get the attributes for %s, err=%04lx", m_port.c_str(), ::GetLastError()); ::CloseHandle(m_fd); m_fd = INVALID_HANDLE_VALUE; return false; } dcb.BaudRate = 9600; dcb.ByteSize = 8; dcb.Parity = NOPARITY; dcb.fParity = FALSE; dcb.StopBits = ONESTOPBIT; dcb.fInX = FALSE; dcb.fOutX = FALSE; dcb.fOutxCtsFlow = FALSE; dcb.fOutxDsrFlow = FALSE; dcb.fDsrSensitivity = FALSE; dcb.fDtrControl = DTR_CONTROL_DISABLE; dcb.fRtsControl = RTS_CONTROL_DISABLE; if (::SetCommState(m_fd, &dcb) == 0) { ::LogError(LOG_HOST, "Cannot set the attributes for %s, err=%04lx", m_port.c_str(), ::GetLastError()); ::CloseHandle(m_fd); m_fd = INVALID_HANDLE_VALUE; return false; } #else // If reusing an existing file descriptor from RTS PTT, don't open a new one if (reuseFd >= 0) { m_fd = reuseFd; m_ownsFd = false; // Only COR can close file descriptor ::LogInfo(LOG_HOST, "CTS COR Controller reusing file descriptor from RTS PTT on %s", m_port.c_str()); m_isOpen = true; return true; } m_ownsFd = true; // COR owns the file descriptor // Open port if not available m_fd = ::open(m_port.c_str(), O_RDONLY | O_NOCTTY | O_NDELAY, 0); if (m_fd < 0) { // Try rw if ro fails m_fd = ::open(m_port.c_str(), O_RDWR | O_NOCTTY | O_NDELAY, 0); if (m_fd < 0) { ::LogError(LOG_HOST, "Cannot open CTS COR device - %s", m_port.c_str()); return false; } } if (::isatty(m_fd) == 0) { ::LogError(LOG_HOST, "%s is not a TTY device", m_port.c_str()); ::close(m_fd); m_fd = -1; return false; } // Save current RTS state before configuring termios int savedModemState = 0; if (::ioctl(m_fd, TIOCMGET, &savedModemState) < 0) { ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str()); ::close(m_fd); m_fd = -1; return false; } bool savedRtsState = (savedModemState & TIOCM_RTS) != 0; if (!setTermios()) { ::close(m_fd); m_fd = -1; return false; } // Restore RTS to its original state int currentModemState = 0; if (::ioctl(m_fd, TIOCMGET, ¤tModemState) < 0) { ::LogError(LOG_HOST, "Cannot get the control attributes for %s after termios", m_port.c_str()); ::close(m_fd); m_fd = -1; return false; } bool currentRtsState = (currentModemState & TIOCM_RTS) != 0; if (currentRtsState != savedRtsState) { // Restore RTS to original state if (savedRtsState) { currentModemState |= TIOCM_RTS; } else { currentModemState &= ~TIOCM_RTS; } if (::ioctl(m_fd, TIOCMSET, ¤tModemState) < 0) { ::LogError(LOG_HOST, "Cannot restore RTS state for %s", m_port.c_str()); ::close(m_fd); m_fd = -1; return false; } ::LogDebug(LOG_HOST, "CTS COR: Restored RTS to %s on %s", savedRtsState ? "HIGH" : "LOW", m_port.c_str()); } #endif // defined(_WIN32) ::LogInfo(LOG_HOST, "CTS COR Controller opened on %s (RTS preserved)", m_port.c_str()); m_isOpen = true; return true; } /* Closes the serial port. */ void CtsCorController::close() { if (!m_isOpen) return; #if defined(_WIN32) if (m_fd != INVALID_HANDLE_VALUE) { ::CloseHandle(m_fd); m_fd = INVALID_HANDLE_VALUE; } #else // Only close the file descriptor if we opened it ourselves // If we're reusing a descriptor from RTS PTT, don't close it if (m_fd != -1 && m_ownsFd) { ::close(m_fd); m_fd = -1; } else if (m_fd != -1 && !m_ownsFd) { m_fd = -1; } #endif // defined(_WIN32) m_isOpen = false; ::LogInfo(LOG_HOST, "CTS COR Controller closed"); } /* Return wether the CTS signal is high (asserted CTS) to trigger COR detection. */ bool CtsCorController::isCtsAsserted() { if (!m_isOpen) return false; #if defined(_WIN32) DWORD modemStat = 0; if (::GetCommModemStatus(m_fd, &modemStat) == 0) { ::LogError(LOG_HOST, "Cannot read modem status for %s, err=%04lx", m_port.c_str(), ::GetLastError()); return false; } return (modemStat & MS_CTS_ON) != 0; #else int modemState = 0; if (::ioctl(m_fd, TIOCMGET, &modemState) < 0) { ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str()); return false; } return (modemState & TIOCM_CTS) != 0; #endif // defined(_WIN32) } // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- /* Sets the termios settings on the serial port. */ bool CtsCorController::setTermios() { #if !defined(_WIN32) termios termios; if (::tcgetattr(m_fd, &termios) < 0) { ::LogError(LOG_HOST, "Cannot get the attributes for %s", m_port.c_str()); return false; } termios.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK); termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL); termios.c_iflag &= ~(IXON | IXOFF | IXANY); termios.c_oflag &= ~(OPOST); // Important: Disable hardware flow control (CRTSCTS) to avoid affecting RTS // We only want to read CTS, not control RTS termios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS); termios.c_cflag |= (CS8 | CLOCAL | CREAD); termios.c_lflag &= ~(ISIG | ICANON | IEXTEN); termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); termios.c_cc[VMIN] = 0; termios.c_cc[VTIME] = 10; ::cfsetospeed(&termios, B9600); ::cfsetispeed(&termios, B9600); if (::tcsetattr(m_fd, TCSANOW, &termios) < 0) { ::LogError(LOG_HOST, "Cannot set the attributes for %s", m_port.c_str()); return false; } #endif // !defined(_WIN32) return true; }