001package jmri.jmrix.nce.usbinterface; 002 003import java.awt.Dimension; 004import java.awt.GridBagConstraints; 005import java.awt.GridBagLayout; 006import java.awt.event.ActionEvent; 007import java.text.MessageFormat; 008 009import javax.swing.*; 010 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 015import jmri.jmrix.nce.*; 016 017/** 018 * Panel for configuring an NCE USB interface. 019 * 020 * @author ken cameron Copyright (C) 2013 021 */ 022public class UsbInterfacePanel extends jmri.jmrix.nce.swing.NcePanel implements jmri.jmrix.nce.NceListener { 023 024 private int replyLen = 0; // expected byte length 025 private int waiting = 0; // to catch responses not 026 // intended for this module 027 private int minCabNum = -1; // either the USB or serial size depending on what we connect to 028 private int maxCabNum = -1; // either the USB or serial size depending on what we connect to 029 private int minCabSetNum = -1; 030 private int maxCabSetNum = -1; 031 private static final int CAB_MIN_USB = 2; // USB cabs start at 2 032 private static final int CAB_MIN_PRO = 2; // Serial cabs start at 2 033 private static final int CAB_MAX_USB_128 = 4; // There are up to 4 cabs on 1.28 034 private static final int CAB_MAX_USB_165 = 10; // There are up to 10 cabs on 1.65 035 private static final int CAB_MAX_PRO = 63; // There are up to 63 cabs 036 private static final int CAB_MAX_SB3 = 5; // There are up to 5 cabs 037 038 private static final int REPLY_1 = 1; // reply length of 1 byte 039 private static final int REPLY_2 = 2; // reply length of 2 byte 040 private static final int REPLY_4 = 4; // reply length of 4 byte 041 042 Thread nceCabUpdateThread; 043 private boolean setRequested = false; 044 private int setCabId = -1; 045 046 private NceTrafficController tc = null; 047 048 JTextField newCabId = new JTextField(5); 049 JLabel oldCabId = new JLabel(" "); 050 JButton setButton = new JButton(Bundle.getMessage("ButtonSet")); 051 052 JLabel space1 = new JLabel(" "); 053 JLabel space2 = new JLabel(" "); 054 JLabel space3 = new JLabel(" "); 055 JLabel space4 = new JLabel(" "); 056 JLabel space5 = new JLabel(" "); 057 058 JLabel statusText = new JLabel(); 059 060 public UsbInterfacePanel() { 061 super(); 062 } 063 064 @Override 065 public void initContext(Object context) { 066 if (context instanceof NceSystemConnectionMemo) { 067 initComponents((NceSystemConnectionMemo) context); 068 } 069 } 070 071 @Override 072 public String getHelpTarget() { 073 return "package.jmri.jmrix.nce.usbinterface.UsbInterfacePanel"; 074 } 075 076 @Override 077 public String getTitle() { 078 StringBuilder x = new StringBuilder(); 079 if (memo != null) { 080 x.append(memo.getUserName()); 081 } else { 082 x.append("NCE_"); 083 } 084 x.append(": "); 085 x.append(Bundle.getMessage("TitleUsbInterface")); 086 return x.toString(); 087 } 088 089 @Override 090 public void initComponents(NceSystemConnectionMemo m) { 091 this.memo = m; 092 this.tc = m.getNceTrafficController(); 093 094 minCabNum = CAB_MIN_PRO; 095 maxCabNum = CAB_MAX_PRO; 096 minCabSetNum = CAB_MIN_PRO + 1; 097 maxCabSetNum = CAB_MAX_PRO; 098 if ((tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) 099 && (tc.getCmdGroups() & NceTrafficController.CMDS_MEM) != 0) { 100 minCabNum = CAB_MIN_USB; 101 maxCabNum = CAB_MAX_USB_165; 102 } else if (tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_POWERPRO) { 103 minCabNum = CAB_MIN_PRO; 104 maxCabNum = CAB_MAX_PRO; 105 } else if (tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_SB3) { 106 minCabNum = CAB_MIN_PRO; 107 maxCabNum = CAB_MAX_SB3; 108 } else if (tc.getCommandOptions() >= NceTrafficController.OPTION_1_65) { 109 maxCabSetNum = CAB_MAX_USB_165; 110 } else { 111 maxCabSetNum = CAB_MAX_USB_128; 112 } 113 // general GUI config 114 115 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 116 117 JPanel p1 = new JPanel(); 118 p1.setLayout(new GridBagLayout()); 119 p1.setPreferredSize(new Dimension(400, 75)); 120 121 addItem(p1, new JLabel(Bundle.getMessage("LabelSetCabId")), 1, 2); 122 newCabId.setText(" "); 123 addItem(p1, newCabId, 2, 2); 124 addItem(p1, setButton, 3, 2); 125 add(p1); 126 127 JPanel p2 = new JPanel(); 128 p2.setLayout(new GridBagLayout()); 129 addItem(p2, new JLabel(Bundle.getMessage("LabelStatus")), 1, 1); 130 statusText.setText(" "); 131 addItem(p2, statusText, 2, 1); 132 add(p2); 133 134 JPanel p3 = new JPanel(); 135 add(p3); 136 137 addButtonAction(setButton); 138 } 139 140 // validate value as legal cab id for the system 141 // needed since there are gaps in the USB based command stations 142 public boolean validateCabId(int id) { 143 if ((id < minCabNum) || (id > maxCabNum)) { 144 // rough range check 145 return false; 146 } 147 if ((tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_POWERCAB) 148 && (tc.getCmdGroups() & NceTrafficController.CMDS_MEM) != 0) { 149 // is a 1.65 or better firmware, has gaps, for PowerCab only 150 if ((id == 6) || (id == 7)) 151 return false; 152 } 153 return true; 154 } 155 156 // button actions 157 public void buttonActionPerformed(ActionEvent ae) { 158 Object src = ae.getSource(); 159 if (src == setButton) { 160 changeCabId(); 161 } else { 162 log.error("unknown action performed: {}", src); 163 } 164 } 165 166 private void changeCabId() { 167 int i = -1; 168 try { 169 i = Integer.parseInt(newCabId.getText().trim()); 170 if (validateCabId(i)) { 171 processMemory(true, i); 172 } else { 173 statusText.setText(MessageFormat.format(Bundle.getMessage("StatusInvalidCabIdEntered"), i)); 174 } 175 } catch (RuntimeException e) { 176 // presume it failed to convert. 177 log.debug("failed to convert {}", i); 178 } 179 } 180 181 private void processMemory(boolean doSet, int cabId) { 182 if (doSet) { 183 setRequested = true; 184 setCabId = cabId; 185 } 186 // Set up a separate thread to access CS memory 187 if (nceCabUpdateThread != null && nceCabUpdateThread.isAlive()) { 188 return; // thread is already running 189 } 190 nceCabUpdateThread = new Thread(new Runnable() { 191 @Override 192 public void run() { 193 if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) { 194 if (setRequested) { 195 cabSetIdUsb(); 196 } 197 } 198 } 199 }); 200 nceCabUpdateThread.setName(Bundle.getMessage("ThreadTitle")); 201 nceCabUpdateThread.setPriority(Thread.MIN_PRIORITY); 202 nceCabUpdateThread.start(); 203 } 204 205 private boolean firstTime = true; // wait for panel to display 206 207 // Thread to set cab id, allows the use of sleep or wait, for NCE-USB connection 208 private void cabSetIdUsb() { 209 210 if (firstTime) { 211 try { 212 Thread.sleep(1000); // wait for panel to display 213 } catch (InterruptedException e) { 214 log.error("Thread interrupted.", e); 215 } 216 } 217 218 firstTime = false; 219 recChar = -1; 220 setRequested = false; 221 if (validateCabId(setCabId)) { 222 statusText.setText(MessageFormat.format(Bundle.getMessage("StatusSetIdStart"), setCabId)); 223 writeUsbCabId(setCabId); 224 if (!waitNce()) { 225 return; 226 } 227 if (recChar != NceMessage.NCE_OKAY) { 228 statusText.setText(MessageFormat.format(Bundle.getMessage("StatusUsbErrorCode"), recChars[0])); 229 } else { 230 statusText.setText(MessageFormat.format(Bundle.getMessage("StatusSetIdFinished"), setCabId)); 231 } 232 synchronized (this) { 233 try { 234 wait(1000); 235 } catch (InterruptedException e) { 236 //nothing to see here, move along 237 } 238 } 239 } else { 240 statusText.setText(MessageFormat.format(Bundle.getMessage("StatusInvalidCabId"), setCabId, minCabSetNum, maxCabSetNum)); 241 } 242 this.setVisible(true); 243 this.repaint(); 244 } 245 246 @Override 247 public void message(NceMessage m) { 248 } // ignore replies 249 250 // response from read 251 int recChar = 0; 252 int[] recChars = new int[16]; 253 254 @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification = "Thread wait from main transfer loop") 255 @Override 256 public void reply(NceReply r) { 257 if (log.isDebugEnabled()) { 258 log.debug("Receive character"); 259 } 260 if (waiting <= 0) { 261 log.error("unexpected response. Len: {} code: {}", r.getNumDataElements(), r.getElement(0)); 262 return; 263 } 264 waiting--; 265 if (r.getNumDataElements() != replyLen) { 266 statusText.setText(Bundle.getMessage("StatusError")); 267 return; 268 } 269 // Read one byte 270 if (replyLen == REPLY_1) { 271 // Looking for proper response 272 recChar = r.getElement(0); 273 } 274 // Read two byte 275 if (replyLen == REPLY_2) { 276 // Looking for proper response 277 for (int i = 0; i < REPLY_2; i++) { 278 recChars[i] = r.getElement(i); 279 } 280 } 281 // Read four byte 282 if (replyLen == REPLY_4) { 283 // Looking for proper response 284 for (int i = 0; i < REPLY_4; i++) { 285 recChars[i] = r.getElement(i); 286 } 287 } 288 // wake up thread 289 synchronized (this) { 290 notify(); 291 } 292 } 293 294 // puts the thread to sleep while we wait for the read CS memory to complete 295 private boolean waitNce() { 296 int count = 100; 297 if (log.isDebugEnabled()) { 298 log.debug("Going to sleep"); 299 } 300 while (waiting > 0) { 301 synchronized (this) { 302 try { 303 wait(100); 304 } catch (InterruptedException e) { 305 //nothing to see here, move along 306 } 307 } 308 count--; 309 if (count < 0) { 310 statusText.setText(Bundle.getMessage("StatusReplyTimeout")); 311 return false; 312 } 313 } 314 if (log.isDebugEnabled()) { 315 log.debug("awake!"); 316 } 317 return true; 318 } 319 320 // USB set Cab Id in USB 321 private void writeUsbCabId(int value) { 322 replyLen = REPLY_1; // Expect 1 byte response 323 waiting++; 324 byte[] bl = NceBinaryCommand.usbSetCabId(value); 325 NceMessage m = NceMessage.createBinaryMessage(tc, bl, REPLY_1); 326 tc.sendNceMessage(m, this); 327 } 328 329 /** 330 * Add item to a panel. 331 * 332 * @param p Panel Id 333 * @param c Component Id 334 * @param x Column 335 * @param y Row 336 */ 337 protected void addItem(JPanel p, JComponent c, int x, int y) { 338 GridBagConstraints gc = new GridBagConstraints(); 339 gc.gridx = x; 340 gc.gridy = y; 341 gc.weightx = 100.0; 342 gc.weighty = 100.0; 343 p.add(c, gc); 344 } 345 346 private void addButtonAction(JButton b) { 347 b.addActionListener(new java.awt.event.ActionListener() { 348 @Override 349 public void actionPerformed(java.awt.event.ActionEvent e) { 350 buttonActionPerformed(e); 351 } 352 }); 353 } 354 355 private final static Logger log = LoggerFactory.getLogger(UsbInterfacePanel.class); 356 357}