From 2cf126f2ce2c34e22572971b8f1c398683b4660e Mon Sep 17 00:00:00 2001 From: "Lorenzo L. Romero" Date: Fri, 22 Aug 2025 19:29:28 -0400 Subject: [PATCH] Add support in dvmbridge for a serial PTT activation switch. RTS is asserted on the serial port defined in bridge-config.yml for the duration of audio received, then is removed. --- configs/bridge-config.example.yml | 6 + src/bridge/HostBridge.cpp | 84 ++++++++++ src/bridge/HostBridge.h | 21 +++ src/bridge/RtsPttController.cpp | 264 ++++++++++++++++++++++++++++++ src/bridge/RtsPttController.h | 91 ++++++++++ src/host/modem/port/ISerialPort.h | 11 ++ src/host/modem/port/UARTPort.cpp | 60 +++++++ src/host/modem/port/UARTPort.h | 11 ++ 8 files changed, 548 insertions(+) create mode 100644 src/bridge/RtsPttController.cpp create mode 100644 src/bridge/RtsPttController.h diff --git a/configs/bridge-config.example.yml b/configs/bridge-config.example.yml index 36fc829ab..2006df91a 100644 --- a/configs/bridge-config.example.yml +++ b/configs/bridge-config.example.yml @@ -179,3 +179,9 @@ system: trace: false # Flag indicating whether or not debug logging is enabled. debug: false + + # RTS PTT Configuration + # Flag indicating whether RTS PTT control is enabled. + rtsPttEnable: false + # Serial port device for RTS PTT control (e.g., /dev/ttyUSB0). + rtsPttPort: "/dev/ttyUSB0" diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index a6607420f..de35d6df2 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -123,6 +123,13 @@ void audioCallback(ma_device* device, void* output, const void* input, ma_uint32 pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); pcmIdx += 2; } + + // Assert RTS PTT when audio is being sent to output + bridge->assertRtsPtt(); + } + else { + // Deassert RTS PTT when no audio is being sent to output + bridge->deassertRtsPtt(); } } @@ -371,6 +378,10 @@ HostBridge::HostBridge(const std::string& confFile) : m_running(false), m_trace(false), m_debug(false), + m_rtsPttEnable(false), + m_rtsPttPort(), + m_rtsPttController(nullptr), + m_rtsPttActive(false), m_rtpSeqNo(0U), m_rtpTimestamp(INVALID_TS), m_usrpSeqNo(0U) @@ -411,6 +422,12 @@ HostBridge::HostBridge(const std::string& confFile) : HostBridge::~HostBridge() { + if (m_rtsPttController != nullptr) { + m_rtsPttController->close(); + delete m_rtsPttController; + m_rtsPttController = nullptr; + } + delete[] m_ambeBuffer; delete[] m_netLDU1; delete[] m_netLDU2; @@ -518,6 +535,11 @@ int HostBridge::run() if (!ret) return EXIT_FAILURE; + // initialize RTS PTT control + ret = initializeRtsPtt(); + if (!ret) + return EXIT_FAILURE; + ma_result result; if (m_localAudio) { // initialize audio devices @@ -1031,6 +1053,10 @@ bool HostBridge::readParams() m_trace = systemConf["trace"].as(false); m_debug = systemConf["debug"].as(false); + // RTS PTT Configuration + m_rtsPttEnable = systemConf["rtsPttEnable"].as(false); + m_rtsPttPort = systemConf["rtsPttPort"].as("/dev/ttyUSB0"); + LogInfo("General Parameters"); LogInfo(" Rx Audio Gain: %.1f", m_rxAudioGain); LogInfo(" Vocoder Decoder Audio Gain: %.1f", m_vocoderDecoderAudioGain); @@ -1048,6 +1074,10 @@ bool HostBridge::readParams() LogInfo(" Grant Demands: %s", m_grantDemand ? "yes" : "no"); LogInfo(" Local Audio: %s", m_localAudio ? "yes" : "no"); LogInfo(" UDP Audio: %s", m_udpAudio ? "yes" : "no"); + LogInfo(" RTS PTT Enable: %s", m_rtsPttEnable ? "yes" : "no"); + if (m_rtsPttEnable) { + LogInfo(" RTS PTT Port: %s", m_rtsPttPort.c_str()); + } if (m_debug) { LogInfo(" Debug: yes"); @@ -1662,6 +1692,8 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst if (m_localAudio) { m_outputAudio.addData(samples, MBE_SAMPLES_LENGTH); + // Assert RTS PTT when audio is being sent to output + assertRtsPtt(); } if (m_udpAudio) { @@ -2341,6 +2373,8 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI if (m_localAudio) { m_outputAudio.addData(samples, MBE_SAMPLES_LENGTH); + // Assert RTS PTT when audio is being sent to output + assertRtsPtt(); } if (m_udpAudio) { @@ -3512,3 +3546,53 @@ void* HostBridge::threadCallWatchdog(void* arg) return nullptr; } + +/* Helper to initialize RTS PTT control. */ + +bool HostBridge::initializeRtsPtt() +{ + if (!m_rtsPttEnable) + return true; + + if (m_rtsPttPort.empty()) { + ::LogError(LOG_HOST, "RTS PTT port is not specified"); + return false; + } + + m_rtsPttController = new RtsPttController(m_rtsPttPort); + if (!m_rtsPttController->open()) { + ::LogError(LOG_HOST, "Failed to open RTS PTT port %s", m_rtsPttPort.c_str()); + delete m_rtsPttController; + m_rtsPttController = nullptr; + return false; + } + + ::LogInfo(LOG_HOST, "RTS PTT Controller initialized on %s", m_rtsPttPort.c_str()); + return true; +} + +/* Helper to assert RTS PTT (start transmission). */ + +void HostBridge::assertRtsPtt() +{ + if (!m_rtsPttEnable || m_rtsPttController == nullptr || m_rtsPttActive) + return; + + if (m_rtsPttController->setPTT()) { + m_rtsPttActive = true; + ::LogDebug(LOG_HOST, "RTS PTT asserted"); + } +} + +/* Helper to deassert RTS PTT (stop transmission). */ + +void HostBridge::deassertRtsPtt() +{ + if (!m_rtsPttEnable || m_rtsPttController == nullptr || !m_rtsPttActive) + return; + + if (m_rtsPttController->clearPTT()) { + m_rtsPttActive = false; + ::LogDebug(LOG_HOST, "RTS PTT deasserted"); + } +} diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index 398935e8c..e7ac64eb8 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -32,6 +32,7 @@ #include "audio/miniaudio.h" #include "mdc/mdc_decode.h" #include "network/PeerNetwork.h" +#include "RtsPttController.h" #include #include @@ -278,6 +279,12 @@ class HOST_SW_API HostBridge { bool m_trace; bool m_debug; + // RTS PTT Control + bool m_rtsPttEnable; + std::string m_rtsPttPort; + RtsPttController* m_rtsPttController; + bool m_rtsPttActive; + uint16_t m_rtpSeqNo; uint32_t m_rtpTimestamp; @@ -517,6 +524,20 @@ class HOST_SW_API HostBridge { */ void processTEKResponse(p25::kmm::KeyItem* ki, uint8_t algId, uint8_t keyLength); + /** + * @brief Helper to initialize RTS PTT control. + * @returns bool True, if RTS PTT was initialized successfully, otherwise false. + */ + bool initializeRtsPtt(); + /** + * @brief Helper to assert RTS PTT (start transmission). + */ + void assertRtsPtt(); + /** + * @brief Helper to deassert RTS PTT (stop transmission). + */ + void deassertRtsPtt(); + /** * @brief Entry point to audio processing thread. * @param arg Instance of the thread_t structure. diff --git a/src/bridge/RtsPttController.cpp b/src/bridge/RtsPttController.cpp new file mode 100644 index 000000000..e82be9c99 --- /dev/null +++ b/src/bridge/RtsPttController.cpp @@ -0,0 +1,264 @@ +// 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) 2024-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2025 Lorenzo L. Romero, K2LLR + * + */ +#include "Defines.h" +#include "RtsPttController.h" + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the RtsPttController class. */ + +RtsPttController::RtsPttController(const std::string& port) : + m_port(port), + m_isOpen(false), +#if defined(_WIN32) + m_fd(INVALID_HANDLE_VALUE) +#else + m_fd(-1) +#endif // defined(_WIN32) +{ + assert(!port.empty()); +} + +/* Finalizes a instance of the RtsPttController class. */ + +RtsPttController::~RtsPttController() +{ + close(); +} + +/* Opens the serial port for RTS control. */ + +bool RtsPttController::open() +{ + if (m_isOpen) + return true; + +#if defined(_WIN32) + assert(m_fd == INVALID_HANDLE_VALUE); + + 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 RTS PTT 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; + } + + // Clear RTS initially + if (::EscapeCommFunction(m_fd, CLRRTS) == 0) { + ::LogError(LOG_HOST, "Cannot clear RTS for %s, err=%04lx", m_port.c_str(), ::GetLastError()); + ::CloseHandle(m_fd); + m_fd = INVALID_HANDLE_VALUE; + return false; + } + +#else + assert(m_fd == -1); + + m_fd = ::open(m_port.c_str(), O_RDWR | O_NOCTTY | O_NDELAY, 0); + if (m_fd < 0) { + ::LogError(LOG_HOST, "Cannot open RTS PTT 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; + } + + if (!setTermios()) { + ::close(m_fd); + m_fd = -1; + return false; + } +#endif // defined(_WIN32) + + ::LogInfo(LOG_HOST, "RTS PTT Controller opened on %s", m_port.c_str()); + m_isOpen = true; + return true; +} + +/* Closes the serial port. */ + +void RtsPttController::close() +{ + if (!m_isOpen) + return; + + // Clear RTS before closing + clearPTT(); + +#if defined(_WIN32) + if (m_fd != INVALID_HANDLE_VALUE) { + ::CloseHandle(m_fd); + m_fd = INVALID_HANDLE_VALUE; + } +#else + if (m_fd != -1) { + ::close(m_fd); + m_fd = -1; + } +#endif // defined(_WIN32) + + m_isOpen = false; + ::LogInfo(LOG_HOST, "RTS PTT Controller closed"); +} + +/* Sets RTS signal high (asserts RTS) to trigger PTT. */ + +bool RtsPttController::setPTT() +{ + if (!m_isOpen) + return false; + +#if defined(_WIN32) + if (::EscapeCommFunction(m_fd, SETRTS) == 0) { + ::LogError(LOG_HOST, "Cannot set RTS PTT for %s, err=%04lx", m_port.c_str(), ::GetLastError()); + return false; + } +#else + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str()); + return false; + } + + y |= TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot set RTS PTT for %s", m_port.c_str()); + return false; + } +#endif // defined(_WIN32) + + ::LogDebug(LOG_HOST, "RTS PTT asserted on %s", m_port.c_str()); + return true; +} + +/* Sets RTS signal low (clears RTS) to release PTT. */ + +bool RtsPttController::clearPTT() +{ + if (!m_isOpen) + return false; + +#if defined(_WIN32) + if (::EscapeCommFunction(m_fd, CLRRTS) == 0) { + ::LogError(LOG_HOST, "Cannot clear RTS PTT for %s, err=%04lx", m_port.c_str(), ::GetLastError()); + return false; + } +#else + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str()); + return false; + } + + y &= ~TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot clear RTS PTT for %s", m_port.c_str()); + return false; + } +#endif // defined(_WIN32) + + ::LogDebug(LOG_HOST, "RTS PTT cleared on %s", m_port.c_str()); + return true; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Sets the termios settings on the serial port. */ + +bool RtsPttController::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); + 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; + } + + // Clear RTS initially + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str()); + return false; + } + + y &= ~TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot clear RTS for %s", m_port.c_str()); + return false; + } +#endif // !defined(_WIN32) + + return true; +} diff --git a/src/bridge/RtsPttController.h b/src/bridge/RtsPttController.h new file mode 100644 index 000000000..5cc8860c4 --- /dev/null +++ b/src/bridge/RtsPttController.h @@ -0,0 +1,91 @@ +// 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) 2024-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2025 Lorenzo L. Romero, K2LLR + * + */ +/** + * @file RtsPttController.h + * @ingroup bridge + * @file RtsPttController.cpp + * @ingroup bridge + */ +#if !defined(__RTS_PTT_CONTROLLER_H__) +#define __RTS_PTT_CONTROLLER_H__ + +#include "Defines.h" +#include "common/Log.h" + +#include + +#if defined(_WIN32) +#include +#else +#include +#include +#include +#include +#endif // defined(_WIN32) + +// --------------------------------------------------------------------------- +// Class Declaration +// --------------------------------------------------------------------------- + +/** + * @brief This class implements RTS PTT control for the bridge. + * @ingroup bridge + */ +class HOST_SW_API RtsPttController { +public: + /** + * @brief Initializes a new instance of the RtsPttController class. + * @param port Serial port device (e.g., /dev/ttyUSB0). + */ + RtsPttController(const std::string& port); + /** + * @brief Finalizes a instance of the RtsPttController class. + */ + ~RtsPttController(); + + /** + * @brief Opens the serial port for RTS control. + * @returns bool True, if port was opened successfully, otherwise false. + */ + bool open(); + /** + * @brief Closes the serial port. + */ + void close(); + + /** + * @brief Sets RTS signal high (asserts RTS) to trigger PTT. + * @returns bool True, if RTS was set successfully, otherwise false. + */ + bool setPTT(); + /** + * @brief Sets RTS signal low (clears RTS) to release PTT. + * @returns bool True, if RTS was cleared successfully, otherwise false. + */ + bool clearPTT(); + +private: + std::string m_port; + bool m_isOpen; +#if defined(_WIN32) + HANDLE m_fd; +#else + int m_fd; +#endif // defined(_WIN32) + + /** + * @brief Sets the termios settings on the serial port. + * @returns bool True, if settings are set, otherwise false. + */ + bool setTermios(); +}; + +#endif // __RTS_PTT_CONTROLLER_H__ diff --git a/src/host/modem/port/ISerialPort.h b/src/host/modem/port/ISerialPort.h index 3407f827c..256b94523 100644 --- a/src/host/modem/port/ISerialPort.h +++ b/src/host/modem/port/ISerialPort.h @@ -62,6 +62,17 @@ namespace modem * @brief Closes the connection to the port. */ virtual void close() = 0; + + /** + * @brief Sets RTS signal high (asserts RTS). + * @returns bool True, if RTS was set successfully, otherwise false. + */ + virtual bool setRTS() = 0; + /** + * @brief Sets RTS signal low (clears RTS). + * @returns bool True, if RTS was cleared successfully, otherwise false. + */ + virtual bool clearRTS() = 0; }; } // namespace port } // namespace modem diff --git a/src/host/modem/port/UARTPort.cpp b/src/host/modem/port/UARTPort.cpp index cbd4592ec..18dec869f 100644 --- a/src/host/modem/port/UARTPort.cpp +++ b/src/host/modem/port/UARTPort.cpp @@ -544,3 +544,63 @@ bool UARTPort::setTermios() m_isOpen = true; return true; } + +/* Sets RTS signal high (asserts RTS). */ + +bool UARTPort::setRTS() +{ + if (!m_isOpen) + return false; + +#if defined(_WIN32) + if (::EscapeCommFunction(m_fd, SETRTS) == 0) { + ::LogError(LOG_HOST, "Cannot set RTS for %s, err=%04lx", m_device.c_str(), ::GetLastError()); + return false; + } +#else + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_device.c_str()); + return false; + } + + y |= TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot set RTS for %s", m_device.c_str()); + return false; + } +#endif // defined(_WIN32) + + return true; +} + +/* Sets RTS signal low (clears RTS). */ + +bool UARTPort::clearRTS() +{ + if (!m_isOpen) + return false; + +#if defined(_WIN32) + if (::EscapeCommFunction(m_fd, CLRRTS) == 0) { + ::LogError(LOG_HOST, "Cannot clear RTS for %s, err=%04lx", m_device.c_str(), ::GetLastError()); + return false; + } +#else + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_device.c_str()); + return false; + } + + y &= ~TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot clear RTS for %s", m_device.c_str()); + return false; + } +#endif // defined(_WIN32) + + return true; +} diff --git a/src/host/modem/port/UARTPort.h b/src/host/modem/port/UARTPort.h index b5a4a1a7d..c8411b97b 100644 --- a/src/host/modem/port/UARTPort.h +++ b/src/host/modem/port/UARTPort.h @@ -108,6 +108,17 @@ namespace modem */ void close() override; + /** + * @brief Sets RTS signal high (asserts RTS). + * @returns bool True, if RTS was set successfully, otherwise false. + */ + bool setRTS() override; + /** + * @brief Sets RTS signal low (clears RTS). + * @returns bool True, if RTS was cleared successfully, otherwise false. + */ + bool clearRTS() override; + #if defined(__APPLE__) /** * @brief Helper on Apple to set serial port to non-blocking.