001package jmri.jmrix.can.adapters.gridconnect; 002 003import java.io.DataInputStream; 004import java.io.FilterInputStream; 005import java.io.IOException; 006import java.io.InputStream; 007import java.util.concurrent.BlockingQueue; 008import java.util.concurrent.LinkedBlockingQueue; 009 010import jmri.jmrix.ConnectionStatus; 011import jmri.jmrix.can.TrafficController; 012 013/** 014 * Implements SerialPortAdapter for the GridConnect protocol. 015 * 016 * @author Bob Jacobsen Copyright (C) 2001, 2002 017 * @author Andrew Crosland Copyright (C) 2008 018 * @author Balazs Racz Copyright (C) 2017 019 */ 020public class GcSerialDriverAdapter extends GcPortController { 021 022 protected FlowControl flowControl = FlowControl.NONE; // disabled to start 023 024 /** 025 * Creates a new CAN GridConnect Network Driver Adapter. 026 */ 027 public GcSerialDriverAdapter() { 028 super(new jmri.jmrix.can.CanSystemConnectionMemo()); 029 option1Name = "Protocol"; // NOI18N 030 options.put(option1Name, new Option(Bundle.getMessage("ConnectionProtocol"), 031 jmri.jmrix.can.ConfigurationManager.getSystemOptions())); 032 this.manufacturerName = jmri.jmrix.merg.MergConnectionTypeList.MERG; 033 allowConnectionRecovery = true; 034 } 035 036 /** 037 * Creates a new CAN GridConnect Network Driver Adapter. 038 * <p> 039 * Allows for default systemPrefix other than "M". 040 * @param prefix System Prefix. 041 */ 042 public GcSerialDriverAdapter(String prefix) { 043 super(new jmri.jmrix.can.CanSystemConnectionMemo(prefix)); 044 option1Name = "Protocol"; // NOI18N 045 options.put(option1Name, new Option(Bundle.getMessage("ConnectionProtocol"), 046 jmri.jmrix.can.ConfigurationManager.getSystemOptions())); 047 this.manufacturerName = jmri.jmrix.merg.MergConnectionTypeList.MERG; 048 allowConnectionRecovery = true; 049 } 050 051 /** 052 * Creates a new CAN GridConnect Network Driver Adapter. 053 * <p> 054 * Allows for default systemPrefix other than "M". 055 * @param prefix System Prefix. 056 * @param flow flow control, true for RTS/CTS 057 */ 058 public GcSerialDriverAdapter(String prefix, FlowControl flow) { 059 super(new jmri.jmrix.can.CanSystemConnectionMemo(prefix)); 060 option1Name = "Protocol"; // NOI18N 061 options.put(option1Name, new Option(Bundle.getMessage("ConnectionProtocol"), 062 jmri.jmrix.can.ConfigurationManager.getSystemOptions())); 063 this.manufacturerName = jmri.jmrix.merg.MergConnectionTypeList.MERG; 064 allowConnectionRecovery = true; 065 flowControl = flow; 066 } 067 068 /** 069 * {@inheritDoc} 070 */ 071 @Override 072 public String openPort(String portName, String appName) { 073 074 // get and open the primary port 075 currentSerialPort = activatePort(portName, log); 076 if (currentSerialPort == null) { 077 log.error("failed to connect CAN to {}", portName); 078 return Bundle.getMessage("SerialPortNotFound", portName); 079 } 080 log.info("Connecting CAN to {} {}", portName, currentSerialPort); 081 082 // try to set it for communication via SerialDriver 083 // find the baud rate value, configure comm options 084 int baud = currentBaudNumber(mBaudRate); 085 setBaudRate(currentSerialPort, baud); 086 configureLeads(currentSerialPort, true, true); 087 localSetFlowControl(); 088 089 // get and save stream 090 serialStream = currentSerialPort.getInputStream(); 091 // this is referenced in several other methods, 092 // so can't easily be removed. 093 094 // report status 095 reportPortStatus(log, portName); 096 097 opened = true; 098 099 return null; // indicates OK return 100 } 101 102 /** 103 * Local set up the flow contro, here to allow override 104 */ 105 protected void localSetFlowControl() { 106 setFlowControl(currentSerialPort, flowControl); 107 } 108 109 /** 110 * Set up all of the other objects to operate with a CAN RS adapter 111 * connected to this port. 112 * {@inheritDoc} 113 */ 114 @Override 115 public void configure() { 116 // Register the CAN traffic controller being used for this connection 117 //GcTrafficController.instance(); 118 TrafficController tc = makeGcTrafficController(); 119 this.getSystemConnectionMemo().setTrafficController(tc); 120 121 // Now connect to the traffic controller 122 log.debug("Connecting port"); 123 tc.connectPort(this); 124 125 this.getSystemConnectionMemo().setProtocol(getOptionState(option1Name)); 126 127 // do central protocol-specific configuration 128 //jmri.jmrix.can.ConfigurationManager.configure(getOptionState(option1Name)); 129 this.getSystemConnectionMemo().configureManagers(); 130 } 131 132 /** 133 * {@inheritDoc} 134 * Reconnects to Traffic Controller. 135 * Updates connection status. 136 */ 137 @Override 138 protected void resetupConnection() { 139 if (!getSystemConnectionMemo().getTrafficController().status()) { 140 getSystemConnectionMemo().getTrafficController().connectPort(this); 141 ConnectionStatus.instance().setConnectionState(getSystemConnectionMemo(), 142 ((getSystemConnectionMemo().getTrafficController().status() && status()) ? ConnectionStatus.CONNECTION_UP : ConnectionStatus.CONNECTION_DOWN)); 143 } 144 } 145 146 /** 147 * {@inheritDoc} 148 * 149 * Closes serial streams. 150 */ 151 @Override 152 protected void closeConnection(){ 153 log.info("Closing connection {}.",getCurrentPortName()); 154 try { 155 if (serialStream!=null) { 156 serialStream.close(); 157 } 158 serialStream = null; 159 if (bufferedStream!=null) { 160 bufferedStream.close(); 161 } 162 bufferedStream = null; 163 } 164 catch ( IOException e ) { 165 log.error("unable to close {}",this.currentSerialPort); 166 } 167 if (currentSerialPort!=null) { 168 currentSerialPort.closePort(); 169 } 170 currentSerialPort = null; 171 } 172 173 /** 174 * Make a new GC Traffic Controller. 175 * @return new GridConnect Traffic Controller. 176 */ 177 protected GcTrafficController makeGcTrafficController() { 178 return new GcTrafficController(); 179 } 180 181 /** 182 * Helper class wrapping the input serial port's InputStream. 183 * <p> 184 * It starts a helper thread at high priority that reads the input serial 185 * port as fast as it can, buffering all incoming data in memory in a queue. 186 * <p> 187 * The queue is unbounded and readers will get the data from the queue. 188 * <p> 189 * This class is thread-safe. 190 */ 191 private static class AsyncBufferInputStream extends FilterInputStream { 192 193 // written by close() on another thread 194 private volatile boolean active; 195 196 /** 197 * Create new AsyncBufferInputStream. 198 * @param inputStream Input Stream. 199 * @param portName Port Name. 200 */ 201 AsyncBufferInputStream(InputStream inputStream, String portName) { 202 super(inputStream); 203 this.portName = portName; 204 active = true; 205 Thread rt = jmri.util.ThreadingUtil.newThread(this::readThreadBody); 206 rt.setName("GcSerialPort InputBufferThread " + portName); 207 rt.setDaemon(true); 208 rt.setPriority(Thread.MAX_PRIORITY); 209 rt.start(); 210 } 211 212 /** 213 * Helper function that tries to perform a read from the underlying port 214 * with a given maximum length. 215 * 216 * @param maxLen how many bytes to request from the port. Setting this to 1 217 * will apparently block the thread if there are zero bytes 218 * available. 219 * @return a block of data read, or nullptr if fatal IO errors make 220 * further use of this port impossible. 221 */ 222 private BufferEntry tryRead(int maxLen) { 223 BufferEntry tail = new BufferEntry(); 224 try { 225 // read what's available, up to maxLen, but always at least 1 226 int len = Math.max (1, Math.min(maxLen, in.available())); 227 tail.data = new byte[len]; 228 tail.len = in.read(tail.data, 0, len); 229 errorCount = 0; 230 } catch (IOException e) { 231 tail.e = e; 232 if (++errorCount > MAX_IO_ERRORS_TO_ABORT) { 233 log.error("Closing read thread due to too many IO errors", e); 234 return null; 235 } else { 236 log.debug("Error reading serial port {}", portName, e); 237 } 238 } 239 return tail; 240 } 241 242 /** 243 * Implementation of the buffering thread. 244 */ 245 private void readThreadBody() { 246 BufferEntry tail; 247 while (active) { 248 // Try to read one byte to block the thread. 249 tail = tryRead(1); 250 if (tail == null) { 251 return; 252 } 253 // End of stream. jSerialComm signals a removed USB serial port 254 // (eg. a CANUSB4 unplugged on Windows) by read() returning -1 255 // rather than by throwing. Retrying would spin this maximum 256 // priority thread on a whole core forever, and the reader 257 // would never find out. Hand the end of stream on to the 258 // reader, so the traffic controller sees the connection fail 259 // and starts recovery, and stop this thread. 260 if (tail.len < 0) { 261 readAhead.add(tail); 262 return; 263 } 264 if (tail.len > 0 || tail.e != null) { 265 readAhead.add(tail); 266 } else { 267 continue; 268 } 269 // Read as many bytes as we have in large increments. Reading 128 bytes is a good 270 // compromise between throughput (4 gridconnect packets per kernel IO) but not 271 // wasting a lot of memory if less data actually shows up. 272 do { 273 tail = tryRead(128); 274 if (tail == null) { 275 return; 276 } 277 if (tail.len < 0) { 278 readAhead.add(tail); // end of stream, see above 279 return; 280 } 281 if (tail.len > 0 || tail.e != null) { 282 readAhead.add(tail); 283 } else { 284 break; 285 } 286 } while (true); 287 } 288 } 289 290 /** 291 * We queue objects of this class between the read thread and the actual 292 * read() methods. 293 */ 294 private static class BufferEntry { 295 296 // data payload 297 byte[] data; 298 // how many bytes of data are filled in 299 int len = 0; 300 // an exception was caught reading the input stream 301 IOException e = null; 302 } 303 304 /** 305 * {@inheritDoc} 306 */ 307 @Override 308 public int read() throws IOException { 309 throw new UnsupportedOperationException(); 310 } 311 312 /** 313 * {@inheritDoc} 314 */ 315 @Override 316 public int read(byte[] bytes) throws IOException { 317 throw new UnsupportedOperationException(); 318 } 319 320 /** 321 * {@inheritDoc} 322 */ 323 @Override 324 public synchronized int read(byte[] bytes, int skip, int len) throws IOException { 325 if (skip != 0) { 326 throw new UnsupportedOperationException(); 327 } 328 if (head != null && head.len < 0) { 329 return -1; // end of stream already reached, the read thread has stopped 330 } 331 if (head == null || headOfs >= head.len) { 332 try { 333 head = readAhead.take(); 334 } catch (InterruptedException e) { 335 // Interrupted while waiting, eg. connection closing. 336 // Must not fall through, as head is either null or the 337 // previous, already returned, entry, which would be replayed. 338 Thread.currentThread().interrupt(); 339 throw new java.io.InterruptedIOException("Interrupted waiting for data from " + portName); 340 } 341 if (head.e != null) { 342 throw head.e; 343 } 344 headOfs = 0; 345 if (head.len < 0) { 346 return -1; 347 } 348 } 349 int cp = head.len - headOfs; 350 if (cp > len) { 351 cp = len; 352 } 353 System.arraycopy(head.data, headOfs, bytes, 0, cp); 354 headOfs += cp; 355 return cp; 356 } 357 358 private final String portName; 359 // After this many consecutive read attempts resulting in an exception we will terminate 360 // the read thread and return the last exception to the reader. 361 private static final int MAX_IO_ERRORS_TO_ABORT = 10; 362 // Queue holding the buffered data. 363 private final BlockingQueue<BufferEntry> readAhead = new LinkedBlockingQueue<>(); 364 // The last entry we got from the queue if there are still bytes we need to return from it. 365 BufferEntry head = null; 366 // Offset of next live byte in head. 367 int headOfs = 0; 368 // How many of the last consecutive read attempts have resulted in an exception. 369 int errorCount = 0; 370 371 @Override 372 public void close() throws IOException { 373 active = false; 374 super.close(); 375 } 376 } 377 378 /** 379 * Base class methods for the PortController interface. 380 * {@inheritDoc} 381 */ 382 @Override 383 public DataInputStream getInputStream() { 384 if (!opened) { 385 log.error("getInputStream called before load(), stream not available"); 386 return null; 387 } 388 synchronized (this) { 389 if (bufferedStream == null) { 390 bufferedStream = new AsyncBufferInputStream(serialStream, currentSerialPort.toString()); 391 } 392 return new DataInputStream(bufferedStream); 393 } 394 } 395 396 /** 397 * {@inheritDoc} 398 * 399 * @return array of localized valid baud rates 400 */ 401 @Override 402 public String[] validBaudRates() { 403 return new String[]{Bundle.getMessage("Baud57600"), 404 Bundle.getMessage("Baud115200"), Bundle.getMessage("Baud230400"), 405 Bundle.getMessage("Baud250000"), Bundle.getMessage("Baud333333"), 406 Bundle.getMessage("Baud460800")}; 407 } 408 409 /** 410 * Get an array of valid baud rates. 411 * 412 * @return valid baud rates 413 */ 414 @Override 415 public int[] validBaudNumbers() { 416 return new int[]{57600, 115200, 230400, 250000, 333333, 460800}; 417 } 418 419 /** 420 * {@inheritDoc} 421 */ 422 @Override 423 public int defaultBaudIndex() { 424 return 0; 425 } 426 427 // private control members 428 InputStream serialStream = null; 429 // Stream wrapper that buffers the input bytes. 430 private InputStream bufferedStream = null; 431 432 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GcSerialDriverAdapter.class); 433 434}