001package jmri.jmrix.loconet.swing.lnsv1prog; 002 003import jmri.InstanceManager; 004import jmri.UserPreferencesManager; 005import jmri.jmrit.beantable.EnablingCheckboxRenderer; 006import jmri.jmrix.loconet.*; 007import jmri.jmrix.loconet.lnsvf1.Lnsv1Device; 008import jmri.jmrix.loconet.lnsvf1.Lnsv1MessageContents; 009import jmri.swing.JTablePersistenceManager; 010import jmri.util.swing.JmriJOptionPane; 011import jmri.util.table.ButtonEditor; 012import jmri.util.table.ButtonRenderer; 013 014import javax.swing.*; 015import javax.swing.border.Border; 016import javax.swing.table.TableRowSorter; 017import java.awt.*; 018 019/** 020 * Frame for discovery and display of LocoNet LNSVf1 boards, e.g. LocoIO. 021 * Derived from lncvprog. Verified with HDL and GCA hardware. 022 * <p> 023 * Some of the message formats used in this class are Copyright Digitrax 024 * and used with permission as part of the JMRI project. That permission does 025 * not extend to uses in other software products. If you wish to use this code, 026 * algorithm or these message formats outside of JMRI, please contact Digitrax. 027 * <p> 028 * Buttons in table row allow to add roster entry for device, and switch to the 029 * DecoderPro ops mode programmer. 030 * 031 * @author Egbert Broerse Copyright (C) 2021, 2022, 2025 032 */ 033public class Lnsv1ProgPane extends jmri.jmrix.loconet.swing.LnPanel implements LocoNetListener { 034 035 private LocoNetSystemConnectionMemo memo; 036 protected JButton probeAllButton = new JButton(); 037 protected JButton setAllAddressButton = new JButton(); 038 protected JButton readButton = new JButton(Bundle.getMessage("ButtonRead")); 039 protected JButton writeButton = new JButton(Bundle.getMessage("ButtonWrite")); 040 protected JTextField addressField = new JTextField(4); 041 protected JTextField subAddressField = new JTextField(4); 042 protected JTextField svField = new JTextField(4); 043 protected JTextField valueField = new JTextField(4); 044 protected JCheckBox rawCheckBox = new JCheckBox(Bundle.getMessage("ButtonShowRaw")); 045 protected JCheckBox decimalCheckBox = new JCheckBox(Bundle.getMessage("ButtonShowDecimal")); 046 protected JTable moduleTable = null; 047 protected Lnsv1ProgTableModel moduleTableModel = null; 048 public static final int ROW_HEIGHT = (new JButton("X").getPreferredSize().height)*9/10; 049 050 protected JPanel tablePanel = null; 051 protected JLabel statusText1 = new JLabel(); 052 protected JLabel statusText2 = new JLabel(); 053 protected JLabel sepFieldLabel = new JLabel("/", JLabel.RIGHT); 054 protected JLabel addressFieldLabel = new JLabel(Bundle.getMessage("LabelModuleAddress", JLabel.RIGHT)); 055 protected JLabel svFieldLabel = new JLabel(Bundle.getMessage("LabelSv"), JLabel.RIGHT); 056 protected JLabel valueFieldLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("HeadingValue")), JLabel.RIGHT); 057 protected JTextArea result = new JTextArea(6,50); 058 protected String reply = ""; 059 protected int addr; 060 protected int subAddr; 061 protected int sv = 0; 062 protected int val; 063 boolean writeConfirmed = false; 064 private final String rawDataCheck = this.getClass().getName() + ".RawData"; // NOI18N 065 private final String decimalDataCheck = this.getClass().getName() + ".DecimalData"; // NOI18N 066 private UserPreferencesManager pm; 067 private transient TableRowSorter<Lnsv1ProgTableModel> sorter; 068 private Lnsv1DevicesManager lnsv1dm; 069 070 /** 071 * Constructor method 072 */ 073 public Lnsv1ProgPane() { 074 super(); 075 } 076 077 /** 078 * {@inheritDoc} 079 */ 080 @Override 081 public String getHelpTarget() { 082 return "package.jmri.jmrix.loconet.swing.lnsv1prog.Lnsv1ProgPane"; // NOI18N 083 } 084 085 @Override 086 public String getTitle() { 087 return Bundle.getMessage("MenuItemLnsv1Prog"); 088 } 089 090 /** 091 * Initialize the config window 092 */ 093 @Override 094 public void initComponents() { 095 setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); 096 // buttons at top, like SE8c pane 097 add(initButtonPanel()); // requires presence of memo. 098 add(initStatusPanel()); // positioned after ButtonPanel so to keep it simple also delayed 099 // creation of table must wait for memo + tc to be available, see initComponents(memo) next 100 } 101 102 @Override 103 public synchronized void initComponents(LocoNetSystemConnectionMemo memo) { 104 super.initComponents(memo); 105 this.memo = memo; 106 lnsv1dm = memo.getLnsv1DevicesManager(); 107 pm = InstanceManager.getDefault(UserPreferencesManager.class); 108 // connect to the LnTrafficController 109 if (memo.getLnTrafficController() == null) { 110 log.error("No traffic controller is available"); 111 } else { 112 // add listener 113 memo.getLnTrafficController().addLocoNetListener(~0, this); 114 } 115 116 // create the data model and its table 117 moduleTableModel = new Lnsv1ProgTableModel(this, memo); 118 moduleTable = new JTable(moduleTableModel); 119 moduleTable.setRowSelectionAllowed(false); 120 moduleTable.setPreferredScrollableViewportSize(new Dimension(300, 200)); 121 moduleTable.setRowHeight(ROW_HEIGHT); 122 moduleTable.setDefaultEditor(JButton.class, new ButtonEditor(new JButton())); 123 moduleTable.setDefaultRenderer(JButton.class, new ButtonRenderer()); 124 moduleTable.setDefaultRenderer(Boolean.class, new EnablingCheckboxRenderer()); 125 moduleTable.setRowSelectionAllowed(true); 126 moduleTable.getSelectionModel().addListSelectionListener(event -> { 127 synchronized (this) { 128 if (moduleTable.getSelectedRow() > -1 && moduleTable.getSelectedRow() < moduleTable.getRowCount()) { 129 // copy composite board address, svNuma and value from selected row 130 copyEntrytoFields((int) moduleTable.getValueAt(moduleTable.getSelectedRow(), Lnsv1ProgTableModel.MODADDR_COLUMN)); 131 setCvFields((int) moduleTable.getValueAt(moduleTable.getSelectedRow(), Lnsv1ProgTableModel.CV_COLUMN), 132 (int) moduleTable.getValueAt(moduleTable.getSelectedRow(), Lnsv1ProgTableModel.VALUE_COLUMN)); 133 } 134 } 135 }); 136 // establish row sorting for the table 137 sorter = new TableRowSorter<>(moduleTableModel); 138 moduleTable.setRowSorter(sorter); 139 // establish table physical characteristics persistence 140 moduleTable.setName("LNSV1 Device Management"); // NOI18N 141 // Reset and then persist the table's ui state 142 InstanceManager.getOptionalDefault(JTablePersistenceManager.class).ifPresent((tpm) -> { 143 synchronized (this) { 144 tpm.resetState(moduleTable); 145 tpm.persist(moduleTable, true); 146 } 147 }); 148 149 JScrollPane tableScrollPane = new JScrollPane(moduleTable); 150 tablePanel = new JPanel(); 151 Border resultBorder = BorderFactory.createEtchedBorder(); 152 Border resultTitled = BorderFactory.createTitledBorder(resultBorder, Bundle.getMessage("Lnsv1TableTitle")); 153 tablePanel.setBorder(resultTitled); 154 tablePanel.setLayout(new BoxLayout(tablePanel, BoxLayout.Y_AXIS)); 155 tablePanel.add(tableScrollPane); 156 157 JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, tablePanel, getMonitorPanel()); 158 splitPane.setOneTouchExpandable(true); 159 splitPane.setAlignmentX(Component.CENTER_ALIGNMENT); 160 splitPane.setBorder(BorderFactory.createEmptyBorder()); 161 add(splitPane); 162 163 rawCheckBox.setSelected(pm.getSimplePreferenceState(rawDataCheck)); 164 decimalCheckBox.setSelected(pm.getSimplePreferenceState(decimalDataCheck)); 165 166 // Probe when ready 167 probeAllButtonActionPerformed(); 168 } 169 170 /* 171 * Initialize the LNSV1 Monitor panel. 172 */ 173 protected JPanel getMonitorPanel() { 174 JPanel panel3 = new JPanel(); 175 panel3.setLayout(new BoxLayout(panel3, BoxLayout.Y_AXIS)); 176 177 JPanel panel31 = new JPanel(); 178 panel31.setLayout(new BoxLayout(panel31, BoxLayout.Y_AXIS)); 179 JScrollPane resultScrollPane = new JScrollPane(result); 180 panel31.add(resultScrollPane); 181 182 JPanel panel31b = new JPanel(); 183 panel31b.setLayout(new BoxLayout(panel31b, BoxLayout.X_AXIS)); 184 panel31b.add(rawCheckBox); 185 rawCheckBox.setVisible(true); 186 rawCheckBox.setToolTipText(Bundle.getMessage("TooltipShowRaw")); 187 panel31b.add(decimalCheckBox); 188 decimalCheckBox.setVisible(true); 189 decimalCheckBox.setToolTipText(Bundle.getMessage("TooltipShowDecimal")); 190 panel31.add(panel31b); 191 192 panel3.add(panel31); 193 Border panel3Border = BorderFactory.createEtchedBorder(); 194 Border panel3Titled = BorderFactory.createTitledBorder(panel3Border, Bundle.getMessage("Lnsv1MonitorTitle")); 195 panel3.setBorder(panel3Titled); 196 return panel3; 197 } 198 199 /* 200 * Initialize the Button panel. Requires presence of memo to send and receive. 201 */ 202 protected JPanel initButtonPanel() { 203 // Set up buttons and entry fields 204 JPanel panel4 = new JPanel(); 205 panel4.setLayout(new BoxLayout(panel4, BoxLayout.X_AXIS)); 206 panel4.add(Box.createHorizontalGlue()); // this will expand/contract 207 208 JPanel panel41 = new JPanel(); 209 panel41.setLayout(new BoxLayout(panel41, BoxLayout.PAGE_AXIS)); 210 probeAllButton.setText(Bundle.getMessage("ButtonProbe")); 211 probeAllButton.setToolTipText(Bundle.getMessage("TipProbeAllButton")); 212 probeAllButton.addActionListener(e -> probeAllButtonActionPerformed()); 213 panel41.add(probeAllButton); 214 215 setAllAddressButton.setText(Bundle.getMessage("ButtonSetModuleAddress")); 216 setAllAddressButton.setToolTipText(Bundle.getMessage("TipSetModuleAddrButton")); 217 setAllAddressButton.addActionListener(e -> setAllAddressButtonActionPerformed()); 218 panel41.add(setAllAddressButton); 219 panel4.add(panel41); 220 221 JPanel panel42 = new JPanel(); 222 panel42.setLayout(new BoxLayout(panel42, BoxLayout.PAGE_AXIS)); 223 224 JPanel panel421 = new JPanel(); // default FlowLayout 225 panel421.add(addressFieldLabel); 226 // entry field (decimal) 227 JPanel panel4211 = new JPanel(); // keep entry fields together 228 addressField.setToolTipText(Bundle.getMessage("TipModuleAddrEntry")); 229 panel4211.add(addressField); // entry field (decimal) for Module Low Address 230 panel4211.add(sepFieldLabel); // holds the slash between base and subaddress 231 subAddressField.setToolTipText(Bundle.getMessage("TipModuleSubaddrEntry")); 232 panel4211.add(subAddressField); // entry field (decimal) for Module Subaddress 233 panel421.add(panel4211); 234 panel42.add(panel421); 235 panel4.add(panel42); 236 237 JPanel panel43 = new JPanel(); // CV num + value 238 Border panel43Border = BorderFactory.createEtchedBorder(); 239 panel43.setBorder(panel43Border); 240 panel43.setLayout(new BoxLayout(panel43, BoxLayout.LINE_AXIS)); 241 242 JPanel panel431 = new JPanel(); // labels 243 panel431.setLayout(new BoxLayout(panel431, BoxLayout.PAGE_AXIS)); 244 svFieldLabel.setAlignmentX(Component.RIGHT_ALIGNMENT); 245 svFieldLabel.setMinimumSize(new Dimension(60, new JTextField("X").getHeight() + 5)); 246 panel431.add(svFieldLabel); 247 248 valueFieldLabel.setAlignmentX(Component.RIGHT_ALIGNMENT); 249 valueFieldLabel.setMinimumSize(new Dimension(60, new JTextField("X").getHeight() + 5)); 250 panel431.add(valueFieldLabel); 251 panel43.add(panel431); 252 253 JPanel panel432 = new JPanel(); // entry fields 254 panel432.setMaximumSize(new Dimension(50, 50)); 255 panel432.setPreferredSize(new Dimension(50, 50)); 256 panel432.setMinimumSize(new Dimension(50, 50)); 257 panel432.setLayout(new BoxLayout(panel432, BoxLayout.PAGE_AXIS)); 258 panel432.add(svField); // entry field (decimal) for SV number to read/write 259 panel432.add(valueField); // entry field (decimal) for CV value 260 panel43.add(panel432); 261 262 JPanel panel433 = new JPanel(); // read/write buttons 263 panel433.setLayout(new BoxLayout(panel433, BoxLayout.PAGE_AXIS)); 264 panel433.add(readButton); 265 readButton.setEnabled(true); 266 readButton.addActionListener(e -> readButtonActionPerformed()); 267 268 panel433.add(writeButton); 269 writeButton.setEnabled(false); // disabled button, to write we point to Roster in button tooltip 270 writeButton.addActionListener(e -> writeButtonActionPerformed()); 271 writeButton.setToolTipText(Bundle.getMessage("ButtonWriteInactiveTip")); 272 panel43.add(panel433); 273 panel4.add(panel43); 274 275 panel4.add(Box.createHorizontalGlue()); // this will expand/contract 276 panel4.setAlignmentX(Component.CENTER_ALIGNMENT); 277 278 return panel4; 279 } 280 281 /* 282 * Initialize the Status panel. 283 */ 284 protected JPanel initStatusPanel() { 285 JPanel panel2 = new JPanel(); 286 panel2.setLayout(new BoxLayout(panel2, BoxLayout.PAGE_AXIS)); 287 288 statusText1.setText(" "); 289 statusText1.setHorizontalAlignment(JLabel.CENTER); 290 panel2.add(statusText1); 291 292 statusText2.setText(" "); 293 statusText2.setHorizontalAlignment(JLabel.CENTER); 294 panel2.add(statusText2); 295 296 panel2.setAlignmentX(Component.CENTER_ALIGNMENT); 297 return panel2; 298 } 299 300 /** 301 * PROBE button. 302 */ 303 public void probeAllButtonActionPerformed() { 304 // send probeAll command onto LocoNet 305 statusText1.setText(Bundle.getMessage("FeedBackProbing")); 306 probeAllButton.setText(Bundle.getMessage("ButtonProbing")); 307 LocoNetMessage m = Lnsv1MessageContents.createBroadcastProbeAll(); 308 memo.getLnTrafficController().sendLocoNetMessage(m); 309 // wait for replies 310 try { 311 Thread.sleep(200); 312 } catch (InterruptedException e) { 313 Thread.currentThread().interrupt(); // retain if needed later 314 return; // interrupt kills the thread 315 } 316 statusText1.setText(Bundle.getMessage("FeedBackProbingStop")); 317 probeAllButton.setText(Bundle.getMessage("ButtonProbe")); 318 } 319 320 // MODULE_SET_ADDRESS button 321 /** 322 * Write SV1 and (optionally) SV2. 323 */ 324 public void setAllAddressButtonActionPerformed() { 325 addressField.setBackground(Color.WHITE); 326 subAddressField.setBackground(Color.WHITE); 327 if (addressField.getText().isEmpty() || subAddressField.getText().isEmpty()) { 328 statusText1.setText(Bundle.getMessage("FeedBackEnterHiLoAddress")); 329 if (addressField.getText().isEmpty()) { 330 addressField.setBackground(Color.RED); 331 } else { 332 subAddressField.setBackground(Color.RED); 333 } 334 setAllAddressButton.setSelected(false); 335 return; 336 } 337 // show dialog to protect unwanted ALL messages 338 Object[] dialogBoxButtonOptions = { 339 Bundle.getMessage("ButtonProceed"), 340 Bundle.getMessage("ButtonCancel")}; 341 int userReply = JmriJOptionPane.showOptionDialog(this.getParent(), 342 Bundle.getMessage("DialogAllLnsv1Warning"), 343 Bundle.getMessage("WarningTitle"), 344 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, 345 null, dialogBoxButtonOptions, dialogBoxButtonOptions[1]); 346 if (userReply != 0 ) { // not array position 0 ButtonProceed 347 return; 348 } 349 if ((!addressField.getText().isEmpty()) && (!subAddressField.getText().isEmpty())) { 350 try { 351 addr = inDomain(addressField.getText(), 1, 127); // goes in DST_L as module low address 352 subAddr = inDomain(subAddressField.getText(), 1,127); // goes in d5 as module high address 353 // check & warn for reserved LocoBuffer 0x50/0d80 address 354 if (addr == 0x50) { 355 locoBufferReservedAddress(); 356 return; 357 } 358 setAllAddressButton.setEnabled(false); 359 statusText1.setText(Bundle.getMessage("FeedBackModAddrStart", addr, subAddr)); 360 addressField.setEditable(false); // lock addressL & H fields to prevent accidentally changing it 361 subAddressField.setEditable(false); 362 LocoNetMessage[] messageArray = Lnsv1MessageContents.createBroadcastSetAddress(addr, subAddr); 363 // send Lnsv1 broadcast write address command(s) onto LocoNet 364 for (LocoNetMessage m : messageArray) { 365 if (m != null) { 366 memo.getLnTrafficController().sendLocoNetMessage(m); 367 } 368 } 369 // wait a second for replies 370 try { 371 Thread.sleep(500); 372 } catch (InterruptedException e) { 373 Thread.currentThread().interrupt(); // retain if needed later 374 return; // interrupt kills the thread 375 } 376 setAllAddressButton.setEnabled(true); 377 addressField.setEditable(true); // unlock addressL fields 378 subAddressField.setEditable(true); 379 } catch (NumberFormatException e) { 380 statusText1.setText(Bundle.getMessage("FeedBackEnterHiLoAddress")); 381 log.error("invalid entry, both must be numbers"); 382 } 383 } 384 } 385 386 // READ_SV button 387 /** 388 * Handle Read CV button, assemble LNSVf1 read message. Requires presence of memo. 389 */ 390 public void readButtonActionPerformed() { 391 addressField.setBackground(Color.WHITE); 392 subAddressField.setBackground(Color.WHITE); 393 svField.setBackground(Color.WHITE); 394 if (addressField.getText().isEmpty() || subAddressField.getText().isEmpty()) { 395 statusText1.setText(Bundle.getMessage("FeedBackEnterHiLoAddress")); 396 if (addressField.getText().isEmpty()) { 397 addressField.setBackground(Color.RED); 398 } else { 399 subAddressField.setBackground(Color.RED); 400 } 401 setAllAddressButton.setSelected(false); 402 return; 403 } 404 if (svField.getText().isEmpty()) { 405 svField.setBackground(Color.RED); 406 statusText1.setText(Bundle.getMessage("FeedBackEnterSv")); 407 return; 408 } 409 try { 410 addr = inDomain(addressField.getText(), 1,127); // goes in DST_L, used as module base address 411 subAddr = inDomain(subAddressField.getText(), 1,127); // goes in D5, used as module subaddress 412 // check & warn for reserved LocoBuffer 0x50/0d80 address 413 if (addr == 0x50) { 414 locoBufferReservedAddress(); 415 return; 416 } 417 sv = inDomain(svField.getText(), 0,127); // decimal entry 418 log.debug("ReadButtonPressed adrL={}, sub={}, sv={}", addr, subAddr, sv); 419 LocoNetMessage m = Lnsv1MessageContents.createSv1ReadRequest(addr, subAddr, sv); 420 memo.getLnTrafficController().sendLocoNetMessage(m); 421 } catch (NumberFormatException e) { 422 statusText1.setText(Bundle.getMessage("FeedBackEnterNumbers")); 423 log.error("invalid entry, must be numbers"); 424 } 425 // stop and inform user 426 statusText1.setText(Bundle.getMessage("FeedBackRead", "LNSV1")); 427 } 428 429 // WriteCV button 430 /** 431 * Handle Write button click, assemble LNSVf1 write message. Requires presence of memo. 432 */ 433 public void writeButtonActionPerformed() { 434 if (addressField.getText() != null && subAddressField.getText() != null 435 && (svField.getText() != null) && (valueField.getText() != null)) { 436 try { 437 addr = inDomain(addressField.getText(), 1,0x7F); // goes in DST_L as module low address 438 subAddr = inDomain(subAddressField.getText(), 1,0x7F); // goes in d5 as module high address 439 // check & warn for reserved LocoBuffer 0x50/0d80 address 440 if (addr == 0x50) { 441 locoBufferReservedAddress(); 442 return; 443 } 444 sv = inDomain(svField.getText(), 1,0x7F); // decimal entry 445 val = inDomain(valueField.getText(), 0,0x7F); // decimal entry 446 if (sv == 100 || sv == 80) { 447 // reserved general module address, warn in status and abort 448 statusText1.setText(Bundle.getMessage("FeedBackValidAddressRange")); 449 valueField.setBackground(Color.RED); 450 return; 451 } 452 writeConfirmed = false; 453 LocoNetMessage m = Lnsv1MessageContents.createSv1WriteRequest(addr, subAddr, sv, val); 454 memo.getLnTrafficController().sendLocoNetMessage(m); 455 valueField.setBackground(Color.ORANGE); 456 } catch (NumberFormatException e) { 457 statusText1.setText(Bundle.getMessage("FeedBackEnterNumbers")); 458 log.error("invalid entry, must be numbers"); 459 } 460 } else { 461 statusText1.setText(Bundle.getMessage("FeedBackEnterHiLoAddress")); 462 return; 463 } 464 // stop and inform user 465 statusText1.setText(Bundle.getMessage("FeedBackWrite", "LNSV1")); 466 } 467 468 private int inDomain(String entry, int min, int max) { 469 int n = -1; 470 try { 471 n = Integer.parseInt(entry); 472 } catch (NumberFormatException e) { 473 log.error("invalid entry, must be number"); 474 } 475 if ((min <= n) && (n <= max)) { 476 return n; 477 } else { 478 statusText1.setText(Bundle.getMessage("FeedBackInputOutsideRange")); 479 return 0; 480 } 481 } 482 483 public void copyEntrytoFields(int adr) { 484 addressField.setText((adr & 0x7F) + ""); 485 subAddressField.setText((((adr >> 8) & 0x7F) + 1) + ""); 486 } 487 488 /** 489 * Show dialog to warn that address 0x50 is reserved and invalid entry in LNSV1 pane 490 */ 491 private void locoBufferReservedAddress() { 492 Object[] dialogBoxButtonOptions = { 493 Bundle.getMessage("ButtonOK")}; 494 JmriJOptionPane.showOptionDialog(this.getParent(), 495 Bundle.getMessage("DialogWarnLbReserved"), 496 Bundle.getMessage("WarningTitle"), 497 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.WARNING_MESSAGE, 498 null, dialogBoxButtonOptions, dialogBoxButtonOptions[0]); 499 } 500 501 /** 502 * {@inheritDoc} 503 * See jmri.jmrix.loconet.lnsvf1.Lnsv1MessageContents.toString(). 504 * Compare to {@link LnOpsModeProgrammer#message(LocoNetMessage)}. 505 * We pick up the SV + value add some details to status line (LnMonitor shows on our pane too). 506 * @param m a message received and analysed for LNSVf1 characteristics 507 */ 508 @Override 509 public synchronized void message(LocoNetMessage m) { // receive a LocoNet message and log it to the monitor 510 // got a LocoNet message, see if it's an LNSV1 response 511 if (Lnsv1MessageContents.isSupportedSv1Message(m)) { 512 // raw data, to display 513 String raw = (rawCheckBox.isSelected() ? ("[" + m + "] ") : ""); 514 // format the message text, expect it to provide consistent \n after each line 515 String formatted = m.toMonitorString(memo.getSystemPrefix()); 516 // copy the formatted data 517 reply += raw + formatted; 518 } else { 519 log.debug("Rejected by isSupportedSv1Message"); 520 return; 521 } 522 // use dec checkbox state to choose either LnMonitor by loconet.messageinterp 523 // using Integer.toHexString(i) and/or String.format("0x%02X", i)) 524 boolean addDec = decimalCheckBox.isSelected(); 525 Lnsv1MessageContents contents = new Lnsv1MessageContents(m); 526 // Use Programmer to (read and) write individual SV's - best done via Ports tab sheet 527 528 if (Lnsv1MessageContents.extractMessageType(m) == Lnsv1MessageContents.Sv1Command.SV1_WRITE) { 529 // it's an LNSV1 WriteReply message, decode contents: 530 log.debug("SV1_WRITE decode contents"); 531 if (contents.getSrcL() == 0x50) { 532 if (contents.getDstL() == 0x00) { 533 log.debug("Write all from LocoBuffer/PC"); 534 if (addDec) { 535 reply += Bundle.getMessage("SV1_WRITE_ALL_INTERPRETED_DEC", 536 contents.getSvNum(), 537 contents.getSv1D4()); // NOI18N 538 } 539 } else { // write request from LocoBuffer 540 log.debug("Write request from LocoBuffer/PC"); 541 if (addDec) { 542 reply += Bundle.getMessage("SV1_WRITE_INTERPRETED_DEC", 543 contents.getSrcL(), 544 contents.getSubAddress(), 545 contents.getSvNum(), 546 contents.getSv1D4()); // NOI18N 547 } 548 } 549 } else { 550 // Write Reply from LocoIO 551 log.debug("Write Reply from LocoIO"); 552 if (addDec) { 553 reply += Bundle.getMessage("SV1_WRITE_REPLY_INTERPRETED_DEC", 554 contents.getSrcL(), 555 contents.getSubAddress(), 556 contents.getSvNum(), 557 contents.getSv1D8()); // NOI18N 558 } 559 } 560 } 561 if (Lnsv1MessageContents.extractMessageType(m) == Lnsv1MessageContents.Sv1Command.SV1_READ) { 562 // it's an LNSV1 ReadReply message, decode contents: 563 log.debug("SV1_READ decode contents"); 564 if (contents.getSrcL() == 0x50) { 565 log.debug("Read request from LocoBuffer/PC"); // nothing else to do 566 if (addDec) { 567 reply += Bundle.getMessage("SV1_READ_INTERPRETED_DEC", 568 contents.getDstL(), 569 contents.getSubAddress(), 570 contents.getSvNum()); // NOI18N 571 } 572 } else { 573 // Read Reply from LocoIO 574 log.debug("Read Reply from LocoIO"); 575 if (addDec) { 576 reply += Bundle.getMessage("SV1_READ_REPLY_INTERPRETED_DEC", 577 contents.getSrcL(), 578 contents.getSubAddress(), 579 contents.getSvNum(), 580 contents.getSv1D6(), 581 contents.getSv1D7(), 582 contents.getSv1D8()); // NOI18N 583 } 584 // storing a Module in the list using the (first) write reply is handled by loconet.Lnsv1DevicesManager 585 // store in Value field if module address matches 586 if (("" + contents.getSrcL()).equals(addressField.getText()) && 587 ("" + contents.getSubAddress()).equals(subAddressField.getText()) && 588 ("" + contents.getSvNum()).equals(svField.getText())) { 589 valueField.setText("" + contents.getSvValue()); 590 } 591 // store the (last read) cvNum and cvValue to the Lnsv1Device 592 Lnsv1Device dev = memo.getLnsv1DevicesManager().getDevice(addr, subAddr); 593 if (dev != null) { 594 dev.setCvNum(contents.getSvNum()); 595 dev.setCvValue(contents.getSv1D4()); 596 } 597 memo.getLnsv1DevicesManager().firePropertyChange("DeviceListChanged", true, false); 598 } 599 } 600 601 if (reply != null) { // we fool allProgFinished (copied from LNSV2 class) 602 allProgFinished(null); 603 } 604 } 605 606 /** 607 * AllProg Session callback. 608 * 609 * @param error feedback from Finish process 610 */ 611 public void allProgFinished(String error) { 612 if (error != null) { 613 log.error("LNSV1 process finished with error: {}", error); 614 statusText2.setText(Bundle.getMessage("FeedBackDiscoverFail")); 615 } else { 616 synchronized (this) { 617 if (lnsv1dm.getDeviceCount() == 1) { 618 statusText2.setText(Bundle.getMessage("FeedBackDiscoverSuccessOne")); 619 } else { 620 statusText2.setText(Bundle.getMessage("FeedBackDiscoverSuccess", lnsv1dm.getDeviceCount())); 621 } 622 result.setText(reply); 623 } 624 } 625 } 626 627 /** 628 * {@inheritDoc} 629 */ 630 @Override 631 public void dispose() { 632 if (memo != null && memo.getLnTrafficController() != null) { 633 // disconnect from the LnTrafficController, normally attached/detached after Discovery completed 634 memo.getLnTrafficController().removeLocoNetListener(~0, this); 635 } 636 // and unwind swing 637 if (pm != null) { 638 pm.setSimplePreferenceState(rawDataCheck, rawCheckBox.isSelected()); 639 pm.setSimplePreferenceState(decimalDataCheck, decimalCheckBox.isSelected()); 640 } 641 super.setVisible(false); 642 643 InstanceManager.getOptionalDefault(JTablePersistenceManager.class).ifPresent((tpm) -> { 644 synchronized (this) { 645 tpm.stopPersisting(moduleTable); 646 } 647 }); 648 649 super.dispose(); 650 } 651 652 // Testing methods 653 654 protected synchronized String getMonitorContents(){ 655 return reply; 656 } 657 658 protected void setCvFields(int cvNum, int cvVal) { 659 svField.setText(""+cvNum); 660 if (cvVal > -1) { 661 valueField.setText("" + cvVal); 662 } else { 663 valueField.setText(""); 664 } 665 } 666 667 protected synchronized Lnsv1Device getModule(int i) { 668 if (lnsv1dm == null) { 669 lnsv1dm = memo.getLnsv1DevicesManager(); 670 } 671 //log.debug("lncvdm.getDeviceCount()={}", lnsv1dm.getDeviceCount()); 672 if (i > -1 && i < lnsv1dm.getDeviceCount()) { 673 return lnsv1dm.getDeviceList().getDevice(i); 674 } else { 675 log.debug("getModule({}) failed", i); 676 return null; 677 } 678 } 679 680 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Lnsv1ProgPane.class); 681 682}