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}