001package jmri.jmrix.easydcc.simulator; 002 003import java.io.DataInputStream; 004import java.io.DataOutputStream; 005import java.io.IOException; 006import java.io.PipedInputStream; 007import java.io.PipedOutputStream; 008import jmri.jmrix.easydcc.EasyDccMessage; 009import jmri.jmrix.easydcc.EasyDccPortController; // no special xSimulatorController 010import jmri.jmrix.easydcc.EasyDccReply; 011import jmri.jmrix.easydcc.EasyDccSystemConnectionMemo; 012import jmri.util.ImmediatePipedOutputStream; 013import org.slf4j.Logger; 014import org.slf4j.LoggerFactory; 015 016/** 017 * Provide access to a simulated EasyDCC system. 018 * <p> 019 * Currently, the EasyDCC SimulatorAdapter reacts to commands sent from the user interface 020 * with an appropriate reply message. 021 * Based on jmri.jmrix.lenz.xnetsimulator.XNetSimulatorAdapter / DCCppSimulatorAdapter 2017 022 * <p> 023 * NOTE: Some material in this file was modified from other portions of the 024 * support infrastructure. 025 * 026 * @author Paul Bender, Copyright (C) 2009-2010 027 * @author Mark Underwood, Copyright (C) 2015 028 * @author Egbert Broerse, Copyright (C) 2017 029 */ 030public class SimulatorAdapter extends EasyDccPortController implements Runnable { 031 032 // private control members 033 private boolean opened = false; 034 private Thread sourceThread; 035 036 final static int SENSOR_MSG_RATE = 10; 037 038 private boolean outputBufferEmpty = true; 039 private final boolean checkBuffer = true; 040 // Simulator responses 041 char EDC_OPS = 0x4F; 042 char EDC_PROG = 0x50; 043 044 public SimulatorAdapter() { 045 super(new EasyDccSystemConnectionMemo("E", "EasyDCC Simulator")); // pass customized user name 046 setManufacturer(jmri.jmrix.easydcc.EasyDccConnectionTypeList.EASYDCC); 047 } 048 049 /** 050 * {@inheritDoc} 051 * Simulated input/output pipes. 052 */ 053 @Override 054 public String openPort(String portName, String appName) { 055 try { 056 PipedOutputStream tempPipeI = new ImmediatePipedOutputStream(); 057 pout = new DataOutputStream(tempPipeI); 058 inpipe = new DataInputStream(new PipedInputStream(tempPipeI)); 059 PipedOutputStream tempPipeO = new ImmediatePipedOutputStream(); 060 outpipe = new DataOutputStream(tempPipeO); 061 pin = new DataInputStream(new PipedInputStream(tempPipeO)); 062 } catch (java.io.IOException e) { 063 log.error("init (pipe): Exception: {}", e.toString()); 064 } 065 opened = true; 066 return null; // indicates OK return 067 } 068 069 /** 070 * Set if the output buffer is empty or full. This should only be set to 071 * false by external processes. 072 * 073 * @param s true if output buffer is empty; false otherwise 074 */ 075 synchronized public void setOutputBufferEmpty(boolean s) { 076 outputBufferEmpty = s; 077 } 078 079 /** 080 * Can the port accept additional characters? The state of CTS determines 081 * this, as there seems to be no way to check the number of queued bytes and 082 * buffer length. This might go false for short intervals, but it might also 083 * stick off if something goes wrong. 084 * 085 * @return true if port can accept additional characters; false otherwise 086 */ 087 public boolean okToSend() { 088 if (checkBuffer) { 089 log.debug("Buffer Empty: {}", outputBufferEmpty); 090 return (outputBufferEmpty); 091 } else { 092 log.debug("No Flow Control or Buffer Check"); 093 return (true); 094 } 095 } 096 097 /** 098 * Set up all of the other objects to operate with an EasyDccSimulator 099 * connected to this port. 100 */ 101 @Override 102 public void configure() { 103 // connect to the traffic controller, which is provided via the memo 104 log.debug("set tc for memo {}", getSystemConnectionMemo().getUserName()); 105 106 getSystemConnectionMemo().getTrafficController().connectPort(this); 107 108 // do the common manager config 109 this.getSystemConnectionMemo().configureManagers(); 110 111 // start the simulator 112 sourceThread = new Thread(this); 113 sourceThread.setName("EasyDCC Simulator"); 114 sourceThread.setPriority(Thread.MIN_PRIORITY); 115 sourceThread.start(); 116 } 117 118 /** 119 * {@inheritDoc} 120 */ 121 @Override 122 public void connect() throws java.io.IOException { 123 log.debug("connect called"); 124 super.connect(); 125 } 126 127 // Base class methods for the EasyDccPortController simulated interface 128 129 /** 130 * {@inheritDoc} 131 */ 132 @Override 133 public DataInputStream getInputStream() { 134 if (!opened || pin == null) { 135 log.error("getInputStream called before load(), stream not available"); 136 } 137 log.debug("DataInputStream pin returned"); 138 return pin; 139 } 140 141 /** 142 * {@inheritDoc} 143 */ 144 @Override 145 public DataOutputStream getOutputStream() { 146 if (!opened || pout == null) { 147 log.error("getOutputStream called before load(), stream not available"); 148 } 149 log.debug("DataOutputStream pout returned"); 150 return pout; 151 } 152 153 /** 154 * {@inheritDoc} 155 */ 156 @Override 157 public boolean status() { 158 return opened; 159 } 160 161 /** 162 * {@inheritDoc} 163 * 164 * @return null 165 */ 166 @Override 167 public String[] validBaudRates() { 168 log.debug("validBaudRates should not have been invoked"); 169 return new String[]{}; 170 } 171 172 /** 173 * {@inheritDoc} 174 */ 175 @Override 176 public int[] validBaudNumbers() { 177 return new int[]{}; 178 } 179 180 @Override 181 public String getCurrentBaudRate() { 182 return ""; 183 } 184 185 @Override 186 public String getCurrentPortName(){ 187 return ""; 188 } 189 190 @Override 191 public void run() { // start a new thread 192 // This thread has one task. It repeatedly reads from the input pipe 193 // and writes an appropriate response to the output pipe. This is the heart 194 // of the EasyDCC command station simulation. 195 log.info("EasyDCC Simulator Started"); 196 while (true) { 197 try { 198 synchronized (this) { 199 wait(50); 200 } 201 } catch (InterruptedException e) { 202 log.debug("interrupted, ending"); 203 return; 204 } 205 EasyDccMessage m = readMessage(); 206 EasyDccReply r; 207 if (log.isDebugEnabled()) { 208 StringBuilder buf = new StringBuilder(); 209 if (m != null) { 210 for (int i = 0; i < m.getNumDataElements(); i++) { 211 buf.append(Integer.toHexString(0xFF & m.getElement(i))).append(" "); 212 } 213 } else { 214 buf.append("null message buffer"); 215 } 216 log.trace("EasyDCC Simulator Thread received message: {}", buf); // generates a lot of traffic 217 } 218 if (m != null) { 219 r = generateReply(m); 220 writeReply(r); 221 if (log.isDebugEnabled()) { 222 StringBuilder buf = new StringBuilder(); 223 for (int i = 0; i < r.getNumDataElements(); i++) { 224 buf.append(Integer.toHexString(0xFF & r.getElement(i))).append(" "); 225 } 226 log.debug("EasyDCC Simulator Thread sent reply: {}", buf); 227 } 228 } 229 } 230 } 231 232 /** 233 * Read one incoming message from the buffer 234 * and set outputBufferEmpty to true. 235 */ 236 private EasyDccMessage readMessage() { 237 EasyDccMessage msg = null; 238// log.debug("Simulator reading message"); 239 try { 240 if (inpipe != null && inpipe.available() > 0) { 241 msg = loadChars(); 242 } 243 } catch (java.io.IOException e) { 244 // should do something meaningful here. 245 } 246 setOutputBufferEmpty(true); 247 return (msg); 248 } 249 250 /** 251 * This is the heart of the simulation. It translates an 252 * incoming EasyDccMessage into an outgoing EasyDccReply. 253 * 254 * As yet, not all messages receive a meaningful reply. TODO: Throttle, Program 255 */ 256 private EasyDccReply generateReply(EasyDccMessage msg) { 257 log.debug("Generate Reply to message type {} (string = {})", msg.toString().charAt(0), msg.toString()); 258 259 EasyDccReply reply = new EasyDccReply(); 260 int i = 0; 261 char command = msg.toString().charAt(0); 262 log.debug("Message type = {}", command); 263 switch (command) { 264 265 case 'X': // eXit programming 266 case 'S': // Send packet 267 case 'D': // Dequeue packet 268 case 'Q': // Queue packet 269 case 'F': // display memory 270 case 'C': // program loCo 271 reply.setElement(i++, EDC_OPS); // capital O for Operation 272 break; 273 274 case 'P': 275 case 'M': 276 reply.setElement(i++, EDC_PROG); // capital P for Programming 277 break; 278 279 case 'E': 280 log.debug("TRACK_POWER_ON detected"); 281 reply.setElement(i++, EDC_OPS); // capital O for Operation 282 break; 283 284 case 'K': 285 log.debug("TRACK_POWER_OFF detected"); 286 reply.setElement(i++, EDC_OPS); // capital O for Operation 287 break; 288 289 case 'V': 290 log.debug("Read_CS_Version detected"); 291 String replyString = "V999 01 01 1999"; 292 reply = new EasyDccReply(replyString); // fake version number reply 293 i = replyString.length(); 294// reply.setElement(i++, 0x0d); // add CR for second reply line 295// reply.setElement(i++, EDC_OPS); // capital O for Operation 296 break; 297 298 case 'G': // Consist 299 log.debug("Consist detected"); 300 if (msg.toString().charAt(0) == 'D') { // Display consist 301 replyString = "G" + msg.getElement(2) + msg.getElement(3) + "0000"; 302 reply = new EasyDccReply(replyString); // fake version number reply 303 i = replyString.length(); 304// reply.setElement(i++, 0x0d); // add CR 305 break; 306 } 307 reply.setElement(i++, EDC_OPS); // capital O for Operation, anyway 308 break; 309 310 case 'L': // Read Loco 311 log.debug("Read Loco detected"); 312 replyString = "L" + msg.getElement(1) + msg.getElement(2) + msg.getElement(3) + msg.getElement(4) + "000000"; 313 reply = new EasyDccReply(replyString); // fake reply dir = 00 step = 00 F5-12=00 314 i = replyString.length(); 315// reply.setElement(i++, 0x0d); // add CR for second reply line 316// reply.setElement(i++, EDC_OPS); // capital O for Operation, anyway 317 break; 318 319 case 'R': 320 log.debug("Read_CV detected"); 321 replyString = "--"; 322 reply = new EasyDccReply(replyString); // cannot read 323 i = replyString.length(); 324// reply.setElement(i++, 0x0d); // add CR for second reply line 325// reply.setElement(i++, EDC_PROG); // capital O for Operation 326 break; 327 328 default: 329 log.debug("non-reply message detected"); 330 reply.setElement(i++, '?'); // per page 2 of the EasyDCC computer 331 // operations manual, an invalid 332 // command returns ?<CR> 333 } 334 log.debug("Reply generated = {}", reply.toString()); 335 reply.setElement(i++, 0x0d); // add final CR for all replies 336 return (reply); 337 } 338 339 /** 340 * Write reply to output. 341 * <p> 342 * Copied from jmri.jmrix.nce.simulator.SimulatorAdapter. 343 * 344 * @param r reply on message 345 */ 346 private void writeReply(EasyDccReply r) { 347 if (r == null) { 348 return; // there is no reply to be sent 349 } 350 for (int i = 0; i < r.getNumDataElements(); i++) { 351 try { 352 outpipe.writeByte((byte) r.getElement(i)); 353 } catch (java.io.IOException ex) { 354 } 355 } 356 try { 357 outpipe.flush(); 358 } catch (java.io.IOException ex) { 359 } 360 } 361 362 /** 363 * Get characters from the input source. 364 * <p> 365 * Only used in the Receive thread. 366 * 367 * @return filled message, only when the message is complete. 368 * @throws IOException when presented by the input source. 369 */ 370 private EasyDccMessage loadChars() throws java.io.IOException { 371 int nchars; 372 byte[] rcvBuffer = new byte[32]; 373 374 nchars = inpipe.read(rcvBuffer, 0, 32); 375 //log.debug("new message received"); 376 EasyDccMessage msg = new EasyDccMessage(nchars); 377 378 for (int i = 0; i < nchars; i++) { 379 msg.setElement(i, rcvBuffer[i] & 0xFF); 380 } 381 return msg; 382 } 383 384 // streams to share with user class 385 private DataOutputStream pout = null; // this is provided to classes who want to write to us 386 private DataInputStream pin = null; // this is provided to classes who want data from us 387 // internal ends of the pipes 388 private DataOutputStream outpipe = null; // feed pin 389 private DataInputStream inpipe = null; // feed pout 390 391 private final static Logger log = LoggerFactory.getLogger(SimulatorAdapter.class); 392 393}