396 lines
11 KiB
C++
396 lines
11 KiB
C++
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include "serialport.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <strsafe.h>
|
|
#endif
|
|
|
|
extern int g_verbose;
|
|
|
|
Serialport::Serialport() {
|
|
m_currentPttInputState = false;
|
|
m_pttMonitorThreadEnding = false;
|
|
m_pttMonitoringThread = nullptr;
|
|
com_handle = COM_HANDLE_INVALID;
|
|
}
|
|
|
|
Serialport::~Serialport() {
|
|
if (isopen()) {
|
|
closeport();
|
|
}
|
|
}
|
|
|
|
// returns true if comm port opened OK, false if there was a problem
|
|
|
|
bool Serialport::openport(const char name[], bool useRTS, bool RTSPos, bool useDTR, bool DTRPos)
|
|
{
|
|
if (g_verbose) fprintf(stderr, "starting openport(), name: %s strlen(name): %d useRTS: %d RTSPos: %d useDTR: %d DTRPos: %d\n",
|
|
name, (int)strlen(name), useRTS, RTSPos, useDTR, DTRPos);
|
|
|
|
if (com_handle != COM_HANDLE_INVALID) {
|
|
closeport();
|
|
if (g_verbose) fprintf(stderr, "comm_handle invalid, closing\n");
|
|
}
|
|
|
|
m_useRTS = useRTS;
|
|
m_RTSPos = RTSPos;
|
|
m_useDTR = useDTR;
|
|
m_DTRPos = DTRPos;
|
|
|
|
|
|
#ifdef _WIN32
|
|
{
|
|
COMMTIMEOUTS timeouts;
|
|
DCB dcb;
|
|
TCHAR lpszFunction[100];
|
|
|
|
// As per:
|
|
// [1] https://support.microsoft.com/en-us/help/115831/howto-specify-serial-ports-larger-than-com9
|
|
// [2] Hamlib lib/termios.c, win32_serial_open()
|
|
|
|
/*
|
|
To test change of COM port for USB serial device on Windows
|
|
|
|
1/ Run->devmgmnt.msc
|
|
2/ Change COM port Ports (COM & LPT) -> Serial Device -> Properties Tab -> Advanced
|
|
3/ Unplug USB serial device and plug in again. This is really important. FreeDV won't recognise
|
|
new COM port number until this is done.
|
|
4/ Test PTT on FreeDV Tools->PTT
|
|
*/
|
|
|
|
TCHAR nameWithStrangePrefix[100];
|
|
StringCchPrintf(nameWithStrangePrefix, 100, TEXT("\\\\.\\%hs"), name);
|
|
if (g_verbose) _tprintf(_T("nameWithStrangePrefix: %s\r\n"), nameWithStrangePrefix);
|
|
|
|
if((com_handle=CreateFile(nameWithStrangePrefix
|
|
,GENERIC_READ | GENERIC_WRITE/* Access */
|
|
,0 /* Share mode */
|
|
,NULL /* Security attributes */
|
|
,OPEN_EXISTING /* Create access */
|
|
,0 /* File attributes */
|
|
,NULL /* Template */
|
|
))==INVALID_HANDLE_VALUE) {
|
|
StringCchPrintf(lpszFunction, 100, TEXT("%s"), TEXT("CreateFile"));
|
|
goto error;
|
|
}
|
|
if (g_verbose) fprintf(stderr, "CreateFileA OK\n");
|
|
|
|
if(GetCommTimeouts(com_handle, &timeouts)) {
|
|
if (g_verbose) fprintf(stderr, "GetCommTimeouts OK\n");
|
|
|
|
timeouts.ReadIntervalTimeout=MAXDWORD;
|
|
timeouts.ReadTotalTimeoutMultiplier=0;
|
|
timeouts.ReadTotalTimeoutConstant=0; // No-wait read timeout
|
|
timeouts.WriteTotalTimeoutMultiplier=0;
|
|
timeouts.WriteTotalTimeoutConstant=5000; // 5 seconds
|
|
if (!SetCommTimeouts(com_handle,&timeouts)) {
|
|
StringCchPrintf(lpszFunction, 100, TEXT("%s"), TEXT("SetCommTimeouts"));
|
|
goto error;
|
|
}
|
|
if (g_verbose) fprintf(stderr, "SetCommTimeouts OK\n");
|
|
} else {
|
|
StringCchPrintf(lpszFunction, 100, TEXT("%s"), TEXT("GetCommTimeouts"));
|
|
goto error;
|
|
}
|
|
|
|
/* Force N-8-1 mode: */
|
|
if(GetCommState(com_handle, &dcb)==TRUE) {
|
|
if (g_verbose) fprintf(stderr, "GetCommState OK\n");
|
|
|
|
dcb.ByteSize = 8;
|
|
dcb.Parity = NOPARITY;
|
|
dcb.StopBits = ONESTOPBIT;
|
|
dcb.DCBlength = sizeof(DCB);
|
|
dcb.fBinary = TRUE;
|
|
dcb.fOutxCtsFlow = FALSE;
|
|
dcb.fOutxDsrFlow = FALSE;
|
|
dcb.fDtrControl = DTR_CONTROL_DISABLE;
|
|
dcb.fDsrSensitivity = FALSE;
|
|
dcb.fTXContinueOnXoff= TRUE;
|
|
dcb.fOutX = FALSE;
|
|
dcb.fInX = FALSE;
|
|
dcb.fRtsControl = RTS_CONTROL_DISABLE;
|
|
dcb.fAbortOnError = FALSE;
|
|
if (!SetCommState(com_handle, &dcb)) {
|
|
StringCchPrintf(lpszFunction, 100, TEXT("%s"), TEXT("SetCommState"));
|
|
goto error;
|
|
}
|
|
if (g_verbose) fprintf(stderr, "SetCommState OK\n");
|
|
} else {
|
|
StringCchPrintf(lpszFunction, 100, TEXT("%s"), TEXT("GetCommState"));
|
|
goto error;
|
|
}
|
|
|
|
return true;
|
|
|
|
error:
|
|
if (g_verbose)
|
|
{
|
|
#ifdef UNICODE
|
|
std::vector<char> buffer;
|
|
std::string errFn = "[Unknown Function]"
|
|
int size = WideCharToMultiByte(CP_UTF8, 0, text, -1, NULL, 0, NULL, NULL);
|
|
if (size > 0)
|
|
{
|
|
buffer.resize(size);
|
|
WideCharToMultiByte(CP_UTF8, 0, text, -1, static_cast<BYTE*>(&buffer[0]), buffer.size(), NULL, NULL);
|
|
errFn = std::string(&buffer[0]);
|
|
}
|
|
else
|
|
{
|
|
// Error handling, probably shouldn't reach here
|
|
}
|
|
fprintf(stderr, "%s failed\n", errFn.c_str());
|
|
#else
|
|
fprintf(stderr, "%s failed\n", lpszFunction);
|
|
#endif // UNICODE
|
|
}
|
|
|
|
// Retrieve the system error message for the last-error code
|
|
|
|
LPVOID lpMsgBuf;
|
|
LPVOID lpDisplayBuf;
|
|
DWORD dw = GetLastError();
|
|
|
|
FormatMessage(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
dw,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
(LPTSTR) &lpMsgBuf,
|
|
0, NULL );
|
|
|
|
// Display the error message
|
|
|
|
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
|
|
(lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR));
|
|
StringCchPrintf((LPTSTR)lpDisplayBuf,
|
|
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
|
|
TEXT("%s failed with error %d: %s"),
|
|
lpszFunction, dw, lpMsgBuf);
|
|
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
|
|
|
|
LocalFree(lpMsgBuf);
|
|
LocalFree(lpDisplayBuf);
|
|
|
|
return false;
|
|
}
|
|
#else
|
|
{
|
|
struct termios t;
|
|
|
|
if((com_handle=open(name, O_NONBLOCK|O_RDWR))== COM_HANDLE_INVALID)
|
|
return false;
|
|
|
|
if(tcgetattr(com_handle, &t)==-1) {
|
|
close(com_handle);
|
|
com_handle = COM_HANDLE_INVALID;
|
|
return false;
|
|
}
|
|
|
|
t.c_iflag = (
|
|
IGNBRK /* ignore BREAK condition */
|
|
| IGNPAR /* ignore (discard) parity errors */
|
|
);
|
|
t.c_oflag = 0; /* No output processing */
|
|
t.c_cflag = (
|
|
CS8 /* 8 bits */
|
|
| CREAD /* enable receiver */
|
|
|
|
/*
|
|
Fun snippet from the FreeBSD manpage:
|
|
|
|
If CREAD is set, the receiver is enabled. Otherwise, no character is
|
|
received. Not all hardware supports this bit. In fact, this flag is
|
|
pretty silly and if it were not part of the termios specification it
|
|
would be omitted.
|
|
*/
|
|
| CLOCAL /* ignore modem status lines */
|
|
);
|
|
|
|
t.c_lflag = 0; /* No local modes */
|
|
if(tcsetattr(com_handle, TCSANOW, &t)==-1) {
|
|
close(com_handle);
|
|
com_handle = COM_HANDLE_INVALID;
|
|
return false;
|
|
}
|
|
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
|
|
// fixme: this takes about one second to close under Linux
|
|
|
|
void Serialport::closeport()
|
|
{
|
|
m_pttMonitorThreadEnding = true;
|
|
if (m_pttMonitoringThread != nullptr && m_pttMonitoringThread->IsAlive())
|
|
{
|
|
m_pttMonitoringThread->Delete(NULL, wxTHREAD_WAIT_BLOCK);
|
|
|
|
// Since the thread is detachable, we cannot delete it here.
|
|
// wxWidgets takes care of cleanup for us.
|
|
m_pttMonitoringThread = nullptr;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
CloseHandle(com_handle);
|
|
#else
|
|
close(com_handle);
|
|
#endif
|
|
com_handle = COM_HANDLE_INVALID;
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// (raise|lower)(RTS|DTR)()
|
|
//
|
|
// Raises/lowers the specified signal
|
|
//----------------------------------------------------------------
|
|
|
|
void Serialport::raiseDTR(void)
|
|
{
|
|
if(com_handle == COM_HANDLE_INVALID)
|
|
return;
|
|
#ifdef _WIN32
|
|
EscapeCommFunction(com_handle, SETDTR);
|
|
#else
|
|
{ // For C89 happiness
|
|
int flags = TIOCM_DTR;
|
|
ioctl(com_handle, TIOCMBIS, &flags);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Serialport::raiseRTS(void)
|
|
{
|
|
if(com_handle == COM_HANDLE_INVALID)
|
|
return;
|
|
#ifdef _WIN32
|
|
EscapeCommFunction(com_handle, SETRTS);
|
|
#else
|
|
{ // For C89 happiness
|
|
int flags = TIOCM_RTS;
|
|
ioctl(com_handle, TIOCMBIS, &flags);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Serialport::lowerDTR(void)
|
|
{
|
|
if(com_handle == COM_HANDLE_INVALID)
|
|
return;
|
|
#ifdef _WIN32
|
|
EscapeCommFunction(com_handle, CLRDTR);
|
|
#else
|
|
{ // For C89 happiness
|
|
int flags = TIOCM_DTR;
|
|
ioctl(com_handle, TIOCMBIC, &flags);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Serialport::lowerRTS(void)
|
|
{
|
|
if(com_handle == COM_HANDLE_INVALID)
|
|
return;
|
|
#ifdef _WIN32
|
|
EscapeCommFunction(com_handle, CLRRTS);
|
|
#else
|
|
{ // For C89 happiness
|
|
int flags = TIOCM_RTS;
|
|
ioctl(com_handle, TIOCMBIC, &flags);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Serialport::ptt(bool tx) {
|
|
|
|
/* Truth table:
|
|
|
|
g_tx RTSPos RTS
|
|
-------------------
|
|
0 1 0
|
|
1 1 1
|
|
0 0 1
|
|
1 0 0
|
|
|
|
exclusive NOR
|
|
*/
|
|
|
|
if (com_handle != COM_HANDLE_INVALID) {
|
|
if (m_useRTS) {
|
|
//fprintf(stderr, "g_tx: %d m_boolRTSPos: %d serialLine: %d\n", g_tx, wxGetApp().appConfiguration.rigControlConfiguration.serialPTTPolarityRTS, g_tx == wxGetApp().appConfiguration.rigControlConfiguration.serialPTTPolarityRTS);
|
|
if (tx == m_RTSPos)
|
|
raiseRTS();
|
|
else
|
|
lowerRTS();
|
|
}
|
|
if (m_useDTR) {
|
|
//fprintf(stderr, "g_tx: %d m_boolDTRPos: %d serialLine: %d\n", g_tx, wxGetApp().appConfiguration.rigControlConfiguration.serialPTTPolarityDTR, g_tx == wxGetApp().appConfiguration.rigControlConfiguration.serialPTTPolarityDTR);
|
|
if (tx == m_DTRPos)
|
|
raiseDTR();
|
|
else
|
|
lowerDTR();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
wxThread::ExitCode Serialport::PttMonitorThread::Entry()
|
|
{
|
|
while (!TestDestroy())
|
|
{
|
|
bool pttState = m_port->getCTS();
|
|
if (pttState != m_port->m_currentPttInputState)
|
|
{
|
|
m_port->m_currentPttInputState = pttState;
|
|
m_port->m_pttChangeFn(m_port->m_currentPttInputState == m_ctsPos);
|
|
}
|
|
|
|
wxThread::This()->Sleep(PTT_INPUT_MONITORING_TIME_MS);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
void Serialport::enablePttInputMonitoring(bool ctsPos, std::function<void(bool)> pttChangeFn)
|
|
{
|
|
// Set RTS and DTR enabled for certain PTT input interfaces
|
|
raiseRTS();
|
|
raiseDTR();
|
|
|
|
m_pttChangeFn = pttChangeFn;
|
|
m_currentPttInputState = false;
|
|
m_pttMonitoringThread = new PttMonitorThread(this, ctsPos);
|
|
if (m_pttMonitoringThread->Run() != wxTHREAD_NO_ERROR)
|
|
{
|
|
delete m_pttMonitoringThread;
|
|
m_pttMonitoringThread = nullptr;
|
|
}
|
|
}
|
|
|
|
bool Serialport::getCTS()
|
|
{
|
|
if(com_handle == COM_HANDLE_INVALID)
|
|
return false;
|
|
#ifdef _WIN32
|
|
DWORD modemFlags = 0;
|
|
GetCommModemStatus(com_handle, &modemFlags);
|
|
return (modemFlags & MS_CTS_ON);
|
|
#else
|
|
{ // For C89 happiness
|
|
int flags = 0;
|
|
ioctl(com_handle, TIOCMGET, &flags);
|
|
return flags & TIOCM_CTS;
|
|
}
|
|
#endif
|
|
}
|