001package jmri.jmrix.sprog.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.sprog.SprogConstants.SprogMode; 009import jmri.jmrix.sprog.SprogMessage; 010import jmri.jmrix.sprog.SprogPortController; // no special xSimulatorController 011import jmri.jmrix.sprog.SprogReply; 012import jmri.jmrix.sprog.SprogSystemConnectionMemo; 013import jmri.jmrix.sprog.SprogTrafficController; 014import jmri.util.ImmediatePipedOutputStream; 015import org.slf4j.Logger; 016import org.slf4j.LoggerFactory; 017 018/** 019 * Provide access to a simulated SPROG system. 020 * <p> 021 * Can be loaded as either a Programmer or Command Station. 022 * The SPROG SimulatorAdapter reacts to commands sent from the user interface 023 * with an appropriate reply message. 024 * <p> 025 * Based on jmri.jmrix.lenz.xnetsimulator.XNetSimulatorAdapter / DCCppSimulatorAdapter 2017 026 * 027 * @author Paul Bender, Copyright (C) 2009-2010 028 * @author Mark Underwood, Copyright (C) 2015 029 * @author Egbert Broerse, Copyright (C) 2018 030 */ 031public class SimulatorAdapter extends SprogPortController implements Runnable { 032 033 // private control members 034 private Thread sourceThread; 035 private final SprogTrafficController control; 036 037 private boolean outputBufferEmpty = true; 038 private boolean checkBuffer = true; 039 private SprogMode operatingMode = SprogMode.SERVICE; 040 041 // Simulator responses 042 String SPR_OK = "OK"; 043 String SPR_NO = "No Ack"; 044 String SPR_PR = "\nP> "; // prompt 045 046 public SimulatorAdapter() { 047 super(new SprogSystemConnectionMemo(SprogMode.SERVICE)); // use default user name 048 // starts as SERVICE mode (Programmer); may be set to OPS (Command Station) from connection option 049 setManufacturer(jmri.jmrix.sprog.SprogConnectionTypeList.SPROG); 050 this.getSystemConnectionMemo().setUserName(Bundle.getMessage("SprogSimulatorTitle")); 051 // create the traffic controller 052 control = new SprogTrafficController(this.getSystemConnectionMemo()); 053 this.getSystemConnectionMemo().setSprogTrafficController(control); 054 055 options.put("NumSlots", // NOI18N 056 new Option(Bundle.getMessage("MakeLabel", Bundle.getMessage("NumSlotOptions")), // NOI18N 057 new String[]{"16", "8", "32", "48", "64"}, true)); 058 059 options.put("OperatingMode", // NOI18N 060 new Option(Bundle.getMessage("MakeLabel", Bundle.getMessage("SprogSimOption")), // NOI18N 061 new String[]{Bundle.getMessage("SprogProgrammerTitle"), 062 Bundle.getMessage("SprogCSTitle")}, true)); 063 } 064 065 /** 066 * {@inheritDoc} 067 * Simulated input/output pipes. 068 */ 069 @Override 070 public String openPort(String portName, String appName) { 071 try { 072 PipedOutputStream tempPipeI = new ImmediatePipedOutputStream(); 073 log.debug("tempPipeI created"); 074 pout = new DataOutputStream(tempPipeI); 075 inpipe = new DataInputStream(new PipedInputStream(tempPipeI)); 076 log.debug("inpipe created {}", inpipe != null); 077 PipedOutputStream tempPipeO = new ImmediatePipedOutputStream(); 078 outpipe = new DataOutputStream(tempPipeO); 079 pin = new DataInputStream(new PipedInputStream(tempPipeO)); 080 } catch (java.io.IOException e) { 081 log.error("init (pipe): Exception: {}", e.toString()); 082 } 083 opened = true; 084 return null; // indicates OK return 085 } 086 087 /** 088 * Set if the output buffer is empty or full. This should only be set to 089 * false by external processes. 090 * 091 * @param s true if output buffer is empty; false otherwise 092 */ 093 synchronized public void setOutputBufferEmpty(boolean s) { 094 outputBufferEmpty = s; 095 } 096 097 /** 098 * Can the port accept additional characters? The state of CTS determines 099 * this, as there seems to be no way to check the number of queued bytes and 100 * buffer length. This might go false for short intervals, but it might also 101 * stick off if something goes wrong. 102 * 103 * @return true if port can accept additional characters; false otherwise 104 */ 105 public boolean okToSend() { 106 if (checkBuffer) { 107 log.debug("Buffer Empty: {}", outputBufferEmpty); 108 return (outputBufferEmpty); 109 } else { 110 log.debug("No Flow Control or Buffer Check"); 111 return (true); 112 } 113 } 114 115 /** 116 * Set up all of the other objects to operate with a Sprog Simulator. 117 */ 118 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = "SLF4J_FORMAT_SHOULD_BE_CONST", 119 justification = "passing exception text") 120 @Override 121 public void configure() { 122 // connect to the traffic controller 123 this.getSystemConnectionMemo().getSprogTrafficController().connectPort(this); 124 125 if (getOptionState("OperatingMode") != null && getOptionState("OperatingMode").equals(Bundle.getMessage("SprogProgrammerTitle"))) { 126 operatingMode = SprogMode.SERVICE; 127 } else { // default, also used after Locale change 128 operatingMode = SprogMode.OPS; 129 } 130 131 String slots = getOptionState("NumSlots"); 132 int numSlots; 133 try { 134 numSlots = Integer.parseInt(slots); 135 } 136 catch (NumberFormatException e) { 137 numSlots = 16; 138 } 139 140 this.getSystemConnectionMemo().setSprogMode(operatingMode); // first update mode in memo 141 this.getSystemConnectionMemo().configureCommandStation(numSlots); // CS only if in OPS mode, memo will take care of that 142 this.getSystemConnectionMemo().configureManagers(); // wait for mode to be correct 143 144 if (getOptionState("TrackPowerState") != null && getOptionState("TrackPowerState").equals(Bundle.getMessage("PowerStateOn"))) { 145 try { 146 this.getSystemConnectionMemo().getPowerManager().setPower(jmri.PowerManager.ON); 147 } catch (jmri.JmriException e) { 148 log.error(e.toString()); 149 } 150 } 151 152 log.debug("SimulatorAdapter configure() with prefix = {}", this.getSystemConnectionMemo().getSystemPrefix()); 153 // start the simulator 154 sourceThread = new Thread(this); 155 sourceThread.setName("SPROG Simulator"); 156 sourceThread.setPriority(Thread.MIN_PRIORITY); 157 sourceThread.start(); 158 } 159 160 /** 161 * {@inheritDoc} 162 */ 163 @Override 164 public void connect() throws java.io.IOException { 165 log.debug("connect called"); 166 super.connect(); 167 } 168 169 // Base class methods for the SprogPortController simulated interface 170 171 /** 172 * {@inheritDoc} 173 */ 174 @Override 175 public DataInputStream getInputStream() { 176 if (!opened || pin == null) { 177 log.error("getInputStream called before load(), stream not available"); 178 } 179 log.debug("DataInputStream pin returned"); 180 return pin; 181 } 182 183 /** 184 * {@inheritDoc} 185 */ 186 @Override 187 public DataOutputStream getOutputStream() { 188 if (!opened || pout == null) { 189 log.error("getOutputStream called before load(), stream not available"); 190 } 191 log.debug("DataOutputStream pout returned"); 192 return pout; 193 } 194 195 /** 196 * {@inheritDoc} 197 */ 198 @Override 199 public boolean status() { 200 return (pout != null && pin != null); 201 } 202 203 /** 204 * {@inheritDoc} 205 * 206 * @return null 207 */ 208 @Override 209 public String[] validBaudRates() { 210 log.debug("validBaudRates should not have been invoked"); 211 return new String[]{}; 212 } 213 214 /** 215 * {@inheritDoc} 216 */ 217 @Override 218 public int[] validBaudNumbers() { 219 return new int[]{}; 220 } 221 222 @Override 223 public String getCurrentBaudRate() { 224 return ""; 225 } 226 227 @Override 228 public String getCurrentPortName(){ 229 return ""; 230 } 231 232 @Override 233 public void run() { // start a new thread 234 // This thread has one task. It repeatedly reads from the input pipe 235 // and writes an appropriate response to the output pipe. This is the heart 236 // of the SPROG command station simulation. 237 log.info("SPROG Simulator Started"); 238 while (true) { 239 try { 240 synchronized (this) { 241 wait(50); 242 } 243 } catch (InterruptedException e) { 244 log.debug("interrupted, ending"); 245 return; 246 } 247 SprogMessage m = readMessage(); 248 SprogReply r; 249 if (log.isDebugEnabled()) { 250 StringBuffer buf = new StringBuffer(); 251 buf.append("SPROG Simulator Thread received message: "); 252 if (m != null) { 253 buf.append(m); 254 } else { 255 buf.append("null message buffer"); 256 } 257 //log.debug(buf.toString()); // generates a lot of output 258 } 259 if (m != null) { 260 r = generateReply(m); 261 writeReply(r); 262 log.debug("Simulator Thread sent Reply: \"{}\"", r); 263 } 264 } 265 } 266 267 /** 268 * Read one incoming message from the buffer 269 * and set outputBufferEmpty to true. 270 */ 271 private SprogMessage readMessage() { 272 SprogMessage msg = null; 273 // log.debug("Simulator reading message"); 274 try { 275 if (inpipe != null && inpipe.available() > 0) { 276 msg = loadChars(); 277 } 278 } catch (java.io.IOException e) { 279 // should do something meaningful here. 280 } 281 setOutputBufferEmpty(true); 282 return (msg); 283 } 284 285 /** 286 * This is the heart of the simulation. It translates an 287 * incoming SprogMessage into an outgoing SprogReply. 288 * 289 * Based on SPROG information from A. Crosland. 290 * @see jmri.jmrix.sprog.SprogReply#value() 291 */ 292 private SprogReply generateReply(SprogMessage msg) { 293 log.debug("Generate Reply to message type {} (string = {})", msg.toString().charAt(0), msg.toString()); 294 295 SprogReply reply = new SprogReply(); 296 int i = 0; 297 char command = msg.toString().charAt(0); 298 log.debug("Message type = {}", command); 299 switch (command) { 300 301 case 'I': 302 log.debug("CurrentQuery detected"); 303 reply = new SprogReply("= h3E7\n"); // reply fictionary current (decimal 999mA) 304 break; 305 306 case 'C': 307 case 'V': 308 log.debug("Read/Write CV detected"); 309 reply = new SprogReply("= h" + msg.toString().substring(2) + "\n"); // echo CV value (hex) 310 break; 311 312 case 'D': 313 case 'U': 314 log.debug("Read/Write CV with hint detected"); 315 reply = new SprogReply("= h" + msg.toString().substring(2) + "\n"); // echo CV hint value (hex) 316 break; 317 318 case 'O': 319 log.debug("Send packet command detected"); 320 reply = new SprogReply("= " + msg.toString().substring(2) + "\n"); // echo command (hex) 321 break; 322 323 case 'A': 324 log.debug("Address (open Throttle) command detected"); 325 reply = new SprogReply(msg.toString().substring(2) + "\n"); // echo address (decimal) 326 break; 327 328 case '>': 329 log.debug("Set speed (Throttle) command detected"); 330 reply = new SprogReply(msg.toString().substring(1) + "\n"); // echo speed (decimal) 331 break; 332 333 case '+': 334 log.debug("TRACK_POWER_ON detected"); 335 //reply = new SprogReply(SPR_PR); 336 break; 337 338 case '-': 339 log.debug("TRACK_POWER_OFF detected"); 340 //reply = new SprogReply(SPR_PR); 341 break; 342 343 case '?': 344 log.debug("Read_Sprog_Version detected"); 345 String replyString = "\nSPROG II Ver 4.5\n"; 346 reply = new SprogReply(replyString); 347 break; 348 349 case 'M': 350 log.debug("Mode Word detected"); 351 reply = new SprogReply("P>M=h800\n"); // default mode reply 352 break; 353 354 case 'S': 355 log.debug("getStatus detected"); 356 reply = new SprogReply("OK\n"); 357 break; 358 359 case ' ': 360 log.debug("null command detected"); 361 reply = new SprogReply("\n"); 362 break; 363 364 default: 365 log.debug("non-reply message detected: {}", msg.toString()); 366 reply = new SprogReply("!E\n"); // SPROG error reply 367 } 368 i = reply.toString().length(); 369 reply.setElement(i++, 'P'); // add prompt to all replies 370 reply.setElement(i++, '>'); 371 reply.setElement(i++, ' '); 372 log.debug("Reply generated = \"{}\"", reply.toString()); 373 return reply; 374 } 375 376 /** 377 * Write reply to output. 378 * <p> 379 * Copied from jmri.jmrix.nce.simulator.SimulatorAdapter, 380 * adapted for {@link jmri.jmrix.sprog.SprogTrafficController#handleOneIncomingReply()}. 381 * 382 * @param r reply on message 383 */ 384 private void writeReply(SprogReply r) { 385 if (r == null) { 386 return; // there is no reply to be sent 387 } 388 int len = r.getNumDataElements(); 389 for (int i = 0; i < len; i++) { 390 try { 391 outpipe.writeByte((byte) r.getElement(i)); 392 //log.debug("{} of {} bytes written to outpipe", i + 1, len); 393 if (pin.available() > 0) { 394 control.handleOneIncomingReply(); 395 } 396 } catch (java.io.IOException ex) { 397 } 398 } 399 try { 400 outpipe.flush(); 401 } catch (java.io.IOException ex) { 402 } 403 } 404 405 /** 406 * Get characters from the input source. 407 * <p> 408 * Only used in the Receive thread. 409 * 410 * @return filled message, only when the message is complete. 411 * @throws IOException when presented by the input source. 412 */ 413 private SprogMessage loadChars() throws java.io.IOException { 414 // code copied from EasyDcc/NCE Simulator 415 int nchars; 416 byte[] rcvBuffer = new byte[32]; 417 418 nchars = inpipe.read(rcvBuffer, 0, 32); 419 //log.debug("new message received"); 420 SprogMessage msg = new SprogMessage(nchars); 421 422 for (int i = 0; i < nchars; i++) { 423 msg.setElement(i, rcvBuffer[i] & 0xFF); 424 } 425 return msg; 426 } 427 428 @Override 429 public void dispose() { 430 super.dispose(); 431 control.dispose(); 432 } 433 434 // streams to share with user class 435 private DataOutputStream pout = null; // this is provided to classes who want to write to us 436 private DataInputStream pin = null; // this is provided to classes who want data from us 437 // internal ends of the pipes 438 private DataOutputStream outpipe = null; // feed pin 439 private DataInputStream inpipe = null; // feed pout 440 441 private final static Logger log = LoggerFactory.getLogger(SimulatorAdapter.class); 442 443}