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}