001package jmri.jmrix.tmcc.simulator; 002 003import java.io.DataInputStream; 004import java.io.DataOutputStream; 005import java.io.IOException; 006import java.io.PipedInputStream; 007import java.io.PipedOutputStream; 008 009import jmri.jmrix.tmcc.SerialMessage; 010import jmri.jmrix.tmcc.SerialPortController; // no special xSimulatorController 011import jmri.jmrix.tmcc.SerialReply; 012import jmri.jmrix.tmcc.SerialTrafficController; 013import jmri.jmrix.tmcc.TmccSystemConnectionMemo; 014import jmri.util.ImmediatePipedOutputStream; 015import org.slf4j.Logger; 016import org.slf4j.LoggerFactory; 017 018/** 019 * Provide access to a simulated TMCC system. 020 * <p> 021 * Currently, the TMCC SimulatorAdapter reacts to commands sent from the user interface 022 * with messages an appropriate reply message. 023 * Based on jmri.jmrix.lenz.xnetsimulator.XNetSimulatorAdapter / DCCppSimulatorAdapter 2017 024 * <p> 025 * NOTE: Some material in this file was modified from other portions of the 026 * support infrastructure. 027 * 028 * @author Paul Bender, Copyright (C) 2009-2010 029 * @author Mark Underwood, Copyright (C) 2015 030 * @author Egbert Broerse, Copyright (C) 2017 031 */ 032public class SimulatorAdapter extends SerialPortController implements Runnable { 033 034 // private control members 035 private Thread sourceThread; 036 037 final static int SENSOR_MSG_RATE = 10; 038 039 private boolean outputBufferEmpty = true; 040 private boolean checkBuffer = true; 041 // Simulator responses 042 char EDC_OPS = 0x4F; 043 char EDC_PROG = 0x50; 044 045 public SimulatorAdapter() { 046 super(new TmccSystemConnectionMemo("T", "TMCC Simulator")); // pass customized user name 047 setManufacturer(jmri.jmrix.tmcc.SerialConnectionTypeList.LIONEL); 048 } 049 050 /** 051 * {@inheritDoc} 052 * Simulated input/output pipes. 053 */ 054 @Override 055 public String openPort(String portName, String appName) { 056 try { 057 PipedOutputStream tempPipeI = new ImmediatePipedOutputStream(); 058 log.debug("tempPipeI created"); 059 pout = new DataOutputStream(tempPipeI); 060 inpipe = new DataInputStream(new PipedInputStream(tempPipeI)); 061 log.debug("inpipe created {}", inpipe != null); 062 PipedOutputStream tempPipeO = new ImmediatePipedOutputStream(); 063 outpipe = new DataOutputStream(tempPipeO); 064 pin = new DataInputStream(new PipedInputStream(tempPipeO)); 065 } catch (java.io.IOException e) { 066 log.error("init (pipe): Exception: {}", e.toString()); 067 } 068 opened = true; 069 return null; // indicates OK return 070 } 071 072 /** 073 * Set if the output buffer is empty or full. This should only be set to 074 * false by external processes. 075 * 076 * @param s true if output buffer is empty; false otherwise 077 */ 078 synchronized public void setOutputBufferEmpty(boolean s) { 079 outputBufferEmpty = s; 080 } 081 082 /** 083 * Can the port accept additional characters? The state of CTS determines 084 * this, as there seems to be no way to check the number of queued bytes and 085 * buffer length. This might go false for short intervals, but it might also 086 * stick off if something goes wrong. 087 * 088 * @return true if port can accept additional characters; false otherwise 089 */ 090 public boolean okToSend() { 091 if (checkBuffer) { 092 log.debug("Buffer Empty: {}", outputBufferEmpty); 093 return (outputBufferEmpty); 094 } else { 095 log.debug("No Flow Control or Buffer Check"); 096 return (true); 097 } 098 } 099 100 /** 101 * Set up all of the other objects to operate with a TMCCSimulator 102 * connected to this port. 103 */ 104 @Override 105 public void configure() { 106 // connect to the traffic controller 107 log.debug("set tc for memo {}", getSystemConnectionMemo().getUserName()); 108 SerialTrafficController control = new SerialTrafficController(getSystemConnectionMemo()); 109 //compare with: XNetTrafficController packets = new XNetPacketizer(new LenzCommandStation()); 110 control.connectPort(this); 111 this.getSystemConnectionMemo().setTrafficController(control); 112 // do the common manager config 113 this.getSystemConnectionMemo().configureManagers(); 114 115 // start the simulator 116 sourceThread = new Thread(this); 117 sourceThread.setName("TMCC Simulator"); 118 sourceThread.setPriority(Thread.MIN_PRIORITY); 119 sourceThread.start(); 120 } 121 122 // Base class methods for the SerialPortController simulated interface 123 124 /** 125 * {@inheritDoc} 126 */ 127 @Override 128 public DataInputStream getInputStream() { 129 if (!opened || pin == null) { 130 log.error("getInputStream called before load(), stream not available"); 131 } 132 log.debug("DataInputStream pin returned"); 133 return pin; 134 } 135 136 /** 137 * {@inheritDoc} 138 */ 139 @Override 140 public DataOutputStream getOutputStream() { 141 if (!opened || pout == null) { 142 log.error("getOutputStream called before load(), stream not available"); 143 } 144 log.debug("DataOutputStream pout returned"); 145 return pout; 146 } 147 148 /** 149 * {@inheritDoc} 150 */ 151 @Override 152 public boolean status() { 153 return opened; 154 } 155 156 /** 157 * {@inheritDoc} 158 * 159 * @return null 160 */ 161 @Override 162 public String[] validBaudRates() { 163 log.debug("validBaudRates should not have been invoked"); 164 return new String[]{}; 165 } 166 167 /** 168 * {@inheritDoc} 169 */ 170 @Override 171 public int[] validBaudNumbers() { 172 return new int[]{}; 173 } 174 175 @Override 176 public String getCurrentBaudRate() { 177 return ""; 178 } 179 180 @Override 181 public String getCurrentPortName(){ 182 return ""; 183 } 184 185 @Override 186 public void run() { // start a new thread 187 // This thread has one task. It repeatedly reads from the input pipe 188 // and writes an appropriate response to the output pipe. This is the heart 189 // of the TMCC command station simulation. 190 log.info("TMCC Simulator Started"); 191 while (true) { 192 try { 193 synchronized (this) { 194 wait(50); 195 } 196 } catch (InterruptedException e) { 197 log.debug("interrupted, ending"); 198 return; 199 } 200 SerialMessage m = readMessage(); 201 SerialReply r; 202 if (log.isDebugEnabled()) { 203 StringBuilder buf = new StringBuilder(); 204 buf.append("TMCC Simulator Thread received message: "); 205 if (m != null) { 206 for (int i = 0; i < m.getNumDataElements(); i++) { 207 buf.append(Integer.toHexString(0xFF & m.getElement(i))).append(" "); 208 } 209 } else { 210 buf.append("null message buffer"); 211 } 212// log.debug(buf.toString()); 213 } 214 if (m != null) { 215 r = generateReply(m); 216 writeReply(r); 217 if (log.isDebugEnabled()) { 218 StringBuilder buf = new StringBuilder(); 219 for (int i = 0; i < r.getNumDataElements(); i++) { 220 buf.append(Integer.toHexString(0xFF & r.getElement(i))).append(" "); 221 } 222 log.debug("TMCC Simulator Thread sent reply: {}", buf ); 223 } 224 } 225 } 226 } 227 228 /** 229 * Read one incoming message from the buffer 230 * and set outputBufferEmpty to true. 231 */ 232 private SerialMessage readMessage() { 233 SerialMessage msg = null; 234 // log.debug("Simulator reading message"); 235 try { 236 if (inpipe != null && inpipe.available() > 0) { 237 msg = loadChars(); 238 } 239 } catch (java.io.IOException e) { 240 // should do something meaningful here. 241 } 242 setOutputBufferEmpty(true); 243 return (msg); 244 } 245 246 /** 247 * This is the heart of the simulation. It translates an 248 * incoming SerialMessage into an outgoing SerialReply. 249 * 250 * As yet, no a meaningful reply. TODO: Throttle 251 */ 252 private SerialReply generateReply(SerialMessage msg) { 253 log.debug("Generate Reply to message type {} (string = {})", msg.toString().charAt(0), msg.toString()); 254 255 SerialReply reply = new SerialReply(); 256 char command = msg.toString().charAt(0); 257 log.debug("Message type = {}", command); 258 switch (command) { 259 260 default: 261 log.debug("non-reply message detected"); 262 } 263 log.debug("Reply generated = {}", reply.toString()); 264 // no confirm sequence for TMCC 265 return (reply); 266 } 267 268 /** 269 * Write reply to output. 270 * <p> 271 * Copied from jmri.jmrix.nce.simulator.SimulatorAdapter. 272 * 273 * @param r reply on message 274 */ 275 private void writeReply(SerialReply r) { 276 if (r == null) { 277 return; // there is no reply to be sent 278 } 279 for (int i = 0; i < r.getNumDataElements(); i++) { 280 try { 281 outpipe.writeByte((byte) r.getElement(i)); 282 } catch (java.io.IOException ex) { 283 } 284 } 285 try { 286 outpipe.flush(); 287 } catch (java.io.IOException ex) { 288 } 289 } 290 291 /** 292 * Get characters from the input source. 293 * <p> 294 * Only used in the Receive thread. 295 * 296 * @return filled message, only when the message is complete. 297 * @throws IOException when presented by the input source. 298 */ 299 private SerialMessage loadChars() throws java.io.IOException { 300 int nchars; 301 byte[] rcvBuffer = new byte[32]; 302 303 nchars = inpipe.read(rcvBuffer, 0, 32); 304 //log.debug("new message received"); 305 SerialMessage msg = new SerialMessage(nchars); 306 307 for (int i = 0; i < nchars; i++) { 308 msg.setElement(i, rcvBuffer[i] & 0xFF); 309 } 310 return msg; 311 } 312 313 // streams to share with user class 314 private DataOutputStream pout = null; // this is provided to classes who want to write to us 315 private DataInputStream pin = null; // this is provided to classes who want data from us 316 // internal ends of the pipes 317 private DataOutputStream outpipe = null; // feed pin 318 private DataInputStream inpipe = null; // feed pout 319 320 private final static Logger log = LoggerFactory.getLogger(SimulatorAdapter.class); 321 322}