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