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