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}