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