org.bidib.jbidibc.spsw.SpswSerialConnector Maven / Gradle / Ivy
The newest version!
package org.bidib.jbidibc.spsw;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
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.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 de.ibapl.spsw.api.DataBits;
import de.ibapl.spsw.api.FlowControl;
import de.ibapl.spsw.api.Parity;
import de.ibapl.spsw.api.SerialPortSocket;
import de.ibapl.spsw.api.SerialPortSocketFactory;
import de.ibapl.spsw.api.Speed;
import de.ibapl.spsw.api.StopBits;
import de.ibapl.spsw.api.TimeoutIOException;
public class SpswSerialConnector extends AbstractBaseBidib {
private static final Logger LOGGER = LoggerFactory.getLogger(SpswSerialConnector.class);
private static final Logger MSG_RAW_LOGGER = LoggerFactory.getLogger("RAW");
private static final String IMPL_JNI = "de.ibapl.spsw.jniprovider";
private SerialPortSocket port;
private ReceiverThread receiverThread;
private final ServiceLoader serialPortSocketLoader;
private SerialPortSocketFactory serialPortFactory;
private boolean useHardwareFlowControl;
private LineStatusListener lineStatusListener;
private MessageReceiver messageReceiver;
private ScheduledExecutorService lineStatusWorker;
public SpswSerialConnector() {
serialPortSocketLoader = ServiceLoader.load(SerialPortSocketFactory.class);
for (SerialPortSocketFactory factory : serialPortSocketLoader) {
if (IMPL_JNI.contentEquals(factory.getClass().getPackage().getName())) {
this.serialPortFactory = factory;
break;
}
}
if (serialPortFactory == null) {
throw new IllegalArgumentException("Failed to fetch the SPSW serial port factory.");
}
}
/**
* @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 lineStatusListener
*/
public LineStatusListener getLineStatusListener() {
return lineStatusListener;
}
/**
* @param lineStatusListener
* the lineStatusListener to set
*/
public void setLineStatusListener(LineStatusListener lineStatusListener) {
this.lineStatusListener = lineStatusListener;
}
public List getPortIdentifiers() {
List portIdentifiers = new ArrayList<>();
try {
// get the comm port identifiers
portIdentifiers.addAll(serialPortFactory.getPortNames(true));
}
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());
}
return portIdentifiers;
}
// @Override
protected boolean isImplAvaiable() {
return (port != null);
}
@Override
public boolean isOpened() {
return port != null && port.isOpen();
}
@Override
protected void internalOpen(String portName, Context context) throws PortNotFoundException, PortNotOpenedException {
super.internalOpen(portName, context);
LOGGER.info("Create the lineStatusWorker thread pool: {}", lineStatusWorker);
this.lineStatusWorker =
Executors
.newScheduledThreadPool(1,
new ThreadFactoryBuilder().setNameFormat("spswLineStatusWorkers-thread-%d").build());
final MessageReceiver serialMessageReceiver = getMessageReceiver();
Boolean useHardwareFlowControl = context.get("serial.useHardwareFlowControl", Boolean.class, Boolean.TRUE);
LOGGER.info("Open port with portName: {}, useHardwareFlowControl: {}", portName, useHardwareFlowControl);
Set flowControl = FlowControl.getFC_NONE();
if (useHardwareFlowControl) {
LOGGER.info("Set flow control mode to RTS_CTS!");
flowControl = FlowControl.getFC_RTS_CTS();
this.useHardwareFlowControl = true;
}
else {
LOGGER.info("Set flow control mode to NONE!");
this.useHardwareFlowControl = false;
}
Integer baudRate = context.get("serial.baudrate", Integer.class, Integer.valueOf(115200));
LOGGER.info("Open port with baudRate: {}", baudRate);
try {
SerialPortSocket serialPort =
serialPortFactory
.open(portName, Speed.fromNative(baudRate), DataBits.DB_8, StopBits.SB_1, Parity.NONE, flowControl);
// keep the port
this.port = serialPort;
this.port.setTimeouts(2, 100, 100);
}
catch (IOException ex) {
LOGGER.warn("Open the SPSW com port failed.", ex);
if ("Port is open".contentEquals(ex.getMessage())) {
throw new PortNotOpenedException("The port is open already.", ex.getMessage());
}
else if (ex.getMessage().startsWith("Port is busy")) {
final PortNotOpenedException ex1 =
new PortNotOpenedException("The port is in use already.", ex.getMessage());
ex1.setReason(PortNotOpenedException.PORT_IN_USE);
throw ex1;
}
LOGGER.warn("Configure RXTX com port failed.", ex);
throw new InvalidConfigurationException("Configure RXTX com port failed.");
}
startReceiverAndQueues(serialMessageReceiver, context);
// Activate DTR
if (this.useHardwareFlowControl) {
try {
LOGGER.info("Activate DTR.");
this.port.setDTR(true);
}
catch (Exception e) {
LOGGER.warn("Set DTR true failed.", e);
}
}
try {
if (this.useHardwareFlowControl) {
fireCtsChanged(this.port.isCTS(), true);
}
else {
fireCtsChanged(true, true);
}
}
catch (Exception e) {
LOGGER.warn("Get CTS value failed.", e);
}
setConnected(true);
}
@Override
public boolean close() {
if (port != null) {
LOGGER.info("Start closing the port: {}", port);
long start = System.currentTimeMillis();
final SerialPortSocket portToClose = this.port;
this.port = null;
// Deactivate DTR
if (this.useHardwareFlowControl) {
try {
LOGGER.info("Deactivate DTR.");
portToClose.setDTR(false); // pin 1 in DIN8; on main connector, this is DTR
}
catch (Exception e) {
LOGGER.warn("Set DTR to false failed.", e);
}
}
LOGGER.info("Set the receiver running flag to false.");
receiverRunning.set(false);
final MessageReceiver serialMessageReceiver = getMessageReceiver();
stopReceiverAndQueues(serialMessageReceiver);
firstPacketSent = false;
try {
fireCtsChanged(false, true);
}
catch (Exception e) {
LOGGER.warn("Get CTS value failed.", e);
}
try {
portToClose.close();
}
catch (IOException ex) {
LOGGER.warn("Close serial port failed.", ex);
}
setConnected(false);
stopReceiverThread();
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;
}
long end = System.currentTimeMillis();
LOGGER.info("Closed the port. duration: {}", end - start);
return true;
}
else {
LOGGER.info("No port to close available.");
}
return false;
}
private final ByteArrayOutputStream sendBuffer = new ByteArrayOutputStream(2048);
private final ByteBuffer bb = ByteBuffer.allocateDirect(2048);
private boolean useIoStream = false;
@Override
protected void sendData(final ByteArrayOutputStream data, final RawMessageListener rawMessageListener) {
if (port != null && data != null) {
try {
sendBuffer.reset();
if (this.useHardwareFlowControl && !port.isCTS()) {
LOGGER.error("CTS not set! The receiving part is not ready!");
throw new RuntimeException("CTS not set! The receiving part is not ready!");
}
OutputStream os = null;
if (useIoStream) {
os = port.getOutputStream();
}
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);
}
if (useIoStream) {
os.write(initialSequence);
os.flush();
}
else {
bb.clear();
bb.put(initialSequence);
bb.flip();
port.write(bb);
}
Thread.sleep(10);
if (MSG_RAW_LOGGER.isInfoEnabled()) {
MSG_RAW_LOGGER
.info(">> [{}] - {}", initialSequence.length, ByteUtils.bytesToHex(initialSequence));
}
if (rawMessageListener != null) {
rawMessageListener.notifySend(initialSequence);
}
if (useIoStream) {
os.write(initialSequence);
os.flush();
}
else {
bb.clear();
bb.put(initialSequence);
bb.flip();
port.write(bb);
}
firstPacketSent = true;
LOGGER.info("Send initial sequence passed.");
}
catch (Exception ex) {
LOGGER.warn("Send initial sequence failed.", ex);
}
}
SerialMessageEncoder.encodeMessage(data, sendBuffer);
if (MSG_RAW_LOGGER.isInfoEnabled()) {
MSG_RAW_LOGGER
.info(">> [{}] - {}", sendBuffer.toByteArray().length,
ByteUtils.bytesToHex(sendBuffer.toByteArray()));
}
if (rawMessageListener != null) {
rawMessageListener.notifySend(sendBuffer.toByteArray());
}
if (useIoStream) {
os.write(sendBuffer.toByteArray());
os.flush();
}
else {
bb.clear();
bb.put(sendBuffer.toByteArray());
bb.flip();
port.write(bb);
}
}
catch (IOException ex) {
byte[] bytes = data.toByteArray();
LOGGER
.warn("Send message to output stream failed: [{}] - {}", bytes.length, ByteUtils.bytesToHex(bytes));
throw new RuntimeException("Send message to output stream failed: " + ByteUtils.bytesToHex(bytes), ex);
}
finally {
sendBuffer.reset();
}
}
}
@Override
public void startReceiverAndQueues(final MessageReceiver serialMessageReceiver, Context context) {
LOGGER.info("Start receiver and queues.");
if (receiverThread == null) {
receiverThread = new ReceiverThread();
}
receiverThread.start();
super.startReceiverAndQueues(serialMessageReceiver, context);
}
@Override
public void stopReceiverAndQueues(final MessageReceiver serialMessageReceiver) {
LOGGER.info("Stop receiver and queues.");
super.stopReceiverAndQueues(serialMessageReceiver);
}
private byte[] inputBuffer = new byte[2048];
private AtomicBoolean receiverRunning = new AtomicBoolean();
private void stopReceiverThread() {
LOGGER.info("Stop the receiver thread by set the running flag to false.");
receiverRunning.set(false);
if (receiverThread != null) {
LOGGER.info("Wait for termination of receiver thread.");
synchronized (receiverThread) {
try {
receiverThread.join(5000);
}
catch (InterruptedException ex) {
LOGGER.warn("Wait for termination of receiver thread failed.", ex);
}
}
LOGGER.info("Free the receiver thread.");
receiverThread = null;
}
}
private boolean useIoStreamRcv = false;
public class ReceiverThread extends Thread {
@Override
public void run() {
receiverRunning.set(true);
final SerialPortSocket serialPort = port;
ByteBuffer recBuffer = ByteBuffer.allocateDirect(inputBuffer.length);
LOGGER.info("Started the receiver thread.");
while (receiverRunning.get()) {
try {
InputStream input = null;
int len = -1;
if (useIoStreamRcv) {
input = serialPort.getInputStream();
len = input.read(inputBuffer, 0, inputBuffer.length);
}
else {
// LOGGER.info("Try to read data");
recBuffer.clear();
len = serialPort.read(recBuffer);
// LOGGER.info("Read len: {}", len);
}
// LOGGER.trace("Read len: {}", len);
if (len < 0) {
// check if the port was closed.
boolean portClosed = !serialPort.isOpen();
LOGGER.info("Port closed: {}", portClosed);
if (portClosed) {
// say good-bye
LOGGER.info("The port is closed. Leave the receiver loop.");
receiverRunning.set(false);
continue;
}
}
if (useIoStreamRcv) {
int remaining = input.available();
if (remaining > 0) {
LOGGER.warn("More data in inputStream might be available, remaining: {}", remaining);
}
if (len > -1) {
receive(inputBuffer, len);
}
}
else {
if (len > 0) {
recBuffer.clear();
recBuffer.get(inputBuffer, 0, len);
receive(inputBuffer, len);
}
}
}
catch (TimeoutIOException ex) {
LOGGER.trace("Timout during wait for data.");
}
catch (IOException ex) {
LOGGER.error("Receive data failed with an exception!", ex);
receiverRunning.set(false);
if (serialPort == null || serialPort.isOpen()) {
triggerClosePort();
}
}
catch (NullPointerException ex) {
LOGGER.error("Receive data failed with an NPE! The port might be closed.", ex);
receiverRunning.set(false);
}
catch (Exception ex) {
LOGGER.error("Message receiver returned from receive with an exception!", ex);
}
}
LOGGER.info("Leaving receiver loop.");
}
}
private void triggerClosePort() {
LOGGER.warn("Close the port.");
Thread worker = new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("Start close port because error was detected.");
try {
// the listeners are notified in close()
close();
}
catch (Exception ex) {
LOGGER.warn("Close after error failed.", ex);
}
LOGGER.warn("The port was closed.");
}
});
worker.start();
}
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);
}
}