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