001package jmri.jmrix.loconet; 002 003import java.awt.event.ActionEvent; 004import java.util.ArrayList; 005import java.util.List; 006 007import javax.annotation.Nonnull; 008 009import jmri.*; 010import jmri.beans.PropertyChangeSupport; 011import jmri.jmrix.ConnectionConfig; 012import jmri.jmrix.ConnectionConfigManager; 013import jmri.jmrix.loconet.hexfile.HexFileFrame; 014import jmri.jmrix.loconet.lnsvf2.LnSv2MessageContents; 015import jmri.jmrix.loconet.uhlenbrock.LncvMessageContents; 016 017import static jmri.jmrix.loconet.uhlenbrock.LncvMessageContents.createCvReadRequest; 018import static jmri.jmrix.loconet.uhlenbrock.LncvMessageContents.createCvWriteRequest; 019 020/** 021 * Provide an Ops Mode Programmer via a wrapper that works with the LocoNet 022 * SlotManager object. 023 * Specific handling for message formats: 024 * <ul> 025 * <li>LOCONETOPSBOARD</li> 026 * <li>LOCONETSV1MODE</li> 027 * <li>LOCONETSV2MODE</li> 028 * <li>LOCONETLNCVMODE</li> 029 * <li>LOCONETBDOPSWMODE</li> 030 * <li>LOCONETBD7OPSWMODE</li> 031 * <li>LOCONETCSOPSWMODE</li> 032 * </ul> 033 * as defined in {@link LnProgrammerManager} 034 * 035 * Note that running a simulated LocoNet connection, {@link HexFileFrame#configure()} will substitute the 036 * {@link jmri.progdebugger.ProgDebugger} for the {@link jmri.jmrix.loconet.LnOpsModeProgrammer}, 037 * overriding {@link #readCV(String, ProgListener)} and {@link #writeCV(String, int, ProgListener)}. 038 * 039 * @see jmri.Programmer 040 * @author Bob Jacobsen Copyright (C) 2002 041 * @author B. Milhaupt, Copyright (C) 2018 042 * @author Egbert Broerse, Copyright (C) 2020 043 */ 044public class LnOpsModeProgrammer extends PropertyChangeSupport implements AddressedProgrammer, LocoNetListener { 045 046 LocoNetSystemConnectionMemo memo; 047 int mAddress; 048 boolean mLongAddr; 049 ProgListener p; 050 boolean doingWrite; 051 boolean boardOpSwWriteVal; 052 private int artNum; 053 private javax.swing.Timer bdOpSwAccessTimer = null; 054 private javax.swing.Timer sv2AccessTimer = null; 055 private javax.swing.Timer lncvAccessTimer = null; 056 private boolean firstReply; 057 058 private boolean csIsPresent; 059 060 public LnOpsModeProgrammer(LocoNetSystemConnectionMemo memo, 061 int pAddress, boolean pLongAddr) { 062 this.memo = memo; 063 mAddress = pAddress; 064 mLongAddr = pLongAddr; 065 // register to listen 066 memo.getLnTrafficController().addLocoNetListener(~0, this); 067 expectCsB4(); 068 } 069 070 private void expectCsB4() { 071 csIsPresent = true; // assumption... 072 073 if (memo.getSlotManager().getCommandStationType() == 074 LnCommandStationType.COMMAND_STATION_STANDALONE) { 075 csIsPresent = false; 076 return; 077 } 078 ConnectionConfig connection[] = {null, null, null, null}; 079 int i = 0; 080 for (ConnectionConfig conn : InstanceManager.getDefault(ConnectionConfigManager.class)) { 081 if (!conn.getDisabled()) { 082 connection[i] = conn; 083 } 084 break; 085 } 086 087 if ((csIsPresent == true) && (connection[0] != null)) { 088 if (connection[0].name().equalsIgnoreCase("LocoNet Simulator")) { 089 csIsPresent = false; 090 } 091 } 092 } 093 094 /** 095 * {@inheritDoc} 096 */ 097 @Override 098 public void writeCV(String CV, int val, ProgListener pL) throws ProgrammerException { 099 if (p != null) { 100 log.error("Will try to null an existing programmer!"); 101 } 102 p = null; 103 // Check mode 104 LocoNetMessage m; 105 if (getMode().equals(LnProgrammerManager.LOCONETCSOPSWMODE)) { 106 memo.getSlotManager().setMode(LnProgrammerManager.LOCONETCSOPSWMODE); 107 memo.getSlotManager().writeCV(CV, val, pL); // deal with this via service-mode programmer 108 } else if (getMode().equals(LnProgrammerManager.LOCONETBDOPSWMODE)) { 109 /* 110 * CV format is e.g. "113.12" where the first part defines the 111 * typeword for the specific board type and the second is the specific bit number 112 * Known values: 113 * <ul> 114 * <li>0x70 112 - PM4 115 * <li>0x71 113 - BDL16 116 * <li>0x72 114 - SE8 117 * <li>0x73 115 - DS64 118 * </ul> 119 */ 120 if (bdOpSwAccessTimer == null) { 121 initializeBdOpsAccessTimer(); 122 } 123 p = pL; 124 doingWrite = true; 125 // Board programming mode 126 log.debug("write CV \"{}\" to {} addr:{}", CV, val, mAddress); 127 String[] parts = CV.split("\\."); 128 int typeWord = Integer.parseInt(parts[0]); 129 int state = Integer.parseInt(parts[parts.length>1 ? 1 : 0]); 130 131 // make message 132 m = new LocoNetMessage(6); 133 m.setOpCode(LnConstants.OPC_MULTI_SENSE); 134 int element = 0x72; 135 if ((mAddress & 0x80) != 0) { 136 element |= 1; 137 } 138 m.setElement(1, element); 139 m.setElement(2, (mAddress-1) & 0x7F); 140 m.setElement(3, typeWord); 141 int loc = (state - 1) / 8; 142 int bit = (state - 1) - loc * 8; 143 m.setElement(4, loc * 16 + bit * 2 + (val&0x01)); 144 145 // save a copy of the written value low bit for use during reply 146 boardOpSwWriteVal = ((val & 0x01) == 1); 147 148 log.debug(" Message {}", m); 149 memo.getLnTrafficController().sendLocoNetMessage(m); 150 bdOpSwAccessTimer.restart(); 151 152 } else if (getMode().equals(LnProgrammerManager.LOCONETBD7OPSWMODE)) { 153 /* 154 * Normal CV format for Digitrax 7th-gen Accy devices 155 */ 156 if (bdOpSwAccessTimer == null) { 157 initializeBdOpsAccessTimer(); 158 } 159 p = pL; 160 doingWrite = true; 161 // Board programming mode 162 log.debug("write CV \"{}\" to {} addr:{}", CV, val, mAddress); 163 164 // get prefix if any 165 String[] parts = CV.split("\\."); 166 int offset = 0; 167 int cv = 0; 168 switch (parts.length) { 169 case 1: // plain CV number 170 cv = Integer.parseInt(parts[0])-1; 171 break; 172 case 2: // offset.CV format 173 offset = Integer.parseInt(parts[0]); 174 cv = Integer.parseInt(parts[1])-1; 175 break; 176 default: 177 log.error("unexpected number of parts in CV {}", CV); 178 } 179 180 int address6th = ((mAddress-1+offset) >> 2) & 0x3F; 181 int upper3 = ~(((mAddress-1+offset) >> 8) & 0x07); 182 int lower2 = (mAddress-1+offset) & 0x03; 183 int address7th = ((upper3 << 4) & 0x70) | (lower2 << 1) | 0x08; 184 185 // make message - send immediate packet with custom content 186 m = new LocoNetMessage(11); 187 m.setOpCode(0xED); 188 m.setElement(1, 0x0B); 189 m.setElement(2, 0x7F); 190 m.setElement(3, 0x54); 191 m.setElement(4, 0x07 | (((val >> 7) & 0x01)<<4)); 192 m.setElement(5, address6th); 193 m.setElement(6, address7th); 194 m.setElement(7, 0x6C | ((cv >> 7) & 0x03)); 195 m.setElement(8, cv&0x7F); // CV number 196 m.setElement(9, val&0x7F); // Data 197 198 // save a copy of the written value low bit for use during reply 199 boardOpSwWriteVal = ((val & 0x01) == 1); 200 201 log.debug(" Message {}", m); 202 firstReply = true; 203 memo.getLnTrafficController().sendLocoNetMessage(m); 204 bdOpSwAccessTimer.restart(); 205 206 } else if (getMode().equals(LnProgrammerManager.LOCONETSV1MODE)) { 207 p = pL; 208 doingWrite = true; 209 // SV1 mode 210 log.debug("write CV \"{}\" to {} addr:{}", CV, val, mAddress); 211 212 // make message 213 int locoIOAddress = mAddress; 214 int locoIOSubAddress = ((mAddress+256)/256)&0x7F; 215 m = jmri.jmrix.loconet.locoio.LocoIO.writeCV(locoIOAddress, locoIOSubAddress, decodeCvNum(CV), val); 216 // force version 1 tag 217 m.setElement(4, 0x01); 218 log.debug(" Message {}", m); 219 memo.getLnTrafficController().sendLocoNetMessage(m); 220 221 } else if (getMode().equals(LnProgrammerManager.LOCONETSV2MODE)) { 222 if (sv2AccessTimer == null) { 223 initializeSV2AccessTimer(); 224 } 225 p = pL; 226 // SV2 mode 227 log.debug("write CV \"{}\" to {} addr:{}", CV, val, mAddress); 228 // make message 229 m = new LocoNetMessage(16); 230 loadSV2MessageFormat(m, mAddress, decodeCvNum(CV), val); 231 m.setElement(3, 0x01); // 1 byte write 232 log.debug(" Message {}", m); 233 memo.getLnTrafficController().sendLocoNetMessage(m); 234 sv2AccessTimer.restart(); 235 } else if (getMode().equals(LnProgrammerManager.LOCONETLNCVMODE)) { 236 if (lncvAccessTimer == null) { 237 initializeLncvAccessTimer(); 238 } 239 /* 240 * CV format is e.g. "5033.12" where the first part defines the 241 * article number (type/module class) for the board and the second is the specific bit number. 242 * Modules without their own art. no. use 65535 (broadcast mode). 243 */ 244 // LNCV Module programming mode 245 String[] parts = CV.split("\\."); 246 if (parts.length > 1) { 247 artNum = Integer.parseInt(parts[0]); // stored for comparison 248 } 249 int cvNum = Integer.parseInt(parts[parts.length > 1 ? 1 : 0]); 250 p = pL; 251 doingWrite = true; 252 // LNCV mode 253 log.debug("write CV \"{}\" to {} addr:{} (art. {})", cvNum, val, mAddress, artNum); 254 // make message 255 m = createCvWriteRequest(artNum, cvNum, val); 256 // module must be in Programming mode (handled by LNCV tool), note that mAddress is not included in LNCV Write message 257 log.debug(" Message {}", m); 258 memo.getLnTrafficController().sendLocoNetMessage(m); 259 lncvAccessTimer.restart(); 260 } else { 261 // LOCONETOPSBOARD decoder i.e. getMode().equals(LnProgrammerManager.LOCONETOPSBOARD) 262 // and the remaining case of DCC ops mode 263 memo.getSlotManager().setAcceptAnyLACK(); 264 memo.getSlotManager().writeCVOpsMode(CV, val, pL, mAddress, mLongAddr); 265 } 266 } 267 268 /** 269 * {@inheritDoc} 270 * @param CV the CV to read, could be a composite string that is split in this method te pass eg. the module type 271 * @param pL the listener that will be notified of the read 272 */ 273 @Override 274 public void readCV(String CV, ProgListener pL) throws ProgrammerException { 275 if (this.p != null) { 276 log.error("Will try to null an existing programmer!"); 277 } 278 this.p = null; 279 // Check mode 280 String[] parts; 281 LocoNetMessage m; 282 if (getMode().equals(LnProgrammerManager.LOCONETCSOPSWMODE)) { 283 memo.getSlotManager().setMode(LnProgrammerManager.LOCONETCSOPSWMODE); 284 memo.getSlotManager().readCV(CV, pL); // deal with this via service-mode programmer 285 } else if (getMode().equals(LnProgrammerManager.LOCONETBDOPSWMODE)) { 286 /* 287 * CV format is e.g. "113.12" where the first part defines the 288 * typeword for the specific board type and the second is the specific bit number 289 * Known values: 290 * <ul> 291 * <li>0x70 112 - PM4 292 * <li>0x71 113 - BDL16 293 * <li>0x72 114 - SE8 294 * <li>0x73 115 - DS64 295 * </ul> 296 */ 297 if (bdOpSwAccessTimer == null) { 298 initializeBdOpsAccessTimer(); 299 } 300 p = pL; 301 doingWrite = false; 302 // Board programming mode 303 log.debug("read CV \"{}\" addr:{}", CV, mAddress); 304 parts = CV.split("\\."); 305 int typeWord = Integer.parseInt(parts[0]); 306 int state = Integer.parseInt(parts[parts.length>1 ? 1 : 0]); 307 308 // make message 309 m = new LocoNetMessage(6); 310 m.setOpCode(LnConstants.OPC_MULTI_SENSE); 311 int element = 0x62; 312 if ((mAddress & 0x80) != 0) { 313 element |= 1; 314 } 315 m.setElement(1, element); 316 m.setElement(2, (mAddress-1) & 0x7F); 317 m.setElement(3, typeWord); 318 int loc = (state - 1) / 8; 319 int bit = (state - 1) - loc * 8; 320 m.setElement(4, loc * 16 + bit * 2); 321 322 log.debug(" Message {}", m); 323 memo.getLnTrafficController().sendLocoNetMessage(m); 324 bdOpSwAccessTimer.restart(); 325 326 } else if (getMode().equals(LnProgrammerManager.LOCONETBD7OPSWMODE)) { 327 /* 328 * Normal CV format 329 */ 330 if (bdOpSwAccessTimer == null) { 331 initializeBdOpsAccessTimer(); 332 } 333 p = pL; 334 doingWrite = false; 335 // Board programming mode 336 log.debug("read CV \"{}\" addr:{}", CV, mAddress); 337 338 // get prefix if any 339 parts = CV.split("\\."); 340 int offset = 0; 341 int cv = 0; 342 switch (parts.length) { 343 case 1: // plain CV number 344 cv = Integer.parseInt(parts[0])-1; 345 break; 346 case 2: // offset.CV format 347 offset = Integer.parseInt(parts[0]); 348 cv = Integer.parseInt(parts[1])-1; 349 break; 350 default: 351 log.error("unexpected number of parts in CV {}", CV); 352 } 353 354 int address6th = ((mAddress-1+offset) >> 2) & 0x3F; 355 int upper3 = ~(((mAddress-1+offset) >> 8) & 0x07); 356 int lower2 = (mAddress-1+offset) & 0x03; 357 int address7th = ((upper3 << 4) & 0x70) | (lower2 << 1) | 0x08; 358 359 // make message - send immediate packet with custom content 360 m = new LocoNetMessage(11); 361 m.setOpCode(0xED); 362 m.setElement(1, 0x0B); 363 m.setElement(2, 0x7F); 364 m.setElement(3, 0x54); 365 m.setElement(4, 0x07); 366 m.setElement(5, address6th); 367 m.setElement(6, address7th); 368 m.setElement(7, 0x64 | ((cv >> 7) & 0x03)); 369 m.setElement(8, cv&0x7F); // CV number 370 m.setElement(9, 0); 371 372 log.debug(" Message {}", m); 373 firstReply = true; 374 memo.getLnTrafficController().sendLocoNetMessage(m); 375 bdOpSwAccessTimer.restart(); 376 } else if (getMode().equals(LnProgrammerManager.LOCONETSV1MODE)) { 377 p = pL; 378 doingWrite = false; 379 // SV1 mode 380 log.debug("read CV \"{}\" addr:{}", CV, mAddress); 381 // make message 382 int locoIOAddress = mAddress&0xFF; 383 int locoIOSubAddress = ((mAddress+256)/256)&0x7F; 384 m = jmri.jmrix.loconet.locoio.LocoIO.readCV(locoIOAddress, locoIOSubAddress, decodeCvNum(CV)); 385 // force version 1 tag 386 m.setElement(4, 0x01); 387 log.debug(" Message {}", m); 388 memo.getLnTrafficController().sendLocoNetMessage(m); 389 390 } else if (getMode().equals(LnProgrammerManager.LOCONETSV2MODE)) { 391 if (sv2AccessTimer == null) { 392 initializeSV2AccessTimer(); 393 } 394 p = pL; 395 // SV2 mode 396 log.debug("read CV \"{}\" addr:{}", CV, mAddress); 397 // make message 398 m = new LocoNetMessage(16); 399 loadSV2MessageFormat(m, mAddress, decodeCvNum(CV), 0); 400 m.setElement(3, 0x02); // 1 byte read 401 log.debug(" Message {}", m); 402 memo.getLnTrafficController().sendLocoNetMessage(m); 403 sv2AccessTimer.restart(); 404 } else if (getMode().equals(LnProgrammerManager.LOCONETLNCVMODE)) { 405 if (lncvAccessTimer == null) { 406 initializeLncvAccessTimer(); 407 } 408 /* 409 * CV format passed by SymbolicProg is formed "5033.12", where the first part defines the 410 * article number (type/module class) for the board and the second is the specific bit number. 411 * Modules without their own art. no. use 65535 (broadcast mode), so cannot use decoder definition. 412 */ 413 parts = CV.split("\\."); 414 if (parts.length > 1) { 415 artNum = Integer.parseInt(parts[0]); // stored for comparison 416 } 417 int cvNum = Integer.parseInt(parts[parts.length > 1 ? 1 : 0]); 418 doingWrite = false; 419 // numberformat "113.12" is simply consumed by ProgDebugger (HexFile sim connection) 420 p = pL; 421 // LNCV mode 422 log.debug("read LNCV \"{}\" addr:{}", CV, mAddress); 423 // make message 424 m = createCvReadRequest(artNum, mAddress, cvNum); // module must be in Programming mode (is handled by LNCV tool) 425 log.debug(" Message {}", m); 426 memo.getLnTrafficController().sendLocoNetMessage(m); 427 lncvAccessTimer.restart(); 428 } else if (getMode().equals(LnProgrammerManager.LOCONETOPSBOARD)) { 429 // LOCONETOPSBOARD decoder 430 log.trace("LOCONETOPSBOARD start operation"); 431 memo.getSlotManager().setAcceptAnyLACK(); 432 memo.getSlotManager().readCVOpsMode(CV, pL, mAddress, mLongAddr); 433 } else { 434 // DCC ops mode 435 memo.getSlotManager().readCVOpsMode(CV, pL, mAddress, mLongAddr); 436 } 437 } 438 439 /** 440 * {@inheritDoc} 441 */ 442 @Override 443 public void confirmCV(String CV, int val, ProgListener pL) throws ProgrammerException { 444 if (this.p != null) { 445 log.error("Will try to null an existing programmer!"); 446 } 447 p = null; 448 // Check mode 449 if (getMode().equals(LnProgrammerManager.LOCONETCSOPSWMODE)) { 450 memo.getSlotManager().setMode(LnProgrammerManager.LOCONETCSOPSWMODE); 451 memo.getSlotManager().readCV(CV, pL); // deal with this via service-mode programmer 452 } else if (getMode().equals(LnProgrammerManager.LOCONETBDOPSWMODE)) { 453 readCV(CV, pL); 454 } else if (getMode().equals(LnProgrammerManager.LOCONETBD7OPSWMODE)) { 455 readCV(CV, pL); 456 } else if (getMode().equals(LnProgrammerManager.LOCONETSV2MODE)) { 457 // SV2 mode 458 log.warn("confirm CV \"{}\" addr:{} in SV2 mode not implemented", CV, mAddress); 459 notifyProgListenerEnd(pL, 0, ProgListener.UnknownError); 460 } else if (getMode().equals(LnProgrammerManager.LOCONETLNCVMODE)) { 461 // LNCV (Uhlenbrock) mode 462 log.warn("confirm CV \"{}\" addr:{} in LNCV mode not (yet) implemented", CV, mAddress); 463 readCV(CV, pL); 464 //notifyProgListenerEnd(pL, 0, ProgListener.UnknownError); 465 } else if (getMode().equals(LnProgrammerManager.LOCONETOPSBOARD)) { 466 // LOCONETOPSBOARD decoder 467 memo.getSlotManager().setAcceptAnyLACK(); 468 memo.getSlotManager().confirmCVOpsMode(CV, val, pL, mAddress, mLongAddr); 469 } else { 470 // DCC ops mode 471 memo.getSlotManager().confirmCVOpsMode(CV, val, pL, mAddress, mLongAddr); 472 } 473 } 474 475 /** 476 * {@inheritDoc} 477 */ 478 @Override 479 public void message(LocoNetMessage m) { 480 if (getMode().equals(LnProgrammerManager.LOCONETBDOPSWMODE)) { 481 // are we programming? If not, ignore 482 if (p == null) { 483 log.warn("received board-program reply message with no reply object: {}", m); 484 return; 485 } 486 // check for right type, unit 487 if (m.getOpCode() != LnConstants.OPC_LONG_ACK 488 || ((m.getElement(1) != 0x00) && (m.getElement(1) != 0x50))) { 489 return; 490 } 491 // got a message that is LONG_ACK reply to an BdOpsSw access 492 bdOpSwAccessTimer.stop(); // kill the timeout timer 493 // LACK with 0x00 or 0x50 in byte 1; assume it's to us 494 if (doingWrite) { 495 int code = ProgListener.OK; 496 int val = (boardOpSwWriteVal ? 1 : 0); 497 ProgListener temp = p; 498 p = null; 499 notifyProgListenerEnd(temp, val, code); 500 return; 501 } 502 503 int val = 0; 504 if ((m.getElement(2) & 0x20) != 0) { 505 val = 1; 506 } 507 508 // successful read if LACK return status is not 0x7F 509 int code = ProgListener.OK; 510 if ((m.getElement(2) == 0x7f)) { 511 code = ProgListener.UnknownError; 512 } 513 514 ProgListener temp = p; 515 p = null; 516 notifyProgListenerEnd(temp, val, code); 517 518 } else if (getMode().equals(LnProgrammerManager.LOCONETBD7OPSWMODE)) { 519 // Deal with Digitrax 7th-gen Accessory board CV accesses 520 // Are we programming? If not, ignore 521 if (p == null) { 522 log.warn("7th-gen Accessory board Ops programmer received reply message with no reply object: {}", m); 523 return; 524 } 525 // check for right type, unit 526 // ignore if not Long_ack 527 if ((m.getOpCode() != LnConstants.OPC_LONG_ACK)){ 528 return; 529 } 530 if (!((m.getElement(1) == 0x6E) || 531 ( m.getElement(1) == 0x6D))) { 532 // ignore if not Long_ack or either of two appropriate 533 // Long-ack response types 534 log.debug("Ignoring OPC_LONG_ACK with <LOPC> {}, <ACK1> {}.", 535 Integer.toHexString(m.getElement(1)), 536 Integer.toHexString(m.getElement(2)) 537 ); 538 return; 539 } 540 541 // is a OPC_LONG_ACK og 0x6D or 0x6E. 542 543 if (firstReply && csIsPresent) { 544 firstReply = false; 545 log.debug("Ignoring first OPC_LONG_ACK from 7th gen access. access from cs."); 546 return; 547 } 548 549 // got a message that is LONG_ACK reply to a 7th-gen BdOpsSw access 550 bdOpSwAccessTimer.stop(); // kill the timeout timer 551 int code = ProgListener.UnknownError; 552 int val = 0; 553 554 // LACK with 0x6E in byte 1; assume it's to us 555 if (doingWrite 556 && m.getElement(1) == 0x6D 557 && (m.getElement(2) == 0x55 || m.getElement(2) == 0x5A)) { 558 code = ProgListener.OK; 559 val = (boardOpSwWriteVal ? 1 : 0); 560 } else { 561 code = ProgListener.OK; 562 val = m.getElement(2) + ((m.getElement(1) & 1) << 7); 563 } 564 565 scheduleReplyForLater(val, code); 566 567 } else if (getMode().equals(LnProgrammerManager.LOCONETSV1MODE)) { 568 // see if reply to LNSV 1 or LNSV2 request 569 if ((m.getOpCode() != LnConstants.OPC_PEER_XFER) || 570 (m.getElement( 1) != 0x10) || 571 (m.getElement( 4) != 0x01) || // format 1 572 ((m.getElement( 5) & 0x70) != 0x00)) { 573 return; 574 } 575 576 // check for src address (?) moved to 0x50 577 // this might not be the right way to tell.... 578 if ((m.getElement(3) & 0x7F) != 0x50) { 579 return; 580 } 581 582 // more checks needed? E.g. addresses? 583 584 // Mode 1 return data comes back in 585 // byte index 12, with the MSB in 0x01 of byte index 10 586 // 587 588 // check pending activity 589 if (p == null) { 590 log.debug("received SV reply message with no reply object: {}", m); 591 } else { 592 log.debug("returning SV programming reply: {}", m); 593 int code = ProgListener.OK; 594 int val; 595 if (doingWrite) { 596 val = m.getPeerXfrData()[7]; 597 } else { 598 val = m.getPeerXfrData()[5]; 599 } 600 ProgListener temp = p; 601 p = null; 602 notifyProgListenerEnd(temp, val, code); 603 } 604 } else if (getMode().equals(LnProgrammerManager.LOCONETSV2MODE)) { 605 // see if reply to LNSV 1 or LNSV2 request 606 if (((m.getOpCode() & 0xFF) != LnConstants.OPC_PEER_XFER) || 607 ((m.getElement( 1) & 0xFF) != 0x10) || 608 ((m.getElement( 3) != 0x41) && (m.getElement(3) != 0x42)) || // need a "Write One Reply", or a "Read One Reply" 609 ((m.getElement( 4) & 0xFF) != 0x02) || // format 2) 610 ((m.getElement( 5) & 0x70) != 0x10) || // need SVX1 high nibble = 1 611 ((m.getElement(10) & 0x70) != 0x10) // need SVX2 high nibble = 1 612 ) { 613 return; 614 } 615 // more checks needed? E.g. addresses? 616 617 // return reply 618 if (p == null) { 619 log.error("received SV reply message with no reply object: {}", m); 620 } else { 621 log.debug("returning SV programming reply: {}", m); 622 623 sv2AccessTimer.stop(); // kill the timeout timer 624 625 int code = ProgListener.OK; 626 int val = (m.getElement(11)&0x7F)|(((m.getElement(10)&0x01) != 0x00)? 0x80:0x00); 627 628 ProgListener temp = p; 629 p = null; 630 notifyProgListenerEnd(temp, val, code); 631 } 632 } else if (getMode().equals(LnProgrammerManager.LOCONETLNCVMODE)) { 633 // see if reply to LNCV request 634 // (compare this part to that in LNCV Tool jmri.jmrix.loconet.swing.lncvprog.LncvProgPane.message) 635 // is it a LACK write confirmation response from module? 636 int code; 637 if ((m.getOpCode() == LnConstants.OPC_LONG_ACK) && 638 (m.getElement(1) == 0x6D) && doingWrite) { // elem 1 = OPC (matches 0xED), elem 2 = ack1 639 // convert Uhlenbrock LNCV error codes to ProgListener codes, TODO extend that list to match? 640 switch (m.getElement(2)) { 641 case 0x7f: 642 code = ProgListener.OK; 643 break; 644 case 2: 645 case 3: 646 code = ProgListener.NotImplemented; 647 break; 648 case 1: 649 default: 650 code = ProgListener.UnknownError; 651 } 652 if (lncvAccessTimer != null) { 653 lncvAccessTimer.stop(); // kill the timeout timer 654 } 655 // LACK with 0x00 or 0x50 in byte 1; assume it's to us. 656 ProgListener temp = p; 657 p = null; 658 notifyProgListenerEnd(temp, 0, code); 659 } 660 if ((LncvMessageContents.extractMessageType(m) == LncvMessageContents.LncvCommand.LNCV_READ_REPLY) || 661 (LncvMessageContents.extractMessageType(m) == LncvMessageContents.LncvCommand.LNCV_READ_REPLY2)) { 662 // it's an LNCV ReadReply message, decode contents 663 LncvMessageContents contents = new LncvMessageContents(m); 664 int artReturned = contents.getLncvArticleNum(); 665 int valReturned = contents.getCvValue(); 666 code = ProgListener.OK; 667 // forward write reply 668 if (artReturned != artNum) { // it's not for us? 669 //code = ProgListener.ConfirmFailed; 670 log.warn("LNCV read reply received for article {}, expected article {}", artReturned, artNum); 671 } 672 if (lncvAccessTimer != null) { 673 lncvAccessTimer.stop(); // kill the timeout timer 674 } 675 ProgListener temp = p; 676 p = null; 677 notifyProgListenerEnd(temp, valReturned, code); 678 } 679 } 680 } 681 682 private void scheduleReplyForLater(int val, int code) { 683 int _val = val; 684 int _code = code; 685 686 jmri.util.TimerUtil.scheduleOnLayoutThread(new java.util.TimerTask() { 687 @Override 688 public void run() { 689 log.debug("Passing result from 7g Accy Ops access."); 690 ProgListener tempProgListener = p; 691 p = null; 692 notifyProgListenerEnd(tempProgListener, _val, _code); 693 } 694 }, 50); 695 } 696 697 int decodeCvNum(String CV) { 698 try { 699 return Integer.parseInt(CV); 700 } catch (java.lang.NumberFormatException e) { 701 return 0; 702 } 703 } 704 705 /** Fill in an SV2 format LocoNet message from parameters provided. 706 * Compare to SV2 message handler in {@link LnSv2MessageContents#createSv2Message(int, int, int, int, int, int, int, int)} 707 * 708 * @param m Base LocoNet message to fill 709 * @param mAddress Destination board address 710 * @param cvAddr Dest. board CV number 711 * @param data Value to put into CV 712 */ 713 void loadSV2MessageFormat(LocoNetMessage m, int mAddress, int cvAddr, int data) { 714 m.setElement(0, LnConstants.OPC_PEER_XFER); 715 m.setElement(1, 0x10); 716 m.setElement(2, 0x01); 717 // 3 SV_CMD to be filled in later 718 m.setElement(4, 0x02); 719 // 5 will come back to SVX1 720 m.setElement(6, mAddress&0xFF); 721 m.setElement(7, (mAddress>>8)&0xFF); 722 m.setElement(8, cvAddr&0xFF); 723 m.setElement(9, (cvAddr/256)&0xFF); 724 725 // set SVX1 726 int svx1 = 0x10 727 |((m.getElement(6)&0x80) != 0 ? 0x01 : 0) // DST_L 728 |((m.getElement(7)&0x80) != 0 ? 0x02 : 0) // DST_L 729 |((m.getElement(8)&0x80) != 0 ? 0x04 : 0) // DST_L 730 |((m.getElement(9)&0x80) != 0 ? 0x08 : 0); // SV_ADRH 731 m.setElement(5, svx1); 732 m.setElement(6, m.getElement(6)&0x7F); 733 m.setElement(7, m.getElement(7)&0x7F); 734 m.setElement(8, m.getElement(8)&0x7F); 735 m.setElement(9, m.getElement(9)&0x7F); 736 737 // 10 will come back to SVX2 738 m.setElement(11, data&0xFF); 739 m.setElement(12, (data>>8)&0xFF); 740 m.setElement(13, (data>>16)&0xFF); 741 m.setElement(14, (data>>24)&0xFF); 742 743 // set SVX2 744 int svx2 = 0x10 745 |((m.getElement(11)&0x80) != 0 ? 0x01 : 0) 746 |((m.getElement(12)&0x80) != 0 ? 0x02 : 0) 747 |((m.getElement(13)&0x80) != 0 ? 0x04 : 0) 748 |((m.getElement(14)&0x80) != 0 ? 0x08 : 0); 749 m.setElement(10, svx2); 750 m.setElement(11, m.getElement(11)&0x7F); 751 m.setElement(12, m.getElement(12)&0x7F); 752 m.setElement(13, m.getElement(13)&0x7F); 753 m.setElement(14, m.getElement(14)&0x7F); 754 } 755 756 // handle mode 757 protected ProgrammingMode mode = ProgrammingMode.OPSBYTEMODE; 758 759 /** 760 * {@inheritDoc} 761 */ 762 @Override 763 public final void setMode(ProgrammingMode m) { 764 if (getSupportedModes().contains(m)) { 765 mode = m; 766 firePropertyChange("Mode", mode, m); // NOI18N 767 } else { 768 throw new IllegalArgumentException("Invalid requested mode: " + m); // NOI18N 769 } 770 } 771 772 /** 773 * {@inheritDoc} 774 */ 775 @Override 776 public final ProgrammingMode getMode() { 777 return mode; 778 } 779 780 /** 781 * {@inheritDoc} 782 */ 783 @Override 784 @Nonnull 785 public List<ProgrammingMode> getSupportedModes() { 786 List<ProgrammingMode> ret = new ArrayList<>(4); 787 ret.add(ProgrammingMode.OPSBYTEMODE); 788 ret.add(LnProgrammerManager.LOCONETBD7OPSWMODE); 789 ret.add(LnProgrammerManager.LOCONETOPSBOARD); 790 ret.add(LnProgrammerManager.LOCONETSV1MODE); 791 ret.add(LnProgrammerManager.LOCONETSV2MODE); 792 ret.add(LnProgrammerManager.LOCONETLNCVMODE); 793 ret.add(LnProgrammerManager.LOCONETBDOPSWMODE); 794 ret.add(LnProgrammerManager.LOCONETCSOPSWMODE); 795 return ret; 796 } 797 798 /** 799 * {@inheritDoc} 800 * 801 * Confirmation mode by programming mode; not that this doesn't 802 * yet know whether BDL168 hardware is present to allow DecoderReply 803 * to function; that should be a preference eventually. See also DCS240... 804 * 805 * @param addr CV address ignored, as there's no variance with this in LocoNet 806 * @return depends on programming mode 807 */ 808 @Nonnull 809 @Override 810 public Programmer.WriteConfirmMode getWriteConfirmMode(String addr) { 811 if (getMode().equals(ProgrammingMode.OPSBYTEMODE)) { 812 return WriteConfirmMode.NotVerified; 813 } 814 return WriteConfirmMode.DecoderReply; 815 } 816 817 /** 818 * {@inheritDoc} 819 * 820 * Can this ops-mode programmer read back values? Yes, if transponding 821 * hardware is present and regular ops mode, or if in any other mode. 822 * 823 * @return always true 824 */ 825 @Override 826 public boolean getCanRead() { 827 if (getMode().equals(ProgrammingMode.OPSBYTEMODE)) return memo.getSlotManager().getTranspondingAvailable(); // only way can be false 828 return true; 829 } 830 831 /** 832 * {@inheritDoc} 833 */ 834 @Override 835 public boolean getCanRead(String addr) { 836 return getCanRead(); 837 } 838 839 /** 840 * {@inheritDoc} 841 */ 842 @Override 843 public boolean getCanWrite() { 844 return true; 845 } 846 847 /** 848 * {@inheritDoc} 849 */ 850 @Override 851 public boolean getCanWrite(String addr) { 852 return getCanWrite() && Integer.parseInt(addr) <= 1024; 853 } 854 855 /** 856 * {@inheritDoc} 857 */ 858 @Override 859 @Nonnull 860 public String decodeErrorCode(int i) { 861 return memo.getSlotManager().decodeErrorCode(i); 862 } 863 864 /** 865 * {@inheritDoc} 866 */ 867 @Override 868 public boolean getLongAddress() { 869 return mLongAddr; 870 } 871 872 /** 873 * {@inheritDoc} 874 */ 875 @Override 876 public int getAddressNumber() { 877 return mAddress; 878 } 879 880 /** 881 * {@inheritDoc} 882 */ 883 @Override 884 public String getAddress() { 885 return "" + getAddressNumber() + " " + getLongAddress(); 886 } 887 888 void initializeBdOpsAccessTimer() { 889 if (bdOpSwAccessTimer == null) { 890 bdOpSwAccessTimer = new javax.swing.Timer(1000, (ActionEvent e) -> { 891 ProgListener temp = p; 892 p = null; 893 notifyProgListenerEnd(temp, 0, ProgListener.FailedTimeout); 894 }); 895 bdOpSwAccessTimer.setInitialDelay(1000); 896 bdOpSwAccessTimer.setRepeats(false); 897 } 898 } 899 900 void initializeSV2AccessTimer() { 901 if (sv2AccessTimer == null) { 902 sv2AccessTimer = new javax.swing.Timer(1000, (ActionEvent e) -> { 903 ProgListener temp = p; 904 p = null; 905 notifyProgListenerEnd(temp, 0, ProgListener.FailedTimeout); 906 }); 907 sv2AccessTimer.setInitialDelay(1000); 908 sv2AccessTimer.setRepeats(false); 909 } 910 } 911 912 void initializeLncvAccessTimer() { 913 if (lncvAccessTimer == null) { 914 lncvAccessTimer = new javax.swing.Timer(1000, (ActionEvent e) -> { 915 ProgListener temp = p; 916 p = null; 917 notifyProgListenerEnd(temp, 0, ProgListener.FailedTimeout); 918 }); 919 lncvAccessTimer.setInitialDelay(1000); 920 lncvAccessTimer.setRepeats(false); 921 } 922 } 923 924 @Override 925 public void dispose() { 926 memo.getLnTrafficController().removeLocoNetListener(~0, this); 927 } 928 929 // initialize logging 930 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LnOpsModeProgrammer.class); 931 932}