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