
com.embeddedunveiled.serial.SerialComLooper Maven / Gradle / Ivy
Show all versions of scm Show documentation
/**
* Author : Rishi Gupta
*
* This file is part of 'serial communication manager' library.
*
* The 'serial communication manager' is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* The 'serial communication manager' 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. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with serial communication manager. If not, see .
*/
package com.embeddedunveiled.serial;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* This class 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.
*/
public final class SerialComLooper {
private final boolean DEBUG = true;
private final int MAX_NUM_EVENTS = 5000;
private SerialComJNINativeInterface mNativeInterface = null;
private SerialComErrorMapper mErrMapper = null;
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;
}else {
if(DEBUG) e.printStackTrace();
}
}
}
}
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;
}else {
if(DEBUG) e.printStackTrace();
}
}
}
}
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;
}else {
if(DEBUG) e.printStackTrace();
}
}
}
exitEventThread.set(false); // Reset exit flag
mEventQueue = null; // release memory for GC
}
}
/**
* Allocates a new SerialComLooper object.
*/
public SerialComLooper(SerialComJNINativeInterface nativeInterface, SerialComErrorMapper errMapper) {
mNativeInterface = nativeInterface;
mErrMapper = errMapper;
}
/**
* This method is called from native code to pass data bytes.
*/
public void insertInDataQueue(byte[] newData) {
if(mDataQueue.remainingCapacity() == 0) {
mDataQueue.poll();
}
try {
mDataQueue.offer(new SerialComDataEvent(newData));
} catch (Exception e) {
if(DEBUG) e.printStackTrace();
}
}
/**
* 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.
*/
public void insertInEventQueue(int newEvent) {
newLineState = newEvent & appliedMask;
if(mEventQueue.remainingCapacity() == 0) {
mEventQueue.poll();
}
try {
mEventQueue.offer(new SerialComLineEvent(oldLineState, newLineState));
} catch (Exception e) {
if(DEBUG) e.printStackTrace();
}
oldLineState = newLineState;
}
/**
* This method insert error info in error queue which will be later delivered to application.
*/
public void insertInDataErrorQueue(int newData) {
if(mDataErrorQueue.remainingCapacity() == 0) {
mDataErrorQueue.poll();
}
try {
mDataErrorQueue.offer(newData);
} catch (Exception e) {
if(DEBUG) e.printStackTrace();
}
}
/**
* Start the thread to loop over data queue.
*/
public void startDataLooper(long handle, ISerialComDataListener dataListener, String portName) {
try {
mDataListener = dataListener;
mDataQueue = new ArrayBlockingQueue(MAX_NUM_EVENTS);
mDataErrorQueue = new ArrayBlockingQueue(MAX_NUM_EVENTS);
mDataLooperThread = new Thread(new DataLooper(), "SCM DataLooper for handle " + handle + " and port " + portName);
mDataErrorLooperThread = new Thread(new DataErrorLooper(), "SCM DataErrorLooper for handle " + handle + " and port " + portName);
mDataLooperThread.start();
mDataErrorLooperThread.start();
} catch (Exception e) {
if(DEBUG) e.printStackTrace();
}
}
/**
* 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() {
try {
exitDataThread.set(true);
exitDataErrorThread.set(true);
mDataLooperThread.interrupt();
mDataErrorLooperThread.interrupt();
} catch (Exception e) {
if(DEBUG) e.printStackTrace();
}
}
/**
* Get initial status of control lines and start thread.
*/
public void startEventLooper(long handle, ISerialComEventListener eventListener, String portName) throws SerialComException {
int state = 0;
int[] linestate = new int[8];
try {
linestate = mNativeInterface.getLinesStatus(handle);
if (linestate[0] < 0) {
throw new SerialComException("getLinesStatus()", mErrMapper.getMappedError(linestate[0]));
}
// Bit mask CTS | DSR | DCD | RI
state = linestate[1] | linestate[2] | linestate[3] | linestate[4];
oldLineState = state & appliedMask;
mEventQueue = new ArrayBlockingQueue(MAX_NUM_EVENTS);
exitEventThread = new AtomicBoolean(false);
mEventListener = eventListener;
mEventLooperThread = new Thread(new EventLooper(), "SCM EventLooper for handle " + handle + " and port " + portName);
mEventLooperThread.start();
} catch (Exception e) {
if(DEBUG) e.printStackTrace();
}
}
/**
* 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 stopEventLooper() throws SerialComException {
try {
exitEventThread.set(true);
mEventLooperThread.interrupt();
} catch (Exception e) {
if(DEBUG) e.printStackTrace();
}
}
/**
* 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.
*/
public void setEventsMask(int newMask) {
appliedMask = newMask;
}
/**
* Gives the event mask currently active.
*/
public int getEventsMask() {
return appliedMask;
}
}