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;
010
011import jmri.InstanceManager;
012import jmri.JmriException;
013import jmri.NamedBean;
014import jmri.Sensor;
015import jmri.jmrix.AbstractSerialPortController;
016import jmri.jmrix.DefaultSystemConnectionMemo;
017
018import org.slf4j.Logger;
019import org.slf4j.LoggerFactory;
020
021import purejavacomm.CommPortIdentifier;
022import purejavacomm.NoSuchPortException;
023import purejavacomm.PortInUseException;
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    purejavacomm.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 = (purejavacomm.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,
072                        purejavacomm.SerialPort.DATABITS_8,
073                        purejavacomm.SerialPort.STOPBITS_1,
074                        purejavacomm.SerialPort.PARITY_NONE);
075            } catch (UnsupportedCommOperationException e) {
076                log.error("Cannot set serial parameters on port {}: {}", portName, e.getMessage());
077                return "Cannot set serial parameters on port " + portName + ": " + e.getMessage();
078            }
079
080            // disable flow control; hardware lines used for signaling, XON/XOFF might appear in data
081            // set RTS active low, DTR inactive high
082            configureLeadsAndFlowControl(activeSerialPort, 0, true, false);
083
084            // set timeout
085            // activeSerialPort.enableReceiveTimeout(1000);
086            log.debug("Serial timeout was observed as: {} {}", activeSerialPort.getReceiveTimeout(),
087                                    activeSerialPort.isReceiveTimeoutEnabled());
088
089            // arrange to notify of sensor changes
090            activeSerialPort.addEventListener(new SerialPortEventListener() {
091                @Override
092                public void serialEvent(purejavacomm.SerialPortEvent e) {
093                    int type = e.getEventType();
094                    switch (type) {
095                        case purejavacomm.SerialPortEvent.DSR:
096                            log.info("SerialEvent: DSR is {}", e.getNewValue());
097                            notify("1", e.getNewValue());
098                            return;
099                        case purejavacomm.SerialPortEvent.CD:
100                            log.info("SerialEvent: CD is {}", e.getNewValue());
101                            notify("2", e.getNewValue());
102                            return;
103                        case purejavacomm.SerialPortEvent.CTS:
104                            log.info("SerialEvent: CTS is {}", e.getNewValue());
105                            notify("3", e.getNewValue());
106                            return;
107                        default:
108                            if (log.isDebugEnabled()) {
109                                log.debug("SerialEvent of type: {} value: {}", type, e.getNewValue());
110                            }
111                            return;
112                    }
113                }
114
115                /**
116                 * Do a sensor change on the event queue
117                 */
118                public void notify(String sensor, boolean value) {
119                    javax.swing.SwingUtilities.invokeLater(new SerialNotifier(sensor, value));
120                }
121            });
122            // turn on notification
123            activeSerialPort.notifyOnCTS(true);
124            activeSerialPort.notifyOnDSR(true);
125            activeSerialPort.notifyOnCarrierDetect(true);
126
127            // get and save stream
128            serialStream = activeSerialPort.getInputStream();
129
130            // purge contents, if any
131            purgeStream(serialStream);
132
133            // report status?
134            if (log.isInfoEnabled()) {
135                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());
136            }
137
138            opened = true;
139
140        } catch (NoSuchPortException ex1) {
141            log.error("No such port {} ", portName, ex1);
142            return "No such port " + portName + ": " + ex1;
143        } catch (TooManyListenersException ex3) {
144            log.error("Too Many Listeners on port {} ", portName, ex3);
145            return "Too Many Listeners on port " + portName + ": " + ex3;
146        } catch (IOException ex4) {
147            log.error("I/O error on port {} ", portName, ex4);
148            return "I/O error on port " + portName + ": " + ex4;
149        }
150
151        return null; // indicates OK return
152    }
153
154    @Override
155    public DataInputStream getInputStream() {
156        if (!opened) {
157            log.error("getInputStream called before load(), stream not available");
158            return null;
159        }
160        return new DataInputStream(serialStream);
161    }
162
163    @Override
164    public DataOutputStream getOutputStream() {
165        if (!opened) {
166            log.error("getOutputStream called before load(), stream not available");
167        }
168        try {
169            return new DataOutputStream(activeSerialPort.getOutputStream());
170        } catch (java.io.IOException e) {
171            log.error("getOutputStream exception: ", e);
172        }
173        return null;
174    }
175
176    @Override
177    public boolean status() {
178        return opened;
179    }
180
181    /**
182     * {@inheritDoc}
183     * Currently only 19,200 bps.
184     */
185    @Override
186    public String[] validBaudRates() {
187        return new String[]{"9,600 bps"};
188    }
189
190    /**
191     * {@inheritDoc}
192     */
193    @Override
194    public int[] validBaudNumbers() {
195        return new int[]{9600};
196    }
197
198    @Override
199    public int defaultBaudIndex() {
200        return 0;
201    }
202
203    // private control members
204    private boolean opened = false;
205    InputStream serialStream = null;
206
207    /**
208     * Do a sensor change on the event queue.
209     * @param sensor sensor
210     * @param value true if sensor changes on, else false.
211     */
212    public void notify(String sensor, boolean value) {
213    }
214
215    /**
216     * Internal class to remember the Message object and destination listener
217     * when a message is queued for notification.
218     */
219    static class SerialNotifier implements Runnable {
220
221        String mSensor;
222        boolean mValue;
223
224        SerialNotifier(String pSensor, boolean pValue) {
225            mSensor = pSensor;
226            mValue = pValue;
227        }
228
229        @Override
230        public void run() {
231            log.debug("serial sensor notify starts");
232            int value = Sensor.INACTIVE;
233            if (mValue) {
234                value = Sensor.ACTIVE;
235            }
236            try {
237                InstanceManager.sensorManagerInstance().provideSensor(mSensor)
238                        .setKnownState(value);
239            } catch (JmriException | IllegalArgumentException e) {
240                log.error("Exception setting state: ", e);
241            }
242        }
243    }
244
245    private final static Logger log = LoggerFactory.getLogger(SerialSensorAdapter.class);
246
247}