001package jmri.jmrix.can.swing.send; 002 003import java.awt.Color; 004import java.awt.event.ActionEvent; 005import java.awt.event.ActionListener; 006import java.awt.GridLayout; 007 008import javax.swing.BorderFactory; 009import javax.swing.BoxLayout; 010import javax.swing.JButton; 011import javax.swing.JCheckBox; 012import javax.swing.JLabel; 013import javax.swing.JPanel; 014import javax.swing.JSpinner; 015import javax.swing.JTextField; 016import javax.swing.JToggleButton; 017import javax.swing.SpinnerNumberModel; 018import javax.swing.SwingConstants; 019 020import jmri.jmrix.can.CanMessage; 021import jmri.jmrix.can.CanReply; 022import jmri.jmrix.can.CanSystemConnectionMemo; 023import jmri.jmrix.can.cbus.CbusConstants; 024import jmri.jmrix.can.cbus.CbusMessage; 025import jmri.jmrix.can.TrafficController; 026import jmri.jmrix.can.cbus.CbusAddress; 027import jmri.util.StringUtil; 028import jmri.util.swing.JmriJOptionPane; 029 030/** 031 * User interface for sending CAN frames to exercise the system 032 * <p> 033 * When sending a sequence of operations: 034 * <ul> 035 * <li>Send the next message and start a timer 036 * <li>When the timer trips, repeat if buttons still down. 037 * </ul> 038 * 039 * @author Bob Jacobsen Copyright (C) 2008 040 */ 041public class CanSendPane extends jmri.jmrix.can.swing.CanPanel { 042 043 // member declarations 044 JLabel jLabel1 = new JLabel(); 045 JButton sendButton = new JButton(); 046 JTextField packetTextField = new JTextField(12); 047 JCheckBox cbusPriorityCheckbox = new JCheckBox(Bundle.getMessage("AddCbusPriorFull")); 048 JCheckBox sendAsMessage = new JCheckBox(Bundle.getMessage("SendAsMessage")); 049 JCheckBox sendAsReply = new JCheckBox(Bundle.getMessage("SendAsReply")); 050 051 public CanSendPane() { 052 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 053 054 // Handle single-packet part 055 JPanel topPane = new JPanel(); 056 // Add a nice border 057 topPane.setBorder(BorderFactory.createTitledBorder( 058 BorderFactory.createEtchedBorder(), Bundle.getMessage("SendFrameTitle"))); 059 060 JPanel pane1 = new JPanel(); 061 pane1.setLayout(new BoxLayout(pane1, BoxLayout.X_AXIS)); 062 063 JPanel entry = new JPanel(); 064 jLabel1.setText(Bundle.getMessage("FrameLabel")); 065 jLabel1.setVisible(true); 066 067 sendButton.setText(Bundle.getMessage("ButtonSend")); 068 sendButton.setVisible(true); 069 sendButton.setToolTipText(Bundle.getMessage("SendToolTip")); 070 071 entry.add(jLabel1); 072 entry.add(packetTextField); 073 packetTextField.setToolTipText(Bundle.getMessage("EnterFrameToolTip")); 074 topPane.add(entry); 075 topPane.add(sendButton); 076 077 ActionListener l = ae -> { 078 sendButtonActionPerformed(ae); 079 }; 080 sendButton.addActionListener(l); 081 packetTextField.addActionListener(l); 082 083 // Configure the sequence 084 JPanel bottomPane = new JPanel(); 085 // Add a nice border 086 bottomPane.setBorder(BorderFactory.createTitledBorder( 087 BorderFactory.createEtchedBorder(), Bundle.getMessage("SendSeqTitle"))); 088 bottomPane.setLayout(new BoxLayout(bottomPane, BoxLayout.Y_AXIS)); 089 090 JPanel pane2 = new JPanel(); 091 pane2.setLayout(new GridLayout(MAXSEQUENCE + 2, 3)); 092 pane2.add(new JLabel(" ")); 093 pane2.add(new JLabel(Bundle.getMessage("PacketLabel"))); 094 pane2.add(new JLabel(Bundle.getMessage("WaitLabel"))); 095 for (int i = 0; i < MAXSEQUENCE; i++) { 096 JPanel numbercheckboxpane = new JPanel(); 097 numbercheckboxpane.add(new JLabel(Integer.toString(i + 1)+" ",SwingConstants.RIGHT)); 098 mUseField[i] = new JCheckBox(); 099 mPacketField[i] = new JTextField(14); 100 numberSpinner[i] = new JSpinner(new SpinnerNumberModel(1500, 1, 1000000, 1)); 101 numbercheckboxpane.add(mUseField[i]); 102 pane2.add(numbercheckboxpane); 103 pane2.add(mPacketField[i]); 104 mPacketField[i].setToolTipText(Bundle.getMessage("EnterFrameToolTip")); 105 pane2.add(numberSpinner[i]); 106 } 107 108 pane2.add(new JLabel(" ")); 109 pane2.add(mRunButton); 110 bottomPane.add(pane2); 111 112 JPanel optionholder = new JPanel(); 113 optionholder.setBorder(BorderFactory.createTitledBorder( 114 BorderFactory.createEtchedBorder(), Bundle.getMessage("Options"))); 115 JPanel optionlist = new JPanel(); 116 117 optionlist.setLayout(new BoxLayout(optionlist, BoxLayout.Y_AXIS)); 118 optionlist.add(cbusPriorityCheckbox); 119 optionlist.add(sendAsMessage); 120 optionlist.add(sendAsReply); 121 122 cbusPriorityCheckbox.setSelected(true); 123 sendAsMessage.setSelected(true); 124 125 optionholder.add(optionlist); 126 127 add(topPane); 128 add(bottomPane); 129 add(optionholder); 130 131 mRunButton.setToolTipText(Bundle.getMessage("StartToolTip")); 132 mRunButton.addActionListener(this::runButtonActionPerformed); 133 } 134 135 // internal members to hold sequence widgets 136 static final int MAXSEQUENCE = 4; 137 JTextField mPacketField[] = new JTextField[MAXSEQUENCE]; 138 JCheckBox mUseField[] = new JCheckBox[MAXSEQUENCE]; 139 JSpinner numberSpinner[] = new JSpinner[MAXSEQUENCE]; 140 JToggleButton mRunButton = new JToggleButton(Bundle.getMessage("ButtonStart")); 141 static final Color[] FILTERCOLORS = { 142 new Color(110, 235, 131), // green ish as will have black text on top 143 new Color(68, 235, 255), // cyan ish 144 new Color(228, 255, 26), // yellow ish 145 new Color(255, 132, 84) // orange ish 146 }; 147 148 @Override 149 public void initComponents(CanSystemConnectionMemo memo) { 150 super.initComponents(memo); 151 tc = memo.getTrafficController(); 152 } 153 154 @Override 155 public String getHelpTarget() { 156 return "package.jmri.jmrix.can.swing.send.CanSendFrame"; 157 } 158 159 @Override 160 public String getTitle() { 161 return prependConnToString(Bundle.getMessage("MenuItemSendFrame")); 162 } 163 164 public void sendButtonActionPerformed(java.awt.event.ActionEvent e) { 165 String input = packetTextField.getText(); 166 // TODO check input + feedback on error. Too easy to cause NPE 167 try { 168 CanMessage m = createPacket(input.replaceAll("\\s", "")); 169 if (cbusPriorityCheckbox.isSelected()) { 170 CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY); 171 } 172 if (sendAsMessage.isSelected()) { 173 tc.sendCanMessage(m, null); 174 } 175 if (sendAsReply.isSelected()) { 176 CanReply mr = new CanReply(m); 177 tc.sendCanReply(mr, null); 178 } 179 } catch (StringIndexOutOfBoundsException | IllegalArgumentException ex) { 180 JmriJOptionPane.showMessageDialog(this, 181 (Bundle.getMessage("NoMakeFrame",ex.getMessage())), Bundle.getMessage("WarningTitle"), 182 JmriJOptionPane.ERROR_MESSAGE); 183 } 184 } 185 186 // control sequence operation 187 int mNextSequenceElement = 0; 188 javax.swing.Timer timer = null; 189 190 /** 191 * Internal routine to handle timer starts and restarts 192 * @param delay in ms 193 */ 194 protected void restartTimer(int delay) { 195 if (timer == null) { 196 timer = new javax.swing.Timer(delay, (ActionEvent e) -> { 197 sendNextItem(); 198 }); 199 } 200 timer.stop(); 201 timer.setInitialDelay(delay); 202 timer.setRepeats(false); 203 timer.start(); 204 } 205 206 /** 207 * Internal routine to handle a timeout and send next item 208 */ 209 synchronized protected void timeout() { 210 sendNextItem(); 211 } 212 213 /** 214 * Run button pressed down, start the sequence operation. 215 * @param e Button Event 216 */ 217 public void runButtonActionPerformed(ActionEvent e) { 218 if (!mRunButton.isSelected()) { 219 mRunButton.setText(Bundle.getMessage("ButtonStart")); 220 return; 221 } 222 // make sure at least one is checked 223 boolean ok = false; 224 for (int i = 0; i < MAXSEQUENCE; i++) { 225 if (mUseField[i].isSelected()) { 226 ok = true; 227 } 228 } 229 if (!ok) { 230 mRunButton.setSelected(false); 231 mRunButton.setText(Bundle.getMessage("ButtonStart")); 232 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("NoSelectionDialog"), 233 Bundle.getMessage("WarningTitle"), JmriJOptionPane.ERROR_MESSAGE); 234 return; 235 } 236 // start the operation 237 mNextSequenceElement = 0; 238 mRunButton.setText(Bundle.getMessage("ButtonStop")); 239 sendNextItem(); 240 } 241 242 /** 243 * Echo has been heard, start delay for next packet. 244 */ 245 private void startSequenceDelay() { 246 // at the start, mNextSequenceElement contains index we're 247 // working on 248 int delay = (Integer) numberSpinner[mNextSequenceElement].getValue(); 249 // increment to next line at completion 250 mNextSequenceElement++; 251 // start timer 252 restartTimer(delay); 253 } 254 255 /** 256 * Send next item; may be used for the first item or when a delay has 257 * elapsed. 258 */ 259 private void sendNextItem() { 260 // reset all backgrounds 261 for (int i = 0; i < MAXSEQUENCE; i++) { 262 mPacketField[i].setBackground(packetTextField.getBackground()); // known unaltered textfield 263 } 264 // check if still running 265 if (!mRunButton.isSelected()) { 266 mRunButton.setText(Bundle.getMessage("ButtonStart")); 267 return; 268 } 269 270 // have we run off the end? 271 if (mNextSequenceElement >= MAXSEQUENCE) { 272 // past the end, go back 273 mNextSequenceElement = 0; 274 } 275 // is this one enabled? 276 if (mUseField[mNextSequenceElement].isSelected()) { 277 278 mPacketField[mNextSequenceElement].setBackground(FILTERCOLORS[mNextSequenceElement]); 279 280 try { 281 // make the packet 282 CanMessage m = createPacket(mPacketField[mNextSequenceElement].getText().replaceAll("\\s", "")); 283 if (cbusPriorityCheckbox.isSelected()) { 284 CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY); 285 } 286 287 // send it 288 if (sendAsMessage.isSelected()) { 289 tc.sendCanMessage(m, null); 290 } 291 if (sendAsReply.isSelected()) { 292 CanReply mr = new CanReply(m); 293 tc.sendCanReply(mr, null); 294 } 295 startSequenceDelay(); 296 } catch (StringIndexOutOfBoundsException | IllegalArgumentException ex) { 297 JmriJOptionPane.showMessageDialog(this, 298 (Bundle.getMessage("NoMakeFrame", ex.getMessage())), Bundle.getMessage("WarningTitle"), 299 JmriJOptionPane.ERROR_MESSAGE); 300 mRunButton.setSelected(false); 301 mRunButton.setText(Bundle.getMessage("ButtonStart")); 302 } 303 } else { 304 // ask for the next one 305 mNextSequenceElement++; 306 sendNextItem(); 307 } 308 } 309 310 /** 311 * Create a well-formed message from a String. String is expected to be space 312 * seperated hex bytes or CbusAddress, e.g.: 12 34 56 or +n4e1 313 * @param s Input information 314 * @return The packet, with contents filled-in 315 */ 316 CanMessage createPacket(String s) { 317 CanMessage m; 318 // Try to convert using CbusAddress class 319 CbusAddress a = new CbusAddress(s); 320 if (a.check()) { 321 m = a.makeMessage(tc.getCanid()); 322 } else { 323 m = new CanMessage(tc.getCanid()); 324 // check for header 325 if (s.charAt(0) == '[') { 326 // extended header 327 m.setExtended(true); 328 int i = s.indexOf(']'); 329 String h = s.substring(1, i); 330 m.setHeader(Integer.parseInt(h, 16)); 331 s = s.substring(i + 1, s.length()); 332 } else if (s.charAt(0) == '(') { 333 // standard header 334 int i = s.indexOf(')'); 335 String h = s.substring(1, i); 336 m.setHeader(Integer.parseInt(h, 16)); 337 s = s.substring(i + 1, s.length()); 338 } 339 // Try to get hex bytes 340 byte b[] = StringUtil.bytesFromHexString(s); 341 m.setNumDataElements(b.length); 342 // Use &0xff to ensure signed bytes are stored as unsigned ints 343 for (int i = 0; i < b.length; i++) { 344 m.setElement(i, b[i] & 0xff); 345 } 346 } 347 return m; 348 } 349 350 /** 351 * When the window closes, stop any sequences running 352 */ 353 @Override 354 public void dispose() { 355 mRunButton.setSelected(false); 356 super.dispose(); 357 } 358 359 // private data 360 private TrafficController tc = null; 361 362 /** 363 * Nested class to create one of these using old-style defaults. 364 */ 365 static public class Default extends jmri.jmrix.can.swing.CanNamedPaneAction { 366 367 public Default() { 368 super(Bundle.getMessage("MenuItemSendFrame"), 369 new jmri.util.swing.sdi.JmriJFrameInterface(), 370 CanSendPane.class.getName(), 371 jmri.InstanceManager.getDefault(CanSystemConnectionMemo.class)); 372 } 373 } 374 375 // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CanSendPane.class); 376 377}