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

org.bidib.jbidibc.rxtx.debug.DebugReader Maven / Gradle / Ivy

package org.bidib.jbidibc.rxtx.debug;

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 java.util.concurrent.Semaphore;

import org.bidib.jbidibc.debug.AbstractDebugReader;
import org.bidib.jbidibc.debug.DebugMessageProcessor;
import org.bidib.jbidibc.debug.LineEndingEnum;
import org.bidib.jbidibc.debug.exception.InvalidLibraryException;
import org.bidib.jbidibc.messages.ConnectionListener;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import gnu.io.CommPortIdentifier;
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 DebugReader extends AbstractDebugReader {
    private static final Logger LOGGER = LoggerFactory.getLogger(DebugReader.class);

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

    static final int DEFAULT_TIMEOUT = /* 1500 */300;

    private SerialPort port;

    // private DebugMessageProcessor messageReceiver;

    // private String requestedPortName;

    private ConnectionListener connectionListener;

    private Semaphore portSemaphore = new Semaphore(1);

    private Semaphore sendSemaphore = new Semaphore(1);

    // private BlockingQueue receiveQueue = new LinkedBlockingQueue<>();
    //
    // private Thread receiveQueueWorker;
    //
    // private AtomicBoolean receiverRunning = new AtomicBoolean();
    //
    // private AtomicLong receiveQueueWorkerThreadId = new AtomicLong();

    public DebugReader(final DebugMessageProcessor messageReceiver) {
        super(messageReceiver);
    }

    @Override
    public void initialize() {

    }

    @Override
    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 DebugMessageProcessor getMessageReceiver() {
    // return messageReceiver;
    // }
    //
    // /**
    // * @return the connectionListener
    // */
    // @Override
    // public ConnectionListener getConnectionListener() {
    // return connectionListener;
    // }
    //
    // /**
    // * @param connectionListener
    // * the connectionListener to set
    // */
    // @Override
    // public void setConnectionListener(ConnectionListener connectionListener) {
    // this.connectionListener = connectionListener;
    // }

    private SerialPort internalOpen(CommPortIdentifier commPort, int baudRate, Context context)
        throws PortInUseException, UnsupportedCommOperationException, TooManyListenersException {

        startReceiveQueueWorker();

        // open the port
        SerialPort serialPort = commPort.open(DebugReader.class.getName(), 2000);

        getConnectionListener().opened(commPort.getName());

        LOGGER.info("Set flow control mode to SerialPort.FLOWCONTROL_NONE!");
        serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);

        // serialPort.enableReceiveTimeout(DEFAULT_TIMEOUT);

        serialPort.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);

        clearInputStream(serialPort);

        // enable the message receiver before the event listener is added
        getMessageReceiver().enable();
        serialPort.addEventListener(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:

                        try {
                            receive(port);
                        }
                        catch (Exception ex) {
                            LOGGER.warn("Process received bytes failed.", ex);
                        }

                        break;
                    case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
                        LOGGER.info("The output buffer is empty.");

                        if (MSG_RAW_LOGGER.isInfoEnabled()) {
                            MSG_RAW_LOGGER.info(">> Notify output empty");
                        }

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

                        if (event.getNewValue() == false) {
                            LOGGER.warn("CTS has changed to false.");
                        }
                        break;
                    case SerialPortEvent.HARDWARE_ERROR:
                        LOGGER.warn("HARDWARE_ERROR is signalled.");
                        if (event.getNewValue() == true) {
                            LOGGER.warn("Close the port.");
                            Thread worker = new Thread(() -> {

                                LOGGER.info("Start close port because hardware 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;
                    default:
                        LOGGER
                            .warn("SerialPortEvent was triggered, type: {}, old value: {}, new value: {}",
                                new Object[] { event.getEventType(), event.getOldValue(), event.getNewValue() });
                        break;
                }
            }
        });
        serialPort.notifyOnDataAvailable(true);
        // react on port removed ...
        serialPort.notifyOnCTS(true);
        serialPort.notifyOnCarrierDetect(true);
        serialPort.notifyOnBreakInterrupt(true);
        serialPort.notifyOnDSR(true);
        serialPort.notifyOnOverrunError(true);

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

        return serialPort;
    }

    private void clearInputStream(final SerialPort serialPort) {

        // get and clear stream

        try {
            InputStream serialStream = serialPort.getInputStream();
            // purge contents, if any
            int count = serialStream.available();
            LOGGER.debug("input stream shows {} bytes available", count);
            while (count > 0) {
                serialStream.skip(count);
                count = serialStream.available();
            }
            LOGGER.debug("input stream shows {} bytes available after purge.", count);
        }
        catch (Exception e) {
            LOGGER.warn("Clear input stream failed.", e);
        }

    }

    @Override
    public void close() {
        if (port != null) {
            LOGGER.debug("Close the port.");
            long start = System.currentTimeMillis();

            // Deactivate RTS
            try {
                LOGGER.info("Deactivate RTS.");
                port.setRTS(false);

                Thread.sleep(100);
            }
            catch (Exception e) {
                LOGGER.warn("Set RTS to false failed.", e);
            }

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

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

            port = null;

            // no longer process received messages
            getMessageReceiver().disable();

            stopReceiveQueueWorker();

            if (connectionListener != null) {
                connectionListener.closed(getRequestedPortName());
            }

            setRequestedPortName(null);
        }
    }

    @Override
    public boolean isOpened() {
        boolean isOpened = false;
        try {
            portSemaphore.acquire();

            LOGGER.debug("Check if port is opened: {}", port);
            isOpened = (port != null && port.getOutputStream() != null);
        }
        catch (InterruptedException ex) {
            LOGGER.warn("Wait for portSemaphore was interrupted.", ex);
        }
        catch (IOException ex) {
            LOGGER.warn("OutputStream is not available.", ex);
        }
        finally {
            portSemaphore.release();
        }
        return isOpened;
    }

    @Override
    public void open(String portName, int baudRate, ConnectionListener connectionListener, Context context)
        throws PortNotFoundException, PortNotOpenedException {

        setConnectionListener(connectionListener);

        if (port == null) {
            if (portName == null || portName.trim().isEmpty()) {
                throw new PortNotFoundException("");
            }

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

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

            LOGGER.info("Set the requestedPortName: {}, baudRate: {}", portName, baudRate);
            setRequestedPortName(portName);

            try {
                portSemaphore.acquire();

                try {
                    // try to open the port
                    close();
                    port = internalOpen(commPort, baudRate, context);
                    LOGGER.info("The port was opened internally.");
                }
                catch (PortInUseException ex) {
                    LOGGER.warn("Open communication failed because port is in use.", ex);
                    try {
                        close();
                    }
                    catch (Exception e4) {
                        // ignore
                    }
                    throw new PortNotOpenedException(portName, PortNotOpenedException.PORT_IN_USE);
                }
                catch (TooManyListenersException | UnsupportedCommOperationException ex) {
                    LOGGER.warn("Open communication failed because port has thrown an exception.", ex);
                    try {
                        close();
                    }
                    catch (Exception e4) {
                        // ignore
                    }
                    throw new PortNotOpenedException(portName, PortNotOpenedException.UNKNOWN);
                }
            }
            catch (InterruptedException ex) {
                LOGGER.warn("Wait for portSemaphore was interrupted.", ex);
                throw new PortNotOpenedException(portName, PortNotOpenedException.UNKNOWN);
            }
            finally {
                portSemaphore.release();
            }
        }
        else {
            LOGGER.warn("Port is already opened.");
        }
    }

    /**
     * Send the bytes of the message to the outputstream and add <CR>+<LF>.
     * 
     * @param bytes
     *            the bytes to send
     */
    @Override
    public void send(final String message, LineEndingEnum lineEnding) {
        if (port != null) {
            try {
                sendSemaphore.acquire();

                if (MSG_RAW_LOGGER.isInfoEnabled()) {
                    MSG_RAW_LOGGER.info(">> '{}'", message);
                }

                OutputStream output = port.getOutputStream();

                output.write(message.getBytes());
                output.write(lineEnding.getValues());

                // output.flush();
            }
            catch (Exception e) {
                throw new RuntimeException("Send message to output stream failed.", e);
            }
            finally {
                sendSemaphore.release();
            }
        }
    }

    @Override
    public void send(byte[] content) {
        if (port != null) {
            try {
                sendSemaphore.acquire();

                if (MSG_RAW_LOGGER.isInfoEnabled()) {
                    MSG_RAW_LOGGER.info(">> [{}] - {}", content.length, ByteUtils.bytesToHex(content));
                }

                OutputStream output = port.getOutputStream();

                output.write(content);
                // output.flush();
            }
            catch (Exception e) {
                throw new RuntimeException("Send message to output stream failed.", e);
            }
            finally {
                sendSemaphore.release();
            }
        }
    }

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

    private ByteArrayOutputStream output = new ByteArrayOutputStream();

    private void receive(final SerialPort port) {

        LOGGER.debug("Start receiving messages.");

        synchronized (this) {
            LOGGER.debug("Starting message receiver.");
            try {
                InputStream input = null;

                if (port != null) {
                    input = port.getInputStream();
                }
                if (input != null) {

                    // read the values from in the port
                    int len = input.read(inputBuffer);
                    if (len > 0) {
                        output.write(inputBuffer, 0, len);

                        if (MSG_RAW_LOGGER.isInfoEnabled()) {
                            MSG_RAW_LOGGER
                                .info("<<<< len: {}, data: {}", output.size(),
                                    ByteUtils.bytesToHex(output.toByteArray()));
                        }

                        addDataToReceiveQueue(output);

                        if (output != null && output.size() > 0) {

                            LOGGER.warn("Data in output: {}", output.toString());
                        }
                    }
                }
                else {
                    LOGGER.error("No input available.");
                }

            }
            catch (Exception e) {
                LOGGER.warn("Exception detected in message receiver!", e);
                throw new RuntimeException(e);
            }
        }
    }

    // @Override
    // private void addDataToReceiveQueue(ByteArrayOutputStream output) {
    //
    // byte[] bytes = output.toByteArray();
    //
    // byte[] buffer = new byte[bytes.length];
    // System.arraycopy(bytes, 0, buffer, 0, bytes.length);
    //
    // if (MSG_RAW_LOGGER.isInfoEnabled()) {
    // MSG_RAW_LOGGER.info("<<<< len: {}, data: {}", bytes.length, ByteUtils.bytesToHex(buffer));
    // }
    //
    // boolean added = receiveQueue.offer(buffer);
    // if (!added) {
    // LOGGER.error("The message was not added to the receive queue: {}", ByteUtils.bytesToHex(buffer));
    // }
    //
    // output.reset();
    // }
    //
    // private void startReceiveQueueWorker() {
    // receiverRunning.set(true);
    //
    // LOGGER.info("Start the receiveQueueWorker. Current receiveQueueWorker: {}", receiveQueueWorker);
    // receiveQueueWorker = new Thread(new Runnable() {
    //
    // @Override
    // public void run() {
    // try {
    // processReceiveQueue();
    // }
    // catch (Exception ex) {
    // LOGGER.warn("The processing of the receive queue was terminated with an exception!", ex);
    //
    // // running.set(false);
    // }
    //
    // LOGGER.info("Process receive queue has finished.");
    // }
    // }, "receiveQueueWorker");
    //
    // try {
    // receiveQueueWorkerThreadId.set(receiveQueueWorker.getId());
    // receiveQueueWorker.start();
    // }
    // catch (Exception ex) {
    // LOGGER.error("Start the receiveQueueWorker failed.", ex);
    // }
    // }
    //
    // @Override
    // private void stopReceiveQueueWorker() {
    // LOGGER.info("Stop the receive queue worker.");
    // receiverRunning.set(false);
    //
    // try {
    // receiveQueueWorker.interrupt();
    //
    // receiveQueueWorker.join(1000);
    //
    // LOGGER.info("receiveQueueWorker has finished.");
    // }
    // catch (Exception ex) {
    // LOGGER.warn("Interrupt receiveQueueWorker failed.", ex);
    // }
    // receiveQueueWorker = null;
    // }
    //
    // private void processReceiveQueue() {
    // byte[] bytes = null;
    // LOGGER.info("The receiveQueueWorker is ready for processing.");
    //
    // // final DebugMessageProcessor serialMessageReceiver = getMessageReceiver();
    // while (receiverRunning.get()) {
    // try {
    // // get the message to process
    // bytes = receiveQueue.take();
    //
    // if (bytes != null) {
    // // process
    // try {
    //
    // ByteArrayOutputStream output = new ByteArrayOutputStream();
    // output.write(bytes);
    //
    // getMessageReceiver().processMessages(output);
    // }
    // catch (Exception ex) {
    // LOGGER.warn("Process received bytes failed.", ex);
    // }
    //
    // }
    // }
    // catch (InterruptedException ex) {
    // LOGGER.warn("Get message from receiveQueue failed because thread was interrupted.");
    // }
    // catch (Exception ex) {
    // LOGGER.warn("Get message from receiveQueue failed.", ex);
    // bytes = null;
    // }
    //
    // }
    //
    // LOGGER.info("The receiveQueueWorker has finished processing.");
    // receiveQueueWorkerThreadId.set(0);
    // }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy