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

org.bidib.jbidibc.rxtx.RxtxSerialConnector Maven / Gradle / Ivy

package org.bidib.jbidibc.rxtx;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.TooManyListenersException;

import org.bidib.jbidibc.core.BidibLibrary;
import org.bidib.jbidibc.core.MessageReceiver;
import org.bidib.jbidibc.core.base.AbstractBaseBidib;
import org.bidib.jbidibc.core.base.RawMessageListener;
import org.bidib.jbidibc.core.exception.InvalidConfigurationException;
import org.bidib.jbidibc.core.exception.InvalidLibraryException;
import org.bidib.jbidibc.core.exception.PortNotFoundException;
import org.bidib.jbidibc.core.exception.PortNotOpenedException;
import org.bidib.jbidibc.core.helpers.Context;
import org.bidib.jbidibc.core.utils.ByteUtils;
import org.bidib.jbidibc.serial.LineStatusListener;
import org.bidib.jbidibc.serial.SerialMessageEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import gnu.io.CommPortIdentifier;
import gnu.io.CommPortOwnershipListener;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;

public class RxtxSerialConnector extends AbstractBaseBidib {

    private static final Logger LOGGER = LoggerFactory.getLogger(RxtxSerialConnector.class);

    private static final Logger MSG_RAW_LOGGER = LoggerFactory.getLogger("RAW");

    private SerialPort port;

    private CommPortOwnershipListener commPortOwnershipListener;

    private CommPortIdentifier commPort;

    private MessageReceiver messageReceiver;

    protected Object receiveLock = new Object();

    private byte[] inputBuffer = new byte[2048];

    private boolean useHardwareFlowControl;

    private LineStatusListener lineStatusListener;

    /**
     * @return the messageReceiver
     */
    public MessageReceiver getMessageReceiver() {
        return messageReceiver;
    }

    /**
     * @param messageReceiver
     *            the messageReceiver to set
     */
    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;
    }

    @Override
    protected void internalOpen(String portName, final Context context)
        throws PortNotFoundException, PortNotOpenedException {

        super.internalOpen(portName, context);

        LOGGER.info("Internal open the port.");

        if (commPort == null) {
            try {
                commPort = CommPortIdentifier.getPortIdentifier(portName);
            }
            catch (NoSuchPortException ex) {
                LOGGER.warn("Requested port is not available: {}", portName, ex);
                throw new PortNotFoundException(portName);
            }
        }
        else {
            LOGGER.info("Use existing commPort: {}", commPort);
        }

        if (commPortOwnershipListener == null) {
            commPortOwnershipListener = new CommPortOwnershipListener() {

                @Override
                public void ownershipChange(int type) {
                    LOGGER.info("Ownership changed, type: {}", type);
                }
            };
        }
        commPort.addPortOwnershipListener(commPortOwnershipListener);

        // open the port
        SerialPort scmPort = null;
        try {
            scmPort = (SerialPort) commPort.open(RxtxSerialBidib.class.getName(), 2000);

            Boolean useHardwareFlowControl = context.get("serial.useHardwareFlowControl", Boolean.class, Boolean.TRUE);

            LOGGER.info("Open port with portName: {}, useHardwareFlowControl: {}", portName, useHardwareFlowControl);

            if (useHardwareFlowControl) {
                LOGGER
                    .info(
                        "Set flow control mode to SerialPort.FLOWCONTROL_RTSCTS_IN | SerialPort.FLOWCONTROL_RTSCTS_OUT!");
                scmPort.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_IN | SerialPort.FLOWCONTROL_RTSCTS_OUT);

                this.useHardwareFlowControl = true;
            }
            else {
                LOGGER.info("Set flow control mode to SerialPort.FLOWCONTROL_NONE!");
                scmPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);

                this.useHardwareFlowControl = false;
            }

            Integer baudRate = context.get("serial.baudrate", Integer.class, Integer.valueOf(115200));
            LOGGER.info("Open port with baudRate: {}", baudRate);

            scmPort.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
        }
        catch (UnsupportedCommOperationException ex) {
            LOGGER.warn("Configure RXTX com port failed.", ex);
            throw new InvalidConfigurationException("Configure RXTX com port failed.");
        }
        catch (PortInUseException ex) {
            LOGGER.warn("The port is in use already.", ex);
            throw new PortNotOpenedException("The port is in use already.", ex.getMessage());
        }

        final SerialPort serialPort = scmPort;

        // start the receiver and queues
        startReceiverAndQueues(messageReceiver, context);

        final SerialPortEventListener eventListener = new SerialPortEventListener() {

            @Override
            public void serialEvent(SerialPortEvent event) {
                // this callback is called every time data is available
                LOGGER.trace("serialEvent received: {}", event);
                switch (event.getEventType()) {
                    case SerialPortEvent.DATA_AVAILABLE:
                        // MSG_OUTPUTSTREAM_LOGGER.info("<<<<");
                        try {
                            synchronized (receiveLock) {
                                InputStream input = serialPort.getInputStream();
                                int len = -1;
                                try {
                                    len = input.read(inputBuffer, 0, inputBuffer.length);

                                    int remaining = input.available();
                                    if (remaining > 0) {
                                        LOGGER
                                            .warn("More data in inputStream might be available, remaining: {}",
                                                remaining);
                                    }
                                }
                                catch (IOException ex) {
                                    LOGGER.warn("Read data from input stream to buffer failed.", ex);
                                }

                                if (len > -1) {
                                    receive(inputBuffer, len);
                                }
                            }
                        }
                        catch (Exception ex) {
                            LOGGER.error("Message receiver returned from receive with an exception!", ex);
                        }
                        break;
                    case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
                        LOGGER.trace("The output buffer is empty.");

                        // // only the sendQueueWorker must release the semaphore
                        // if (sendQueueWorkerThreadId.get() == Thread.currentThread().getId()) {
                        // MSG_OUTPUTSTREAM_LOGGER.info(">>>> OBE");
                        // // sendSemaphore.release();
                        // }
                        break;
                    case SerialPortEvent.CD:
                        LOGGER.warn("CD is signalled.");
                        break;
                    case SerialPortEvent.CTS:
                        LOGGER
                            .warn("The CTS value has changed, old value: {}, new value: {}",
                                new Object[] { event.getOldValue(), event.getNewValue() });

                        // TODO this should only stop sending messages but not close the port
                        fireCtsChanged(event.getNewValue());

                        if (event.getNewValue() == false) {
                            LOGGER.warn("Close the port.");
                            Thread worker = new Thread(new Runnable() {
                                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();
                        }
                        break;
                    case SerialPortEvent.OE:
                        LOGGER.warn("OE (overrun error) is signalled.");
                        break;
                    case SerialPortEvent.DSR:
                        LOGGER.warn("DSR is signalled.");
                        break;
                    default:
                        LOGGER
                            .warn("SerialPortEvent was triggered, type: {}, old value: {}, new value: {}",
                                new Object[] { event.getEventType(), event.getOldValue(), event.getNewValue() });
                        break;
                }
            }
        };

        try {
            serialPort.addEventListener(eventListener);
        }
        catch (TooManyListenersException ex) {
            LOGGER.warn("Add eventlistener to RXTX com port failed.", ex);
            throw new InvalidConfigurationException("Add eventlistener to RXTX com port failed.");
        }

        // react on port removed ...
        serialPort.notifyOnCTS(true);
        serialPort.notifyOnCarrierDetect(true);
        serialPort.notifyOnBreakInterrupt(true);
        serialPort.notifyOnDSR(true);
        serialPort.notifyOnOverrunError(true);
        serialPort.notifyOnOutputEmpty(true);

        if (this.useHardwareFlowControl) {
            // Activate DTR
            try {
                LOGGER.info("Activate DTR.");

                serialPort.setDTR(true); // pin 1 in DIN8; on main connector, this is DTR
            }
            catch (Exception e) {
                LOGGER.warn("Set DTR true failed.", e);
            }
        }

        LOGGER.info("Let the serial port notify data available.");
        serialPort.notifyOnDataAvailable(true);

        if (this.useHardwareFlowControl) {
            // Activate RTS
            try {
                LOGGER.info("Activate RTS.");
                serialPort.setRTS(true);
            }
            catch (Exception e) {
                LOGGER.warn("Set RTS true failed.", e);
            }
        }

        try {
            if (this.useHardwareFlowControl) {
                fireCtsChanged(serialPort.isCTS());
            }
            else {
                fireCtsChanged(true);
            }
        }
        catch (Exception e) {
            LOGGER.warn("Get CTS value failed.", e);
        }

        // keep the port
        port = serialPort;
    }

    protected void fireCtsChanged(boolean ready) {
        LOGGER.info("CTS has changed, ready: {}", ready);

        // signal changed line status to the bidib implementation
        if (lineStatusListener != null) {
            lineStatusListener.notifyLineStatusChanged(ready);
        }
    }

    public List getPortIdentifiers() {
        List portIdentifiers = new ArrayList();

        try {
            // get the comm port identifiers
            Enumeration e = CommPortIdentifier.getPortIdentifiers();
            while (e.hasMoreElements()) {
                CommPortIdentifier id = (CommPortIdentifier) e.nextElement();
                LOGGER
                    .debug("Process current CommPortIdentifier, name: {}, portType: {}", id.getName(),
                        id.getPortType());

                if (id.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                    portIdentifiers.add(id.getName());
                }
                else {
                    LOGGER
                        .debug("Skip port because no serial port, name: {}, portType: {}", id.getName(),
                            id.getPortType());
                }
            }
        }
        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
    public boolean isOpened() {
        boolean isOpened = false;
        try {
            LOGGER.debug("Check if port is opened: {}", port);
            isOpened = (port != null && port.getOutputStream() != null);
        }
        catch (IOException ex) {
            LOGGER.warn("OutputStream is not available.", ex);
        }
        return isOpened;
    }

    protected boolean isImplAvaiable() {
        return (port != null);
    }

    @Override
    public boolean close() {

        if (port != null) {
            LOGGER.info("Start closing the port: {}", port);
            long start = System.currentTimeMillis();

            // Deactivate DTR
            if (this.useHardwareFlowControl) {
                try {
                    LOGGER.info("Deactivate DTR.");

                    port.setDTR(false); // pin 1 in DIN8; on main connector, this is DTR
                }
                catch (Exception e) {
                    LOGGER.warn("Set DTR to false failed.", e);
                }

                // Deactivate RTS
                try {
                    LOGGER.info("Deactivate RTS.");
                    port.setRTS(false);
                }
                catch (Exception e) {
                    LOGGER.warn("Set RTS to false failed.", e);
                }
            }

            // this makes the close operation faster ...
            try {
                LOGGER.info("Remove event listener.");
                port.removeEventListener();
                LOGGER.info("Removed event listener.");
                // port.enableReceiveTimeout(200);
            }
            catch (Exception e) {
                LOGGER.warn("Remove event listener failed.", e);
            }

            // stop the receiver and queues
            final MessageReceiver serialMessageReceiver = getMessageReceiver();
            stopReceiverAndQueues(serialMessageReceiver);

            try {
                if (this.useHardwareFlowControl) {
                    fireCtsChanged(port.isCTS());
                }
                else {
                    fireCtsChanged(false);
                }
            }
            catch (Exception e) {
                LOGGER.warn("Get CTS value failed.", e);
            }

            LOGGER.info("Close the port.");
            try {
                port.close();
            }
            catch (Exception e) {
                LOGGER.warn("Close port failed.", e);
            }

            fireCtsChanged(false);

            long end = System.currentTimeMillis();
            LOGGER.info("Closed the port. duration: {}", end - start);

            if (commPortOwnershipListener != null) {
                LOGGER.info("Remove the PortOwnershipListener from commPort: {}", commPort);
                try {
                    commPort.removePortOwnershipListener(commPortOwnershipListener);
                }
                catch (Exception ex) {
                    LOGGER.warn("Remove port ownership listener failed.", ex);
                }
                commPortOwnershipListener = null;
            }

            port = null;
            commPort = null;

            // cleanupAfterClose(serialMessageReceiver);
            return true;
        }
        else {
            LOGGER.info("No port to close available.");
        }

        return false;
    }

    private final ByteArrayOutputStream sendBuffer = new ByteArrayOutputStream(2048);

    @Override
    protected void sendData(final ByteArrayOutputStream data, final RawMessageListener rawMessageListener) {

        if (port != null && data != null) {
            try {
                OutputStream os = port.getOutputStream();

                if (!firstPacketSent) {
                    LOGGER.info("Send initial sequence.");

                    try {
                        byte[] initialSequence = new byte[] { (byte) BidibLibrary.BIDIB_PKT_MAGIC };
                        if (MSG_RAW_LOGGER.isInfoEnabled()) {
                            MSG_RAW_LOGGER
                                .info(">> [{}] - {}", initialSequence.length, ByteUtils.bytesToHex(initialSequence));
                        }

                        if (rawMessageListener != null) {
                            rawMessageListener.notifySend(initialSequence);
                        }

                        os.write(initialSequence);
                        Thread.sleep(10);
                        if (MSG_RAW_LOGGER.isInfoEnabled()) {
                            MSG_RAW_LOGGER
                                .info(">> [{}] - {}", initialSequence.length, ByteUtils.bytesToHex(initialSequence));
                        }

                        if (rawMessageListener != null) {
                            rawMessageListener.notifySend(initialSequence);
                        }

                        os.write(initialSequence);

                        firstPacketSent = true;

                        LOGGER.info("Send initial sequence passed.");
                    }
                    catch (Exception ex) {
                        LOGGER.warn("Send initial sequence failed.", ex);
                    }
                }

                sendBuffer.reset();
                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());
                }

                os.write(sendBuffer.toByteArray());

                os.flush();
            }
            catch (Exception ex) {
                byte[] bytes = data.toByteArray();

                LOGGER
                    .warn("Send message to output stream failed: [{}] - {}", data.size(), ByteUtils.bytesToHex(bytes));

                throw new RuntimeException("Send message to output stream failed: " + ByteUtils.bytesToHex(bytes), ex);
            }
            finally {
                sendBuffer.reset();
            }
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy