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