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}