001package jmri.jmrix.loconet.swing.lncvprog; 002 003import javax.swing.*; 004import javax.swing.border.Border; 005import javax.swing.table.TableRowSorter; 006import java.awt.*; 007import java.awt.event.*; 008import java.util.Objects; 009 010import jmri.InstanceManager; 011import jmri.UserPreferencesManager; 012import jmri.jmrix.loconet.*; 013import jmri.jmrix.loconet.uhlenbrock.LncvDevice; 014import jmri.jmrix.loconet.uhlenbrock.LncvMessageContents; 015import jmri.swing.JTablePersistenceManager; 016import jmri.util.JmriJFrame; 017import jmri.util.swing.JmriJOptionPane; 018import jmri.util.table.ButtonEditor; 019import jmri.util.table.ButtonRenderer; 020 021/** 022 * Frame for discovery and display of LocoNet LNCV boards. 023 * Derived from xbee node config. Verified with Digikeijs DR5033 hardware. 024 * 025 * Some of the message formats used in this class are Copyright Uhlenbrock.de 026 * and used with permission as part of the JMRI project. That permission does 027 * not extend to uses in other software products. If you wish to use this code, 028 * algorithm or these message formats outside of JMRI, please contact Uhlenbrock. 029 * 030 * Buttons in table row allows to add roster entry for device, and switch to the 031 * DecoderPro ops mode programmer. 032 * 033 * @author Egbert Broerse Copyright (C) 2021, 2022 034 */ 035public class LncvProgPane extends jmri.jmrix.loconet.swing.LnPanel implements LocoNetListener { 036 037 private LocoNetSystemConnectionMemo memo; 038 protected JToggleButton allProgButton = new JToggleButton(); 039 protected JToggleButton modProgButton = new JToggleButton(); 040 protected JButton readButton = new JButton(Bundle.getMessage("ButtonRead")); 041 protected JButton writeButton = new JButton(Bundle.getMessage("ButtonWrite")); 042 protected JTextField articleField = new JTextField(4); 043 protected JTextField addressField = new JTextField(4); 044 protected JTextField cvField = new JTextField(4); 045 protected JTextField valueField = new JTextField(4); 046 protected JCheckBox directCheckBox = new JCheckBox(Bundle.getMessage("DirectModeBox")); 047 protected JCheckBox rawCheckBox = new JCheckBox(Bundle.getMessage("ButtonShowRaw")); 048 protected JTable moduleTable = null; 049 protected LncvProgTableModel moduleTableModel = null; 050 public static final int ROW_HEIGHT = (new JButton("X").getPreferredSize().height)*9/10; 051 052 protected JPanel tablePanel = null; 053 protected JLabel statusText1 = new JLabel(); 054 protected JLabel statusText2 = new JLabel(); 055 protected JLabel articleFieldLabel = new JLabel(Bundle.getMessage("LabelArticleNum", JLabel.RIGHT)); 056 protected JLabel addressFieldLabel = new JLabel(Bundle.getMessage("LabelModuleAddress", JLabel.RIGHT)); 057 protected JLabel cvFieldLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("HeadingCv")), JLabel.RIGHT); 058 protected JLabel valueFieldLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("HeadingValue")), JLabel.RIGHT); 059 protected JTextArea result = new JTextArea(6,50); 060 protected String reply = ""; 061 protected int art; 062 protected int adr = 1; 063 protected int cv = 0; 064 protected int val; 065 boolean writeConfirmed = false; 066 private final String rawDataCheck = this.getClass().getName() + ".RawData"; // NOI18N 067 private final String dontWarnOnClose = this.getClass().getName() + ".DontWarnOnClose"; // NOI18N 068 private UserPreferencesManager pm; 069 private transient TableRowSorter<LncvProgTableModel> sorter; 070 private LncvDevicesManager lncvdm; 071 072 private boolean allProgRunning = false; 073 private int moduleProgRunning = -1; // stores module address as int during moduleProgramming session, -1 = no session 074 075 /** 076 * Constructor method 077 */ 078 public LncvProgPane() { 079 super(); 080 } 081 082 /** 083 * {@inheritDoc} 084 */ 085 @Override 086 public String getHelpTarget() { 087 return "package.jmri.jmrix.loconet.swing.lncvprog.LncvProgPane"; // NOI18N 088 } 089 090 @Override 091 public String getTitle() { 092 return Bundle.getMessage("MenuItemLncvProg"); 093 } 094 095 /** 096 * Initialize the config window 097 */ 098 @Override 099 public void initComponents() { 100 setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); 101 // buttons at top, like SE8c pane 102 add(initButtonPanel()); // requires presence of memo. 103 add(initDirectPanel()); // starts hidden, to set bits in Direct Mode only 104 add(initStatusPanel()); // positioned after ButtonPanel so to keep it simple also delayed 105 // creation of table must wait for memo + tc to be available, see initComponents(memo) next 106 107 // only way to get notice of the tool being closed, as a JPanel is silently embedded in some JFrame 108 addHierarchyListener(e -> { 109 if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) { 110 Component comp = e.getChanged(); 111 if (comp instanceof JmriJFrame) { 112 JmriJFrame toolFrame = (JmriJFrame) comp; 113 if ((Objects.equals(toolFrame.getTitle(), this.getTitle()) && 114 !toolFrame.isVisible())) { // it was closed/hidden a moment ago 115 handleCloseEvent(); 116 log.debug("Component hidden: {}", comp); 117 } 118 } 119 } 120 }); 121 } 122 123 @Override 124 public synchronized void initComponents(LocoNetSystemConnectionMemo memo) { 125 super.initComponents(memo); 126 this.memo = memo; 127 lncvdm = memo.getLncvDevicesManager(); 128 pm = InstanceManager.getDefault(UserPreferencesManager.class); 129 // connect to the LnTrafficController 130 if (memo.getLnTrafficController() == null) { 131 log.error("No traffic controller is available"); 132 } else { 133 // add listener 134 memo.getLnTrafficController().addLocoNetListener(~0, this); 135 } 136 137 // create the data model and its table 138 moduleTableModel = new LncvProgTableModel(this, memo); 139 moduleTable = new JTable(moduleTableModel); 140 moduleTable.setRowSelectionAllowed(false); 141 moduleTable.setPreferredScrollableViewportSize(new Dimension(300, 200)); 142 moduleTable.setRowHeight(ROW_HEIGHT); 143 moduleTable.setDefaultEditor(JButton.class, new ButtonEditor(new JButton())); 144 moduleTable.setDefaultRenderer(JButton.class, new ButtonRenderer()); 145 moduleTable.setRowSelectionAllowed(true); 146 moduleTable.getSelectionModel().addListSelectionListener(event -> { 147 synchronized (this) { 148 if (moduleTable.getSelectedRow() > -1 && moduleTable.getSelectedRow() < moduleTable.getRowCount()) { 149 // print first column value from selected row 150 copyEntry((int) moduleTable.getValueAt(moduleTable.getSelectedRow(), 1), (int) moduleTable.getValueAt(moduleTable.getSelectedRow(), 2)); 151 } 152 } 153 }); 154 // establish row sorting for the table 155 sorter = new TableRowSorter<>(moduleTableModel); 156 moduleTable.setRowSorter(sorter); 157 // establish table physical characteristics persistence 158 moduleTable.setName("LNCV Device Management"); // NOI18N 159 // Reset and then persist the table's ui state 160 InstanceManager.getOptionalDefault(JTablePersistenceManager.class).ifPresent((tpm) -> { 161 synchronized (this) { 162 tpm.resetState(moduleTable); 163 tpm.persist(moduleTable, true); 164 } 165 }); 166 167 JScrollPane tableScrollPane = new JScrollPane(moduleTable); 168 tablePanel = new JPanel(); 169 Border resultBorder = BorderFactory.createEtchedBorder(); 170 Border resultTitled = BorderFactory.createTitledBorder(resultBorder, Bundle.getMessage("LncvTableTitle")); 171 tablePanel.setBorder(resultTitled); 172 tablePanel.setLayout(new BoxLayout(tablePanel, BoxLayout.Y_AXIS)); 173 tablePanel.add(tableScrollPane, BorderLayout.CENTER); 174 175 // this does not fill the full width, why? 176// JSplitPane holder = new JSplitPane(JSplitPane.VERTICAL_SPLIT, 177// tablePanel, getMonitorPanel()); 178// holder.setMinimumSize(new Dimension(1000, 400)); 179// holder.setPreferredSize(new Dimension(1000, 400)); 180// holder.setDividerSize(8); 181// holder.setOneTouchExpandable(true); 182// add(holder, BorderLayout.LINE_START); 183 add(tablePanel); 184 add(getMonitorPanel()); 185 rawCheckBox.setSelected(pm.getSimplePreferenceState(rawDataCheck)); 186 } 187 188 /* 189 * Initialize the LNCV Monitor panel. 190 */ 191 protected JPanel getMonitorPanel() { 192 JPanel panel3 = new JPanel(); 193 panel3.setLayout(new BoxLayout(panel3, BoxLayout.Y_AXIS)); 194 195 JPanel panel31 = new JPanel(); 196 panel31.setLayout(new BoxLayout(panel31, BoxLayout.Y_AXIS)); 197 JScrollPane resultScrollPane = new JScrollPane(result); 198 panel31.add(resultScrollPane); 199 200 panel31.add(rawCheckBox); 201 rawCheckBox.setVisible(true); 202 rawCheckBox.setToolTipText(Bundle.getMessage("TooltipShowRaw")); 203 panel3.add(panel31); 204 Border panel3Border = BorderFactory.createEtchedBorder(); 205 Border panel3Titled = BorderFactory.createTitledBorder(panel3Border, Bundle.getMessage("LncvMonitorTitle")); 206 panel3.setBorder(panel3Titled); 207 return panel3; 208 } 209 210 /* 211 * Initialize the Button panel. Requires presence of memo to send and receive. 212 */ 213 protected JPanel initButtonPanel() { 214 // Set up buttons and entry fields 215 JPanel panel4 = new JPanel(); 216 panel4.setLayout(new FlowLayout()); 217 218 JPanel panel41 = new JPanel(); 219 panel41.setLayout(new BoxLayout(panel41, BoxLayout.PAGE_AXIS)); 220 allProgButton.setText(allProgRunning ? 221 Bundle.getMessage("ButtonStopAllProg") : Bundle.getMessage("ButtonStartAllProg")); 222 allProgButton.setToolTipText(Bundle.getMessage("TipAllProgButton")); 223 allProgButton.addActionListener(e -> allProgButtonActionPerformed()); 224 panel41.add(allProgButton); 225 226 modProgButton.setText((moduleProgRunning >= 0) ? 227 Bundle.getMessage("ButtonStopModProg") : Bundle.getMessage("ButtonStartModProg")); 228 modProgButton.setToolTipText(Bundle.getMessage("TipModuleProgButton")); 229 modProgButton.addActionListener(e -> modProgButtonActionPerformed()); 230 panel41.add(modProgButton); 231 panel4.add(panel41); 232 233 JPanel panel42 = new JPanel(); 234 panel42.setLayout(new BoxLayout(panel42, BoxLayout.PAGE_AXIS)); 235 JPanel panel421 = new JPanel(); 236 panel421.add(articleFieldLabel); 237 // entry field (decimal) 238 articleField.setToolTipText(Bundle.getMessage("TipModuleArticleField")); 239 panel421.add(articleField); 240 panel42.add(panel421); 241 242 JPanel panel422 = new JPanel(); 243 panel422.add(addressFieldLabel); 244 // entry field (decimal) for Module Address 245 addressField.setText("1"); 246 panel422.add(addressField); 247 panel42.add(panel422); 248 panel42.add(directCheckBox); 249 directCheckBox.addActionListener(e -> directActionPerformed()); 250 directCheckBox.setToolTipText(Bundle.getMessage("TipDirectMode")); 251 panel4.add(panel42); 252 253 JPanel panel43 = new JPanel(); 254 Border panel43Border = BorderFactory.createEtchedBorder(); 255 panel43.setBorder(panel43Border); 256 panel43.setLayout(new BoxLayout(panel43, BoxLayout.LINE_AXIS)); 257 258 JPanel panel431 = new JPanel(); 259 panel431.setLayout(new BoxLayout(panel431, BoxLayout.PAGE_AXIS)); 260 JPanel panel4311 = new JPanel(); 261 panel4311.add(cvFieldLabel); 262 // entry field (decimal) for CV number to read/write 263 //cvField.setToolTipText(Bundle.getMessage("TipModuleCvField")); 264 cvField.setText("0"); 265 panel4311.add(cvField); 266 panel431.add(panel4311); 267 268 JPanel panel4312 = new JPanel(); 269 panel4312.add(valueFieldLabel); 270 // entry field (decimal) for CV value 271 //valueField.setToolTipText(Bundle.getMessage("TipModuleValueField")); 272 valueField.setText("1"); 273 panel4312.add(valueField); 274 panel431.add(panel4312); 275 panel43.add(panel431); 276 277 JPanel panel432 = new JPanel(); 278 panel432.setLayout(new BoxLayout(panel432, BoxLayout.PAGE_AXIS)); 279 panel432.add(readButton); 280 readButton.setEnabled(false); 281 readButton.addActionListener(e -> readButtonActionPerformed()); 282 283 panel432.add(writeButton); 284 writeButton.setEnabled(false); 285 writeButton.addActionListener(e -> writeButtonActionPerformed()); 286 panel43.add(panel432); 287 panel4.add(panel43); 288 289 return panel4; 290 } 291 292 /* 293 * Initialize the Status panel. 294 */ 295 protected JPanel initStatusPanel() { 296 JPanel panel2 = new JPanel(); 297 panel2.setLayout(new BoxLayout(panel2, BoxLayout.PAGE_AXIS)); 298 JPanel panel21 = new JPanel(); 299 panel21.setLayout(new FlowLayout()); 300 301 statusText1.setText(" "); 302 statusText1.setHorizontalAlignment(JLabel.CENTER); 303 panel21.add(statusText1); 304 panel2.add(panel21); 305 306 statusText2.setText(" "); 307 statusText2.setHorizontalAlignment(JLabel.CENTER); 308 panel2.add(statusText2); 309 return panel2; 310 } 311 312 /** 313 * GENERALPROG button. 314 */ 315 public void allProgButtonActionPerformed() { 316 if (moduleProgRunning >= 0) { 317 statusText1.setText(Bundle.getMessage("FeedBackModProgRunning")); 318 return; 319 } 320 if (directCheckBox.isSelected()) { 321 statusText1.setText(Bundle.getMessage("FeedBackDirectRunning")); 322 return; 323 } 324 // provide user feedback 325 readButton.setEnabled(!allProgRunning); 326 writeButton.setEnabled(!allProgRunning); 327 log.debug("AllProg pressed, allProgRunning={}", allProgRunning); 328 if (allProgRunning) { 329 log.debug("Session was running, closing"); 330 // send LncvAllProgEnd command on LocoNet 331 memo.getLnTrafficController().sendLocoNetMessage(LncvMessageContents.createAllProgEndRequest(art)); 332 statusText1.setText(Bundle.getMessage("FeedBackStopAllProg")); 333 allProgButton.setText(Bundle.getMessage("ButtonStartAllProg")); 334 articleField.setEditable(true); 335 addressField.setEditable(true); 336 allProgRunning = false; 337 return; 338 } 339 articleField.setEditable(false); 340 addressField.setEditable(false); 341 art = -1; 342 if (!articleField.getText().equals("")) { 343 try { 344 art = inDomain(articleField.getText(), 9999); 345 } catch (NumberFormatException e) { 346 // fine, will do broadcast all 347 } 348 } 349 // show dialog to protect unwanted ALL messages 350 Object[] dialogBoxButtonOptions = { 351 Bundle.getMessage("ButtonProceed"), 352 Bundle.getMessage("ButtonCancel")}; 353 int userReply = JmriJOptionPane.showOptionDialog(this.getParent(), 354 Bundle.getMessage("DialogAllWarning"), 355 Bundle.getMessage("WarningTitle"), 356 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, 357 null, dialogBoxButtonOptions, dialogBoxButtonOptions[1]); 358 if (userReply != 0 ) { // not array position 0 ButtonProceed 359 return; 360 } 361 statusText1.setText(Bundle.getMessage("FeedBackStartAllProg")); 362 // send LncvProgSessionStart command on LocoNet 363 LocoNetMessage m = LncvMessageContents.createAllProgStartRequest(art); 364 memo.getLnTrafficController().sendLocoNetMessage(m); 365 // stop and inform user 366 statusText1.setText(Bundle.getMessage("FeedBackStartAllProg")); 367 allProgButton.setText(Bundle.getMessage("ButtonStopAllProg")); 368 allProgRunning = true; 369 log.debug("AllProgRunning=TRUE, allProgButtonActionPerformed ready"); 370 } 371 372 // MODULEPROG button 373 /** 374 * Handle Start/End Module Prog button. 375 */ 376 public void modProgButtonActionPerformed() { 377 if (allProgRunning) { 378 statusText1.setText(Bundle.getMessage("FeedBackAllProgRunning")); 379 return; 380 } 381 if (directCheckBox.isSelected()) { 382 statusText1.setText(Bundle.getMessage("FeedBackDirectRunning")); 383 return; 384 } 385 if (articleField.getText().equals("")) { 386 statusText1.setText(Bundle.getMessage("FeedBackEnterArticle")); 387 articleField.setBackground(Color.RED); 388 modProgButton.setSelected(false); 389 return; 390 } 391 if (addressField.getText().equals("")) { 392 statusText1.setText(Bundle.getMessage("FeedBackEnterAddress")); 393 addressField.setBackground(Color.RED); 394 modProgButton.setSelected(false); 395 return; 396 } 397 // provide user feedback 398 articleField.setBackground(Color.WHITE); // reset 399 readButton.setEnabled(moduleProgRunning < 0); 400 writeButton.setEnabled(moduleProgRunning < 0); 401 if (moduleProgRunning >= 0) { // stop prog 402 try { 403 art = inDomain(articleField.getText(), 9999); 404 adr = moduleProgRunning; // use module address that was used to start Modprog 405 memo.getLnTrafficController().sendLocoNetMessage(LncvMessageContents.createModProgEndRequest(art, adr)); 406 statusText1.setText(Bundle.getMessage("FeedBackModProgClosed", adr)); 407 modProgButton.setText(Bundle.getMessage("ButtonStartModProg")); 408 moduleProgRunning = -1; 409 articleField.setEditable(true); 410 addressField.setEditable(true); 411 } catch (NumberFormatException e) { 412 statusText1.setText(Bundle.getMessage("FeedBackEnterArticle")); 413 modProgButton.setSelected(true); 414 } 415 return; 416 } 417 if ((!articleField.getText().equals("")) && (!addressField.getText().equals(""))) { 418 try { 419 art = inDomain(articleField.getText(), 9999); 420 adr = inDomain(addressField.getText(), 65535); // goes in d5-d6 as module address 421 memo.getLnTrafficController().sendLocoNetMessage(LncvMessageContents.createModProgStartRequest(art, adr)); 422 statusText1.setText(Bundle.getMessage("FeedBackModProgOpen", adr)); 423 modProgButton.setText(Bundle.getMessage("ButtonStopModProg")); 424 moduleProgRunning = adr; // store address during modProg, so next line is mostly as UI indication: 425 articleField.setEditable(false); 426 addressField.setEditable(false); // lock address field to prevent accidentally changing it 427 428 } catch (NumberFormatException e) { 429 log.error("invalid entry, must be number"); 430 } 431 } 432 // stop and inform user 433 } 434 435 // READCV button 436 /** 437 * Handle Read CV button, assemble LNCV read message. Requires presence of memo. 438 */ 439 public void readButtonActionPerformed() { 440 String sArt = "65535"; // LncvMessageContents.LNCV_ALL = broadcast 441 if (moduleProgRunning >= 0) { 442 sArt = articleField.getText(); 443 articleField.setBackground(Color.WHITE); // reset 444 } 445 if ((sArt != null) && (addressField.getText() != null) && (cvField.getText() != null)) { 446 try { 447 art = inDomain(sArt, 9999); // limited according to Uhlenbrock info 448 adr = inDomain(addressField.getText(), 65535); // used as address for reply 449 cv = inDomain(cvField.getText(), 9999); // decimal entry 450 memo.getLnTrafficController().sendLocoNetMessage(LncvMessageContents.createCvReadRequest(art, adr, cv)); 451 } catch (NumberFormatException e) { 452 log.error("invalid entry, must be number"); 453 } 454 } else { 455 statusText1.setText(Bundle.getMessage("FeedBackEnterArticle")); 456 articleField.setBackground(Color.RED); 457 return; 458 } 459 // stop and inform user 460 statusText1.setText(Bundle.getMessage("FeedBackRead")); 461 } 462 463 // WriteCV button 464 /** 465 * Handle Write button click, assemble LNCV write message. Requires presence of memo. 466 */ 467 public void writeButtonActionPerformed() { 468 String sArt = "65535"; // LncvMessageContents.LNCV_ALL; 469 if (moduleProgRunning >= 0) { 470 sArt = articleField.getText(); 471 } 472 if ((sArt != null) && (cvField.getText() != null) && (valueField.getText() != null)) { 473 articleField.setBackground(Color.WHITE); 474 try { 475 art = inDomain(sArt, 9999); 476 cv = inDomain(cvField.getText(), 9999); // decimal entry 477 val = inDomain(valueField.getText(), 65535); // decimal entry 478 if (cv == 0 && (val > 65534 || val < 1)) { 479 // reserved general module address, warn in status and abort 480 statusText1.setText(Bundle.getMessage("FeedBackValidAddressRange")); 481 valueField.setBackground(Color.RED); 482 return; 483 } 484 writeConfirmed = false; 485 memo.getLnTrafficController().sendLocoNetMessage(LncvMessageContents.createCvWriteRequest(art, cv, val)); 486 valueField.setBackground(Color.ORANGE); 487 } catch (NumberFormatException e) { 488 log.error("invalid entry, must be number"); 489 } 490 } else { 491 statusText1.setText(Bundle.getMessage("FeedBackEnterArticle")); 492 articleField.setBackground(Color.RED); 493 return; 494 } 495 // stop and inform user 496 statusText1.setText(Bundle.getMessage("FeedBackWrite")); 497 // LACK reply will be received separately 498 // if (received) { 499 // writeConfirmed = true; 500 // } 501 } 502 503 private JPanel ledPanel; 504 505 // a row of checkboxes to set LEDs in module on/off 506 private JPanel initDirectPanel() { 507 ledPanel = new JPanel(); 508 for (int i = 0; i < 16; i++) { 509 JCheckBox ledBox = new JCheckBox(""+i); 510 ledPanel.add(ledBox); 511 } 512 JPanel options = new JPanel(); 513 options.setLayout(new BoxLayout(options, BoxLayout.Y_AXIS)); 514 JToggleButton buttonAll = new JToggleButton(Bundle.getMessage("AllOn")); 515 buttonAll.addActionListener(e -> toggleAll(buttonAll.isSelected())); 516 options.add(buttonAll); 517 JCheckBox serieTwo = new JCheckBox("LED2"); 518 serieTwo.addActionListener(e -> renumber(serieTwo.isSelected())); 519 options.add(serieTwo); // place to the right of Set button 520 ledPanel.add(options); 521 JButton buttonSet = new JButton(Bundle.getMessage("ButtonSetDirect")); 522 ledPanel.add(buttonSet); 523 buttonSet.addActionListener(e -> setDirect(serieTwo.isSelected())); 524 ledPanel.setVisible(false); // initially hide ledPanel 525 return ledPanel; 526 } 527 528 private void toggleAll(boolean on) { 529 for (int j = 0; j < 16 ; j++) { 530 ((JCheckBox)ledPanel.getComponent(j)).setSelected(on); 531 } 532 } 533 534 protected void directActionPerformed() { 535 if (allProgRunning || moduleProgRunning > -1) { 536 directCheckBox.setSelected(false); 537 return; 538 } 539 if (directCheckBox.isSelected()) { 540 articleField.setEditable(false); 541 articleField.setText("6900"); // fixed article number as per documentation 542 articleField.setBackground(Color.WHITE); // reset 543 readButton.setEnabled (false); 544 ledPanel.setVisible(true); 545 } else { 546 articleField.setText(""); 547 articleField.setEditable(true); 548 readButton.setEnabled (true); 549 ledPanel.setVisible(false); 550 } 551 } 552 553 /** 554 * Renumber the checkbox labels to match LED numbers. 555 * @param range2 false for LEDs 0-15, true for LEDs 16-31 556 */ 557 protected void renumber(boolean range2) { 558 for (int j = 0; j < 16 ; j++) { 559 ((JCheckBox)ledPanel.getComponent(j)).setText(range2 ? ""+(j+16) : ""+j); 560 } 561 } 562 563 // SetDirect button 564 /** 565 * Handle SetDirect button, assemble LNCV Direct Set message. Requires presence of memo to send. 566 * @param range2 false for LEDs 0-15, true for LEDs 16-31 567 */ 568 protected void setDirect(boolean range2) { 569 if (addressField.getText() != null) { 570 try { 571 adr = inDomain(addressField.getText(), 65535); 572 int cv = 0x00; 573 // fetch the bits as set on the ledPanel 574 for (int j = 0; j < 16 ; j++) { 575 cv += (((JCheckBox)ledPanel.getComponent(j)).isSelected() ? (1 << j) : 0); 576 //log.debug("j={} cv={}", j, cv); 577 } 578 memo.getLnTrafficController().sendLocoNetMessage(LncvMessageContents.createDirectWriteRequest(adr, cv, range2)); 579 } catch (NumberFormatException e) { 580 log.error("invalid entry, must be number"); 581 } 582 } else { 583 statusText1.setText(Bundle.getMessage("FeedBackEnterArticle")); 584 addressField.setBackground(Color.RED); 585 return; 586 } 587 // stop and inform user 588 statusText1.setText(Bundle.getMessage("FeedBackSetDirect")); 589 } 590 591 private int inDomain(String entry, int max) { 592 int n = -1; 593 try { 594 n = Integer.parseInt(entry); 595 } catch (NumberFormatException e) { 596 log.error("invalid entry, must be number"); 597 } 598 if ((0 <= n) && (n <= max)) { 599 return n; 600 } else { 601 statusText1.setText(Bundle.getMessage("FeedBackInputOutsideRange")); 602 return 0; 603 } 604 } 605 606 public void copyEntry(int art, int mod) { 607 if ((moduleProgRunning < 0) && !allProgRunning) { // protect locked fields while programming 608 articleField.setText(art + ""); 609 addressField.setText(mod + ""); 610 } 611 } 612 613 /** 614 * {@inheritDoc} 615 * Compare to {@link LnOpsModeProgrammer#message(jmri.jmrix.loconet.LocoNetMessage)} 616 * 617 * @param m a message received and analysed for LNCV characteristics 618 */ 619 @Override 620 public synchronized void message(LocoNetMessage m) { // receive a LocoNet message and log it 621 // got a LocoNet message, see if it's an LNCV response 622 //log.debug("LncvProgPane heard message {}", m.toMonitorString()); 623 if (LncvMessageContents.isSupportedLncvMessage(m)) { 624 // raw data, to display 625 String raw = (rawCheckBox.isSelected() ? ("[" + m + "] ") : ""); 626 // format the message text, expect it to provide consistent \n after each line 627 String formatted = m.toMonitorString(memo.getSystemPrefix()); 628 // copy the formatted data 629 reply += raw + formatted; 630 } 631 // or LACK write confirmation response from module? 632 if ((m.getOpCode() == LnConstants.OPC_LONG_ACK) && 633 (m.getElement(1) == 0x6D)) { // elem 1 = OPC (matches 0xED), elem 2 = ack1 634 writeConfirmed = true; 635 if (m.getElement(2) == 0x7f) { 636 valueField.setBackground(Color.GREEN); 637 reply += Bundle.getMessage("LNCV_WRITE_CONFIRMED", moduleProgRunning) + "\n"; 638 } else if (m.getElement(2) == 1) { 639 valueField.setBackground(Color.RED); 640 reply += Bundle.getMessage("LNCV_WRITE_CV_NOTSUPPORTED", moduleProgRunning, cv) + "\n"; 641 } else if (m.getElement(2) == 2) { 642 valueField.setBackground(Color.RED); 643 reply += Bundle.getMessage("LNCV_WRITE_CV_READONLY", moduleProgRunning, cv) + "\n"; 644 } else if (m.getElement(2) == 3) { 645 valueField.setBackground(Color.RED); 646 reply += Bundle.getMessage("LNCV_WRITE_CV_OUTOFBOUNDS", moduleProgRunning, val) + "\n"; 647 } 648 } 649 if (LncvMessageContents.extractMessageType(m) == LncvMessageContents.LncvCommand.LNCV_WRITE) { 650 reply += Bundle.getMessage("LNCV_WRITE_MOD_MONITOR", (moduleProgRunning == -1 ? "ALL" : moduleProgRunning)) + "\n"; 651 } 652 if (LncvMessageContents.extractMessageType(m) == LncvMessageContents.LncvCommand.LNCV_READ) { 653 reply += Bundle.getMessage("LNCV_READ_MOD_MONITOR", (moduleProgRunning == -1 ? "ALL" : moduleProgRunning)) + "\n"; 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 msgArt = contents.getLncvArticleNum(); 660 int msgAdr = moduleProgRunning; 661 int msgCv = contents.getCvNum(); 662 int msgVal = contents.getCvValue(); 663 if ((msgCv == 0) || (msgArt == art)) { // trust last used address. to be sure, check against Article (hardware class) number 664 msgAdr = msgVal; // if cvNum = 0, this is the LNCV module address 665 } 666 String foundMod = "(LNCV) " + Bundle.getMessage("LabelArticle") + art + " " 667 + Bundle.getMessage("LabelAddress") + msgAdr + " " 668 + Bundle.getMessage("LabelCv") + msgCv + " " 669 + Bundle.getMessage("LabelValue")+ msgVal + "\n"; 670 reply += foundMod; 671 log.debug("ReadReply={}", reply); 672 // storing a Module in the list using the (first) write reply is handled by loconet.LncvDevicesManager 673 674 // enter returned CV in CVnum field 675 cvField.setText(msgCv + ""); 676 cvField.setBackground(Color.WHITE); 677 // enter returned value in Value field 678 valueField.setText(msgVal + ""); 679 valueField.setBackground(Color.WHITE); 680 681 LncvDevice dev = memo.getLncvDevicesManager().getDevice(art, adr); 682 if (dev != null) { 683 dev.setCvNum(msgCv); 684 dev.setCvValue(msgVal); 685 } 686 memo.getLncvDevicesManager().firePropertyChange("DeviceListChanged", true, false); 687 } 688 689 if (reply != null) { // we fool allProgFinished (copied from LNSV2 class) 690 allProgFinished(null); 691 } 692 } 693 694 /** 695 * AllProg Session callback. 696 * 697 * @param error feedback from Finish process 698 */ 699 public void allProgFinished(String error) { 700 if (error != null) { 701 log.error("LNCV process finished with error: {}", error); 702 statusText2.setText(Bundle.getMessage("FeedBackDiscoverFail")); 703 } else { 704 synchronized (this) { 705 statusText2.setText(Bundle.getMessage("FeedBackDiscoverSuccess", lncvdm.getDeviceCount())); 706 result.setText(reply); 707 } 708 } 709 } 710 711 /** 712 * Give user feedback on closing of any open programming sessions when tool window is closed. 713 * @see #dispose() for actual closing of sessions 714 */ 715 public void handleCloseEvent() { 716 //log.debug("handleCloseEvent() called in LncvProgPane"); 717 if (allProgRunning || moduleProgRunning > 0) { 718 // adds a Don't remember again checkbox and stores setting in pm 719 // show dialog 720 if (pm != null && !pm.getSimplePreferenceState(dontWarnOnClose)) { 721 final JDialog dialog = new JDialog(); 722 dialog.setTitle(Bundle.getMessage("ReminderTitle")); 723 dialog.setLocationRelativeTo(null); 724 dialog.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE); 725 JPanel container = new JPanel(); 726 container.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); 727 container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS)); 728 729 JLabel question = new JLabel(Bundle.getMessage("DialogRunningWarning"), JLabel.CENTER); 730 question.setAlignmentX(Component.CENTER_ALIGNMENT); 731 container.add(question); 732 733 JButton okButton = new JButton(Bundle.getMessage("ButtonOK")); 734 JPanel buttons = new JPanel(); 735 buttons.setAlignmentX(Component.CENTER_ALIGNMENT); 736 buttons.add(okButton); 737 container.add(buttons); 738 739 final JCheckBox remember = new JCheckBox(Bundle.getMessage("DontRemind")); 740 remember.setAlignmentX(Component.CENTER_ALIGNMENT); 741 remember.setFont(remember.getFont().deriveFont(10f)); 742 container.add(remember); 743 744 okButton.addActionListener(e -> { 745 if ((remember.isSelected()) && (pm != null)) { 746 pm.setSimplePreferenceState(dontWarnOnClose, remember.isSelected()); 747 } 748 dialog.dispose(); 749 }); 750 751 752 dialog.getContentPane().add(container); 753 dialog.pack(); 754 dialog.setModal(true); 755 dialog.setVisible(true); 756 } 757 758 // dispose will take care of actually stopping any open prog session 759 } 760 } 761 762 /** 763 * {@inheritDoc} 764 */ 765 @Override 766 public void dispose() { 767 if (memo != null && memo.getLnTrafficController() != null) { 768 // disconnect from the LnTrafficController, normally attached/detached after Discovery completed 769 memo.getLnTrafficController().removeLocoNetListener(~0, this); 770 } 771 // and unwind swing 772 if (pm != null) { 773 pm.setSimplePreferenceState(rawDataCheck, rawCheckBox.isSelected()); 774 } 775 // prevent closing LNCV tool with programming session left open on module(s). 776 if (moduleProgRunning >= 0) { 777 modProgButtonActionPerformed(); 778 } 779 if (allProgRunning) { 780 allProgButtonActionPerformed(); 781 } 782 super.setVisible(false); 783 784 InstanceManager.getOptionalDefault(JTablePersistenceManager.class).ifPresent((tpm) -> { 785 synchronized (this) { 786 tpm.stopPersisting(moduleTable); 787 } 788 }); 789 790 super.dispose(); 791 } 792 793 /** 794 * Testing methods. 795 * 796 * @return text currently in Article field 797 */ 798 protected String getArticleEntry() { 799 if (!articleField.isEditable()) { 800 return "locked"; 801 } else { 802 return articleField.getText(); 803 } 804 } 805 806 protected String getAddressEntry() { 807 if (!addressField.isEditable()) { 808 return "locked"; 809 } else { 810 return addressField.getText(); 811 } 812 } 813 814 protected synchronized String getMonitorContents(){ 815 return reply; 816 } 817 818 protected void setCvFields(int cvNum, int cvVal) { 819 cvField.setText(""+cvNum); 820 if (cvVal > -1) { 821 valueField.setText("" + cvVal); 822 } else { 823 valueField.setText(""); 824 } 825 } 826 827 protected synchronized LncvDevice getModule(int i) { 828 if (lncvdm == null) { 829 lncvdm = memo.getLncvDevicesManager(); 830 } 831 log.debug("lncvdm.getDeviceCount()={}", lncvdm.getDeviceCount()); 832 if (i > -1 && i < lncvdm.getDeviceCount()) { 833 return lncvdm.getDeviceList().getDevice(i); 834 } else { 835 log.debug("getModule({}) failed", i); 836 return null; 837 } 838 } 839 840 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LncvProgPane.class); 841 842}