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