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