Add async listening events for modem control lines on Posix

This commit is contained in:
Will Hedgecock 2022-01-04 16:04:17 -06:00
parent 083b9e5d0c
commit 9bda1b4a7b
4 changed files with 258 additions and 33 deletions

View File

@ -2,10 +2,10 @@
* PosixHelperFunctions.c
*
* Created on: Mar 10, 2015
* Last Updated on: Dec 16, 2021
* Last Updated on: Jan 04, 2022
* Author: Will Hedgecock
*
* Copyright (C) 2012-2021 Fazecast, Inc.
* Copyright (C) 2012-2022 Fazecast, Inc.
*
* This file is part of jSerialComm.
*
@ -31,6 +31,7 @@
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "PosixHelperFunctions.h"
@ -55,8 +56,16 @@ serialPort* pushBack(serialPortVector* vector, const char* key, const char* frie
else
return NULL;
// Initialize the storage structure
// Initialize the serial port mutex and condition variables
memset(port, 0, sizeof(serialPort));
pthread_mutex_init(&port->eventMutex, NULL);
pthread_condattr_t conditionVariableAttributes;
pthread_condattr_init(&conditionVariableAttributes);
pthread_condattr_setclock(&conditionVariableAttributes, CLOCK_MONOTONIC);
pthread_cond_init(&port->eventReceived, &conditionVariableAttributes);
pthread_condattr_destroy(&conditionVariableAttributes);
// Initialize the storage structure
port->handle = -1;
port->enumerated = 1;
port->portPath = (char*)malloc(strlen(key) + 1);
@ -91,6 +100,8 @@ void removePort(serialPortVector* vector, serialPort* port)
free(port->portDescription);
if (port->readBuffer)
free(port->readBuffer);
pthread_cond_destroy(&port->eventReceived);
pthread_mutex_destroy(&port->eventMutex);
// Move up all remaining ports in the serial port listing
for (int i = 0; i < vector->length; ++i)

View File

@ -2,10 +2,10 @@
* PosixHelperFunctions.h
*
* Created on: Mar 10, 2015
* Last Updated on: Dec 16, 2021
* Last Updated on: Jan 04, 2022
* Author: Will Hedgecock
*
* Copyright (C) 2012-2021 Fazecast, Inc.
* Copyright (C) 2012-2022 Fazecast, Inc.
*
* This file is part of jSerialComm.
*
@ -27,15 +27,18 @@
#define __POSIX_HELPER_FUNCTIONS_HEADER_H__
// Serial port JNI header file
#include <pthread.h>
#include "com_fazecast_jSerialComm_SerialPort.h"
// Serial port data structure
typedef struct serialPort
{
pthread_mutex_t eventMutex;
pthread_cond_t eventReceived;
pthread_t eventsThread1, eventsThread2;
char *portPath, *friendlyName, *portDescription, *portLocation, *readBuffer;
int errorLineNumber, errorNumber, handle, readBufferLength;
volatile char enumerated, eventListenerRunning;
short eventsMask;
int errorLineNumber, errorNumber, handle, readBufferLength, eventsMask, event;
volatile char enumerated, eventListenerRunning, eventListenerUsesThreads;
} serialPort;
// Common storage functionality

View File

@ -2,10 +2,10 @@
* SerialPort_Posix.c
*
* Created on: Feb 25, 2012
* Last Updated on: Dec 16, 2021
* Last Updated on: Jan 04, 2022
* Author: Will Hedgecock
*
* Copyright (C) 2012-2021 Fazecast, Inc.
* Copyright (C) 2012-2022 Fazecast, Inc.
*
* This file is part of jSerialComm.
*
@ -79,6 +79,107 @@ jfieldID eventFlagsField;
// List of available serial ports
serialPortVector serialPorts = { NULL, 0, 0 };
// Event listening threads
void* eventReadingThread1(void *serialPortPointer)
{
// Make this thread immediately and asynchronously cancellable
int oldValue;
serialPort *port = (serialPort*)serialPortPointer;
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldValue);
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldValue);
// Loop forever while open
struct serial_icounter_struct oldSerialLineInterrupts, newSerialLineInterrupts;
int mask = 1, isSupported = !ioctl(port->handle, TIOCGICOUNT, &oldSerialLineInterrupts);
while (isSupported && mask && port->eventListenerRunning && port->eventListenerUsesThreads)
{
// Determine which modem bit changes to listen for
mask = 0;
if (port->eventsMask & com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_CARRIER_DETECT)
mask |= TIOCM_CD;
if (port->eventsMask & com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_CTS)
mask |= TIOCM_CTS;
if (port->eventsMask & com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_DSR)
mask |= TIOCM_DSR;
if (port->eventsMask & com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_RING_INDICATOR)
mask |= TIOCM_RNG;
// Listen forever for a change in the modem lines
isSupported = !ioctl(port->handle, TIOCMIWAIT, mask) && !ioctl(port->handle, TIOCGICOUNT, &newSerialLineInterrupts);
// Return the detected port events
if (isSupported)
{
pthread_mutex_lock(&port->eventMutex);
if (newSerialLineInterrupts.dcd != oldSerialLineInterrupts.dcd)
port->event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_CARRIER_DETECT;
if (newSerialLineInterrupts.cts != oldSerialLineInterrupts.cts)
port->event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_CTS;
if (newSerialLineInterrupts.dsr != oldSerialLineInterrupts.dsr)
port->event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_DSR;
if (newSerialLineInterrupts.rng != oldSerialLineInterrupts.rng)
port->event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_RING_INDICATOR;
memcpy(&oldSerialLineInterrupts, &newSerialLineInterrupts, sizeof(newSerialLineInterrupts));
if (port->event)
pthread_cond_signal(&port->eventReceived);
pthread_mutex_unlock(&port->eventMutex);
}
}
return NULL;
}
void* eventReadingThread2(void *serialPortPointer)
{
// Make this thread immediately and asynchronously cancellable
int oldValue;
serialPort *port = (serialPort*)serialPortPointer;
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldValue);
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldValue);
struct serial_icounter_struct oldSerialLineInterrupts, newSerialLineInterrupts;
ioctl(port->handle, TIOCGICOUNT, &oldSerialLineInterrupts);
// Loop forever while open
while (port->eventListenerRunning && port->eventListenerUsesThreads)
{
// Initialize the polling variables
int pollResult;
short pollEventsMask = ((port->eventsMask & com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_DATA_AVAILABLE) || (port->eventsMask & com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_DATA_RECEIVED)) ? (POLLIN | POLLERR) : POLLERR;
struct pollfd waitingSet = { port->handle, pollEventsMask, 0 };
// Wait for a serial port event
do
{
waitingSet.revents = 0;
pollResult = poll(&waitingSet, 1, 1000);
}
while ((pollResult == 0) && port->eventListenerRunning && port->eventListenerUsesThreads);
// Return the detected port events
pthread_mutex_lock(&port->eventMutex);
if (waitingSet.revents & POLLIN)
port->event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_DATA_AVAILABLE;
if (waitingSet.revents & POLLERR)
if (!ioctl(port->handle, TIOCGICOUNT, &newSerialLineInterrupts))
{
if (oldSerialLineInterrupts.frame != newSerialLineInterrupts.frame)
port->event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_FRAMING_ERROR;
if (oldSerialLineInterrupts.brk != newSerialLineInterrupts.brk)
port->event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_BREAK_INTERRUPT;
if (oldSerialLineInterrupts.overrun != newSerialLineInterrupts.overrun)
port->event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_FIRMWARE_OVERRUN_ERROR;
if (oldSerialLineInterrupts.parity != newSerialLineInterrupts.parity)
port->event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_PARITY_ERROR;
if (oldSerialLineInterrupts.buf_overrun != newSerialLineInterrupts.buf_overrun)
port->event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_SOFTWARE_OVERRUN_ERROR;
memcpy(&oldSerialLineInterrupts, &newSerialLineInterrupts, sizeof(newSerialLineInterrupts));
}
if (port->event)
pthread_cond_signal(&port->eventReceived);
pthread_mutex_unlock(&port->eventMutex);
}
return NULL;
}
JNIEXPORT jobjectArray JNICALL Java_com_fazecast_jSerialComm_SerialPort_getCommPorts(JNIEnv *env, jclass serialComm)
{
// Reset the enumerated flag on all non-open serial ports
@ -320,15 +421,23 @@ JNIEXPORT jboolean JNICALL Java_com_fazecast_jSerialComm_SerialPort_configPort(J
#if defined(__linux__)
// Attempt to set the transmit buffer size
// Attempt to set the transmit buffer size, closing wait time, and latency flags
struct serial_struct serInfo = { 0 };
if (!ioctl(port->handle, TIOCGSERIAL, &serInfo))
{
serInfo.closing_wait = 250;
serInfo.xmit_fifo_size = sendDeviceQueueSize;
serInfo.flags |= ASYNC_LOW_LATENCY;
ioctl(port->handle, TIOCSSERIAL, &serInfo);
}
// Retrieve the driver-reported transmit buffer size
if (!ioctl(port->handle, TIOCGSERIAL, &serInfo))
sendDeviceQueueSize = serInfo.xmit_fifo_size;
receiveDeviceQueueSize = sendDeviceQueueSize;
(*env)->SetIntField(env, obj, sendDeviceQueueSizeField, sendDeviceQueueSize);
(*env)->SetIntField(env, obj, receiveDeviceQueueSizeField, receiveDeviceQueueSize);
// Attempt to set the requested RS-485 mode
struct serial_rs485 rs485Conf = { 0 };
if (!ioctl(port->handle, TIOCGRS485, &rs485Conf))
@ -365,6 +474,11 @@ JNIEXPORT jboolean JNICALL Java_com_fazecast_jSerialComm_SerialPort_configPort(J
}
}
#else
(*env)->SetIntField(env, obj, sendDeviceQueueSizeField, sysconf(_SC_PAGESIZE));
(*env)->SetIntField(env, obj, receiveDeviceQueueSizeField, sysconf(_SC_PAGESIZE));
#endif
// Configure the serial port read and write timeouts
@ -381,9 +495,7 @@ JNIEXPORT jboolean JNICALL Java_com_fazecast_jSerialComm_SerialPort_configTimeou
tcgetattr(port->handle, &options);
// Set up the requested event flags
port->eventsMask = 0;
if ((eventsToMonitor & com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_DATA_AVAILABLE) || (eventsToMonitor & com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_DATA_RECEIVED))
port->eventsMask |= POLLIN;
port->eventsMask = eventsToMonitor;
// Set updated port timeouts
if ((eventsToMonitor & com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_DATA_RECEIVED) > 0)
@ -460,29 +572,70 @@ JNIEXPORT jboolean JNICALL Java_com_fazecast_jSerialComm_SerialPort_flushRxTxBuf
JNIEXPORT jint JNICALL Java_com_fazecast_jSerialComm_SerialPort_waitForEvent(JNIEnv *env, jobject obj, jlong serialPortPointer)
{
// Initialize the local variables
int pollResult;
// Initialize local variables
serialPort *port = (serialPort*)(intptr_t)serialPortPointer;
struct pollfd waitingSet = { port->handle, port->eventsMask, 0 };
jint event = com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_TIMED_OUT;
// TODO: LISTEN FOR ERROR EVENTS IN CASE SERIAL PORT GETS UNPLUGGED? TEST IF WORKS?
/*
ioctl: TIOCMIWAIT
ioctl: TIOCGICOUNT
*/
// Wait for a serial port event
do
// Wait for events differently based on the use of threads
if (port->eventListenerUsesThreads)
{
waitingSet.revents = 0;
pollResult = poll(&waitingSet, 1, 500);
pthread_mutex_lock(&port->eventMutex);
if ((port->event & com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_DATA_AVAILABLE) && !Java_com_fazecast_jSerialComm_SerialPort_bytesAvailable(env, obj, serialPortPointer))
port->event &= ~com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_DATA_AVAILABLE;
if (port->event)
{
event = port->event;
port->event = 0;
}
else
{
struct timespec timeoutTime;
clock_gettime(CLOCK_MONOTONIC, &timeoutTime);
timeoutTime.tv_sec += 1;
pthread_cond_timedwait(&port->eventReceived, &port->eventMutex, &timeoutTime);
if (port->event)
{
event = port->event;
port->event = 0;
}
}
pthread_mutex_unlock(&port->eventMutex);
}
while ((pollResult == 0) && port->eventListenerRunning);
else
{
// Initialize the local variables
int pollResult;
struct serial_icounter_struct oldSerialLineInterrupts, newSerialLineInterrupts;
short pollEventsMask = ((port->eventsMask & com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_DATA_AVAILABLE) || (port->eventsMask & com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_DATA_RECEIVED)) ? (POLLIN | POLLERR) : POLLERR;
struct pollfd waitingSet = { port->handle, pollEventsMask, 0 };
ioctl(port->handle, TIOCGICOUNT, &oldSerialLineInterrupts);
// Return the detected port events
if (waitingSet.revents & POLLIN)
event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_DATA_AVAILABLE;
// Wait for a serial port event
do
{
waitingSet.revents = 0;
pollResult = poll(&waitingSet, 1, 500);
}
while ((pollResult == 0) && port->eventListenerRunning);
// Return the detected port events
if (waitingSet.revents & POLLIN)
event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_DATA_AVAILABLE;
if (waitingSet.revents & POLLERR)
if (!ioctl(port->handle, TIOCGICOUNT, &newSerialLineInterrupts))
{
if (oldSerialLineInterrupts.frame != newSerialLineInterrupts.frame)
event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_FRAMING_ERROR;
if (oldSerialLineInterrupts.brk != newSerialLineInterrupts.brk)
event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_BREAK_INTERRUPT;
if (oldSerialLineInterrupts.overrun != newSerialLineInterrupts.overrun)
event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_FIRMWARE_OVERRUN_ERROR;
if (oldSerialLineInterrupts.parity != newSerialLineInterrupts.parity)
event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_PARITY_ERROR;
if (oldSerialLineInterrupts.buf_overrun != newSerialLineInterrupts.buf_overrun)
event |= com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_SOFTWARE_OVERRUN_ERROR;
}
}
return event;
}
@ -634,7 +787,37 @@ JNIEXPORT jint JNICALL Java_com_fazecast_jSerialComm_SerialPort_writeBytes(JNIEn
JNIEXPORT void JNICALL Java_com_fazecast_jSerialComm_SerialPort_setEventListeningStatus(JNIEnv *env, jobject obj, jlong serialPortPointer, jboolean eventListenerRunning)
{
((serialPort*)(intptr_t)serialPortPointer)->eventListenerRunning = eventListenerRunning;
// Create or cancel a separate event listening thread if required
serialPort *port = (serialPort*)(intptr_t)serialPortPointer;
port->eventListenerRunning = eventListenerRunning;
if (eventListenerRunning && ((port->eventsMask & com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_CARRIER_DETECT) || (port->eventsMask & com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_CTS) ||
(port->eventsMask & com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_DSR) || (port->eventsMask & com_fazecast_jSerialComm_SerialPort_LISTENING_EVENT_RING_INDICATOR)))
{
port->event = 0;
if (!port->eventsThread1)
{
if (!pthread_create(&port->eventsThread1, NULL, eventReadingThread1, port))
pthread_detach(port->eventsThread1);
else
port->eventsThread1 = 0;
}
if (!port->eventsThread2)
{
if (!pthread_create(&port->eventsThread2, NULL, eventReadingThread2, port))
pthread_detach(port->eventsThread2);
else
port->eventsThread2 = 0;
}
port->eventListenerUsesThreads = 1;
}
else if (port->eventListenerUsesThreads)
{
port->eventListenerUsesThreads = 0;
pthread_cancel(port->eventsThread1);
port->eventsThread1 = 0;
pthread_cancel(port->eventsThread2);
port->eventsThread2 = 0;
}
}
JNIEXPORT jboolean JNICALL Java_com_fazecast_jSerialComm_SerialPort_setBreak(JNIEnv *env, jobject obj, jlong serialPortPointer)

View File

@ -262,6 +262,34 @@ public class SerialPortTest
inputScanner.close();
return;
}
System.out.println("\n\nNow waiting asynchronously for all possible listening events...");
ubxPort.addDataListener(new SerialPortDataListener() {
@Override
public int getListeningEvents() { return SerialPort.LISTENING_EVENT_PARITY_ERROR | SerialPort.LISTENING_EVENT_DATA_WRITTEN | SerialPort.LISTENING_EVENT_BREAK_INTERRUPT |
SerialPort.LISTENING_EVENT_CARRIER_DETECT | SerialPort.LISTENING_EVENT_CTS | SerialPort.LISTENING_EVENT_DSR | SerialPort.LISTENING_EVENT_RING_INDICATOR |
SerialPort.LISTENING_EVENT_FRAMING_ERROR | SerialPort.LISTENING_EVENT_FIRMWARE_OVERRUN_ERROR | SerialPort.LISTENING_EVENT_SOFTWARE_OVERRUN_ERROR | SerialPort.LISTENING_EVENT_DATA_AVAILABLE; }
@Override
public void serialEvent(SerialPortEvent event)
{
System.out.println("Received event type: " + event.getEventType());
if (event.getEventType() == SerialPort.LISTENING_EVENT_DATA_AVAILABLE)
{
byte[] buffer = new byte[event.getSerialPort().bytesAvailable()];
event.getSerialPort().readBytes(buffer, buffer.length);
System.out.println(" Reading " + buffer.length + " bytes");
}
}
});
try { Thread.sleep(5000); } catch (Exception e) {}
ubxPort.removeDataListener();
ubxPort.closePort();
openedSuccessfully = ubxPort.openPort(0);
System.out.println("\nReopening " + ubxPort.getSystemPortName() + ": " + ubxPort.getDescriptivePortName() + ": " + openedSuccessfully);
if (!openedSuccessfully)
{
inputScanner.close();
return;
}
System.out.println("\n\nUnplug the device sometime in the next 10 seconds to ensure that it closes properly...\n");
ubxPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, 0, 0);
ubxPort.addDataListener(new SerialPortDataListener() {
@ -279,7 +307,7 @@ public class SerialPortTest
});
try { Thread.sleep(10000); } catch (Exception e) {}
ubxPort.removeDataListener();
System.out.println("\n\nClosing " + ubxPort.getDescriptivePortName() + ": " + ubxPort.closePort());
System.out.println("\nClosing " + ubxPort.getDescriptivePortName() + ": " + ubxPort.closePort());
/*System.out.println("\n\nAttempting to read from two serial ports simultaneously\n");
System.out.println("\nAvailable Ports:\n");