001package jmri.jmrix.loconet.locoio; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import jmri.beans.PropertyChangeSupport; 006import jmri.jmrix.loconet.LnConstants; 007import jmri.jmrix.loconet.LnTrafficController; 008import jmri.jmrix.loconet.LocoNetListener; 009import jmri.jmrix.loconet.LocoNetMessage; 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013/** 014 * Data associated with a LocoIO device. 015 * 016 * @author John Plocher, January 28, 2007 017 */ 018public class LocoIOData extends PropertyChangeSupport 019 implements LocoNetListener, PropertyChangeListener { 020 021 private int sv0; 022 private int unitAddress; 023 private int unitSubAddress; 024 private LnTrafficController tc; 025 026 /** 027 * Define the number of rows in the table, which is also the number of 028 * "channels" in a single LocoIO unit. 029 */ 030 private int _numRows = 16; 031 /** 032 * LocoBuffer always has address 0x01 0x50. 033 */ 034 private static final int LocoBufferAddress = 0x0150; 035 private String locoBufferVersion = Bundle.getMessage("StateUnknown"); 036 private String locoIOVersion = Bundle.getMessage("StateUnknown"); 037 private String status = Bundle.getMessage("StateUnknown"); // LocoIO activity status 038 /** 039 * Per-port SV data. 040 */ 041 private LocoIOMode[] lim = new LocoIOMode[_numRows]; 042 private int[] addr = new int[_numRows]; 043 private int[] sv = new int[_numRows]; 044 private int[] v1 = new int[_numRows]; 045 private int[] v2 = new int[_numRows]; 046 private int[] readState = new int[_numRows]; 047 private int[] writeState = new int[_numRows]; 048 049 /** 050 * Record whether this pin is looking to capture a value from the LocoNet. 051 */ 052 private boolean[] capture = new boolean[_numRows]; 053 private String[] mode = new String[_numRows]; 054 055 private LocoIOModeList validmodes; 056 057 /** 058 * Create a new instance of LocoIOData. 059 * @param unitAddr unit address. 060 * @param unitSubAddr unit SubAddress. 061 * @param tc system connection traffic controller. 062 */ 063 public LocoIOData(int unitAddr, int unitSubAddr, LnTrafficController tc) { 064 timeoutcounter = 0; 065 unitAddress = unitAddr; 066 unitSubAddress = unitSubAddr; 067 validmodes = new LocoIOModeList(); 068 069 for (int i = 0; i < _numRows; i++) { 070 setMode(i, "<none>"); // NOI18N 071 lim[i] = null; 072 setAddr(i, 0); 073 setSV(i, 0); 074 setV1(i, 0); 075 setV2(i, 0); 076 readState[i] = NONE; 077 writeState[i] = NONE; 078 capture[i] = false; 079 } 080 // addPropertyChangeListener(this); 081 this.tc = tc; 082 // for now, we're always listening to LocoNet 083 if (tc != null) { 084 tc.addLocoNetListener(~0, this); 085 } else { 086 log.error("No LocoNet interface available"); // NOI18N 087 } 088 } 089 090 @Override 091 public void propertyChange(PropertyChangeEvent evt) { 092 log.info("LocoIOData: {} := {} from {}", // NOI18N 093 evt.getPropertyName(), evt.getNewValue(), evt.getSource()); 094 } 095 096 /** 097 * Address and SubAddress of this device. 098 * <p> 099 * High byte of the Address is fixed to 0x01 100 * <br> 101 * Low byte Address must be in the range of 0x01 .. 0x4F, 0x51 .. 0x7F 102 * <br> 103 * (0x50 is reserved for the LocoBuffer) 104 * <br> 105 * The subAddress is in the range of 0x01 .. 0x7E 106 * <br> 107 * (0x7F is reserved) 108 * @param unit unit address. 109 * @param unitSub unit subAddress. 110 */ 111 public synchronized void setUnitAddress(int unit, int unitSub) { 112 setUnitAddress(unit); 113 setUnitSubAddress(unitSub); 114 } 115 116 public synchronized void setUnitAddress(int unit) { 117 firePropertyChange("UnitAddress", unitAddress, 0x0100 | (unit & 0x07F)); // NOI18N 118 unitAddress = 0x0100 | (unit & 0x07F); // protect against high bits set 119 } 120 121 public synchronized void setUnitSubAddress(int unitSub) { 122 firePropertyChange("UnitSubAddress", unitSubAddress, unitSub & 0x07F); // NOI18N 123 unitSubAddress = unitSub & 0x07F; 124 } 125 126 public synchronized int getUnitAddress() { 127 return unitAddress & 0x07F; 128 } 129 130 public synchronized int getUnitSubAddress() { 131 return unitSubAddress & 0x07F; 132 } 133 134 /** 135 * No LocoIO Board level configuration. 136 * <pre> 137 * Bit 0: 0 = default, 1 = Port Refresh 138 * Bit 1: 0 = Fixed code PBs, 1 = Alternated code PBs 139 * Bit 2: 0 = default - Not used 140 * Bit 3: 0 = default, 1 = Ports 5-12 are Servo Ports 141 * Bit 4-7: Blink Rate 142 * 143 * Add/support the additional config options for HDL boards - 144 * Work has moved to xml decoder definition Public_Domain_HDL_LocoIO, is included there since 4.21.2 145 * </pre> 146 * @param portRefresh port refresh value, bit 0. 147 * @param altCodePBs alternated code PBs, bit 1. 148 * @param isServo servo port, bit 3. 149 * @param blinkRate blink rate, bits 4-7. 150 */ 151 public void setUnitConfig(int portRefresh, int altCodePBs, int isServo, int blinkRate) { 152 int newsv0 = ((portRefresh & 0x01)) | // bit 0 153 ((altCodePBs & 0x01) << 0x01) | // bit 1 154 // bit 2 is left at zero 155 // later LocoServo boards store 2 pos/4 pos type in bits 2-3, see Public_Domain_HDL_LocoIO 156 ((isServo & 0x01) << 0x03) | // bit 3 157 ((blinkRate & 0x0F) << 0x04); // bits 4-7 158 firePropertyChange("UnitConfig", sv0, newsv0); // NOI18N 159 sv0 = newsv0; 160 } 161 162 public int getUnitConfig() { 163 return sv0 & 0xFF; 164 } 165 166 public void setLBVersion(String version) { 167 locoBufferVersion = version; 168 firePropertyChange("LBVersionChange", "", locoBufferVersion); // NOI18N 169 } 170 171 public String getLBVersion() { 172 return locoBufferVersion; 173 } 174 175 public void setLIOVersion(String version) { 176 locoIOVersion = version; 177 firePropertyChange("LIOVersionChange", "", locoIOVersion); // NOI18N 178 } 179 180 public String getLIOVersion() { 181 return locoBufferVersion; 182 } 183 184 public void setStatus(String msg) { 185 status = msg; 186 firePropertyChange("StatusChange", "", status); // NOI18N 187 } 188 189 public String getStatus() { 190 return status; 191 } 192 193 public void setSV(int channel, int value) { 194 sv[channel] = value & 0xFF; 195 firePropertyChange("PortChange", -1, channel); // NOI18N 196 } 197 198 public int getSV(int channel) { 199 return sv[channel] & 0xFF; 200 } 201 202 public void setV1(int channel, LocoIOMode l, int address) { 203 setV1(channel, validmodes.addressToValue1(l, getAddr(channel))); 204 } 205 206 public void setV1(int channel, int value) { 207 v1[channel] = value & 0xFF; 208 firePropertyChange("PortChange", -1, channel); // NOI18N 209 } 210 211 public int getV1(int channel) { 212 return v1[channel] & 0xFF; 213 } 214 215 public void setV2(int channel, LocoIOMode l, int address) { 216 setV2(channel, validmodes.addressToValue2(l, getAddr(channel))); 217 } 218 219 public void setV2(int channel, int value) { 220 v2[channel] = value & 0xFF; 221 firePropertyChange("PortChange", -1, channel); // NOI18N 222 } 223 224 public int getV2(int channel) { 225 return v2[channel] & 0xFF; 226 } 227 228 /** 229 * Set new value in addr field (for the address info used in each LocoIO channel). 230 * 231 * @param channel integer value of the addresses in use for this row 232 * (0 = invalid) 233 * @param value channel value. 234 */ 235 public void setAddr(int channel, int value) { 236 addr[channel] = value & 0x7FF; 237 firePropertyChange("PortChange", -1, channel); // NOI18N 238 } 239 240 public int getAddr(int channel) { 241 return addr[channel] & 0x7FF; 242 } 243 244 public void setMode(int channel, String m) { 245 mode[channel] = m; 246 firePropertyChange("PortChange", -1, channel); // NOI18N 247 } 248 249 public String getMode(int channel) { 250 return mode[channel]; 251 } 252 253 public void setLIM(int channel, String s) { 254 if (validmodes != null) { 255 setLIM(channel, validmodes.getLocoIOModeFor(s)); 256 } 257 } 258 259 public void setLIM(int channel) { 260 if (validmodes != null) { 261 setLIM(channel, validmodes.getLocoIOModeFor(getSV(channel), getV1(channel), getV2(channel))); 262 } 263 } 264 265 public void setLIM(int channel, LocoIOMode m) { 266 lim[channel] = m; 267 firePropertyChange("PortChange", -1, channel); // NOI18N 268 } 269 270 public LocoIOMode getLIM(int channel) { 271 return lim[channel]; 272 } 273 274 public void readValues(int channel) { 275 readState[channel] = READ; 276 issueNextOperation(); 277 } 278 279 public void captureValues(int channel) { 280 capture[channel] = true; 281 } 282 283 public void writeValues(int channel) { 284 writeState[channel] = WRITE; 285 issueNextOperation(); 286 } 287 288 /** 289 * Start reading all rows back. 290 */ 291 public void readAll() { 292 for (int row = 0; row < _numRows; row++) { 293 readState[row] = READ; 294 } 295 issueNextOperation(); 296 } 297 298 /** 299 * Start writing all rows out. 300 */ 301 public void writeAll() { 302 for (int row = 0; row < _numRows; row++) { 303 writeState[row] = WRITE; 304 } 305 issueNextOperation(); 306 } 307 308 public LocoIOModeList getLocoIOModeList() { 309 return validmodes; 310 } 311 312 /** 313 * Code for read activity needed. 314 * See states NONE, READMODE, READINGMODE, READVALUE1, 315 * READINGVALUE1, READVALUE2, READINGVALUE2 316 */ 317 // int[] needRead = new int[_numRows]; 318 protected final int NONE = 0; 319 protected final int READVALUE1 = 1; 320 protected final int READINGVALUE1 = 2; 321 protected final int READVALUE2 = 3; 322 protected final int READINGVALUE2 = 4; 323 protected final int READMODE = 5; 324 protected final int READINGMODE = 6; 325 326 protected final int READ = READVALUE1; // starting state 327 328 /** 329 * Code for write activity needed. See states NONE, WRITEMODE, WRITINGMODE, 330 * WRITEVALUE1, WRITINGVALUE1, WRITEVALUE2, WRITINGVALUE2 331 */ 332 // int[] needWrite = new int[_numRows]; 333 protected final int WRITEVALUE1 = 11; 334 protected final int WRITINGVALUE1 = 12; 335 protected final int WRITEVALUE2 = 13; 336 protected final int WRITINGVALUE2 = 14; 337 protected final int WRITEMODE = 15; 338 protected final int WRITINGMODE = 16; 339 340 protected final int WRITE = WRITEVALUE1; // starting state 341 342 private int lastOpCv = -1; 343 private boolean reading = false; // false means write in progress 344 //private boolean writing = false; 345 346 protected int highPart(int value) { // generally value 1 347 return value / 256; 348 } 349 350 protected int lowPart(int value) { // generally value 2 351 return value - 256 * highPart(value); 352 } 353 354 private String dotme(int val) { 355 int dit; 356 int x = val; 357 StringBuffer ret = new StringBuffer(); 358 if (val == 0) { 359 return "0"; // NOI18N 360 } 361 while (x != 0) { 362 dit = x % 10; 363 ret.insert(0, Integer.toString(dit)); 364 x = x / 10; 365 if (x != 0) { 366 ret.insert(0, "."); 367 } 368 } 369 return ret.toString(); 370 } 371 372 /** 373 * Listen to the LocoNet. We're listening for incoming OPC_PEER_XFR 374 * messages, which might be part of a read or write sequence. We're also 375 * _sometimes_ listening for commands as part of a "capture" operation. 376 * <p> 377 * The incoming LocoNet OPC_PEER_XFR messages don't retain any information 378 * about the CV number or whether it was a read or write operation. We store 379 * the data regardless of whether it was read or write, but we need to 380 * remember the cv number in the lastOpCv member. 381 * 382 * @param m Incoming message 383 */ 384 @Override 385 public synchronized void message(LocoNetMessage m) { 386 // sort out the opCode 387 int opCode = m.getOpCode(); 388 switch (opCode) { 389 case LnConstants.OPC_PEER_XFER: 390 // could be read or write operation 391 // check that src_low_address is our unit, and 392 // dst is "our" LocoBufferAddress 393 int src = m.getElement(2); 394 int dst = m.getElement(3) + m.getElement(4) * 256; 395 int[] packet = m.getPeerXfrData(); 396 397 if (src == lowPart(LocoBufferAddress)) { 398 String lbv = ((packet[2] != 0) ? dotme(packet[2]) : "1.0"); 399 setLBVersion(lbv); 400 } 401 402 if (dst == LocoBufferAddress && src == lowPart(unitAddress) && (packet[4] == unitSubAddress)) { 403 // yes, we assume this is a reply to us 404 stopTimer(); 405 replyReceived(); // advance state 406 407 String fw = ((packet[2] != 0) ? dotme(packet[2]) : "1.3.2"); // NOI18N 408 setLIOVersion(fw); 409 if (packet[0] == LocoIO.LOCOIO_SV_READ || reading) { // read command 410 // get data and store 411 if (lastOpCv >= 0 && lastOpCv <= 50) { 412 413 // there are two formats of the return packet... 414 int data = (packet[2] != 0) ? packet[5] : packet[7]; 415 int channel = (lastOpCv / 3) - 1; 416 if (channel < 0) { 417 log.warn("... channel is less than zero!!!"); // NOI18N 418 channel = 0; 419 } 420 int type = lastOpCv - (channel * 3 + 3); 421 // type = 0 for cv, 1 for value1, 2 for value2 422 // We can't update the mode until we have all three values 423 // Sequence (from state machine below) is V2, V1, Mode 424 log.debug("... updating port {} SV{}({}) = 0x{}", channel, type, type == 1 ? "value1" // NOI18N 425 : type == 2 ? "value2" // NOI18N 426 : type == 0 ? "mode" // NOI18N 427 : "unknown", Integer.toHexString(data)); 428 if (type == 2) { // v2 429 setV2(channel, data); 430 setMode(channel, "<none>"); // NOI18N 431 } else if (type == 1) { // v1 432 setV1(channel, data); 433 setMode(channel, "<none>"); // NOI18N 434 } else if (type == 0) { // cv 435 setSV(channel, data); 436 // Now that we have all the pieces, recalculate mode 437 LocoIOMode lim = validmodes.getLocoIOModeFor(getSV(channel), getV1(channel), getV2(channel)); 438 if (lim == null) { 439 setMode(channel, "<none>"); // NOI18N 440 setAddr(channel, 0); 441 log.debug("Could not find mode!"); // NOI18N 442 } else { 443 setMode(channel, lim.getFullMode()); 444 setAddr(channel, validmodes.valuesToAddress(lim.getOpCode(), getSV(channel), getV1(channel), getV2(channel))); 445 } 446 log.debug("... decoded address (cv={} v1={} v2={}) is {}(0x{})", 447 Integer.toHexString(getSV(channel)), Integer.toHexString(getV1(channel)), 448 Integer.toHexString(getV2(channel)), getAddr(channel), 449 Integer.toHexString(getAddr(channel))); // NOI18N 450 } else { 451 log.warn("OPC_PEER_XFR: Type ({}) is not {0,1,2} for channel {}", type, channel); // NOI18N 452 } 453 // } else { 454 // log.error("last CV recorded is invalid: {}", lastOpCv); 455 } 456 } // end of read processing 457 458 // check for anything else to do 459 issueNextOperation(); 460 return; 461 } else { 462 return; 463 } 464 case LnConstants.OPC_INPUT_REP: // Block Sensors and other general sensor codes 465 log.debug("{} received", LnConstants.OPC_NAME(opCode)); // NOI18N 466 // these might require capture 467 for (int i = 0; i < _numRows; i++) { 468 if (capture[i]) { 469 log.debug("row set for capture: {}", i); // NOI18N 470 // This is a capture request, get address bytes 471 int val1 = m.getElement(1); 472 int val2 = m.getElement(2); 473 // calculate address from val's, save result, mark as done 474 // INPUT_REP's use val2's OPC_SW_REQ_DIR bit as LSB...' 475 setAddr(i, ((val2 & 0x0F) << 5) * 256 + ((val1 & 0x7f) << 1) 476 | (((val2 & LnConstants.OPC_SW_REQ_DIR) == LnConstants.OPC_SW_REQ_DIR) ? 0x01 : 0x00)); 477 capture[i] = false; 478 } 479 } 480 return; 481 482 case LnConstants.OPC_SW_REQ: // Turnout SWITCH Request 483 log.debug("{} received", LnConstants.OPC_NAME(opCode)); // NOI18N 484 // these might require capture 485 for (int i = 0; i < _numRows; i++) { 486 if (capture[i]) { 487 log.debug("row set for capture: {}", i); // NOI18N 488 // This is a capture request, get address bytes 489 int val1 = m.getElement(1); 490 int val2 = m.getElement(2); 491 // calculate address from val's, save result, mark as done 492 int addr = LocoIO.SENSOR_ADR(val1, val2); 493 setAddr(i, addr); 494 capture[i] = false; 495 } 496 } 497 return; 498 default: // we ignore all other LocoNet messages 499 log.debug("{} received (ignored)", LnConstants.OPC_NAME(opCode)); 500 } 501 } 502 503 /** 504 * A valid reply has been received, so the read/write worked, and the state 505 * should be advanced. 506 */ 507 protected synchronized void replyReceived() { 508 timeoutcounter = 0; 509 // READ operations state machine 510 switch (readState[currentPin]) { 511 case NONE: 512 break; // try the write operations 513 case READVALUE1: 514 case READINGVALUE1: 515 readState[currentPin] = READVALUE2; 516 return; 517 case READVALUE2: 518 case READINGVALUE2: 519 readState[currentPin] = READMODE; 520 return; 521 case READMODE: 522 case READINGMODE: 523 readState[currentPin] = NONE; 524 return; 525 default: 526 log.error("Pin {} unexpected read state, can't advance {}", currentPin, readState[currentPin]); // NOI18N 527 readState[currentPin] = NONE; 528 return; 529 } 530 // WRITE operations state machine 531 switch (writeState[currentPin]) { 532 case NONE: 533 return; 534 case WRITEVALUE1: 535 case WRITINGVALUE1: 536 writeState[currentPin] = WRITEVALUE2; 537 return; 538 case WRITEVALUE2: 539 case WRITINGVALUE2: 540 writeState[currentPin] = WRITEMODE; 541 return; 542 case WRITEMODE: 543 case WRITINGMODE: 544 writeState[currentPin] = NONE; 545 return; 546 default: 547 log.error("Pin {} unexpected write state, can't advance {}", currentPin, writeState[currentPin]); // NOI18N 548 writeState[currentPin] = NONE; 549 return; 550 } 551 } 552 553 private int currentPin = 0; 554 555 /** 556 * Look through the table to find the next thing that needs to be read. 557 */ 558 protected synchronized void issueNextOperation() { 559 // stop the timer while we figure this out 560 stopTimer(); 561 // find the first item that needs to be read 562 for (int i = 0; i < _numRows; i++) { 563 currentPin = i; 564 if (readState[i] != NONE) { 565 // yes, needs read. Find what kind 566 log.debug("iNO: readState[{}] = {}", i, readState[i]); 567 switch (readState[i]) { 568 case READVALUE1: 569 case READINGVALUE1: 570 // set new state, send read, then done 571 readState[i] = READINGVALUE1; 572 lastOpCv = i * 3 + 4; 573 setStatus(Bundle.getMessage("StatusReading", lastOpCv, i + 1, 1)); // number port like table 574 sendReadCommand(unitAddress, unitSubAddress, lastOpCv); 575 return; 576 case READVALUE2: 577 case READINGVALUE2: 578 // set new state, send read, then done 579 readState[i] = READINGVALUE2; 580 lastOpCv = i * 3 + 5; 581 setStatus(Bundle.getMessage("StatusReading", lastOpCv, i + 1, 2)); 582 sendReadCommand(unitAddress, unitSubAddress, lastOpCv); 583 return; 584 case READMODE: 585 case READINGMODE: 586 // set new state, send read, then done 587 readState[i] = READINGMODE; 588 lastOpCv = i * 3 + 3; 589 setStatus(Bundle.getMessage("StatusReadMode", lastOpCv, i + 1)); 590 sendReadCommand(unitAddress, unitSubAddress, lastOpCv); 591 return; 592 default: 593 log.error("found an unexpected state: {} on port {}", readState[1], i + 1); // NOI18N 594 return; 595 } 596 } 597 } 598 // no reads, so continue to check writes 599 for (int i = 0; i < _numRows; i++) { 600 currentPin = i; 601 if (writeState[i] != NONE) { 602 // yes, needs read. Find what kind 603 log.debug("iNO: writeState[{}] = {}", i, readState[i]); 604 switch (writeState[i]) { 605 case WRITEVALUE1: 606 case WRITINGVALUE1: 607 // set new state, send read, then done 608 writeState[i] = WRITINGVALUE1; 609 lastOpCv = i * 3 + 4; 610 setStatus(Bundle.getMessage("StatusWriting", lastOpCv, i + 1, 1)); 611 sendWriteCommand(unitAddress, unitSubAddress, lastOpCv, getV1(i)); 612 return; 613 case WRITEVALUE2: 614 case WRITINGVALUE2: 615 // set new state, send read, then done 616 writeState[i] = WRITINGVALUE2; 617 lastOpCv = i * 3 + 5; 618 setStatus(Bundle.getMessage("StatusWriting", lastOpCv, i + 1, 2)); 619 sendWriteCommand(unitAddress, unitSubAddress, lastOpCv, getV2(i)); 620 return; 621 case WRITEMODE: 622 case WRITINGMODE: 623 // set new state, send write, then done 624 writeState[i] = WRITINGMODE; 625 lastOpCv = i * 3 + 3; 626 setStatus(Bundle.getMessage("StatusWriteMode", lastOpCv, i + 1)); 627 sendWriteCommand(unitAddress, unitSubAddress, lastOpCv, getSV(i)); 628 return; 629 630 default: 631 log.error("found an unexpected state: {} on port {}", writeState[1], i + 1); // NOI18N 632 return; 633 } 634 } 635 } 636 // nothing of interest found, so just end gracefully 637 log.debug("No operation needed"); 638 setStatus(Bundle.getMessage("StatusOK")); 639 lastOpCv = -1; 640 currentPin = 0; 641 } 642 643 /** 644 * Timer Management Protect against communication failures, addressing 645 * mixups and the like. 646 */ 647 private static final int TIMEOUT = 2000; // ms 648 protected javax.swing.Timer timer = null; 649 private int timeoutcounter; 650 651 /** 652 * Internal routine to handle a timeout during read/write by retrying the 653 * same operation. 654 */ 655 synchronized protected void timeout() { 656 log.debug("timeout!"); // NOI18N 657 setStatus(Bundle.getMessage("Timeout")); 658 if (timeoutcounter++ == 5) { 659 for (int i = 0; i < _numRows; i++) { 660 readState[i] = NONE; 661 writeState[i] = NONE; 662 } 663 setStatus(Bundle.getMessage("StateAborted")); 664 setLIOVersion(Bundle.getMessage("StateUnknown")); // NOI18N 665 timeoutcounter = 0; 666 stopTimer(); 667 } else { 668 issueNextOperation(); 669 } 670 } 671 672 /** 673 * Internal routine to start timer to protect the mode-change. 674 */ 675 protected void startTimer() { 676 restartTimer(TIMEOUT); 677 } 678 679 /** 680 * Internal routine to stop timer, as all is well. 681 */ 682 protected void stopTimer() { 683 if (timer != null) { 684 timer.stop(); 685 } 686 } 687 688 /** 689 * Internal routine to handle timer starts and restarts. 690 * @param delay Milliseconds to wait 691 */ 692 protected void restartTimer(int delay) { 693 if (timer == null) { 694 timer = new javax.swing.Timer(delay, new java.awt.event.ActionListener() { 695 @Override 696 public void actionPerformed(java.awt.event.ActionEvent e) { 697 timeout(); 698 } 699 }); 700 } 701 timer.stop(); 702 timer.setInitialDelay(delay); 703 timer.setRepeats(false); 704 timer.start(); 705 } 706 707 /** 708 * Read an SV from a given LocoIO device. 709 * @param locoIOAddress primary board address 710 * @param locoIOSubAddress subaddress within board 711 * @param cv CV number to access 712 */ 713 void sendReadCommand(int locoIOAddress, int locoIOSubAddress, int cv) { 714 // remember current op is read 715 reading = true; 716 log.debug("sendReadCommand(to {}/{} SV {}", 717 Integer.toHexString(locoIOAddress), Integer.toHexString(locoIOSubAddress), cv); 718 tc.sendLocoNetMessage( 719 LocoIO.readCV(locoIOAddress, locoIOSubAddress, cv)); 720 startTimer(); // and set timeout on reply 721 } 722 723 /** 724 * Write an SV to a given LocoIO device. 725 * @param locoIOAddress primary board address 726 * @param locoIOSubAddress subaddress within board 727 * @param cv CV number to access 728 * @param data value to be written 729 */ 730 void sendWriteCommand(int locoIOAddress, int locoIOSubAddress, int cv, int data) { 731 // remember current op is write 732 reading = false; 733 734 tc.sendLocoNetMessage( 735 LocoIO.writeCV(locoIOAddress, locoIOSubAddress, cv, data)); 736 startTimer(); // and set timeout on reply 737 } 738 739 public void dispose() { 740 log.debug("dispose"); // NOI18N 741 // disconnect from future events 742 stopTimer(); 743 tc.removeLocoNetListener(~0, this); 744 745 // null references, so that they can be gc'd even if this isn't. 746 addr = null; 747 mode = null; 748 sv = null; 749 v1 = null; 750 v2 = null; 751 lim = null; 752 } 753 754 private final static Logger log = LoggerFactory.getLogger(LocoIOData.class); 755 756}