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 * <p> 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 * <p> 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 JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, tablePanel, getMonitorPanel()); 176 splitPane.setOneTouchExpandable(true); 177 splitPane.setAlignmentX(Component.CENTER_ALIGNMENT); 178 splitPane.setBorder(BorderFactory.createEmptyBorder()); 179 add(splitPane); 180 181 rawCheckBox.setSelected(pm.getSimplePreferenceState(rawDataCheck)); 182 } 183 184 /* 185 * Initialize the LNCV Monitor panel. 186 */ 187 protected JPanel getMonitorPanel() { 188 JPanel panel3 = new JPanel(); 189 panel3.setLayout(new BoxLayout(panel3, BoxLayout.Y_AXIS)); 190 191 JPanel panel31 = new JPanel(); 192 panel31.setLayout(new BoxLayout(panel31, BoxLayout.Y_AXIS)); 193 JScrollPane resultScrollPane = new JScrollPane(result); 194 panel31.add(resultScrollPane); 195 196 panel31.add(rawCheckBox); 197 rawCheckBox.setVisible(true); 198 rawCheckBox.setToolTipText(Bundle.getMessage("TooltipShowRaw")); 199 panel3.add(panel31); 200 Border panel3Border = BorderFactory.createEtchedBorder(); 201 Border panel3Titled = BorderFactory.createTitledBorder(panel3Border, Bundle.getMessage("LncvMonitorTitle")); 202 panel3.setBorder(panel3Titled); 203 return panel3; 204 } 205 206 /* 207 * Initialize the Button panel. Requires presence of memo to send and receive. 208 */ 209 protected JPanel initButtonPanel() { 210 // Set up buttons and entry fields 211 JPanel panel4 = new JPanel(); 212 panel4.setLayout(new BoxLayout(panel4, BoxLayout.X_AXIS)); 213 panel4.add(Box.createHorizontalGlue()); // this will expand/contract 214 215 JPanel panel41 = new JPanel(); 216 panel41.setLayout(new BoxLayout(panel41, BoxLayout.PAGE_AXIS)); 217 allProgButton.setText(allProgRunning ? 218 Bundle.getMessage("ButtonStopAllProg") : Bundle.getMessage("ButtonStartAllProg")); 219 allProgButton.setToolTipText(Bundle.getMessage("TipAllProgButton")); 220 allProgButton.addActionListener(e -> allProgButtonActionPerformed()); 221 panel41.add(allProgButton); 222 223 modProgButton.setText((moduleProgRunning >= 0) ? 224 Bundle.getMessage("ButtonStopModProg") : Bundle.getMessage("ButtonStartModProg")); 225 modProgButton.setToolTipText(Bundle.getMessage("TipModuleProgButton")); 226 modProgButton.addActionListener(e -> modProgButtonActionPerformed()); 227 panel41.add(modProgButton); 228 panel4.add(panel41); 229 230 JPanel panel42 = new JPanel(); 231 panel42.setLayout(new BoxLayout(panel42, BoxLayout.PAGE_AXIS)); 232 233 JPanel panel421 = new JPanel(); // default FlowLayout 234 panel421.add(articleFieldLabel); 235 // entry field (decimal) 236 articleField.setToolTipText(Bundle.getMessage("TipModuleArticleField")); 237 panel421.add(articleField); 238 panel42.add(panel421); 239 240 JPanel panel422 = new JPanel(); 241 panel422.add(addressFieldLabel); 242 // entry field (decimal) for Module Address 243 addressField.setText("1"); 244 panel422.add(addressField); 245 panel42.add(panel422); 246 247 panel42.add(directCheckBox); 248 directCheckBox.addActionListener(e -> directActionPerformed()); 249 directCheckBox.setToolTipText(Bundle.getMessage("TipDirectMode")); 250 panel4.add(panel42); 251 252 JPanel panel43 = new JPanel(); 253 Border panel43Border = BorderFactory.createEtchedBorder(); 254 panel43.setBorder(panel43Border); 255 panel43.setLayout(new BoxLayout(panel43, BoxLayout.LINE_AXIS)); 256 257 JPanel panel431 = new JPanel(); // labels 258 panel431.setLayout(new BoxLayout(panel431, BoxLayout.PAGE_AXIS)); 259 cvFieldLabel.setAlignmentX(Component.RIGHT_ALIGNMENT); 260 cvFieldLabel.setMinimumSize(new Dimension(60, new JTextField("X").getHeight() + 5)); 261 panel431.add(cvFieldLabel); 262 //cvField.setToolTipText(Bundle.getMessage("TipModuleCvField")); 263 valueFieldLabel.setMinimumSize(new Dimension(60, new JTextField("X").getHeight() + 5)); 264 valueFieldLabel.setAlignmentX(Component.RIGHT_ALIGNMENT); 265 panel431.add(valueFieldLabel); 266 panel43.add(panel431); 267 268 JPanel panel432 = new JPanel(); // entry fields 269 panel432.setMaximumSize(new Dimension(50, 50)); 270 panel432.setPreferredSize(new Dimension(50, 50)); 271 panel432.setMinimumSize(new Dimension(50, 50)); 272 panel432.setLayout(new BoxLayout(panel432, BoxLayout.PAGE_AXIS)); 273 cvField.setText("0"); 274 panel432.add(cvField); // entry field (decimal) for CV value 275 //valueField.setToolTipText(Bundle.getMessage("TipModuleValueField")); 276 valueField.setText("1"); 277 panel432.add(valueField); // entry field (decimal) for CV number to read/write 278 panel43.add(panel432); 279 280 JPanel panel433 = new JPanel(); // read/write buttons 281 panel433.setLayout(new BoxLayout(panel433, BoxLayout.PAGE_AXIS)); 282 panel433.add(readButton); 283 readButton.setEnabled(false); 284 readButton.addActionListener(e -> readButtonActionPerformed()); 285 286 panel433.add(writeButton); 287 writeButton.setEnabled(false); 288 writeButton.addActionListener(e -> writeButtonActionPerformed()); 289 panel43.add(panel433); 290 panel4.add(panel43); 291 292 panel4.add(Box.createHorizontalGlue()); // this will expand/contract 293 panel4.setAlignmentX(Component.CENTER_ALIGNMENT); 294 295 return panel4; 296 } 297 298 /* 299 * Initialize the Status panel. 300 */ 301 protected JPanel initStatusPanel() { 302 JPanel panel2 = new JPanel(); 303 panel2.setLayout(new BoxLayout(panel2, BoxLayout.PAGE_AXIS)); 304 305 statusText1.setText(" "); 306 statusText1.setHorizontalAlignment(JLabel.CENTER); 307 panel2.add(statusText1); 308 309 statusText2.setText(" "); 310 statusText2.setHorizontalAlignment(JLabel.CENTER); 311 panel2.add(statusText2); 312 313 panel2.setAlignmentX(Component.CENTER_ALIGNMENT); 314 return panel2; 315 } 316 317 /** 318 * GENERALPROG button. 319 */ 320 public void allProgButtonActionPerformed() { 321 if (moduleProgRunning >= 0) { 322 statusText1.setText(Bundle.getMessage("FeedBackModProgRunning")); 323 return; 324 } 325 if (directCheckBox.isSelected()) { 326 statusText1.setText(Bundle.getMessage("FeedBackDirectRunning")); 327 return; 328 } 329 // provide user feedback 330 readButton.setEnabled(!allProgRunning); 331 writeButton.setEnabled(!allProgRunning); 332 log.debug("AllProg pressed, allProgRunning={}", allProgRunning); 333 if (allProgRunning) { 334 log.debug("Session was running, closing"); 335 // send LncvAllProgEnd command on LocoNet 336 memo.getLnTrafficController().sendLocoNetMessage(LncvMessageContents.createAllProgEndRequest(art)); 337 statusText1.setText(Bundle.getMessage("FeedBackStopAllProg")); 338 allProgButton.setText(Bundle.getMessage("ButtonStartAllProg")); 339 articleField.setEditable(true); 340 addressField.setEditable(true); 341 allProgRunning = false; 342 return; 343 } 344 articleField.setEditable(false); 345 addressField.setEditable(false); 346 art = -1; 347 if (!articleField.getText().isEmpty()) { 348 try { 349 art = inDomain(articleField.getText(), 9999); 350 } catch (NumberFormatException e) { 351 // fine, will do broadcast all 352 } 353 } 354 // show dialog to protect unwanted ALL messages 355 Object[] dialogBoxButtonOptions = { 356 Bundle.getMessage("ButtonProceed"), 357 Bundle.getMessage("ButtonCancel")}; 358 int userReply = JmriJOptionPane.showOptionDialog(this.getParent(), 359 Bundle.getMessage("DialogAllWarning"), 360 Bundle.getMessage("WarningTitle"), 361 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, 362 null, dialogBoxButtonOptions, dialogBoxButtonOptions[1]); 363 if (userReply != 0 ) { // not array position 0 ButtonProceed 364 return; 365 } 366 statusText1.setText(Bundle.getMessage("FeedBackStartAllProg")); 367 // send LncvProgSessionStart command on LocoNet 368 LocoNetMessage m = LncvMessageContents.createAllProgStartRequest(art); 369 memo.getLnTrafficController().sendLocoNetMessage(m); 370 // stop and inform user 371 statusText1.setText(Bundle.getMessage("FeedBackStartAllProg")); 372 allProgButton.setText(Bundle.getMessage("ButtonStopAllProg")); 373 allProgRunning = true; 374 log.debug("AllProgRunning=TRUE, allProgButtonActionPerformed ready"); 375 } 376 377 // MODULEPROG button 378 /** 379 * Handle Start/End Module Prog button. 380 */ 381 public void modProgButtonActionPerformed() { 382 if (allProgRunning) { 383 statusText1.setText(Bundle.getMessage("FeedBackAllProgRunning")); 384 return; 385 } 386 if (directCheckBox.isSelected()) { 387 statusText1.setText(Bundle.getMessage("FeedBackDirectRunning")); 388 return; 389 } 390 if (articleField.getText().isEmpty()) { 391 statusText1.setText(Bundle.getMessage("FeedBackEnterArticle")); 392 articleField.setBackground(Color.RED); 393 modProgButton.setSelected(false); 394 return; 395 } 396 if (addressField.getText().isEmpty()) { 397 statusText1.setText(Bundle.getMessage("FeedBackEnterAddress")); 398 addressField.setBackground(Color.RED); 399 modProgButton.setSelected(false); 400 return; 401 } 402 // provide user feedback 403 articleField.setBackground(Color.WHITE); // reset 404 readButton.setEnabled(moduleProgRunning < 0); 405 writeButton.setEnabled(moduleProgRunning < 0); 406 if (moduleProgRunning >= 0) { // stop prog 407 try { 408 art = inDomain(articleField.getText(), 9999); 409 adr = moduleProgRunning; // use module address that was used to start Modprog 410 memo.getLnTrafficController().sendLocoNetMessage(LncvMessageContents.createModProgEndRequest(art, adr)); 411 statusText1.setText(Bundle.getMessage("FeedBackModProgClosed", adr)); 412 modProgButton.setText(Bundle.getMessage("ButtonStartModProg")); 413 moduleProgRunning = -1; 414 articleField.setEditable(true); 415 addressField.setEditable(true); 416 } catch (NumberFormatException e) { 417 statusText1.setText(Bundle.getMessage("FeedBackEnterArticle")); 418 modProgButton.setSelected(true); 419 } 420 return; 421 } 422 if ((!articleField.getText().isEmpty()) && (!addressField.getText().isEmpty())) { 423 try { 424 art = inDomain(articleField.getText(), 9999); 425 adr = inDomain(addressField.getText(), 65535); // goes in d5-d6 as module address 426 memo.getLnTrafficController().sendLocoNetMessage(LncvMessageContents.createModProgStartRequest(art, adr)); 427 statusText1.setText(Bundle.getMessage("FeedBackModProgOpen", adr)); 428 modProgButton.setText(Bundle.getMessage("ButtonStopModProg")); 429 moduleProgRunning = adr; // store address during modProg, so next line is mostly as UI indication: 430 articleField.setEditable(false); 431 addressField.setEditable(false); // lock address field to prevent accidentally changing it 432 433 } catch (NumberFormatException e) { 434 log.error("invalid entry, must be number"); 435 } 436 } 437 // stop and inform user 438 } 439 440 // READCV button 441 /** 442 * Handle Read CV button, assemble LNCV read message. Requires presence of memo. 443 */ 444 public void readButtonActionPerformed() { 445 String sArt = "65535"; // LncvMessageContents.LNCV_ALL = broadcast 446 if (moduleProgRunning >= 0) { 447 sArt = articleField.getText(); 448 articleField.setBackground(Color.WHITE); // reset 449 } 450 if ((sArt != null) && (addressField.getText() != null) && (cvField.getText() != null)) { 451 try { 452 art = inDomain(sArt, 9999); // limited according to Uhlenbrock info 453 adr = inDomain(addressField.getText(), 65535); // used as address for monitor reply 454 cv = inDomain(cvField.getText(), 9999); // decimal entry 455 memo.getLnTrafficController().sendLocoNetMessage(LncvMessageContents.createCvReadRequest(art, adr, cv)); 456 } catch (NumberFormatException e) { 457 log.error("invalid entry, must be number"); 458 } 459 } else { 460 statusText1.setText(Bundle.getMessage("FeedBackEnterArticle")); 461 articleField.setBackground(Color.RED); 462 return; 463 } 464 // stop and inform user 465 statusText1.setText(Bundle.getMessage("FeedBackRead", "LNCV")); 466 } 467 468 // WriteCV button 469 /** 470 * Handle Write button click, assemble LNCV write message. Requires presence of memo. 471 */ 472 public void writeButtonActionPerformed() { 473 String sArt = "65535"; // LncvMessageContents.LNCV_ALL; 474 if (moduleProgRunning >= 0) { 475 sArt = articleField.getText(); 476 } 477 if ((sArt != null) && (cvField.getText() != null) && (valueField.getText() != null)) { 478 articleField.setBackground(Color.WHITE); 479 try { 480 art = inDomain(sArt, 9999); 481 cv = inDomain(cvField.getText(), 9999); // decimal entry 482 val = inDomain(valueField.getText(), 65535); // decimal entry 483 if (cv == 0 && (val > 65534 || val < 1)) { 484 // reserved general module address, warn in status and abort 485 statusText1.setText(Bundle.getMessage("FeedBackValidAddressRange")); 486 valueField.setBackground(Color.RED); 487 return; 488 } 489 writeConfirmed = false; 490 memo.getLnTrafficController().sendLocoNetMessage(LncvMessageContents.createCvWriteRequest(art, cv, val)); 491 valueField.setBackground(Color.ORANGE); 492 } catch (NumberFormatException e) { 493 log.error("invalid entry, must be number"); 494 } 495 } else { 496 statusText1.setText(Bundle.getMessage("FeedBackEnterArticle")); 497 articleField.setBackground(Color.RED); 498 return; 499 } 500 // stop and inform user 501 statusText1.setText(Bundle.getMessage("FeedBackWrite", "LNCV")); 502 // LACK reply will be received separately 503 // if (received) { 504 // writeConfirmed = true; 505 // } 506 } 507 508 private JPanel ledPanel; 509 510 // a row of checkboxes to set LEDs in module on/off 511 private JPanel initDirectPanel() { 512 ledPanel = new JPanel(); 513 for (int i = 0; i < 16; i++) { 514 JCheckBox ledBox = new JCheckBox(""+i); 515 ledPanel.add(ledBox); 516 } 517 JPanel options = new JPanel(); 518 options.setLayout(new BoxLayout(options, BoxLayout.Y_AXIS)); 519 JToggleButton buttonAll = new JToggleButton(Bundle.getMessage("AllOn")); 520 buttonAll.addActionListener(e -> toggleAll(buttonAll.isSelected())); 521 options.add(buttonAll); 522 JCheckBox serieTwo = new JCheckBox("LED2"); 523 serieTwo.addActionListener(e -> renumber(serieTwo.isSelected())); 524 options.add(serieTwo); // place to the right of Set button 525 ledPanel.add(options); 526 JButton buttonSet = new JButton(Bundle.getMessage("ButtonSetDirect")); 527 ledPanel.add(buttonSet); 528 buttonSet.addActionListener(e -> setDirect(serieTwo.isSelected())); 529 ledPanel.setVisible(false); // initially hide ledPanel 530 return ledPanel; 531 } 532 533 private void toggleAll(boolean on) { 534 for (int j = 0; j < 16 ; j++) { 535 ((JCheckBox)ledPanel.getComponent(j)).setSelected(on); 536 } 537 } 538 539 protected void directActionPerformed() { 540 if (allProgRunning || moduleProgRunning > -1) { 541 directCheckBox.setSelected(false); 542 return; 543 } 544 if (directCheckBox.isSelected()) { 545 articleField.setEditable(false); 546 articleField.setText("6900"); // fixed article number as per documentation 547 articleField.setBackground(Color.WHITE); // reset 548 readButton.setEnabled (false); 549 ledPanel.setVisible(true); 550 } else { 551 articleField.setText(""); 552 articleField.setEditable(true); 553 readButton.setEnabled (true); 554 ledPanel.setVisible(false); 555 } 556 } 557 558 /** 559 * Renumber the checkbox labels to match LED numbers. 560 * @param range2 false for LEDs 0-15, true for LEDs 16-31 561 */ 562 protected void renumber(boolean range2) { 563 for (int j = 0; j < 16 ; j++) { 564 ((JCheckBox)ledPanel.getComponent(j)).setText(range2 ? ""+(j+16) : ""+j); 565 } 566 } 567 568 // SetDirect button 569 /** 570 * Handle SetDirect button, assemble LNCV Direct Set message. Requires presence of memo to send. 571 * @param range2 false for LEDs 0-15, true for LEDs 16-31 572 */ 573 protected void setDirect(boolean range2) { 574 if (addressField.getText() != null) { 575 try { 576 adr = inDomain(addressField.getText(), 65535); 577 int cv = 0x00; 578 // fetch the bits as set on the ledPanel 579 for (int j = 0; j < 16 ; j++) { 580 cv += (((JCheckBox)ledPanel.getComponent(j)).isSelected() ? (1 << j) : 0); 581 //log.debug("j={} cv={}", j, cv); 582 } 583 memo.getLnTrafficController().sendLocoNetMessage(LncvMessageContents.createDirectWriteRequest(adr, cv, range2)); 584 } catch (NumberFormatException e) { 585 log.error("invalid entry, must be number"); 586 } 587 } else { 588 statusText1.setText(Bundle.getMessage("FeedBackEnterArticle")); 589 addressField.setBackground(Color.RED); 590 return; 591 } 592 // stop and inform user 593 statusText1.setText(Bundle.getMessage("FeedBackSetDirect")); 594 } 595 596 private int inDomain(String entry, int max) { 597 int n = -1; 598 try { 599 n = Integer.parseInt(entry); 600 } catch (NumberFormatException e) { 601 log.error("invalid entry, must be number"); 602 } 603 if ((0 <= n) && (n <= max)) { 604 return n; 605 } else { 606 statusText1.setText(Bundle.getMessage("FeedBackInputOutsideRange")); 607 return 0; 608 } 609 } 610 611 public void copyEntry(int art, int mod) { 612 if ((moduleProgRunning < 0) && !allProgRunning) { // protect locked fields while programming 613 articleField.setText(art + ""); 614 addressField.setText(mod + ""); 615 } 616 } 617 618 /** 619 * {@inheritDoc} 620 * Compare to {@link LnOpsModeProgrammer#message(jmri.jmrix.loconet.LocoNetMessage)} 621 * 622 * @param m a message received and analysed for LNCV characteristics 623 */ 624 @Override 625 public synchronized void message(LocoNetMessage m) { // receive a LocoNet message and log it 626 // got a LocoNet message, see if it's an LNCV response 627 //log.debug("LncvProgPane heard message {}", m.toMonitorString()); 628 if (LncvMessageContents.isSupportedLncvMessage(m)) { 629 // raw data, to display 630 String raw = (rawCheckBox.isSelected() ? ("[" + m + "] ") : ""); 631 // format the message text, expect it to provide consistent \n after each line 632 String formatted = m.toMonitorString(memo.getSystemPrefix()); 633 // copy the formatted data 634 reply += raw + formatted; 635 } 636 // or LACK write confirmation response from module? 637 if ((m.getOpCode() == LnConstants.OPC_LONG_ACK) && 638 (m.getElement(1) == 0x6D)) { // elem 1 = OPC (matches 0xED), elem 2 = ack1 639 writeConfirmed = true; 640 if (m.getElement(2) == 0x7f) { 641 valueField.setBackground(Color.GREEN); 642 reply += Bundle.getMessage("LNCV_WRITE_CONFIRMED", moduleProgRunning) + "\n"; 643 } else if (m.getElement(2) == 1) { 644 valueField.setBackground(Color.RED); 645 reply += Bundle.getMessage("LNCV_WRITE_CV_NOTSUPPORTED", moduleProgRunning, cv) + "\n"; 646 } else if (m.getElement(2) == 2) { 647 valueField.setBackground(Color.RED); 648 reply += Bundle.getMessage("LNCV_WRITE_CV_READONLY", moduleProgRunning, cv) + "\n"; 649 } else if (m.getElement(2) == 3) { 650 valueField.setBackground(Color.RED); 651 reply += Bundle.getMessage("LNCV_WRITE_CV_OUTOFBOUNDS", moduleProgRunning, val) + "\n"; 652 } 653 } 654 if (LncvMessageContents.extractMessageType(m) == LncvMessageContents.LncvCommand.LNCV_WRITE) { 655 reply += Bundle.getMessage("LNCV_WRITE_MOD_MONITOR", (moduleProgRunning == -1 ? "ALL" : moduleProgRunning)) + "\n"; 656 } 657 if (LncvMessageContents.extractMessageType(m) == LncvMessageContents.LncvCommand.LNCV_READ) { 658 reply += Bundle.getMessage("LNCV_READ_MOD_MONITOR", (moduleProgRunning == -1 ? "ALL" : moduleProgRunning)) + "\n"; 659 } 660 if ((LncvMessageContents.extractMessageType(m) == LncvMessageContents.LncvCommand.LNCV_READ_REPLY) || 661 (LncvMessageContents.extractMessageType(m) == LncvMessageContents.LncvCommand.LNCV_READ_REPLY2)) { 662 // it's an LNCV ReadReply message, decode contents: 663 LncvMessageContents contents = new LncvMessageContents(m); 664 int msgArt = contents.getLncvArticleNum(); 665 int msgAdr = moduleProgRunning; 666 int msgCv = contents.getCvNum(); 667 int msgVal = contents.getCvValue(); 668 if ((msgCv == 0) || (msgArt == art)) { // trust last used address. to be sure, check against Article (hardware class) number 669 msgAdr = msgVal; // if cvNum = 0, this is the LNCV module address 670 } 671 String foundMod = "(LNCV) " + Bundle.getMessage("LabelArticle") + art + " " 672 + Bundle.getMessage("LabelAddress") + msgAdr + " " 673 + Bundle.getMessage("LabelCv") + msgCv + " " 674 + Bundle.getMessage("LabelValue")+ msgVal + "\n"; 675 reply += foundMod; 676 log.debug("ReadReply={}", reply); 677 // storing a Module in the list using the (first) write reply is handled by loconet.LncvDevicesManager 678 679 // enter returned CV in CVnum field 680 cvField.setText(msgCv + ""); 681 cvField.setBackground(Color.WHITE); 682 // enter returned value in Value field 683 valueField.setText(msgVal + ""); 684 valueField.setBackground(Color.WHITE); 685 686 LncvDevice dev = memo.getLncvDevicesManager().getDevice(art, adr); 687 if (dev != null) { 688 dev.setCvNum(msgCv); 689 dev.setCvValue(msgVal); 690 } 691 memo.getLncvDevicesManager().firePropertyChange("DeviceListChanged", true, false); 692 } 693 694 if (reply != null) { // we fool allProgFinished (copied from LNSV2 class) 695 allProgFinished(null); 696 } 697 } 698 699 /** 700 * AllProg Session callback. 701 * 702 * @param error feedback from Finish process 703 */ 704 public void allProgFinished(String error) { 705 if (error != null) { 706 log.error("LNCV process finished with error: {}", error); 707 statusText2.setText(Bundle.getMessage("FeedBackDiscoverFail")); 708 } else { 709 synchronized (this) { 710 if (lncvdm.getDeviceCount() == 1) { 711 statusText2.setText(Bundle.getMessage("FeedBackDiscoverSuccessOne")); 712 } else { 713 statusText2.setText(Bundle.getMessage("FeedBackDiscoverSuccess", lncvdm.getDeviceCount())); 714 } 715 result.setText(reply); 716 } 717 } 718 } 719 720 /** 721 * Give user feedback on closing of any open programming sessions when tool window is closed. 722 * @see #dispose() for actual closing of sessions 723 */ 724 public void handleCloseEvent() { 725 //log.debug("handleCloseEvent() called in LncvProgPane"); 726 if (allProgRunning || moduleProgRunning > 0) { 727 // adds a Don't remember again checkbox and stores setting in pm 728 // show dialog 729 if (pm != null && !pm.getSimplePreferenceState(dontWarnOnClose)) { 730 final JDialog dialog = new JDialog(); 731 dialog.setTitle(Bundle.getMessage("ReminderTitle")); 732 dialog.setLocationRelativeTo(null); 733 dialog.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE); 734 JPanel container = new JPanel(); 735 container.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); 736 container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS)); 737 738 JLabel question = new JLabel(Bundle.getMessage("DialogRunningWarning"), JLabel.CENTER); 739 question.setAlignmentX(Component.CENTER_ALIGNMENT); 740 container.add(question); 741 742 JButton okButton = new JButton(Bundle.getMessage("ButtonOK")); 743 JPanel buttons = new JPanel(); 744 buttons.setAlignmentX(Component.CENTER_ALIGNMENT); 745 buttons.add(okButton); 746 container.add(buttons); 747 748 final JCheckBox remember = new JCheckBox(Bundle.getMessage("DontRemind")); 749 remember.setAlignmentX(Component.CENTER_ALIGNMENT); 750 remember.setFont(remember.getFont().deriveFont(10f)); 751 container.add(remember); 752 753 okButton.addActionListener(e -> { 754 if ((remember.isSelected()) && (pm != null)) { 755 pm.setSimplePreferenceState(dontWarnOnClose, remember.isSelected()); 756 } 757 dialog.dispose(); 758 }); 759 760 761 dialog.getContentPane().add(container); 762 dialog.pack(); 763 dialog.setModal(true); 764 dialog.setVisible(true); 765 } 766 767 // dispose will take care of actually stopping any open prog session 768 } 769 } 770 771 /** 772 * {@inheritDoc} 773 */ 774 @Override 775 public void dispose() { 776 if (memo != null && memo.getLnTrafficController() != null) { 777 // disconnect from the LnTrafficController, normally attached/detached after Discovery completed 778 memo.getLnTrafficController().removeLocoNetListener(~0, this); 779 } 780 // and unwind swing 781 if (pm != null) { 782 pm.setSimplePreferenceState(rawDataCheck, rawCheckBox.isSelected()); 783 } 784 // prevent closing LNCV tool with programming session left open on module(s). 785 if (moduleProgRunning >= 0) { 786 modProgButtonActionPerformed(); 787 } 788 if (allProgRunning) { 789 allProgButtonActionPerformed(); 790 } 791 super.setVisible(false); 792 793 InstanceManager.getOptionalDefault(JTablePersistenceManager.class).ifPresent((tpm) -> { 794 synchronized (this) { 795 tpm.stopPersisting(moduleTable); 796 } 797 }); 798 799 super.dispose(); 800 } 801 802 /** 803 * Testing methods. 804 * 805 * @return text currently in Article field 806 */ 807 protected String getArticleEntry() { 808 if (!articleField.isEditable()) { 809 return "locked"; 810 } else { 811 return articleField.getText(); 812 } 813 } 814 815 protected String getAddressEntry() { 816 if (!addressField.isEditable()) { 817 return "locked"; 818 } else { 819 return addressField.getText(); 820 } 821 } 822 823 protected synchronized String getMonitorContents(){ 824 return reply; 825 } 826 827 protected void setCvFields(int cvNum, int cvVal) { 828 cvField.setText("" + cvNum); 829 if (cvVal > -1) { 830 valueField.setText("" + cvVal); 831 } else { 832 valueField.setText(""); 833 } 834 } 835 836 protected synchronized LncvDevice getModule(int i) { 837 if (lncvdm == null) { 838 lncvdm = memo.getLncvDevicesManager(); 839 } 840 log.debug("lncvdm.getDeviceCount()={}", lncvdm.getDeviceCount()); 841 if (i > -1 && i < lncvdm.getDeviceCount()) { 842 return lncvdm.getDeviceList().getDevice(i); 843 } else { 844 log.debug("getModule({}) failed", i); 845 return null; 846 } 847 } 848 849 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LncvProgPane.class); 850 851}