001package jmri.jmrix.serialsensor;
002
003import java.io.DataInputStream;
004import java.io.DataOutputStream;
005import java.io.IOException;
006import java.io.InputStream;
007import java.util.Comparator;
008import java.util.ResourceBundle;
009import java.util.TooManyListenersException;
010import jmri.InstanceManager;
011import jmri.JmriException;
012import jmri.NamedBean;
013import jmri.Sensor;
014import jmri.jmrix.AbstractSerialPortController;
015import jmri.SystemConnectionMemo;
016import jmri.jmrix.DefaultSystemConnectionMemo;
017
018import org.slf4j.Logger;
019import org.slf4j.LoggerFactory;
020import purejavacomm.CommPortIdentifier;
021import purejavacomm.NoSuchPortException;
022import purejavacomm.PortInUseException;
023import purejavacomm.SerialPort;
024import purejavacomm.SerialPortEvent;
025import purejavacomm.SerialPortEventListener;
026import purejavacomm.UnsupportedCommOperationException;
027
028/**
029 * Implements SerialPortAdapter for connecting to two sensors via the serial
030 * port. Sensor "1" will be via DCD, and sensor "2" via DSR
031 *
032 * @author Bob Jacobsen Copyright (C) 2003
033 */
034public class SerialSensorAdapter extends AbstractSerialPortController {
035
036    SerialPort activeSerialPort = null;
037
038    public SerialSensorAdapter() {
039        super(new DefaultSystemConnectionMemo("S", Bundle.getMessage("TypeSerial")) {
040
041            @Override
042            protected ResourceBundle getActionModelResourceBundle() {
043                return null;
044            }
045
046            @Override
047            public <B extends NamedBean> Comparator<B> getNamedBeanComparator(Class<B> type) {
048                return (o1, o2) -> o1.getSystemName().compareTo(o2.getSystemName());
049            }
050        });
051    }
052
053    @Override
054    public void configure() {
055        log.debug("Configure doesn't do anything here");
056    }
057
058    @Override
059    public String openPort(String portName, String appName) {
060        // open the port, check ability to set moderators
061        try {
062            // get and open the primary port
063            CommPortIdentifier portID = CommPortIdentifier.getPortIdentifier(portName);
064            try {
065                activeSerialPort = (SerialPort) portID.open(appName, 2000);  // name of program, msec to wait
066            } catch (PortInUseException p) {
067                return handlePortBusy(p, portName, log);
068            }
069
070            // try to set it for communication via SerialDriver
071            try {
072                activeSerialPort.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
073            } catch (UnsupportedCommOperationException e) {
074                log.error("Cannot set serial parameters on port {}: {}", portName, e.getMessage());
075                return "Cannot set serial parameters on port " + portName + ": " + e.getMessage();
076            }
077
078            // disable flow control; hardware lines used for signaling, XON/XOFF might appear in data
079            // set RTS active low, DTR inactive high
080            configureLeadsAndFlowControl(activeSerialPort, 0, true, false);
081
082            // set timeout
083            // activeSerialPort.enableReceiveTimeout(1000);
084            log.debug("Serial timeout was observed as: {} {}", activeSerialPort.getReceiveTimeout(),
085                                    activeSerialPort.isReceiveTimeoutEnabled());
086
087            // arrange to notify of sensor changes
088            activeSerialPort.addEventListener(new SerialPortEventListener() {
089                @Override
090                public void serialEvent(SerialPortEvent e) {
091                    int type = e.getEventType();
092                    switch (type) {
093                        case SerialPortEvent.DSR:
094                            log.info("SerialEvent: DSR is {}", e.getNewValue());
095                            notify("1", e.getNewValue());
096                            return;
097                        case SerialPortEvent.CD:
098                            log.info("SerialEvent: CD is {}", e.getNewValue());
099                            notify("2", e.getNewValue());
100                            return;
101                        case SerialPortEvent.CTS:
102                            log.info("SerialEvent: CTS is {}", e.getNewValue());
103                            notify("3", e.getNewValue());
104                            return;
105                        default:
106                            if (log.isDebugEnabled()) {
107                                log.debug("SerialEvent of type: {} value: {}", type, e.getNewValue());
108                            }
109                            return;
110                    }
111                }
112
113                /**
114                 * Do a sensor change on the event queue
115                 */
116                public void notify(String sensor, boolean value) {
117                    javax.swing.SwingUtilities.invokeLater(new SerialNotifier(sensor, value));
118                }
119            });
120            // turn on notification
121            activeSerialPort.notifyOnCTS(true);
122            activeSerialPort.notifyOnDSR(true);
123            activeSerialPort.notifyOnCarrierDetect(true);
124
125            // get and save stream
126            serialStream = activeSerialPort.getInputStream();
127
128            // purge contents, if any
129            purgeStream(serialStream);
130
131            // report status?
132            if (log.isInfoEnabled()) {
133                log.info("{} port opened at {} baud, sees  DTR: {} RTS: {} DSR: {} CTS: {}  CD: {}", portName, activeSerialPort.getBaudRate(), activeSerialPort.isDTR(), activeSerialPort.isRTS(), activeSerialPort.isDSR(), activeSerialPort.isCTS(), activeSerialPort.isCD());
134            }
135
136            opened = true;
137
138        } catch (NoSuchPortException ex1) {
139            log.error("No such port {} ", portName, ex1);
140            return "No such port " + portName + ": " + ex1;
141        } catch (TooManyListenersException ex3) {
142            log.error("Too Many Listeners on port {} ", portName, ex3);
143            return "Too Many Listeners on port " + portName + ": " + ex3;
144        } catch (IOException ex4) {
145            log.error("I/O error on port {} ", portName, ex4);
146            return "I/O error on port " + portName + ": " + ex4;
147        }
148
149        return null; // indicates OK return
150    }
151
152    @Override
153    public DataInputStream getInputStream() {
154        if (!opened) {
155            log.error("getInputStream called before load(), stream not available");
156            return null;
157        }
158        return new DataInputStream(serialStream);
159    }
160
161    @Override
162    public DataOutputStream getOutputStream() {
163        if (!opened) {
164            log.error("getOutputStream called before load(), stream not available");
165        }
166        try {
167            return new DataOutputStream(activeSerialPort.getOutputStream());
168        } catch (java.io.IOException e) {
169            log.error("getOutputStream exception: ", e);
170        }
171        return null;
172    }
173
174    @Override
175    public boolean status() {
176        return opened;
177    }
178
179    /**
180     * {@inheritDoc}
181     * Currently only 19,200 bps.
182     */
183    @Override
184    public String[] validBaudRates() {
185        return new String[]{"9,600 bps"};
186    }
187
188    /**
189     * {@inheritDoc}
190     */
191    @Override
192    public int[] validBaudNumbers() {
193        return new int[]{9600};
194    }
195
196    @Override
197    public int defaultBaudIndex() {
198        return 0;
199    }
200
201    /**
202     * {@inheritDoc}
203     * This currently does nothing, as there's only one
204     * possible value.
205     */
206    @Override
207    public void configureBaudRate(String rate) {
208    }
209
210    // private control members
211    private boolean opened = false;
212    InputStream serialStream = null;
213
214    /**
215     * Do a sensor change on the event queue.
216     * @param sensor sensor
217     * @param value true if sensor changes on, else false.
218     */
219    public void notify(String sensor, boolean value) {
220    }
221
222    /**
223     * Internal class to remember the Message object and destination listener
224     * when a message is queued for notification.
225     */
226    static class SerialNotifier implements Runnable {
227
228        String mSensor;
229        boolean mValue;
230
231        SerialNotifier(String pSensor, boolean pValue) {
232            mSensor = pSensor;
233            mValue = pValue;
234        }
235
236        @Override
237        public void run() {
238            log.debug("serial sensor notify starts");
239            int value = Sensor.INACTIVE;
240            if (mValue) {
241                value = Sensor.ACTIVE;
242            }
243            try {
244                InstanceManager.sensorManagerInstance().provideSensor(mSensor)
245                        .setKnownState(value);
246            } catch (JmriException | IllegalArgumentException e) {
247                log.error("Exception setting state: ", e);
248            }
249        }
250    }
251
252    private final static Logger log = LoggerFactory.getLogger(SerialSensorAdapter.class);
253
254}