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