001package jmri.jmrit.sendpacket; 002 003import java.awt.GridLayout; 004import javax.swing.Box; 005import javax.swing.BoxLayout; 006import javax.swing.JCheckBox; 007import javax.swing.JLabel; 008import javax.swing.JPanel; 009import javax.swing.JSeparator; 010import javax.swing.JSpinner; 011import javax.swing.JTextField; 012import javax.swing.JToggleButton; 013import javax.swing.SpinnerNumberModel; 014import jmri.CommandStation; 015import jmri.InstanceManager; 016import jmri.util.StringUtil; 017import org.slf4j.Logger; 018import org.slf4j.LoggerFactory; 019 020/** 021 * User interface for sending DCC packets. 022 * <p> 023 * This was originally made from jmrix.loconet.logogen, but note that the logic 024 * is somewhat different here. The LocoNet version waited for the sent (LocoNet) 025 * packet to be echo'd, while this starts the timeout immediately. 026 * 027 * @author Bob Jacobsen Copyright (C) 2003 028 */ 029public class SendPacketFrame extends jmri.util.JmriJFrame { 030 031 // member declarations 032 javax.swing.JLabel jLabel1 = new javax.swing.JLabel(); 033 javax.swing.JButton sendButton = new javax.swing.JButton(); 034 javax.swing.JTextField packetTextField = new javax.swing.JTextField(12); 035 036 public SendPacketFrame() { 037 super(); 038 } 039 040 // internal members to hold sequence widgets 041 static final int MAXSEQUENCE = 4; 042 JTextField mPacketField[] = new JTextField[MAXSEQUENCE]; 043 JCheckBox mUseField[] = new JCheckBox[MAXSEQUENCE]; 044 JSpinner mDelaySpinner[] = new JSpinner[MAXSEQUENCE]; 045 JToggleButton mRunButton = new JToggleButton(Bundle.getMessage("ButtonStart")); 046 047 @Override 048 public void initComponents() { 049 050 setTitle(Bundle.getMessage("SendPacketTitle")); 051 getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); 052 053 // handle single-packet part 054 getContentPane().add(new JLabel(Bundle.getMessage("SendOneLabel"))); 055 { 056 JPanel pane1 = new JPanel(); 057 pane1.setLayout(new BoxLayout(pane1, BoxLayout.Y_AXIS)); 058 059 jLabel1.setText(Bundle.getMessage("PacketLabel")); 060 jLabel1.setVisible(true); 061 062 sendButton.setText(Bundle.getMessage("ButtonSend")); 063 sendButton.setVisible(true); 064 sendButton.setToolTipText(Bundle.getMessage("SendToolTip")); 065 066 packetTextField.setToolTipText(Bundle.getMessage("EnterHexBytesToolTip")); 067 068 pane1.add(jLabel1); 069 pane1.add(packetTextField); 070 pane1.add(sendButton); 071 pane1.add(Box.createVerticalGlue()); 072 073 sendButton.addActionListener(new java.awt.event.ActionListener() { 074 @Override 075 public void actionPerformed(java.awt.event.ActionEvent e) { 076 sendButtonActionPerformed(e); 077 } 078 }); 079 080 getContentPane().add(pane1); 081 } 082 083 getContentPane().add(new JSeparator()); 084 085 // Configure the sequence 086 getContentPane().add(new JLabel(Bundle.getMessage("SendSequenceLabel"))); 087 JPanel pane2 = new JPanel(); 088 pane2.setLayout(new GridLayout(MAXSEQUENCE + 2, 4)); 089 pane2.add(new JLabel("")); 090 pane2.add(new JLabel(Bundle.getMessage("ButtonSend"))); 091 pane2.add(new JLabel(Bundle.getMessage("Packet"))); 092 pane2.add(new JLabel(Bundle.getMessage("WaitMsec"))); 093 for (int i = 0; i < MAXSEQUENCE; i++) { 094 pane2.add(new JLabel(Integer.toString(i + 1))); 095 mUseField[i] = new JCheckBox(); 096 mPacketField[i] = new JTextField(10); 097 mDelaySpinner[i] = new JSpinner(new SpinnerNumberModel(100, 0, 1000, 1)); 098 pane2.add(mUseField[i]); 099 pane2.add(mPacketField[i]); 100 pane2.add(mDelaySpinner[i]); 101 } 102 pane2.add(mRunButton); // starts a new row in layout 103 getContentPane().add(pane2); 104 105 mRunButton.addActionListener(new java.awt.event.ActionListener() { 106 @Override 107 public void actionPerformed(java.awt.event.ActionEvent e) { 108 runButtonActionPerformed(e); 109 } 110 }); 111 112 // get the CommandStation reference 113 cs = InstanceManager.getNullableDefault(CommandStation.class); 114 if (cs == null) { 115 log.error("No CommandStation object available"); 116 } 117 118 // add help menu 119 addHelpMenu("package.jmri.jmrit.sendpacket.SendPacketFrame", true); 120 121 // pack to cause display 122 pack(); 123 } 124 125 public void sendButtonActionPerformed(java.awt.event.ActionEvent e) { 126 cs.sendPacket(createPacket(packetTextField.getText()), 1); 127 } 128 129 // control sequence operation 130 int mNextSequenceElement = 0; 131 byte[] mNextEcho = null; 132 javax.swing.Timer timer = null; 133 134 /** 135 * Internal routine to handle timer starts and restarts. 136 * @param delay Milliseconds to wait 137 */ 138 protected void restartTimer(int delay) { 139 if (timer == null) { 140 timer = new javax.swing.Timer(delay, new java.awt.event.ActionListener() { 141 @Override 142 public void actionPerformed(java.awt.event.ActionEvent e) { 143 sendNextItem(); 144 } 145 }); 146 } 147 timer.stop(); 148 timer.setInitialDelay(delay); 149 timer.setRepeats(false); 150 timer.start(); 151 } 152 153 /** 154 * Run button pressed down, start the sequence operation. 155 * @param e unused. 156 */ 157 public void runButtonActionPerformed(java.awt.event.ActionEvent e) { 158 if (!mRunButton.isSelected()) { 159 return; 160 } 161 // make sure at least one is checked 162 boolean ok = false; 163 for (int i = 0; i < MAXSEQUENCE; i++) { 164 if (mUseField[i].isSelected()) { 165 ok = true; 166 } 167 } 168 if (!ok) { 169 mRunButton.setSelected(false); 170 return; 171 } 172 // start the operation 173 mNextSequenceElement = 0; 174 sendNextItem(); 175 } 176 177 /** 178 * Echo has been heard, start delay for next packet. 179 */ 180 void startSequenceDelay() { 181 // at the start, mNextSequenceElement contains index we're 182 // working on 183 int delay = 500; // default delay if non specified, or format bad 184 try { 185 delay = (Integer) mDelaySpinner[mNextSequenceElement].getValue(); 186 } catch (NumberFormatException e) { 187 } 188 189 // increment to next line at completion 190 mNextSequenceElement++; 191 // start timer 192 restartTimer(delay); 193 } 194 195 /** 196 * Send next item; may be used for the first item or when a delay has 197 * elapsed. 198 */ 199 void sendNextItem() { 200 // check if still running 201 if (!mRunButton.isSelected()) { 202 return; 203 } 204 // have we run off the end? 205 if (mNextSequenceElement >= MAXSEQUENCE) { 206 // past the end, go back 207 mNextSequenceElement = 0; 208 } 209 // is this one enabled? 210 if (mUseField[mNextSequenceElement].isSelected()) { 211 // make the packet 212 byte[] m = createPacket(mPacketField[mNextSequenceElement].getText()); 213 // send it 214 mNextEcho = m; 215 if (m != null) { 216 cs.sendPacket(m, 1); 217 } else { 218 log.warn("Message invalid: {}", mPacketField[mNextSequenceElement].getText()); 219 } 220 // and queue the rest of the sequence if we're continuing 221 if (mRunButton.isSelected()) { 222 startSequenceDelay(); 223 } 224 } else { 225 // ask for the next one 226 mNextSequenceElement++; 227 sendNextItem(); 228 } 229 } 230 231 /** 232 * Create a well-formed DCC packet from a String. 233 * @param s Content for packet 234 * @return the packet, with contents filled-in 235 */ 236 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "PZLA_PREFER_ZERO_LENGTH_ARRAYS", 237 justification = "API defined by CommmandStation interface") 238 byte[] createPacket(String s) { 239 // gather bytes in result 240 byte b[] = StringUtil.bytesFromHexString(s); 241 if (b.length == 0) { 242 return null; // no such thing as a zero-length message 243 } 244 return b; 245 } 246 247 /** 248 * When the window closes, stop any sequences running. 249 */ 250 @Override 251 public void dispose() { 252 mRunButton.setSelected(false); 253 super.dispose(); 254 } 255 256 // private data 257 private CommandStation cs = null; 258 259 private final static Logger log = LoggerFactory.getLogger(SendPacketFrame.class); 260 261}