All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy