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

org.bidib.jbidibc.scm.ScmSerialConnector Maven / Gradle / Ivy

There is a newer version: 2.0.29
Show 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);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy