org.bidib.jbidibc.scm.ScmSerialConnector Maven / Gradle / Ivy
The newest version!
package org.bidib.jbidibc.scm;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.bidib.jbidibc.messages.MessageReceiver;
import org.bidib.jbidibc.messages.base.AbstractBaseBidib;
import org.bidib.jbidibc.messages.base.RawMessageListener;
import org.bidib.jbidibc.messages.exception.InvalidConfigurationException;
import org.bidib.jbidibc.messages.exception.InvalidLibraryException;
import org.bidib.jbidibc.messages.exception.PortNotFoundException;
import org.bidib.jbidibc.messages.exception.PortNotOpenedException;
import org.bidib.jbidibc.messages.exception.PortNotReadyForSendException;
import org.bidib.jbidibc.messages.helpers.Context;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.messages.utils.ThreadFactoryBuilder;
import org.bidib.jbidibc.serial.LineStatusListener;
import org.bidib.jbidibc.serial.SerialMessageEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
import com.serialpundit.serial.SerialComManager.BAUDRATE;
import com.serialpundit.serial.SerialComManager.DATABITS;
import com.serialpundit.serial.SerialComManager.FLOWCONTROL;
import com.serialpundit.serial.SerialComManager.PARITY;
import com.serialpundit.serial.SerialComManager.STOPBITS;
public class ScmSerialConnector extends AbstractBaseBidib {
private static final Logger LOGGER = LoggerFactory.getLogger(ScmSerialConnector.class);
private static final Logger MSG_RAW_LOGGER = LoggerFactory.getLogger("RAW");
private static final Logger MSG_RX_LOGGER = LoggerFactory.getLogger("RX");
private String libraryDirectoryName = "jbidibc2";
private SerialComManager scm;
private long handle = -1;
private ISerialComDataListener dataListener;
private ISerialComEventListener eventListener;
private boolean addEventListener = true;
private boolean useHardwareFlowControl = false;
private ByteArrayOutputStream sendBuffer = new ByteArrayOutputStream(100);
private MessageReceiver messageReceiver;
private LineStatusListener lineStatusListener;
private final AtomicBoolean sendEnabled = new AtomicBoolean();
private ScheduledExecutorService lineStatusWorker;
private ScheduledExecutorService queryLineStatusWorker;
/**
* @return the messageReceiver
*/
@Override
public MessageReceiver getMessageReceiver() {
return messageReceiver;
}
/**
* @param messageReceiver
* the messageReceiver to set
*/
@Override
public void setMessageReceiver(MessageReceiver messageReceiver) {
this.messageReceiver = messageReceiver;
}
/**
* @return the libraryDirectoryName
*/
public String getLibraryDirectoryName() {
return libraryDirectoryName;
}
/**
* @param libraryDirectoryName
* the libraryDirectoryName to set
*/
public void setLibraryDirectoryName(String libraryDirectoryName) {
this.libraryDirectoryName = libraryDirectoryName;
}
/**
* @return the lineStatusListener
*/
public LineStatusListener getLineStatusListener() {
return lineStatusListener;
}
/**
* @param lineStatusListener
* the lineStatusListener to set
*/
public void setLineStatusListener(LineStatusListener lineStatusListener) {
this.lineStatusListener = lineStatusListener;
}
// @Override
protected boolean isImplAvaiable() {
return (scm != null);
}
@Override
public boolean isOpened() {
boolean isOpened = (handle > 0);
return isOpened;
}
private BAUDRATE getBaudRate(int baudRate) {
for (BAUDRATE br : BAUDRATE.values()) {
if (br.getValue() == baudRate) {
return br;
}
}
return BAUDRATE.B115200;
}
@Override
protected void internalOpen(String portName, final Context context)
throws PortNotFoundException, PortNotOpenedException {
super.internalOpen(portName, context);
this.lineStatusWorker =
Executors
.newScheduledThreadPool(1,
new ThreadFactoryBuilder().setNameFormat("scmLineStatusWorkers-thread-%d").build());
this.queryLineStatusWorker =
Executors
.newScheduledThreadPool(1,
new ThreadFactoryBuilder().setNameFormat("scmQueryLineStatusWorkers-thread-%d").build());
final MessageReceiver serialMessageReceiver = getMessageReceiver();
String tempDir = System.getProperty("java.io.tmpdir");
File temp = new File(tempDir, libraryDirectoryName);
try {
scm = new SerialComManager("scm", temp.getAbsolutePath(), true, false);
useHardwareFlowControl = context.get("serial.useHardwareFlowControl", Boolean.class, Boolean.TRUE);
LOGGER.info("Open port with portName: {}, useHardwareFlowControl: {}", portName, useHardwareFlowControl);
// open the port
handle = scm.openComPort(portName, true, true, true);
LOGGER.info("Opened serial port, handle: {}", handle);
if (useHardwareFlowControl) {
scm.configureComPortControl(handle, FLOWCONTROL.RTS_CTS, 'x', 'x', false, false);
}
else {
scm.configureComPortControl(handle, FLOWCONTROL.NONE, 'x', 'x', false, false);
}
Integer baudRate = context.get("serial.baudrate", Integer.class, Integer.valueOf(115200));
LOGGER.info("Open port with baudRate: {}", baudRate);
final BAUDRATE scmBaudRate = getBaudRate(baudRate);
scm.configureComPortData(handle, DATABITS.DB_8, STOPBITS.SB_1, PARITY.P_NONE, scmBaudRate, 0);
LOGGER.info("Clear the IO buffers.");
scm.clearPortIOBuffers(handle, true, true);
sendEnabled.set(true);
}
catch (SerialComException ex) {
LOGGER.warn("Open or configure SCM com port failed.", ex);
if (ex.getMessage().startsWith("Access is denied.")) {
final PortNotOpenedException ex1 =
new PortNotOpenedException("The port is in use already.", ex.getMessage());
ex1.setReason(PortNotOpenedException.PORT_IN_USE);
throw ex1;
}
throw new InvalidConfigurationException("Configure SCM com port failed.");
}
catch (IOException ex) {
LOGGER.warn("Configure SCM com port failed.", ex);
throw new InvalidConfigurationException("Configure SCM com port failed.");
}
startReceiverAndQueues(serialMessageReceiver, context);
// add event listener
if (addEventListener) {
LOGGER.info("Add the event listener.");
eventListener = new ISerialComEventListener() {
@Override
public void onNewSerialEvent(SerialComLineEvent lineEvent) {
LOGGER
.error("Received serial com line event, CTS : {}, DSR : {}, DCD: {}, RI: {}",
lineEvent.getCTS(), lineEvent.getDSR(), lineEvent.getDCD(), lineEvent.getRI());
MSG_RAW_LOGGER
.info("Received serial com line event, CTS : {}, DSR : {}, DCD: {}, RI: {}", lineEvent.getCTS(),
lineEvent.getDSR(), lineEvent.getDCD(), lineEvent.getRI());
queryLineStatus();
}
};
try {
scm.registerLineEventListener(handle, eventListener);
// mask CTS, so only changes to CTS line will be reported.
// scm.setEventsMask(eventListener, SerialComManager.CTS);
Thread.sleep(50); // from SCM tests: removing this line causes jni crash if setRTS() is called to fast
}
catch (SerialComException | InterruptedException ex) {
LOGGER.warn("Register line event listener to SCM com port failed.", ex);
throw new InvalidConfigurationException("Register line event listener to SCM com port failed.");
}
}
dataListener = new ISerialComDataListener() {
@Override
public void onNewSerialDataAvailable(final byte[] data) {
// TODO remove the logger
MSG_RAW_LOGGER
.info("<<<< Serial data available, len: {}, data: {}", data.length, ByteUtils.bytesToHex(data));
receive(data, data.length);
// TODO remove the logger
MSG_RAW_LOGGER.info("<<<< Serial data received.");
}
@Override
public void onDataListenerError(int errorNum) {
LOGGER.error("Data listener notified an error: {}", errorNum);
if (isConnected()) {
LOGGER.info("Close the port.");
setConnected(false);
if (dataListener != null) {
try {
LOGGER.info("Unregister data listener.");
scm.unregisterDataListener(handle, dataListener);
dataListener = null;
}
catch (Exception ex) {
LOGGER.warn("Unregister data listener after error detection failed.", ex);
}
}
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("Error detected. Close the port.");
if (dataListener != null) {
try {
scm.unregisterDataListener(handle, dataListener);
dataListener = null;
}
catch (Exception ex) {
LOGGER.warn("Unregister data listener after error detection failed.", ex);
}
}
if (eventListener != null) {
try {
scm.unregisterLineEventListener(handle, eventListener);
eventListener = null;
}
catch (Exception ex) {
LOGGER.warn("Unregister event listener after error detection failed.", ex);
}
}
try {
close();
}
catch (Exception ex) {
LOGGER.warn("Close scm port failed.", ex);
}
}
});
t1.start();
}
else {
LOGGER.info("Port is closed.");
}
}
};
LOGGER.info("Registering data listener for handle: {}.", handle);
// register data listener for this port
try {
scm.registerDataListener(handle, dataListener);
}
catch (SerialComException ex) {
LOGGER.warn("Register data listener to SCM com port failed.", ex);
throw new InvalidConfigurationException("Register data listener to SCM com port failed.");
}
LOGGER.info("Registered data listener.");
if (useHardwareFlowControl) {
try {
LOGGER.info("Activate RTS.");
// scm.setRTS(handle, false);
// Thread.sleep(50);
// scm.setRTS(handle, true);
// Thread.sleep(50);
}
catch (Exception e) {
LOGGER.warn("Set RTS true failed.", e);
}
// Activate DTR
try {
LOGGER.info("Activate DTR.");
scm.setDTR(handle, false); // pin 1 in DIN8; on main connector, this is DTR
Thread.sleep(50);
scm.setDTR(handle, true); // pin 1 in DIN8; on main connector, this is DTR
Thread.sleep(50);
}
catch (Exception e) {
LOGGER.warn("Set DTR true failed.", e);
}
}
// dumpLineStatus(handle);
// queryLineStatus();
fireCtsChanged(true, true);
setConnected(true);
}
protected void queryLineStatus() {
LOGGER.info("Query the line status.");
queryLineStatusWorker.schedule(() -> {
int[] lineStatus = null;
try {
MSG_RAW_LOGGER.info("Get the line status.");
lineStatus = scm.getLinesStatus(handle);
LOGGER
.info(">>> Fetched current line status, CTS: {}, DSR: {}, DCD: {}", lineStatus[0], lineStatus[1],
lineStatus[2]);
boolean isCTS = lineStatus[0] > 0;
MSG_RAW_LOGGER.info("<< CTS changed: {}, CTS line status: {}", isCTS, lineStatus[0]);
MSG_RX_LOGGER.info("<< CTS changed: {}, CTS line status: {}", isCTS, lineStatus[0]);
LOGGER.info("<< CTS changed: {}, CTS line status: {}", isCTS, lineStatus[0]);
if (isCTS == false) {
MSG_RAW_LOGGER
.warn("The CTS value is false. Set sendEnabled to false to prevent send more messages.");
sendEnabled.set(false);
}
else {
MSG_RAW_LOGGER.info("The CTS value is true. Set sendEnabled to true to allow send more messages.");
sendEnabled.set(true);
}
synchronized (sendEnabled) {
sendEnabled.notifyAll();
}
if (!isCTS) {
LOGGER.warn("CTS is not enabled!");
}
else {
LOGGER.warn("CTS is enabled!");
}
fireCtsChanged(isCTS, false);
}
catch (SerialComException ex) {
LOGGER.warn("Get the line status failed.", ex);
}
}, 1, TimeUnit.MILLISECONDS);
}
@Override
public boolean close() {
if (scm != null) {
LOGGER.info("Close the port, handle: {}", handle);
final SerialComManager scmToClose = this.scm;
scm = null;
long start = System.currentTimeMillis();
// unregister data listener
LOGGER.info("Unregister data listener: {}", dataListener);
if (dataListener != null && handle > 0) {
try {
scmToClose.unregisterDataListener(handle, dataListener);
}
catch (SerialComException ex) {
LOGGER.warn("Unregister dataListener failed.", ex);
}
try {
Thread.sleep(200);
}
catch (InterruptedException ex) {
LOGGER.warn("Sleep after unregister data listener failed.", ex);
}
}
dataListener = null;
// unregister line event listener
if (eventListener != null && handle > 0) {
LOGGER.info("Unregister line event listener.");
try {
scmToClose.unregisterLineEventListener(handle, eventListener);
}
catch (SerialComException ex) {
LOGGER.warn("Unregister lineEventListener failed.", ex);
}
try {
Thread.sleep(200);
}
catch (InterruptedException ex) {
LOGGER.warn("Sleep after unregister line event listener failed.", ex);
}
}
eventListener = null;
final MessageReceiver serialMessageReceiver = getMessageReceiver();
stopReceiverAndQueues(serialMessageReceiver);
firstPacketSent = false;
if (handle > 0) {
try {
LOGGER.info("Close the COM port: {}", handle);
scmToClose.closeComPort(handle);
}
catch (Exception e) {
LOGGER.warn("Close port failed.", e);
}
}
else {
LOGGER.info("Don't close port because handle is not valid.");
}
long end = System.currentTimeMillis();
LOGGER.info("Closed the port. duration: {}", end - start);
fireCtsChanged(false, true);
setConnected(false);
// scm = null;
handle = -1;
LOGGER.info("Shutdown the queryLineStatusWorker: {}", queryLineStatusWorker);
if (queryLineStatusWorker != null) {
try {
this.queryLineStatusWorker.shutdownNow();
this.queryLineStatusWorker.awaitTermination(500, TimeUnit.MILLISECONDS);
}
catch (Exception ex) {
LOGGER.warn("Terminate queryLineStatusWorker failed.", ex);
}
// free the queryLineStatus
this.queryLineStatusWorker = null;
}
LOGGER.info("Shutdown the lineStatusWorker: {}", lineStatusWorker);
if (lineStatusWorker != null) {
try {
this.lineStatusWorker.shutdownNow();
this.lineStatusWorker.awaitTermination(500, TimeUnit.MILLISECONDS);
}
catch (Exception ex) {
LOGGER.warn("Terminate lineStatusWorker failed.", ex);
}
// free the lineStatusWorker
this.lineStatusWorker = null;
}
// cleanupAfterClose(serialMessageReceiver);
return true;
}
else {
LOGGER.info("No port to close available.");
}
return false;
}
@Override
protected void sendData(final ByteArrayOutputStream data, final RawMessageListener rawMessageListener) {
if (handle > 0 && data != null) {
while (!sendEnabled.get()) {
synchronized (sendEnabled) {
LOGGER.info("Wait for sendEnabled.");
MSG_RAW_LOGGER.info(">> Wait for sendEnabled.");
try {
sendEnabled.wait(250);
}
catch (InterruptedException ex) {
LOGGER.warn("Wait for sendEnabled to become true was interrupted. Abort send data.");
return;
}
}
LOGGER.info("After wait for sendEnabled.");
}
MSG_RAW_LOGGER.info(">> Enter send.");
try {
if (!firstPacketSent) {
LOGGER.info("Send initial sequence.");
try {
byte[] initialSequence = new byte[] { ByteUtils.MAGIC };
if (MSG_RAW_LOGGER.isInfoEnabled()) {
MSG_RAW_LOGGER
.info(">> [{}] - {}", initialSequence.length, ByteUtils.bytesToHex(initialSequence));
}
if (rawMessageListener != null) {
rawMessageListener.notifySend(initialSequence);
}
scm.writeBytes(handle, initialSequence);
Thread.sleep(10);
if (MSG_RAW_LOGGER.isInfoEnabled()) {
MSG_RAW_LOGGER
.info(">> [{}] - {}", initialSequence.length, ByteUtils.bytesToHex(initialSequence));
}
if (rawMessageListener != null) {
rawMessageListener.notifySend(initialSequence);
}
scm.writeBytes(handle, initialSequence);
firstPacketSent = true;
LOGGER.info("Send initial sequence passed.");
}
catch (Exception ex) {
LOGGER.warn("Send initial sequence failed.", ex);
}
}
// LOGGER.info("Reset the send buffer.");
sendBuffer.reset();
// encode the data for serial transfer
SerialMessageEncoder.encodeMessage(data, sendBuffer);
byte[] refContent = sendBuffer.toByteArray();
if (MSG_RAW_LOGGER.isInfoEnabled()) {
MSG_RAW_LOGGER.info(">> [{}] - {}", refContent.length, ByteUtils.bytesToHex(refContent));
}
if (rawMessageListener != null) {
rawMessageListener.notifySend(refContent);
}
int sent = scm.writeBytes(handle, refContent);
if (sent == 0) {
MSG_RAW_LOGGER.warn(">> sent bytes: {}", sent);
LOGGER
.error("The message has not been sent to handle: {}, message: {}", handle,
ByteUtils.bytesToHex(refContent));
// TODO check if the port is still open
int[] lineStatus = dumpLineStatus(handle);
if (lineStatus[0] == 0 /* CTS */) {
throw new PortNotReadyForSendException(scm.getPortName(handle));
}
throw new RuntimeException("Write message to output failed: " + ByteUtils.bytesToHex(refContent));
}
else {
MSG_RAW_LOGGER.info(">> sent bytes: {}", sent);
}
}
catch (Exception ex) {
byte[] bytes = data.toByteArray();
LOGGER
.warn("Send message to output stream failed: [{}] - {}", bytes.length, ByteUtils.bytesToHex(bytes),
ex);
throw new RuntimeException("Send message to output stream failed: " + ByteUtils.bytesToHex(bytes), ex);
}
finally {
sendBuffer.reset();
}
}
else {
LOGGER.warn("No port handle available. Data is not sent!");
}
}
public List getPortIdentifiers() {
List portIdentifiers = new ArrayList();
try {
String tempDir = System.getProperty("java.io.tmpdir");
File temp = new File(tempDir, "jbidibc");
// temp.mkdirs();
if (scm == null) {
LOGGER.info("Create the scm instance.");
scm = new SerialComManager("scm", temp.getAbsolutePath(), true, false);
}
String[] ports = scm.listAvailableComPorts();
for (String portIdentifier : ports) {
portIdentifiers.add(portIdentifier);
}
}
catch (UnsatisfiedLinkError ule) {
LOGGER.warn("Get comm port identifiers failed.", ule);
throw new InvalidLibraryException(ule.getMessage(), ule.getCause());
}
catch (Error error) {
LOGGER.warn("Get comm port identifiers failed.", error);
throw new RuntimeException(error.getMessage(), error.getCause());
}
catch (IOException ex) {
LOGGER.warn("Get comm port identifiers failed.", ex);
throw new InvalidLibraryException(ex.getMessage(), ex.getCause());
}
return portIdentifiers;
}
private int[] dumpLineStatus(long handle) {
MSG_RAW_LOGGER.info("Dump the line status.");
// Linux OS : CTS, DSR, DCD, RI, LOOP, RTS, DTR respectively.
// MAC OS X : CTS, DSR, DCD, RI, 0, RTS, DTR respectively.
// Windows OS : CTS, DSR, DCD, RI, 0, 0, 0 respectively.
int[] lineStatus = null;
try {
lineStatus = scm.getLinesStatus(handle);
LOGGER
.info(">>> Fetched current line status, CTS: {}, DSR: {}, DCD: {}", lineStatus[0], lineStatus[1],
lineStatus[2]);
boolean isCTS = lineStatus[0] > 0;
MSG_RAW_LOGGER.info("<< CTS changed: {}", isCTS);
MSG_RX_LOGGER.info("<< CTS changed, current CTS: {}", isCTS);
LOGGER.info("<< CTS changed, current CTS: {}", isCTS);
fireCtsChanged(lineStatus[0] > 0 ? true : false, false);
}
catch (SerialComException ex) {
LOGGER.warn("Get the line status failed.", ex);
}
return lineStatus;
}
protected void fireCtsChanged(boolean ready, boolean manualEvent) {
LOGGER.info("CTS has changed, ready: {}, manualEvent: {}", ready, manualEvent);
lineStatusWorker.schedule(() -> {
// signal changed line status to the bidib implementation
if (lineStatusListener != null) {
lineStatusListener.notifyLineStatusChanged(ready, manualEvent);
}
}, 0, TimeUnit.MILLISECONDS);
}
}