001package jmri.jmrix.bachrus; 002 003import java.io.DataInputStream; 004import java.io.OutputStream; 005import java.util.Vector; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008import jmri.jmrix.purejavacomm.SerialPortEvent; 009import jmri.jmrix.purejavacomm.SerialPortEventListener; 010 011/** 012 * Converts Stream-based I/O to/from Speedo messages. The "SpeedoInterface" side 013 * sends/receives message objects. The connection to a SpeedoPortController is 014 * via a pair of *Streams, which then carry sequences of characters for 015 * transmission. Note that this processing is handled in an independent thread. 016 * <p> 017 * Removed Runnable implementation and methods for it. 018 * 019 * @author Bob Jacobsen Copyright (C) 2001 020 * @author Andrew Crosland Copyright (C) 2010 021 * @author Andrew Berridge Copyright (C) 2010 for gnu io (RXTX) 022 */ 023public class SpeedoTrafficController implements SpeedoInterface, SerialPortEventListener { 024 025 private SpeedoReply reply = new SpeedoReply(); 026 027 /** 028 * Create a new SpeedoTrafficController instance. 029 * 030 * @param adaptermemo the associated SystemConnectionMemo 031 */ 032 public SpeedoTrafficController(SpeedoSystemConnectionMemo adaptermemo) { 033 } 034 035 // The methods to implement the SpeedoInterface 036 037 protected Vector<SpeedoListener> cmdListeners = new Vector<SpeedoListener>(); 038 039 @Override 040 public boolean status() { 041 return (ostream != null && istream != null); 042 } 043 044 @Override 045 public synchronized void addSpeedoListener(SpeedoListener l) { 046 // add only if not already registered 047 if (l == null) { 048 throw new java.lang.NullPointerException(); 049 } 050 if (!cmdListeners.contains(l)) { 051 cmdListeners.addElement(l); 052 } 053 } 054 055 @Override 056 public synchronized void removeSpeedoListener(SpeedoListener l) { 057 if (cmdListeners.contains(l)) { 058 cmdListeners.removeElement(l); 059 } 060 } 061 062 SpeedoListener lastSender = null; 063 064 @SuppressWarnings("unchecked") 065 protected void notifyReply(SpeedoReply r) { 066 // make a copy of the listener vector to synchronized (not needed for transmit?) 067 Vector<SpeedoListener> v; 068 synchronized (this) { 069 v = (Vector<SpeedoListener>) cmdListeners.clone(); 070 } 071 // forward to all listeners 072 int cnt = v.size(); 073 for (int i = 0; i < cnt; i++) { 074 SpeedoListener client = v.elementAt(i); 075 try { 076 // skip forwarding to the last sender for now, we'll get them later 077 if (lastSender != client) { 078 client.reply(r); 079 } 080 } catch (Exception e) { 081 log.warn("notify: During dispatch to {} Exception", client, e); 082 } 083 } 084 085 // Forward to the last listener who send a message. 086 // This is done _second_ so monitoring can have already stored the reply 087 // before a response is sent. 088 if (lastSender != null) { 089 lastSender.reply(r); 090 } 091 } 092 093 // methods to connect/disconnect to a source of data in a LnPortController 094 095 private SpeedoPortController controller = null; 096 097 /** 098 * Make connection to existing PortController object. 099 * @param p speedo port controller. 100 */ 101 public void connectPort(SpeedoPortController p) { 102 istream = p.getInputStream(); 103 ostream = p.getOutputStream(); 104 if (controller != null) { 105 log.warn("connectPort: connect called while connected"); 106 } 107 controller = p; 108 } 109 110 /** 111 * Break connection to existing SpeedoPortController object. 112 * Once broken, attempts to send via "message" member will fail. 113 * @param p speedo port controller. 114 */ 115 public void disconnectPort(SpeedoPortController p) { 116 istream = null; 117 ostream = null; 118 if (controller != p) { 119 log.warn("disconnectPort: disconnect called from non-connected LnPortController"); 120 } 121 controller = null; 122 } 123 124 // data members to hold the streams 125 DataInputStream istream = null; 126 OutputStream ostream = null; 127 128 /* 129 * Speedo replies end with ";" 130 */ 131 boolean endReply(SpeedoReply msg) { 132 // Detect that the reply buffer ends with ";" 133 int num = msg.getNumDataElements(); 134 135 // Check for the SPC200R, the first byte is always 0x50, 0x51, 0x52 or 0x53 136 int value = msg.getElement(0); 137 if (value == 0x50 || value == 0x51 || value == 0x52 || value == 0x53) { 138 // SPC200R has a 7 byte output 139 if (num != 7) { 140 return false; 141 } 142 } 143 else 144 { 145 // Check for ';' as Bachrus and KPF-Zeller use ; as termination 146 147 // ptr is offset of last element in SpeedoReply 148 int ptr = num - 1; 149 if (msg.getElement(ptr) != ';') { 150 return false; 151 } 152 } 153 154 unsolicited = true; 155 return true; 156 } 157 158 private boolean unsolicited; 159 160 /** 161 * Respond to an event triggered by RXTX. In this case we are 162 * only dealing with DATA_AVAILABLE but the other events are left here for 163 * reference. 164 */ 165 @Override 166 public void serialEvent(SerialPortEvent event) { 167 switch (event.getEventType()) { 168 case SerialPortEvent.BI: 169 case SerialPortEvent.OE: 170 case SerialPortEvent.FE: 171 case SerialPortEvent.PE: 172 case SerialPortEvent.CD: 173 case SerialPortEvent.CTS: 174 case SerialPortEvent.DSR: 175 case SerialPortEvent.RI: 176 case SerialPortEvent.OUTPUT_BUFFER_EMPTY: 177 break; 178 case SerialPortEvent.DATA_AVAILABLE: 179 // we get here if data has been received 180 //fill the current reply with any data received 181 int replyCurrentSize = this.reply.getNumDataElements(); 182 //log.debug("SerialPortEvent: DATA_AVAILABEL, number of data elements {}", replyCurrentSize); 183 int i; 184 for (i = replyCurrentSize; i < SpeedoReply.maxSize - replyCurrentSize; i++) { 185 try { 186 if (istream.available() == 0) { 187 break; //nothing waiting to be read 188 } 189 byte char1 = istream.readByte(); 190 this.reply.setElement(i, char1); 191 192 } catch (Exception e) { 193 log.debug("Exception handling reply cause {}", e.getCause(), e); 194 } 195 if (endReply(this.reply)) { 196 sendreply(); 197 break; 198 } 199 } 200 201 break; 202 default: 203 log.warn("Unhandled event type: {}", event.getEventType()); 204 break; 205 } 206 } 207 208 /** 209 * Send the current reply - built using data from serialEvent. 210 */ 211 private void sendreply() { 212 //send the reply 213 { 214 final SpeedoReply thisReply = this.reply; 215 if (unsolicited) { 216 thisReply.setUnsolicited(); 217 } 218 final SpeedoTrafficController thisTc = this; 219 // return a notification via the queue to ensure end 220 Runnable r = new Runnable() { 221 222 SpeedoReply msgForLater = thisReply; 223 SpeedoTrafficController myTc = thisTc; 224 225 @Override 226 public void run() { 227 myTc.notifyReply(msgForLater); 228 } 229 }; 230 javax.swing.SwingUtilities.invokeLater(r); 231 } 232 //Create a new reply, ready to be filled 233 this.reply = new SpeedoReply(); 234 } 235 236 private final static Logger log = LoggerFactory.getLogger(SpeedoTrafficController.class); 237 238}