001package jmri.jmrix.openlcb.swing.send; 002 003import java.awt.BorderLayout; 004import java.awt.Dimension; 005 006import javax.swing.Box; 007import javax.swing.BoxLayout; 008import javax.swing.JButton; 009import javax.swing.JCheckBox; 010import javax.swing.JComboBox; 011import javax.swing.JComponent; 012import javax.swing.JLabel; 013import javax.swing.JPanel; 014import javax.swing.JSeparator; 015import javax.swing.JTextField; 016import javax.swing.JToggleButton; 017 018import jmri.jmrix.can.CanListener; 019import jmri.jmrix.can.CanMessage; 020import jmri.jmrix.can.CanReply; 021import jmri.jmrix.can.CanSystemConnectionMemo; 022import jmri.jmrix.can.TrafficController; 023import jmri.jmrix.can.cbus.CbusAddress; 024import jmri.jmrix.openlcb.swing.ClientActions; 025import jmri.util.StringUtil; 026import jmri.util.javaworld.GridLayout2; 027import jmri.util.swing.WrapLayout; 028 029import org.openlcb.*; 030import org.openlcb.can.AliasMap; 031import org.openlcb.implementations.MemoryConfigurationService; 032import org.openlcb.swing.NodeSelector; 033 034/** 035 * User interface for sending OpenLCB CAN frames to exercise the system 036 * <p> 037 * When sending a sequence of operations: 038 * <ul> 039 * <li>Send the next message and start a timer 040 * <li>When the timer trips, repeat if buttons still down. 041 * </ul> 042 * 043 * @author Bob Jacobsen Copyright (C) 2008, 2012 044 * 045 */ 046public class OpenLcbCanSendPane extends jmri.jmrix.can.swing.CanPanel implements CanListener { 047 048 // member declarations 049 final JLabel jLabel1 = new JLabel(); 050 final JButton sendButton = new JButton(); 051 final JTextField packetTextField = new JTextField(60); 052 053 // internal members to hold sequence widgets 054 static final int MAXSEQUENCE = 4; 055 final JTextField[] mPacketField = new JTextField[MAXSEQUENCE]; 056 final JCheckBox[] mUseField = new JCheckBox[MAXSEQUENCE]; 057 final JTextField[] mDelayField = new JTextField[MAXSEQUENCE]; 058 final JToggleButton mRunButton = new JToggleButton("Go"); 059 060 final JTextField srcAliasField = new JTextField(4); 061 NodeSelector nodeSelector; 062 final JTextField sendEventField = new JTextField("02 03 04 05 06 07 00 01 "); // NOI18N 063 final JTextField datagramContentsField = new JTextField("20 61 00 00 00 00 08"); // NOI18N 064 final JTextField configNumberField = new JTextField("40"); // NOI18N 065 final JTextField configAddressField = new JTextField("000000"); // NOI18N 066 final JTextField readDataField = new JTextField(60); 067 final JTextField writeDataField = new JTextField(60); 068 final JComboBox<String> addrSpace = new JComboBox<>(new String[]{"CDI", "All", "Config", "None"}); 069 final JComboBox<String> validitySelector = new JComboBox<String>(new String[]{"Unknown", "Valid", "Invalid"}); 070 071 Connection connection; 072 AliasMap aliasMap; 073 NodeID srcNodeID; 074 MemoryConfigurationService mcs; 075 MimicNodeStore store; 076 OlcbInterface iface; 077 ClientActions actions; 078 079 public OpenLcbCanSendPane() { 080 // most of the action is in initComponents 081 } 082 083 @Override 084 public void initComponents(CanSystemConnectionMemo memo) { 085 super.initComponents(memo); 086 iface = memo.get(OlcbInterface.class); 087 actions = new ClientActions(iface, memo); 088 tc = memo.getTrafficController(); 089 tc.addCanListener(this); 090 connection = memo.get(org.openlcb.Connection.class); 091 srcNodeID = memo.get(org.openlcb.NodeID.class); 092 aliasMap = memo.get(org.openlcb.can.AliasMap.class); 093 094 // register request for notification 095 Connection.ConnectionListener cl = new Connection.ConnectionListener() { 096 @Override 097 public void connectionActive(Connection c) { 098 log.debug("connection active"); 099 // load the alias field 100 srcAliasField.setText(Integer.toHexString(aliasMap.getAlias(srcNodeID))); 101 } 102 }; 103 connection.registerStartNotification(cl); 104 105 mcs = memo.get(MemoryConfigurationService.class); 106 store = memo.get(MimicNodeStore.class); 107 nodeSelector = new NodeSelector(store); 108 109 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 110 111 // handle single-packet part 112 add(getSendSinglePacketJPanel()); 113 114 add(new JSeparator()); 115 116 // Configure the sequence 117 add(new JLabel("Send sequence of frames:")); 118 JPanel pane2 = new JPanel(); 119 pane2.setLayout(new GridLayout2(MAXSEQUENCE + 2, 4)); 120 pane2.add(new JLabel("")); 121 pane2.add(new JLabel("Send")); 122 pane2.add(new JLabel("packet")); 123 pane2.add(new JLabel("wait (msec)")); 124 for (int i = 0; i < MAXSEQUENCE; i++) { 125 pane2.add(new JLabel(Integer.toString(i + 1))); 126 mUseField[i] = new JCheckBox(); 127 mPacketField[i] = new JTextField(20); 128 mDelayField[i] = new JTextField(10); 129 pane2.add(mUseField[i]); 130 pane2.add(mPacketField[i]); 131 pane2.add(mDelayField[i]); 132 } 133 add(pane2); 134 add(mRunButton); // below rows 135 136 mRunButton.addActionListener(this::runButtonActionPerformed); 137 138 // special packet forms 139 add(new JSeparator()); 140 141 pane2 = new JPanel(); 142 pane2.setLayout(new WrapLayout()); 143 add(pane2); 144 pane2.add(new JLabel("Send control frame with source alias:")); 145 pane2.add(srcAliasField); 146 JButton b; 147 b = new JButton("Send CIM"); 148 b.addActionListener(this::sendCimPerformed); 149 pane2.add(b); 150 151 // send OpenLCB messages 152 add(new JSeparator()); 153 154 pane2 = new JPanel(); 155 pane2.setLayout(new WrapLayout()); 156 add(pane2); 157 pane2.add(new JLabel("Send OpenLCB global message:")); 158 b = new JButton("Send Verify Nodes Global"); 159 b.addActionListener(this::sendVerifyNodeGlobal); 160 pane2.add(b); 161 b = new JButton("Send Verify Node Global with NodeID"); 162 b.addActionListener(this::sendVerifyNodeGlobalID); 163 pane2.add(b); 164 165 // event messages 166 add(new JSeparator()); 167 sendEventField.setColumns(24); 168 169 var insert = new JPanel(); 170 insert.setLayout(new WrapLayout()); 171 insert.add(sendEventField); 172 insert.add(validitySelector); 173 174 175 add(addLineLabel("Send OpenLCB event message with eventID:", insert)); 176 pane2 = new JPanel(); 177 pane2.setLayout(new WrapLayout()); 178 add(pane2); 179 b = new JButton("Send Request Consumers"); 180 b.addActionListener(this::sendReqConsumers); 181 pane2.add(b); 182 b = new JButton("Send Consumer Identified"); 183 b.addActionListener(this::sendConsumerID); 184 pane2.add(b); 185 b = new JButton("Send Request Producers"); 186 b.addActionListener(this::sendReqProducers); 187 pane2.add(b); 188 b = new JButton("Send Producer Identified"); 189 b.addActionListener(this::sendProducerID); 190 pane2.add(b); 191 b = new JButton("Send Event Produced"); 192 b.addActionListener(this::sendEventPerformed); 193 pane2.add(b); 194 //pane2.add(new JLabel("Event ID (8 bytes):")); 195 //pane2.add(sendEventField); 196 197 // addressed messages 198 add(new JSeparator()); 199 add(addLineLabel("Send OpenLCB addressed message to:", nodeSelector)); 200 pane2 = new JPanel(); 201 pane2.setLayout(new WrapLayout()); 202 add(pane2); 203 b = new JButton("Send Request Events"); 204 b.addActionListener(this::sendRequestEvents); 205 pane2.add(b); 206 b = new JButton("Send PIP Request"); 207 b.addActionListener(this::sendRequestPip); 208 pane2.add(b); 209 210 pane2 = new JPanel(); 211 pane2.setLayout(new WrapLayout()); 212 add(pane2); 213 b = new JButton("Send Datagram"); 214 b.addActionListener(this::sendDatagramPerformed); 215 pane2.add(b); 216 pane2.add(new JLabel("Contents: ")); 217 datagramContentsField.setColumns(45); 218 pane2.add(datagramContentsField); 219 b = new JButton("Send Datagram Reply"); 220 b.addActionListener(this::sendDatagramReply); 221 pane2.add(b); 222 223 // send OpenLCB Configuration message 224 add(new JSeparator()); 225 226 pane2 = new JPanel(); 227 pane2.setLayout(new WrapLayout()); 228 add(pane2); 229 pane2.add(new JLabel("Send OpenLCB memory request with address: ")); 230 pane2.add(configAddressField); 231 pane2.add(new JLabel("Address Space: ")); 232 pane2.add(addrSpace); 233 pane2 = new JPanel(); 234 pane2.setLayout(new WrapLayout()); 235 add(pane2); 236 pane2.add(new JLabel("Byte Count: ")); 237 pane2.add(configNumberField); 238 b = new JButton("Read"); 239 b.addActionListener(this::readPerformed); 240 pane2.add(b); 241 pane2.add(new JLabel("Data: ")); 242 pane2.add(readDataField); 243 244 pane2 = new JPanel(); 245 pane2.setLayout(new WrapLayout()); 246 add(pane2); 247 b = new JButton("Write"); 248 b.addActionListener(this::writePerformed); 249 pane2.add(b); 250 pane2.add(new JLabel("Data: ")); 251 writeDataField.setText("00 00"); // NOI18N 252 pane2.add(writeDataField); 253 254 b = new JButton("Open CDI Config Tool"); 255 add(b); 256 b.addActionListener(e -> openCdiPane()); 257 258 } 259 260 private JPanel getSendSinglePacketJPanel() { 261 JPanel outer = new JPanel(); 262 outer.setLayout(new BoxLayout(outer, BoxLayout.X_AXIS)); 263 264 JPanel pane1 = new JPanel(); 265 pane1.setLayout(new BoxLayout(pane1, BoxLayout.Y_AXIS)); 266 267 jLabel1.setText("Single Frame: (Raw input format is [123] 12 34 56) "); 268 jLabel1.setVisible(true); 269 270 sendButton.setText("Send"); 271 sendButton.setVisible(true); 272 sendButton.setToolTipText("Send frame"); 273 274 packetTextField.setToolTipText("Frame as hex pairs, e.g. 82 7D; standard header in (), extended in []"); 275 packetTextField.setMaximumSize(packetTextField.getPreferredSize()); 276 277 pane1.add(jLabel1); 278 pane1.add(packetTextField); 279 pane1.add(sendButton); 280 pane1.add(Box.createVerticalGlue()); 281 282 sendButton.addActionListener(this::sendButtonActionPerformed); 283 284 outer.add(Box.createHorizontalGlue()); 285 outer.add(pane1); 286 outer.add(Box.createHorizontalGlue()); 287 return outer; 288 } 289 290 @Override 291 public String getHelpTarget() { 292 return "package.jmri.jmrix.openlcb.swing.send.OpenLcbCanSendFrame"; // NOI18N 293 } 294 295 @Override 296 public String getTitle() { 297 if (memo != null) { 298 return (memo.getUserName() + " Send Can Frame"); 299 } 300 return "Send CAN Frames and OpenLCB Messages"; 301 } 302 303 JComponent addLineLabel(String text) { 304 return addLineLabel(text, null); 305 } 306 307 JComponent addLineLabel(String text, JComponent c) { 308 JLabel lab = new JLabel(text); 309 JPanel p = new JPanel(); 310 p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); 311 if (c != null) { 312 p.add(lab, BorderLayout.EAST); 313 if (c instanceof JTextField) { 314 int height = lab.getMinimumSize().height+4; 315 int width = c.getMinimumSize().width; 316 Dimension d = new Dimension(width, height); 317 c.setMaximumSize(d); 318 } 319 p.add(c); 320 } else { 321 p.add(lab, BorderLayout.EAST); 322 } 323 p.add(Box.createHorizontalGlue()); 324 return p; 325 } 326 327 public void sendButtonActionPerformed(java.awt.event.ActionEvent e) { 328 String input = packetTextField.getText(); 329 // TODO check input + feedback on error. Too easy to cause NPE 330 CanMessage m = createPacket(input); 331 log.debug("sendButtonActionPerformed: {}",m); 332 tc.sendCanMessage(m, this); 333 } 334 335 public void sendCimPerformed(java.awt.event.ActionEvent e) { 336 String data = "[10700" + srcAliasField.getText() + "]"; // NOI18N 337 log.debug("sendCimPerformed: |{}|",data); 338 CanMessage m = createPacket(data); 339 log.debug("sendCimPerformed"); 340 tc.sendCanMessage(m, this); 341 } 342 343 NodeID destNodeID() { 344 return nodeSelector.getSelectedItem(); 345 } 346 347 EventID eventID() { 348 return new EventID(jmri.util.StringUtil.bytesFromHexString(sendEventField.getText() 349 .replace(".", " "))); 350 } 351 352 public void sendVerifyNodeGlobal(java.awt.event.ActionEvent e) { 353 Message m = new VerifyNodeIDNumberGlobalMessage(srcNodeID); 354 connection.put(m, null); 355 } 356 357 public void sendVerifyNodeGlobalID(java.awt.event.ActionEvent e) { 358 Message m = new VerifyNodeIDNumberGlobalMessage(srcNodeID, destNodeID()); 359 connection.put(m, null); 360 } 361 362 public void sendRequestEvents(java.awt.event.ActionEvent e) { 363 Message m = new IdentifyEventsAddressedMessage(srcNodeID, destNodeID()); 364 connection.put(m, null); 365 } 366 367 public void sendRequestPip(java.awt.event.ActionEvent e) { 368 Message m = new ProtocolIdentificationRequestMessage(srcNodeID, destNodeID()); 369 connection.put(m, null); 370 } 371 372 public void sendEventPerformed(java.awt.event.ActionEvent e) { 373 Message m = new ProducerConsumerEventReportMessage(srcNodeID, eventID()); 374 connection.put(m, null); 375 } 376 377 public void sendReqConsumers(java.awt.event.ActionEvent e) { 378 Message m = new IdentifyConsumersMessage(srcNodeID, eventID()); 379 connection.put(m, null); 380 } 381 382 EventState validity() { 383 switch (validitySelector.getSelectedIndex()) { 384 case 1 : return EventState.Valid; 385 case 2 : return EventState.Invalid; 386 case 0 : 387 default: return EventState.Unknown; 388 } 389 } 390 391 public void sendConsumerID(java.awt.event.ActionEvent e) { 392 Message m = new ConsumerIdentifiedMessage(srcNodeID, eventID(), validity()); 393 connection.put(m, null); 394 } 395 396 public void sendReqProducers(java.awt.event.ActionEvent e) { 397 Message m = new IdentifyProducersMessage(srcNodeID, eventID()); 398 connection.put(m, null); 399 } 400 401 public void sendProducerID(java.awt.event.ActionEvent e) { 402 Message m = new ProducerIdentifiedMessage(srcNodeID, eventID(), validity()); 403 connection.put(m, null); 404 } 405 406 public void sendDatagramPerformed(java.awt.event.ActionEvent e) { 407 Message m = new DatagramMessage(srcNodeID, destNodeID(), 408 jmri.util.StringUtil.bytesFromHexString(datagramContentsField.getText())); 409 connection.put(m, null); 410 } 411 412 public void sendDatagramReply(java.awt.event.ActionEvent e) { 413 Message m = new DatagramAcknowledgedMessage(srcNodeID, destNodeID()); 414 connection.put(m, null); 415 } 416 417 public void readPerformed(java.awt.event.ActionEvent e) { 418 int space = 0xFF - addrSpace.getSelectedIndex(); 419 long addr = Integer.parseInt(configAddressField.getText(), 16); 420 int length = Integer.parseInt(configNumberField.getText()); 421 mcs.requestRead(destNodeID(), space, addr, 422 length, new MemoryConfigurationService.McsReadHandler() { 423 @Override 424 public void handleReadData(NodeID dest, int space, long address, byte[] data) { 425 log.debug("Read data received {} bytes",data.length); 426 readDataField.setText(jmri.util.StringUtil.hexStringFromBytes(data)); 427 } 428 429 @Override 430 public void handleFailure(int errorCode) { 431 log.warn("OpenLCB read failed: 0x{}", Integer.toHexString 432 (errorCode)); 433 } 434 }); 435 } 436 437 public void writePerformed(java.awt.event.ActionEvent e) { 438 int space = 0xFF - addrSpace.getSelectedIndex(); 439 long addr = Integer.parseInt(configAddressField.getText(), 16); 440 byte[] content = jmri.util.StringUtil.bytesFromHexString(writeDataField.getText()); 441 mcs.requestWrite(destNodeID(), space, addr, content, new MemoryConfigurationService.McsWriteHandler() { 442 @Override 443 public void handleSuccess() { 444 // no action required on success 445 } 446 447 @Override 448 public void handleFailure(int errorCode) { 449 log.warn("OpenLCB write failed: 0x{}", Integer.toHexString 450 (errorCode)); 451 } 452 }); 453 } 454 455 public void openCdiPane() { 456 actions.openCdiWindow(destNodeID(), destNodeID().toString()); 457 } 458 459 // control sequence operation 460 int mNextSequenceElement = 0; 461 javax.swing.Timer timer = null; 462 463 /** 464 * Internal routine to handle timer starts and restarts 465 * @param delay milliseconds to delay 466 */ 467 protected void restartTimer(int delay) { 468 if (timer == null) { 469 timer = new javax.swing.Timer(delay, e -> sendNextItem()); 470 } 471 timer.stop(); 472 timer.setInitialDelay(delay); 473 timer.setRepeats(false); 474 timer.start(); 475 } 476 477 /** 478 * Internal routine to handle a timeout and send next item 479 */ 480 protected synchronized void timeout() { 481 sendNextItem(); 482 } 483 484 /** 485 * Run button pressed down, start the sequence operation 486 * @param e event from GUI 487 * 488 */ 489 public void runButtonActionPerformed(java.awt.event.ActionEvent e) { 490 if (!mRunButton.isSelected()) { 491 return; 492 } 493 // make sure at least one is checked 494 boolean ok = false; 495 for (int i = 0; i < MAXSEQUENCE; i++) { 496 if (mUseField[i].isSelected()) { 497 ok = true; 498 } 499 } 500 if (!ok) { 501 mRunButton.setSelected(false); 502 return; 503 } 504 // start the operation 505 mNextSequenceElement = 0; 506 sendNextItem(); 507 } 508 509 /** 510 * Echo has been heard, start delay for next packet 511 */ 512 void startSequenceDelay() { 513 // at the start, mNextSequenceElement contains index we're 514 // working on 515 int delay = Integer.parseInt(mDelayField[mNextSequenceElement].getText()); 516 // increment to next line at completion 517 mNextSequenceElement++; 518 // start timer 519 restartTimer(delay); 520 } 521 522 /** 523 * Send next item; may be used for the first item or when a delay has 524 * elapsed. 525 */ 526 void sendNextItem() { 527 // check if still running 528 if (!mRunButton.isSelected()) { 529 return; 530 } 531 // have we run off the end? 532 if (mNextSequenceElement >= MAXSEQUENCE) { 533 // past the end, go back 534 mNextSequenceElement = 0; 535 } 536 // is this one enabled? 537 if (mUseField[mNextSequenceElement].isSelected()) { 538 // make the packet 539 CanMessage m = createPacket(mPacketField[mNextSequenceElement].getText()); 540 // send it 541 tc.sendCanMessage(m, this); 542 startSequenceDelay(); 543 } else { 544 // ask for the next one 545 mNextSequenceElement++; 546 sendNextItem(); 547 } 548 } 549 550 /** 551 * Create a well-formed message from a String String is expected to be space 552 * seperated hex bytes or CbusAddress, e.g.: 12 34 56 +n4e1 553 * @param s string of spaced hex byte codes 554 * @return The packet, with contents filled-in 555 */ 556 CanMessage createPacket(String s) { 557 CanMessage m; 558 // Try to convert using CbusAddress class to reuse a little code 559 CbusAddress a = new CbusAddress(s); 560 if (a.check()) { 561 m = a.makeMessage(tc.getCanid()); 562 } else { 563 m = new CanMessage(tc.getCanid()); 564 // check for header 565 if (s.charAt(0) == '[') { // NOI18N 566 // extended header 567 m.setExtended(true); 568 int i = s.indexOf(']'); // NOI18N 569 String h = s.substring(1, i); 570 m.setHeader(Integer.parseInt(h, 16)); 571 s = s.substring(i + 1); 572 } else if (s.charAt(0) == '(') { // NOI18N 573 // standard header 574 int i = s.indexOf(')'); // NOI18N 575 String h = s.substring(1, i); 576 m.setHeader(Integer.parseInt(h, 16)); 577 s = s.substring(i + 1); 578 } 579 // Try to get hex bytes 580 byte[] b = StringUtil.bytesFromHexString(s); 581 m.setNumDataElements(b.length); 582 // Use &0xff to ensure signed bytes are stored as unsigned ints 583 for (int i = 0; i < b.length; i++) { 584 m.setElement(i, b[i] & 0xff); 585 } 586 } 587 return m; 588 } 589 590 /** 591 * Don't pay attention to messages 592 */ 593 @Override 594 public void message(CanMessage m) { 595 // ignore outgoing messages 596 } 597 598 /** 599 * Don't pay attention to replies 600 */ 601 @Override 602 public void reply(CanReply m) { 603 // ignore incoming replies 604 } 605 606 /** 607 * When the window closes, stop any sequences running 608 */ 609 @Override 610 public void dispose() { 611 mRunButton.setSelected(false); 612 super.dispose(); 613 } 614 615 // private data 616 private TrafficController tc = null; //was CanInterface 617 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OpenLcbCanSendPane.class); 618 619}