com.serialpundit.serial.internal.SerialComLooper Maven / Gradle / Ivy
Show all versions of sp-tty Show documentation
/*
* This file is part of SerialPundit.
*
* Copyright (C) 2014-2016, Rishi Gupta. All rights reserved.
*
* The SerialPundit is DUAL LICENSED. It is made available under the terms of the GNU Affero
* General Public License (AGPL) v3.0 for non-commercial use and under the terms of a commercial
* license for commercial use of this software.
*
* The SerialPundit is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
package com.serialpundit.serial.internal;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import com.serialpundit.core.SerialComException;
import com.serialpundit.serial.ISerialComDataListener;
import com.serialpundit.serial.ISerialComEventListener;
import com.serialpundit.serial.SerialComLineEvent;
import com.serialpundit.serial.SerialComManager;
/**
* Encapsulates environment for data and event looper implementation. This runs in as a
* different thread context and keep looping over data/event queue, delivering data/events
* to the intended registered listener (data/event handler) one by one.
*
* The rate of delivery of data/events are directly proportional to how fast listener finishes
* his job and let us return.
*
* @author Rishi Gupta
*/
public final class SerialComLooper {
private final int MAX_NUM_EVENTS = 5000;
private SerialComPortJNIBridge mComPortJNIBridge;
private BlockingQueue mDataQueue = null;
private ISerialComDataListener mDataListener = null;
private Object mDataLock = new Object();
private Thread mDataLooperThread = null;
private AtomicBoolean deliverDataEvent = new AtomicBoolean(true);
private AtomicBoolean exitDataThread = new AtomicBoolean(false);
private BlockingQueue mDataErrorQueue = null;
private Object mDataErrorLock = new Object();
private Thread mDataErrorLooperThread = null;
private AtomicBoolean exitDataErrorThread = new AtomicBoolean(false);
private BlockingQueue mEventQueue = null;
private ISerialComEventListener mEventListener = null;
private Thread mEventLooperThread = null;
private AtomicBoolean exitEventThread = null;
private int appliedMask = SerialComManager.CTS | SerialComManager.DSR | SerialComManager.DCD | SerialComManager.RI;
private int oldLineState = 0;
private int newLineState = 0;
/**
* This class runs in as a different thread context and keep looping over data queue, delivering
* data to the intended registered listener (data handler) one by one. The rate of delivery of
* new data is directly proportional to how fast listener finishes his job and let us return.
*/
class DataLooper implements Runnable {
@Override
public void run() {
/* take() method blocks if there is no event to deliver. So we don't keep wasting
* CPU cycle in case queue is empty. */
while(true) {
synchronized(mDataLock) {
try {
mDataListener.onNewSerialDataAvailable(mDataQueue.take());
if(deliverDataEvent.get() == false) {
/* Causes the current thread to wait until another thread
* invokes the notify method. */
mDataLock.wait();
}
} catch (InterruptedException e) {
if(exitDataThread.get() == true) {
break;
}
}
}
}
exitDataThread.set(false); // Reset exit flag
mDataQueue = null;
}
}
/**
* This class runs in as a different thread context and keep looping over data error queue, delivering
* error event to the intended registered listener (error data handler) one by one. The rate of delivery of
* new error event is directly proportional to how fast listener finishes his job and let us return.
*/
class DataErrorLooper implements Runnable {
@Override
public void run() {
while(true) {
synchronized(mDataErrorLock) {
try {
mDataListener.onDataListenerError(mDataErrorQueue.take());
if(deliverDataEvent.get() == false) {
mDataErrorLock.wait();
}
} catch (InterruptedException e) {
if(exitDataErrorThread.get() == true) {
break;
}
}
}
}
exitDataErrorThread.set(false); // Reset exit flag
mDataErrorQueue = null;
}
}
/**
* This class runs in as a different thread context and keep looping over event queue, delivering
* events to the intended registered listener (event handler) one by one. The rate of delivery of
* events are directly proportional to how fast listener finishes his job and let us return.
*/
class EventLooper implements Runnable {
@Override
public void run() {
while(true) {
try {
mEventListener.onNewSerialEvent(mEventQueue.take());
} catch (InterruptedException e) {
if(exitEventThread.get() == true) {
break;
}
}
}
exitEventThread.set(false); // Reset exit flag
mEventQueue = null;
}
}
/**
* Allocates a new SerialComLooper object.
*
* @param mComPortJNIBridge interface used to invoke appropriate native function.
*/
public SerialComLooper(SerialComPortJNIBridge mComPortJNIBridge) {
this.mComPortJNIBridge = mComPortJNIBridge;
}
/**
* This method is called from native code to pass data bytes.
* @param newData byte array containing data read from serial port
*/
public void insertInDataQueue(byte[] newData) {
try {
if(mDataQueue.remainingCapacity() == 0) {
mDataQueue.poll();
}
mDataQueue.offer(newData);
} catch (Exception e) {
}
}
/**
* This method insert error info in error queue which will be later delivered to application.
*
* @param errorNum operating system specific error number to be sent to application.
*/
public void insertInDataErrorQueue(int errorNum) {
if(mDataErrorQueue.remainingCapacity() == 0) {
mDataErrorQueue.poll();
}
try {
mDataErrorQueue.offer(errorNum);
} catch (Exception e) {
}
}
/**
* Native side detects the change in status of lines, get the new line status and call this method.
* Based on the mask this method determines whether this event should be sent to application or not.
*
* @param newEvent bit mask representing event on serial port control lines.
*/
public void insertInEventQueue(int newEvent) {
newLineState = newEvent & appliedMask;
if(mEventQueue.remainingCapacity() == 0) {
mEventQueue.poll();
}
try {
mEventQueue.offer(new SerialComLineEvent(oldLineState, newLineState));
} catch (Exception e) {
}
oldLineState = newLineState;
}
/**
* Start the thread to loop over data queue.
*
* @param handle handle of the opened port for which data looper need to be started.
* @param dataListener listener to which data will be delivered.
* @param portName name of port represented by this handle.
*/
public void startDataLooper(long handle, ISerialComDataListener dataListener, String portName) {
mDataListener = dataListener;
mDataQueue = new ArrayBlockingQueue(MAX_NUM_EVENTS);
mDataErrorQueue = new ArrayBlockingQueue(MAX_NUM_EVENTS);
mDataLooperThread = new Thread(new DataLooper(), "SerialPundit DataLooper for handle " + handle + " and port " + portName);
mDataErrorLooperThread = new Thread(new DataErrorLooper(), "SerialPundit DataErrorLooper for handle " + handle + " and port " + portName);
mDataLooperThread.start();
mDataErrorLooperThread.start();
}
/**
* Set the flag to indicate that the thread is supposed to run to completion and exit.
* Interrupt the thread so that take() method can come out of blocked sleep state.
*/
public void stopDataLooper() {
exitDataThread.set(true);
exitDataErrorThread.set(true);
mDataLooperThread.interrupt();
mDataErrorLooperThread.interrupt();
}
/**
* Get initial status of control lines and start Java worker thread.
*
* @param handle handle of the opened port for which event looper need to be started.
* @param eventListener listener to which event will be delivered.
* @param portName name of port represented by this handle.
*
* @throws SerialComException if an error occurs.
*/
public void startEventLooper(long handle, ISerialComEventListener eventListener, String portName) throws SerialComException {
int state = 0;
int[] linestate = null;
// Return sequence is CTS, DSR, DCD, RI, LOOP, RTS, DTR respectively from native layer.
linestate = mComPortJNIBridge.getLinesStatus(handle);
if (linestate == null) {
throw new SerialComException("Could not read current state of lines. Please retry !");
}
// Bit mask CTS | DSR | DCD | RI
state = linestate[0] | linestate[1] | linestate[2] | linestate[3];
oldLineState = state & appliedMask;
mEventQueue = new ArrayBlockingQueue(MAX_NUM_EVENTS);
exitEventThread = new AtomicBoolean(false);
mEventListener = eventListener;
mEventLooperThread = new Thread(new EventLooper(), "SerialPundit EventLooper for handle " + handle + " and port " + portName);
mEventLooperThread.start();
}
/**
* Set the flag to indicate that the thread is supposed to run to completion and exit.
* Interrupt the thread so that take() method can come out of blocked sleep state.
*
* @throws SerialComException if an error occurs.
*/
public void stopEventLooper() throws SerialComException {
exitEventThread.set(true);
mEventLooperThread.interrupt();
}
/**
* Data looper thread refrains from sending new data to the data listener.
*/
public void pause() {
deliverDataEvent.set(false);
}
/**
* Looper starts sending new data again to the data listener.
*/
public void resume() {
deliverDataEvent.set(true);
mDataLock.notify();
mDataErrorLock.notify();
}
/**
* In future we may shift modifying mask in the native code itself, so as to prevent JNI transitions.
* This filters what events should be sent to application. Note that, although we sent only those event
* for which user has set mask, however native code send all the events to java layer as of now.
*
* @param newMask new bit mask for events that will be delivered to application.
*/
public void setEventsMask(int newMask) {
appliedMask = newMask;
}
/**
* Gives the event mask currently active.
*
* @return bit mask of events currently active.
*/
public int getEventsMask() {
return appliedMask;
}
}