001package jmri.jmrix.lenz.xnetsimulator; 002 003import java.io.DataInputStream; 004import java.io.DataOutputStream; 005import java.io.IOException; 006import java.io.PipedInputStream; 007import java.io.PipedOutputStream; 008import java.util.BitSet; 009import jmri.jmrix.ConnectionStatus; 010import jmri.jmrix.lenz.LenzCommandStation; 011import jmri.jmrix.lenz.XNetConstants; 012import jmri.jmrix.lenz.XNetInitializationManager; 013import jmri.jmrix.lenz.XNetMessage; 014import jmri.jmrix.lenz.XNetPacketizer; 015import jmri.jmrix.lenz.XNetReply; 016import jmri.jmrix.lenz.XNetSimulatorPortController; 017import jmri.jmrix.lenz.XNetTrafficController; 018import jmri.util.ImmediatePipedOutputStream; 019import org.slf4j.Logger; 020import org.slf4j.LoggerFactory; 021 022/** 023 * Provide access to a simulated XpressNet system. 024 * <p> 025 * Currently, the XNetSimulator reacts to commands sent from the user interface 026 * with messages an appropriate reply message. 027 * <p> 028 * NOTE: Most XpressNet commands are still unsupported in this implementation. 029 * <p> 030 * Normally controlled by the lenz.XNetSimulator.XNetSimulatorFrame class. 031 * <p> 032 * NOTE: Some material in this file was modified from other portions of the 033 * support infrastructure. 034 * 035 * @author Paul Bender, Copyright (C) 2009-2010 036 */ 037public class XNetSimulatorAdapter extends XNetSimulatorPortController implements Runnable { 038 039 private boolean outputBufferEmpty = true; 040 041 private int csStatus; 042 // status flags from the XpressNet Documentation. 043 private static final int CS_EMERGENCY_STOP = 0x01; // bit 0 044 // 0x00 means normal mode. 045 private static final int CS_NORMAL_MODE = 0x00; 046 047 // information about the last throttle command(s). 048 private int currentSpeedStepMode = XNetConstants.LOCO_SPEED_128; 049 private int currentSpeedStep = 0; 050 private int functionGroup1 = 0; 051 private int functionGroup2 = 0; 052 private int functionGroup3 = 0; 053 private int functionGroup4 = 0; 054 private int functionGroup5 = 0; 055 //private int functionGroup6 = 0; 056 //private int functionGroup7 = 0; 057 //private int functionGroup8 = 0; 058 //private int functionGroup9 = 0; 059 //private int functionGroup10 = 0; 060 061 private int momentaryGroup1 = 0; 062 private int momentaryGroup2 = 0; 063 private int momentaryGroup3 = 0; 064 private int momentaryGroup4 = 0; 065 private int momentaryGroup5 = 0; 066 //private int momentaryGroup6 = 0; 067 //private int momentaryGroup7 = 0; 068 //private int momentaryGroup8 = 0; 069 //private int momentaryGroup9 = 0; 070 //private int momentaryGroup10 = 0; 071 072 /** 073 * Accessory state cache. A "1" bit means THROWN, "0" means 074 * CLOSED. 075 */ 076 private final BitSet accessoryState = new BitSet(1024); 077 078 /** 079 * Bit is set if the accessory was operated. 080 */ 081 private final BitSet accessoryOperated = new BitSet(1024); 082 083 public XNetSimulatorAdapter() { 084 setPort(Bundle.getMessage("None")); 085 try { 086 PipedOutputStream tempPipeI = new ImmediatePipedOutputStream(); 087 pout = new DataOutputStream(tempPipeI); 088 inpipe = new DataInputStream(new PipedInputStream(tempPipeI)); 089 PipedOutputStream tempPipeO = new ImmediatePipedOutputStream(); 090 outpipe = new DataOutputStream(tempPipeO); 091 pin = new DataInputStream(new PipedInputStream(tempPipeO)); 092 } catch (java.io.IOException e) { 093 log.error("init (pipe): Exception",e); 094 return; 095 } 096 csStatus = CS_NORMAL_MODE; 097 } 098 099 @Override 100 public String openPort(String portName, String appName) { 101 // open the port in XpressNet mode, check ability to set moderators 102 setPort(portName); 103 return null; // normal operation 104 } 105 106 /** 107 * Tell if the output buffer is empty or full. This should only be set to 108 * false by external processes. 109 * 110 * @param s true if the buffer is empty; false otherwise 111 */ 112 @Override 113 public synchronized void setOutputBufferEmpty(boolean s) { 114 outputBufferEmpty = s; 115 } 116 117 /** 118 * Can the port accept additional characters? The state of CTS determines 119 * this, as there seems to be no way to check the number of queued bytes and 120 * buffer length. This might go false for short intervals, but it might also 121 * stick off if something goes wrong. 122 */ 123 @Override 124 public boolean okToSend() { 125 boolean checkBuffer = true; 126 if (checkBuffer) { 127 log.debug("Buffer Empty: {}", outputBufferEmpty); 128 return (outputBufferEmpty && super.okToSend()); 129 } else { 130 log.debug("No Flow Control or Buffer Check"); 131 return (super.okToSend()); 132 } 133 } 134 135 /** 136 * Set up all of the other objects to operate with an XNetSimulator connected 137 * to this port. 138 */ 139 @Override 140 public void configure() { 141 // connect to a packetizing traffic controller 142 XNetTrafficController packets = new XNetPacketizer(new LenzCommandStation()); 143 configure(packets); 144 } 145 146 protected void configure(XNetTrafficController packets) { 147 packets.connectPort(this); 148 149 // start operation 150 this.getSystemConnectionMemo().setXNetTrafficController(packets); 151 152 sourceThread = new Thread(this); 153 sourceThread.start(); 154 155 new XNetInitializationManager() 156 .memo(this.getSystemConnectionMemo()) 157 .setDefaults() 158 .versionCheck() 159 .setTimeout(30000) 160 .init(); 161 } 162 163 // Base class methods for the XNetSimulatorPortController interface 164 165 @Override 166 public DataInputStream getInputStream() { 167 if (pin == null) { 168 log.error("getInputStream called before load(), stream not available"); 169 ConnectionStatus.instance().setConnectionState( 170 this.getSystemConnectionMemo().getUserName(), 171 this.getCurrentPortName(), ConnectionStatus.CONNECTION_DOWN); 172 } 173 return pin; 174 } 175 176 @Override 177 public DataOutputStream getOutputStream() { 178 if (pout == null) { 179 log.error("getOutputStream called before load(), stream not available"); 180 ConnectionStatus.instance().setConnectionState( 181 this.getSystemConnectionMemo().getUserName(), 182 this.getCurrentPortName(), ConnectionStatus.CONNECTION_DOWN); 183 } 184 return pout; 185 } 186 187 @Override 188 public boolean status() { 189 return (pout != null && pin != null); 190 } 191 192 @Override 193 public void run() { // start a new thread 194 // this thread has one task. It repeatedly reads from the input pipe 195 // and writes modified data to the output pipe. This is the heart 196 // of the command station simulation. 197 log.debug("Simulator Thread Started"); 198 ConnectionStatus.instance().setConnectionState( 199 this.getSystemConnectionMemo().getUserName(), 200 this.getCurrentPortName(), ConnectionStatus.CONNECTION_UP); 201 for (;;) { 202 XNetMessage m = readMessage(); 203 log.debug("Simulator Thread received message {}", m); 204 XNetReply r = generateReply(m); 205 writeReply(r); 206 log.debug("Simulator Thread sent Reply {}", r); 207 } 208 } 209 210 // Read one incoming message from the buffer 211 // and set outputBufferEmpty to true. 212 private XNetMessage readMessage() { 213 XNetMessage msg = null; 214 try { 215 msg = loadChars(); 216 } catch (java.io.IOException e) { 217 // should do something meaningful here. 218 ConnectionStatus.instance().setConnectionState( 219 this.getSystemConnectionMemo().getUserName(), 220 this.getCurrentPortName(), ConnectionStatus.CONNECTION_DOWN); 221 222 } 223 setOutputBufferEmpty(true); 224 return (msg); 225 } 226 227 // This is the heart of the simulation. It translates an 228 // incoming XNetMessage into an outgoing XNetReply. 229 private XNetReply generateReply(XNetMessage m) { 230 XNetReply reply = new XNetReply(); 231 switch (m.getElement(0) & 0xff) { 232 233 case XNetConstants.CS_REQUEST: 234 switch (m.getElement(1) & 0xff ) { 235 case XNetConstants.CS_VERSION: 236 reply = xNetVersionReply(); 237 break; 238 case XNetConstants.RESUME_OPS: 239 csStatus = CS_NORMAL_MODE; 240 reply = normalOpsReply(); 241 break; 242 case XNetConstants.EMERGENCY_OFF: 243 csStatus = CS_EMERGENCY_STOP; 244 reply = everythingOffReply(); 245 break; 246 case XNetConstants.CS_STATUS: 247 reply = csStatusReply(); 248 break; 249 case XNetConstants.SERVICE_MODE_CSRESULT: 250 default: 251 reply = notSupportedReply(); 252 } 253 break; 254 case XNetConstants.LI_VERSION_REQUEST: 255 reply.setOpCode(XNetConstants.LI_VERSION_RESPONSE); 256 reply.setElement(1, 0x00); // set the hardware type to 0 257 reply.setElement(2, 0x00); // set the firmware version to 0 258 reply.setElement(3, 0x00); // set the parity byte to 0 259 reply.setParity(); 260 break; 261 case XNetConstants.LOCO_OPER_REQ: 262 switch (m.getElement(1) & 0xff ) { 263 case XNetConstants.LOCO_SPEED_14: 264 currentSpeedStepMode = XNetConstants.LOCO_SPEED_14; 265 currentSpeedStep = m.getElement(4); 266 reply = okReply(); 267 break; 268 case XNetConstants.LOCO_SPEED_27: 269 currentSpeedStepMode = XNetConstants.LOCO_SPEED_27; 270 currentSpeedStep = m.getElement(4); 271 reply = okReply(); 272 break; 273 case XNetConstants.LOCO_SPEED_28: 274 currentSpeedStepMode = XNetConstants.LOCO_SPEED_28; 275 currentSpeedStep = m.getElement(4); 276 reply = okReply(); 277 break; 278 case XNetConstants.LOCO_SPEED_128: 279 currentSpeedStepMode = XNetConstants.LOCO_SPEED_128; 280 currentSpeedStep = m.getElement(4); 281 reply = okReply(); 282 break; 283 284 case XNetConstants.LOCO_SET_FUNC_GROUP1: 285 functionGroup1 = m.getElement(4); 286 reply = okReply(); 287 break; 288 case XNetConstants.LOCO_SET_FUNC_GROUP2: 289 functionGroup2 = m.getElement(4); 290 reply = okReply(); 291 break; 292 case XNetConstants.LOCO_SET_FUNC_GROUP3: 293 functionGroup3 = m.getElement(4); 294 reply = okReply(); 295 break; 296 case XNetConstants.LOCO_SET_FUNC_GROUP4: 297 functionGroup4 = m.getElement(4); 298 reply = okReply(); 299 break; 300 case XNetConstants.LOCO_SET_FUNC_GROUP5: 301 functionGroup5 = m.getElement(4); 302 reply = okReply(); 303 break; 304 305 case XNetConstants.LOCO_SET_FUNC_GROUP1_MOMENTARY: 306 momentaryGroup1 = m.getElement(4); 307 reply = okReply(); 308 break; 309 case XNetConstants.LOCO_SET_FUNC_GROUP2_MOMENTARY: 310 momentaryGroup2 = m.getElement(4); 311 reply = okReply(); 312 break; 313 case XNetConstants.LOCO_SET_FUNC_GROUP3_MOMENTARY: 314 momentaryGroup3 = m.getElement(4); 315 reply = okReply(); 316 break; 317 case XNetConstants.LOCO_SET_FUNC_GROUP4_MOMENTARY: 318 momentaryGroup4 = m.getElement(4); 319 reply = okReply(); 320 break; 321 case XNetConstants.LOCO_SET_FUNC_GROUP5_MOMENTARY: 322 momentaryGroup5 = m.getElement(4); 323 reply = okReply(); 324 break; 325 326 327 case XNetConstants.LOCO_SET_FUNC_GROUP6: 328 //functionGroup6 = m.getElement(4); 329 //reply = okReply(); 330 //break; 331 case XNetConstants.LOCO_SET_FUNC_GROUP7: 332 //functionGroup7 = m.getElement(4); 333 //reply = okReply(); 334 //break; 335 case XNetConstants.LOCO_SET_FUNC_GROUP8: 336 //functionGroup8 = m.getElement(4); 337 //reply = okReply(); 338 //break; 339 case XNetConstants.LOCO_SET_FUNC_GROUP9: 340 //functionGroup9 = m.getElement(4); 341 //reply = okReply(); 342 //break; 343 case XNetConstants.LOCO_SET_FUNC_GROUP10: 344 //functionGroup10 = m.getElement(4); 345 //reply = okReply(); 346 //break; 347 case XNetConstants.LOCO_SET_FUNC_GROUP6_MOMENTARY: 348 //momentaryGroup6 = m.getElement(4); 349 //reply = okReply(); 350 //break; 351 case XNetConstants.LOCO_SET_FUNC_GROUP7_MOMENTARY: 352 //momentaryGroup7 = m.getElement(4); 353 //reply = okReply(); 354 //break; 355 case XNetConstants.LOCO_SET_FUNC_GROUP8_MOMENTARY: 356 //momentaryGroup8 = m.getElement(4); 357 //reply = okReply(); 358 //break; 359 case XNetConstants.LOCO_SET_FUNC_GROUP9_MOMENTARY: 360 //momentaryGroup9 = m.getElement(4); 361 //reply = okReply(); 362 //break; 363 case XNetConstants.LOCO_SET_FUNC_GROUP10_MOMENTARY: 364 //momentaryGroup10 = m.getElement(4); 365 reply = okReply(); 366 break; 367 368 case XNetConstants.LOCO_ADD_MULTI_UNIT_REQ: 369 case XNetConstants.LOCO_REM_MULTI_UNIT_REQ: 370 case XNetConstants.LOCO_IN_MULTI_UNIT_REQ_FORWARD: 371 case XNetConstants.LOCO_IN_MULTI_UNIT_REQ_BACKWARD: 372 default: 373 reply = notSupportedReply(); 374 break; 375 } 376 break; 377 case XNetConstants.ALL_ESTOP: // ALL_ESTOP is XNet V4 378 csStatus = CS_EMERGENCY_STOP; 379 reply = emergencyStopReply(); 380 break; 381 case XNetConstants.EMERGENCY_STOP: 382 case XNetConstants.EMERGENCY_STOP_XNETV1V2: 383 reply = okReply(); 384 break; 385 case XNetConstants.ACC_OPER_REQ: 386 // LZ100 and LZV100 respond with an ACC_INFO_RESPONSE. 387 // but XpressNet standard says to no response (which causes 388 // the interface to send an OK reply). 389 reply = accReqReply(m); 390 break; 391 case XNetConstants.ACC_INFO_REQ: 392 reply = accInfoReply(m); 393 break; 394 case XNetConstants.LOCO_STATUS_REQ: 395 switch (m.getElement(1) & 0xff ) { 396 case XNetConstants.LOCO_INFO_REQ_V3: 397 reply.setOpCode(XNetConstants.LOCO_INFO_NORMAL_UNIT); 398 reply.setElement(1, currentSpeedStepMode); 399 reply.setElement(2, currentSpeedStep); // set the speed 400 // direction reverse 401 reply.setElement(3, functionGroup1); // set function group 1 402 reply.setElement(4, (functionGroup2 & 0x0f) + ((functionGroup3 & 0x0f) << 4)); // set function group 2 and 3 403 reply.setElement(5, 0x00); // set the parity byte to 0 404 reply.setParity(); // set the parity correctly. 405 break; 406 case XNetConstants.LOCO_INFO_REQ_FUNC: 407 reply.setOpCode(XNetConstants.LOCO_INFO_RESPONSE); 408 reply.setElement(1, XNetConstants.LOCO_FUNCTION_STATUS); // momentary function status 409 reply.setElement(2, momentaryGroup1); // set function group 1 410 reply.setElement(3, (momentaryGroup2 & 0x0f) + ((momentaryGroup3 * 0x0f) << 4)); // set function group 2 and 3 411 reply.setElement(4, 0x00); // set the parity byte to 0 412 reply.setParity(); // set the parity correctly. 413 break; 414 case XNetConstants.LOCO_INFO_REQ_FUNC_HI_ON: 415 reply.setOpCode(XNetConstants.LOCO_INFO_RESPONSE); 416 reply.setElement(1, XNetConstants.LOCO_FUNCTION_STATUS_HIGH); // F13-F28 function on/off status 417 reply.setElement(2, functionGroup4); // set function group 4 418 reply.setElement(3, functionGroup5); // set function group 5 419 reply.setElement(4, 0x00); // set the parity byte to 0 420 reply.setParity(); // set the parity correctly. 421 break; 422 case XNetConstants.LOCO_INFO_REQ_FUNC_HI_MOM: 423 reply.setOpCode(XNetConstants.LOCO_INFO_NORMAL_UNIT); 424 reply.setElement(1, XNetConstants.LOCO_FUNCTION_STATUS_HIGH_MOM); // F13-F28 momentary function status 425 reply.setElement(2, momentaryGroup4); // set function group 4 426 reply.setElement(3, momentaryGroup5); // set function group 5 427 reply.setElement(4, 0x00); // set the parity byte to 0 428 reply.setParity(); // set the parity correctly. 429 break; 430 default: 431 reply = notSupportedReply(); 432 } 433 break; 434 case XNetConstants.OPS_MODE_PROG_REQ: 435 int operation = m.getElement(4) & 0xFC; 436 switch(operation & 0xff ) { 437 case 0xEC: 438 log.debug("Write CV in Ops Mode Request Received"); 439 reply = okReply(); 440 break; 441 case 0xE4: 442 log.debug("Verify CV in Ops Mode Request Received"); 443 reply = okReply(); 444 break; 445 case 0xE8: 446 log.debug("Ops Mode Bit Request Received"); 447 reply = okReply(); 448 break; 449 default: 450 reply=notSupportedReply(); 451 } 452 break; 453 case XNetConstants.LI101_REQUEST: 454 case XNetConstants.CS_SET_POWERMODE: 455 //case XNetConstants.PROG_READ_REQUEST: //PROG_READ_REQUEST 456 //and CS_SET_POWERMODE 457 //have the same value 458 case XNetConstants.PROG_WRITE_REQUEST: 459 case XNetConstants.LOCO_DOUBLEHEAD: 460 default: 461 reply = notSupportedReply(); 462 } 463 return (reply); 464 } 465 466 // We have a few canned response messages. 467 // Create an Unsupported XNetReply message 468 private XNetReply notSupportedReply() { 469 XNetReply r = new XNetReply(); 470 r.setOpCode(XNetConstants.CS_INFO); 471 r.setElement(1, XNetConstants.CS_NOT_SUPPORTED); 472 r.setElement(2, 0x00); // set the parity byte to 0 473 r.setParity(); 474 return r; 475 } 476 477 // Create an OK XNetReply message 478 private XNetReply okReply() { 479 XNetReply r = new XNetReply(); 480 r.setOpCode(XNetConstants.LI_MESSAGE_RESPONSE_HEADER); 481 r.setElement(1, XNetConstants.LI_MESSAGE_RESPONSE_SEND_SUCCESS); 482 r.setElement(2, 0x00); // set the parity byte to 0 483 r.setParity(); 484 return r; 485 } 486 487 // Create a "Normal Operations Resumed" message 488 private XNetReply normalOpsReply() { 489 XNetReply r = new XNetReply(); 490 r.setOpCode(XNetConstants.CS_INFO); 491 r.setElement(1, XNetConstants.BC_NORMAL_OPERATIONS); 492 r.setElement(2, 0x00); // set the parity byte to 0 493 r.setParity(); 494 return r; 495 } 496 497 // Create a broadcast "Everything Off" reply 498 private XNetReply everythingOffReply() { 499 XNetReply r = new XNetReply(); 500 r.setOpCode(XNetConstants.CS_INFO); 501 r.setElement(1, XNetConstants.BC_EVERYTHING_OFF); 502 r.setElement(2, 0x00); // set the parity byte to 0 503 r.setParity(); 504 return r; 505 } 506 507 // Create a broadcast "Emergency Stop" reply 508 private XNetReply emergencyStopReply() { 509 XNetReply r = new XNetReply(); 510 r.setOpCode(XNetConstants.BC_EMERGENCY_STOP); 511 r.setElement(1, XNetConstants.BC_EVERYTHING_STOP); 512 r.setElement(2, 0x00); // set the parity byte to 0 513 r.setParity(); 514 return r; 515 } 516 517 // Create a reply to a request for the XpressNet Version 518 private XNetReply xNetVersionReply() { 519 XNetReply reply = new XNetReply(); 520 reply.setOpCode(XNetConstants.CS_SERVICE_MODE_RESPONSE); 521 reply.setElement(1, XNetConstants.CS_SOFTWARE_VERSION); 522 reply.setElement(2, 0x40); // indicate we are version 4.0 523 reply.setElement(3, 0x00 ); // indicate we are an LZ100 524 reply.setElement(4, 0x00); // set the parity byte to 0 525 reply.setParity(); 526 return reply; 527 } 528 529 // Create a reply to a request for the Command Station Status 530 private XNetReply csStatusReply() { 531 XNetReply reply = new XNetReply(); 532 reply.setOpCode(XNetConstants.CS_REQUEST_RESPONSE); 533 reply.setElement(1, XNetConstants.CS_STATUS_RESPONSE); 534 reply.setElement(2, csStatus); 535 reply.setElement(3, 0x00); // set the parity byte to 0 536 reply.setParity(); 537 return reply; 538 } 539 540 /** 541 * Return the turnout feedback type. 542 * <ul> 543 * <li>0x00 - turnout without feedback, ie DR5000 544 * <li>0x01 - turnout with feedback, ie NanoX 545 * <li>0x10 - feedback module 546 * </ul> 547 * @return the turnout type reported by this station. 548 */ 549 protected int getTurnoutFeedbackType() { 550 return 0x01; 551 } 552 553 /** 554 * Returns accessory state, in the Operation Info Reply bit format. If the 555 * accessory has not been operated yet, returns 00 (not operated). 556 * 557 * @param a accessory number 558 * @return two bits representing the accessory state. 559 */ 560 protected int getAccessoryStateBits(int a) { 561 if (!accessoryOperated.get(a)) { 562 return 0x00; 563 } 564 boolean state = accessoryState.get(a); 565 int zbits = state ? 0b10 : 0b01; 566 return zbits; 567 } 568 569 protected XNetReply accInfoReply(XNetMessage m) { 570 if (m.getElement(1) >= 64) { 571 return feedbackInfoReply(m); 572 } else { 573 boolean nibble = (m.getElement(2) & 0x01) == 0x01; 574 int ba = m.getElement(1); 575 return accInfoReply(ba, nibble); 576 } 577 } 578 579 protected XNetReply feedbackInfoReply(XNetMessage m) { 580 XNetReply reply = new XNetReply(); 581 reply.setOpCode(XNetConstants.ACC_INFO_RESPONSE); 582 reply.setElement(1, m.getElement(1)); 583 // treat as feedback encoder request. 584 if (m.getElement(2) == 0x80) { 585 reply.setElement(2, 0x40); 586 } else { 587 reply.setElement(2, 0x50); 588 } 589 reply.setElement(3, 0x00); 590 reply.setParity(); 591 return reply; 592 } 593 594 /** 595 * Creates a reply packet for a turnout/accessory. 596 * @param baseAddress base address for the feedback, the 4-turnout block; numbered from 0 597 * @param nibble lower or upper nibble (2 turnout block) delivered in the reply 598 * @return constructed reply. 599 */ 600 protected XNetReply accInfoReply(int baseAddress, boolean nibble) { 601 XNetReply r = new XNetReply(); 602 r.setOpCode(XNetConstants.ACC_INFO_RESPONSE); 603 r.setElement(1, baseAddress); 604 int nibbleVal = 0; 605 int a = baseAddress * 4 + 1; 606 if (nibble) { 607 a += 2; 608 } 609 int zbits = getAccessoryStateBits(a++); 610 nibbleVal |= zbits; 611 zbits = getAccessoryStateBits(a++); 612 nibbleVal |= (zbits << 2); 613 r.setElement(2, // 0 << 7 | // turnout movement completed, unsupported; always done 614 getTurnoutFeedbackType() << 5 | // two bits: accessory without feedback 615 (nibble ? 1 : 0) << 4 | // upper / lower nibble 616 nibbleVal & 0x0f); 617 r.setElement(3, 0); 618 r.setParity(); 619 return r; 620 } 621 622 /** 623 * Generate reply to accessory request command. 624 * The returned XNetReply is the first to be returned by this simulated command station. 625 * @param address the accessory address 626 * @param output the output to be manipulated 627 * @param state true if output should be on, false for off 628 * @param previousAccessoryState the previous accessory state 629 * @return the reply instance. 630 */ 631 protected XNetReply generateAccRequestReply(int address, int output, boolean state, boolean previousAccessoryState) { 632 XNetReply r; 633 634 if (state) { 635 if (accessoryOperated.get(address) && previousAccessoryState == (output != 0)) { 636 // just OK, the accessory is in the same state. 637 return okReply(); 638 } else { 639 accessoryOperated.set(address); 640 r = accInfoReply(address); 641 r.setUnsolicited(); 642 } 643 } else { 644 accessoryOperated.set(address); 645 // generate just OK to OFF 646 r = okReply(); 647 } 648 return r; 649 } 650 651 /** 652 * Creates a reply for the specific turnout dcc address. 653 * @param dccTurnoutAddress the turnout address 654 * @return a reply packet 655 */ 656 protected XNetReply accInfoReply(int dccTurnoutAddress) { 657 dccTurnoutAddress--; 658 int baseAddress = dccTurnoutAddress / 4; 659 boolean upperNibble = dccTurnoutAddress % 4 >= 2; 660 return accInfoReply(baseAddress, upperNibble); 661 } 662 663 protected XNetReply accReqReply(XNetMessage m) { 664 int baseaddress = m.getElement(1); 665 int subaddress = (m.getElement(2) & 0x06) >> 1; 666 int address = (baseaddress * 4) + subaddress + 1; 667 int output = m.getElement(2) & 0x01; 668 boolean on = ((m.getElement(2) & 0x08)) == 0x08; 669 boolean oldState = accessoryState.get(address); 670 if (on) { 671 accessoryState.set(address, output != 0); 672 } 673 log.debug("Received command {} ... {}", m, m.toMonitorString()); 674 return generateAccRequestReply(address, output, on, oldState); 675 } 676 677 private void writeReply(XNetReply r) { 678 int i; 679 int len = (r.getElement(0) & 0x0f) + 2; // opCode+Nbytes+ECC 680 for (i = 0; i < len; i++) { 681 try { 682 outpipe.writeByte((byte) r.getElement(i)); 683 } catch (java.io.IOException ex) { 684 ConnectionStatus.instance().setConnectionState( 685 this.getSystemConnectionMemo().getUserName(), 686 this.getCurrentPortName(), ConnectionStatus.CONNECTION_DOWN); 687 } 688 } 689 } 690 691 /** 692 * Get characters from the input source, and file a message. 693 * <p> 694 * Returns only when the message is complete. 695 * <p> 696 * Only used in the Receive thread. 697 * 698 * @return filled message 699 * @throws IOException when presented by the input source. 700 */ 701 protected XNetMessage loadChars() throws java.io.IOException { 702 int i; 703 byte char1; 704 char1 = readByteProtected(inpipe); 705 int len = (char1 & 0x0f) + 2; // opCode+Nbytes+ECC 706 XNetMessage msg = new XNetMessage(len); 707 msg.setElement(0, char1 & 0xFF); 708 for (i = 1; i < len; i++) { 709 char1 = readByteProtected(inpipe); 710 msg.setElement(i, char1 & 0xFF); 711 } 712 return msg; 713 } 714 715 /** 716 * Read a single byte, protecting against various timeouts, etc. 717 * <p> 718 * When a port is set to have a receive timeout (via the 719 * enableReceiveTimeout() method), some will return zero bytes or an 720 * EOFException at the end of the timeout. In that case, the read should be 721 * repeated to get the next real character. 722 * @param istream the input data source 723 * @return the next byte, waiting for it to become available 724 * @throws java.io.IOException from the underlying operations 725 */ 726 protected byte readByteProtected(DataInputStream istream) throws java.io.IOException { 727 byte[] rcvBuffer = new byte[1]; 728 while (true) { // loop will repeat until character found 729 int nchars; 730 nchars = istream.read(rcvBuffer, 0, 1); 731 if (nchars > 0) { 732 return rcvBuffer[0]; 733 } 734 } 735 } 736 737 private DataOutputStream pout = null; // for output to other classes 738 private DataInputStream pin = null; // for input from other classes 739 // internal ends of the pipes 740 private DataOutputStream outpipe = null; // feed pin 741 private DataInputStream inpipe = null; // feed pout 742 private Thread sourceThread; 743 744 private static final Logger log = LoggerFactory.getLogger(XNetSimulatorAdapter.class); 745 746}