001package jmri.jmrix.ieee802154.xbee.swing.nodeconfig;
002
003import com.digi.xbee.api.RemoteXBeeDevice;
004import com.digi.xbee.api.listeners.IDiscoveryListener;
005
006import java.awt.BorderLayout;
007import java.awt.Container;
008import java.awt.FlowLayout;
009
010import javax.swing.BoxLayout;
011import javax.swing.JComboBox;
012import javax.swing.JLabel;
013import javax.swing.JPanel;
014import javax.swing.JScrollPane;
015import javax.swing.JTable;
016
017import jmri.jmrix.ieee802154.xbee.XBeeConnectionMemo;
018import jmri.jmrix.ieee802154.xbee.XBeeNode;
019import jmri.jmrix.ieee802154.xbee.XBeeTrafficController;
020import jmri.util.swing.JmriJOptionPane;
021
022/**
023 * Frame for user configuration of XBee nodes Derived from node configuration
024 * for c/mri nodes.
025 *
026 * @author Bob Jacobsen Copyright (C) 2004
027 * @author Dave Duchamp Copyright (C) 2004
028 * @author Paul Bender Copyright (C) 2013
029 */
030public class XBeeNodeConfigFrame extends jmri.jmrix.ieee802154.swing.nodeconfig.NodeConfigFrame implements IDiscoveryListener {
031
032    private XBeeTrafficController xtc = null;
033    protected final javax.swing.JButton discoverButton = new javax.swing.JButton(Bundle.getMessage("ButtonDiscover"));
034    private final JComboBox<XBeeNode> nodeField = new javax.swing.JComboBox<>();
035    protected JTable assignmentTable = null;
036    protected javax.swing.table.TableModel assignmentListModel = null;
037
038    protected JPanel assignmentPanel = null;
039
040    /**
041     * Constructor method
042     * @param tc traffic controller for node
043     */
044    public XBeeNodeConfigFrame(XBeeTrafficController tc) {
045        super(tc);
046        xtc = tc;
047    }
048
049    /**
050     * Initialize the config window
051     */
052    @Override
053    public void initComponents() {
054        setTitle(Bundle.getMessage("WindowTitle"));
055        Container contentPane = getContentPane();
056        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
057
058        contentPane.add(initAddressPanel());
059
060        // Set up the pin assignment table
061        assignmentPanel = new JPanel();
062        assignmentPanel.setLayout(new BoxLayout(assignmentPanel, BoxLayout.Y_AXIS));
063        assignmentListModel = new AssignmentTableModel();
064        assignmentTable = new JTable(assignmentListModel);
065        assignmentTable.setRowSelectionAllowed(false);
066        assignmentTable.setPreferredScrollableViewportSize(new java.awt.Dimension(300, 350));
067        JScrollPane assignmentScrollPane = new JScrollPane(assignmentTable);
068        assignmentPanel.add(assignmentScrollPane, BorderLayout.CENTER);
069
070        contentPane.add(assignmentPanel);
071
072        contentPane.add(initNotesPanel());
073        contentPane.add(initButtonPanel());
074
075        // pack for display
076        pack();
077
078        // after the components are configured, set ourselves up as a 
079        // discovery listener.
080        xtc.getXBee().getNetwork().addDiscoveryListener(this);
081
082    }
083
084    /*
085     * Initialize the address panel.
086     */
087    @Override
088    protected JPanel initAddressPanel(){
089        // Set up node address and node type
090        JPanel panel1 = new JPanel();
091        panel1.setLayout(new BoxLayout(panel1, BoxLayout.Y_AXIS));
092        JPanel panel11 = new JPanel();
093        panel11.setLayout(new FlowLayout());
094        panel11.add(new JLabel(Bundle.getMessage("LabelNodeSelection") + " "));
095        panel11.add(nodeField);
096        nodeField.setToolTipText(Bundle.getMessage("TipNodeSelection"));
097        nodeField.addItemListener(e -> nodeSelected());
098
099        initAddressBoxes();
100
101        panel1.add(panel11);
102        return panel1;
103    }
104
105    /*
106     * Initialize the Button panel.
107     */
108    @Override
109    protected JPanel initButtonPanel(){
110
111        // Set up buttons
112        JPanel panel4 = new JPanel();
113        panel4.setLayout(new FlowLayout());
114        addButton.setText(Bundle.getMessage("ButtonAdd"));
115        addButton.setVisible(true);
116        addButton.setToolTipText(Bundle.getMessage("TipAddButton"));
117        addButton.addActionListener(e -> addButtonActionPerformed());
118        panel4.add(addButton);
119        discoverButton.setText(Bundle.getMessage("ButtonDiscover"));
120        discoverButton.setVisible(true);
121        discoverButton.setToolTipText(Bundle.getMessage("TipAddButton"));
122        discoverButton.addActionListener(e -> discoverButtonActionPerformed());
123        discoverButton.setEnabled(!(xtc.getXBee().getNetwork().isDiscoveryRunning()));
124        panel4.add(discoverButton);
125        editButton.setText(Bundle.getMessage("ButtonEdit"));
126        editButton.setVisible(true);
127        editButton.setToolTipText(Bundle.getMessage("TipEditButton"));
128        panel4.add(editButton);
129        editButton.addActionListener(e -> editButtonActionPerformed());
130        panel4.add(deleteButton);
131        deleteButton.setText(Bundle.getMessage("ButtonDelete"));
132        deleteButton.setVisible(true);
133        deleteButton.setToolTipText(Bundle.getMessage("TipDeleteButton"));
134        panel4.add(deleteButton);
135        deleteButton.addActionListener(e -> deleteButtonActionPerformed());
136        panel4.add(doneButton);
137        doneButton.setText(Bundle.getMessage("ButtonDone"));
138        doneButton.setVisible(true);
139        doneButton.setToolTipText(Bundle.getMessage("TipDoneButton"));
140        panel4.add(doneButton);
141        doneButton.addActionListener(e -> doneButtonActionPerformed());
142        panel4.add(updateButton);
143        updateButton.setText(Bundle.getMessage("ButtonUpdate"));
144        updateButton.setVisible(true);
145        updateButton.setToolTipText(Bundle.getMessage("TipUpdateButton"));
146        panel4.add(updateButton);
147        updateButton.addActionListener(e -> updateButtonActionPerformed());
148        updateButton.setVisible(false);
149        panel4.add(cancelButton);
150        cancelButton.setText(Bundle.getMessage("ButtonCancel"));
151        cancelButton.setVisible(true);
152        cancelButton.setToolTipText(Bundle.getMessage("TipCancelButton"));
153        panel4.add(cancelButton);
154        cancelButton.addActionListener(e -> cancelButtonActionPerformed());
155        cancelButton.setVisible(false);
156        return panel4;
157    }
158
159    /**
160     * Method to handle add button
161     */
162    @Override
163    public void addButtonActionPerformed() {
164        // create a new Add Frame and display it.
165        jmri.util.JmriJFrame addFrame = new XBeeAddNodeFrame(xtc,this);
166        try {
167           addFrame.initComponents();
168        } catch(Exception ex) {
169           log.error("Exception initializing Frame: {}",ex.toString());
170           return;
171        }
172        addFrame.setVisible(true);
173    }
174
175    /**
176     * Method to handle discover button
177     */
178    public void discoverButtonActionPerformed() {
179
180        if(xtc.getXBee().getNetwork().isDiscoveryRunning()){
181           log.debug("Discovery process already running");
182           discoverButton.setEnabled(false);
183           statusText1.setText(Bundle.getMessage("FeedBackDiscover"));
184           return;
185        }
186
187        jmri.jmrix.ieee802154.IEEE802154SystemConnectionMemo memo = xtc.getAdapterMemo();
188        if( memo instanceof XBeeConnectionMemo) {
189
190           XBeeConnectionMemo m = (XBeeConnectionMemo) memo;
191
192           // call the node discovery code in the node manager.
193           m.getXBeeNodeManager().startNodeDiscovery();
194
195           discoverButton.setEnabled(false);
196        }
197        // provide user feedback
198        statusText1.setText(Bundle.getMessage("FeedBackDiscover"));
199        errorInStatus1 = true;
200        resetNotes2();
201    }
202
203    /**
204     * Method to handle edit button
205     */
206    @Override
207    public void editButtonActionPerformed() {
208       // get the XBeeNode corresponding to this node address
209       curNode = (XBeeNode) nodeField.getSelectedItem();
210       if (curNode == null) {
211          statusText1.setText(Bundle.getMessage("Error4"));
212          statusText1.setVisible(true);
213          errorInStatus1 = true;
214          resetNotes2();
215          return;
216       }
217
218        // create a new Edit Frame and display it.
219        jmri.util.JmriJFrame editFrame = new XBeeEditNodeFrame(xtc,(XBeeNode)curNode,this);
220        try {
221           editFrame.initComponents();
222        } catch(Exception ex) {
223           log.error("Exception initializing Frame: {}",ex.toString());
224           return;
225        }
226        editFrame.setVisible(true);
227
228    }
229
230
231/**
232     * Method to handle delete button
233     */
234    @Override
235    public void deleteButtonActionPerformed() {
236        // get the XBeeNode corresponding to this node address
237        curNode = (XBeeNode) nodeField.getSelectedItem();
238        if (curNode == null) {
239            statusText1.setText(Bundle.getMessage("Error4"));
240            statusText1.setVisible(true);
241            errorInStatus1 = true;
242            resetNotes2();
243            return;
244        }
245        // confirm deletion with the user
246        if (JmriJOptionPane.OK_OPTION == JmriJOptionPane.showConfirmDialog(
247                this, Bundle.getMessage("ConfirmDelete1") + "\n"
248                + Bundle.getMessage("ConfirmDelete2"), Bundle.getMessage("ConfirmDeleteTitle"),
249                JmriJOptionPane.OK_CANCEL_OPTION,
250                JmriJOptionPane.WARNING_MESSAGE)) {
251            // delete this node
252            xtc.deleteNode((XBeeNode) curNode);
253            // provide user feedback
254            resetNotes();
255            statusText1.setText(Bundle.getMessage("FeedBackDelete") + " " + curNode.toString());
256            errorInStatus1 = true;
257            changedNode = true;
258        } else {
259            // reset as needed
260            resetNotes();
261        }
262        initAddressBoxes();
263    }
264
265    /**
266     * Method to handle done button
267     */
268    @Override
269    public void doneButtonActionPerformed() {
270        if (editMode) {
271            // Reset 
272            editMode = false;
273            curNode = null;
274            // Switch buttons
275            addButton.setVisible(true);
276            editButton.setVisible(true);
277            deleteButton.setVisible(true);
278            doneButton.setVisible(true);
279            updateButton.setVisible(false);
280            cancelButton.setVisible(false);
281        }
282        if (changedNode) {
283            // Remind user to Save new configuration
284            JmriJOptionPane.showMessageDialog(this,
285                    Bundle.getMessage("Reminder1") + "\n" + Bundle.getMessage("Reminder2"),
286                    Bundle.getMessage("ReminderTitle"),
287                    JmriJOptionPane.INFORMATION_MESSAGE);
288        }
289        setVisible(false);
290        dispose();
291    }
292
293    /**
294     * Method to handle update button
295     */
296    @Override
297    public void updateButtonActionPerformed() {
298        // get node information from window
299
300        // check consistency of node information
301        if (!checkConsistency()) {
302            return;
303        }
304        // update node paramaters
305        setNodeParameters();
306        changedNode = true;
307        // Reset Edit Mode
308        editMode = false;
309        curNode = null;
310        // Switch buttons
311        addButton.setVisible(true);
312        editButton.setVisible(true);
313        deleteButton.setVisible(true);
314        doneButton.setVisible(true);
315        updateButton.setVisible(false);
316        cancelButton.setVisible(false);
317        // refresh notes panel
318        statusText2.setText(stdStatus2);
319        statusText3.setText(stdStatus3);
320        // provide user feedback
321        try {
322           statusText1.setText(Bundle.getMessage("FeedBackUpdate") + " " + readNodeAddress());
323           } catch(IllegalArgumentException iae){
324               // we really need to set an error status here.
325               // illegal argument exception is generated by 
326               // readNodeAddress when neither a 16 or 64 bit 
327               // addresses is selected.
328           }
329        errorInStatus1 = true;
330    }
331
332    /**
333     * Method to handle cancel button
334     */
335    @Override
336    public void cancelButtonActionPerformed() {
337        // Reset 
338        editMode = false;
339        curNode = null;
340        // Switch buttons
341        addButton.setVisible(true);
342        editButton.setVisible(true);
343        deleteButton.setVisible(true);
344        doneButton.setVisible(true);
345        updateButton.setVisible(false);
346        cancelButton.setVisible(false);
347        // refresh notes panel
348        statusText1.setText(stdStatus1);
349        statusText2.setText(stdStatus2);
350        statusText3.setText(stdStatus3);
351    }
352
353    /**
354     * Method to close the window when the close box is clicked
355     */
356    @Override
357    public void windowClosing(java.awt.event.WindowEvent e) {
358        doneButtonActionPerformed();
359        super.windowClosing(e);
360    }
361
362    /**
363     * Method to set node parameters The node must exist, and be in 'curNode'
364     */
365    @Override
366    protected void setNodeParameters() {
367        super.setNodeParameters();
368    }
369
370    /**
371     * Method to reset the notes error after error display
372     */
373    private void resetNotes() {
374        if (errorInStatus1) {
375            if (editMode) {
376                statusText1.setText(editStatus1);
377            } else {
378                statusText1.setText(stdStatus1);
379            }
380            errorInStatus1 = false;
381        }
382        resetNotes2();
383    }
384
385    /**
386     * Reset the second line of Notes area
387     */
388    private void resetNotes2() {
389        if (errorInStatus2) {
390            if (editMode) {
391                statusText1.setText(editStatus2);
392            } else {
393                statusText2.setText(stdStatus2);
394            }
395            errorInStatus2 = false;
396        }
397    }
398
399    /**
400     * Read selected node address.
401     * @return The 16 bit node address, if it is not a broadcast address.  
402               The 64 bit node address if the 16 bit address is a broadcast address.
403     * @throws IllegalArgumentException if no address is selected, or the 16 bit
404               address is a broadcast address and no 64 bit address is selected.
405     *
406     */
407    private String readNodeAddress() {
408        String addr = "";
409        addr = (String) nodeAddrField.getSelectedItem();
410        if (addr==null || addr.equals("FF FE ") || addr.equals("FF FF ")) {
411            addr = (String) nodeAddr64Field.getSelectedItem();
412            if(addr == null)
413               throw new IllegalArgumentException("Invalid Address");
414        }
415        return (addr);
416    }
417
418    /**
419     * Check for consistency errors by node type Returns 'true' if successful,
420     * 'false' if an error was detected. If an error is detected, a suitable
421     * error message is placed in the Notes area
422     */
423    @Override
424    protected boolean checkConsistency() {
425        return true;
426    }
427
428    // Initialize the drop down box for the address lists.
429    @Override
430    protected void initAddressBoxes() {
431        nodeField.removeAllItems();
432        for (int i = 0; i < xtc.getNumNodes(); i++) {
433            nodeField.insertItemAt((XBeeNode) xtc.getNode(i),i);
434        }
435        nodeField.insertItemAt(null,0);
436    }
437
438   /*
439    * package protected method to allow child windows to notify
440    * that the list of nodes changed due to an addition/deletion/edit.
441    */
442   void nodeListChanged(){
443       // call initAddressBoxes to update.
444       initAddressBoxes();
445   }
446
447    // Update the display when the selected node changes.
448    @Override
449    protected void nodeSelected() {
450       log.debug("node {} selected",nodeField.getSelectedItem());
451       ((AssignmentTableModel) assignmentListModel).setNode((XBeeNode)nodeField.getSelectedItem());
452    }
453
454    // IDiscoveryListener interface methods
455   
456    /*
457     * Device discovered callback.
458     */
459    @Override
460    public void deviceDiscovered(RemoteXBeeDevice discoveredDevice){
461        log.debug("New Device discovered {}", discoveredDevice.toString());
462    }
463
464    /*
465     * Discovery error callback.
466     */
467    @Override
468    public void discoveryError(String error){
469        log.error("Error during node discovery process: {}", error);
470    }
471
472    /*
473     * Discovery finished callback.
474     */
475    @Override
476    public void discoveryFinished(String error){
477       if(error != null){
478         log.error("Node discovery processed finished with error: {}", error);
479         statusText1.setText(Bundle.getMessage("FeedBackDiscoverFail"));
480       } else {
481         log.debug("Node discovery process completed successfully.");
482         statusText1.setText(Bundle.getMessage("FeedBackDiscoverSuccess"));
483         // reload the node list.
484         initAddressBoxes();
485       }
486       // removing the listener here is causing a
487       // ConcurrentModificationException on an ArrayList in the library.
488       // xtc.getXBee().getNetwork().removeDiscoveryListener(this);
489       discoverButton.setEnabled(true);
490    }
491
492    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(XBeeNodeConfigFrame.class);
493
494}