001package jmri.jmrix.ecos; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.awt.Component; 006import java.awt.HeadlessException; 007import java.awt.event.ActionEvent; 008import java.awt.event.ActionListener; 009import java.util.ArrayList; 010import java.util.Enumeration; 011import java.util.Hashtable; 012import java.util.List; 013 014import javax.annotation.Nonnull; 015import javax.swing.BorderFactory; 016import javax.swing.BoxLayout; 017import javax.swing.JButton; 018import javax.swing.JCheckBox; 019import javax.swing.JDialog; 020import javax.swing.JLabel; 021import javax.swing.JPanel; 022 023import jmri.*; 024import jmri.implementation.AbstractShutDownTask; 025import jmri.jmrit.beantable.ListedTableFrame; 026import jmri.jmrit.roster.Roster; 027import jmri.jmrit.roster.RosterConfigManager; 028import jmri.jmrit.roster.RosterEntry; 029import jmri.jmrix.ecos.utilities.EcosLocoToRoster; 030import jmri.jmrix.ecos.utilities.GetEcosObjectNumber; 031import jmri.jmrix.ecos.utilities.RemoveObjectFromEcos; 032import jmri.jmrix.ecos.utilities.RosterToEcos; 033import jmri.managers.AbstractManager; 034import jmri.profile.ProfileManager; 035 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039/** 040 * Class to manage the ECoS Loco entries within JMRI. 041 * 042 * @author Kevin Dickerson 043 */ 044public class EcosLocoAddressManager extends AbstractManager<NamedBean> implements EcosListener { 045 046 private boolean addLocoToRoster = false; 047 ShutDownTask ecosLocoShutDownTask; 048 private EcosLocoToRoster locoToRoster; 049 private boolean monitorState = false; 050 private boolean processLocoToRosterQueue = true; 051 private EcosPreferences p; 052 private RosterConfigManager rcm; 053 private RosterEntry _re; 054 private String rosterAttribute; 055 private EcosTrafficController tc; 056 private Thread waitPrefLoad; 057 private Hashtable<String, EcosLocoAddress> _tecos = new Hashtable<>(); // stores known Ecos Object ids to DCC 058 private Hashtable<Integer, EcosLocoAddress> _tdcc = new Hashtable<>(); // stores known DCC Address to Ecos Object ids 059 060 public EcosLocoAddressManager(@Nonnull EcosSystemConnectionMemo memo) { 061 super(memo); 062 init(); 063 } 064 065 private void init() { 066 locoToRoster = new EcosLocoToRoster(getMemo()); 067 tc = getMemo().getTrafficController(); 068 p = getMemo().getPreferenceManager(); 069 rosterAttribute = p.getRosterAttribute(); 070 rcm = InstanceManager.getDefault(RosterConfigManager.class); 071 loadEcosData(); 072 try { 073 if (InstanceManager.getNullableDefault(ListedTableFrame.class) == null) { 074 new ListedTableFrame<jmri.Turnout>(); 075 } 076 InstanceManager.getDefault(ListedTableFrame.class).addTable("jmri.jmrix.ecos.swing.locodatabase.EcosLocoTableTabAction", "ECoS Loco Database", false); 077 } catch (HeadlessException he) { 078 // silently ignore inability to display dialog 079 } 080 } 081 082 /** 083 * {@inheritDoc} 084 */ 085 @Override 086 @Nonnull 087 public EcosSystemConnectionMemo getMemo() { 088 return (EcosSystemConnectionMemo) memo; 089 } 090 091 @Override 092 public char typeLetter() { 093 return 'Z'; 094 } // NOI18N 095 096 @Override 097 public Class<NamedBean> getNamedBeanClass() { 098 return NamedBean.class; 099 } 100 101 @Override 102 public int getXMLOrder() { 103 return 65400; 104 } 105 106 /** 107 * EcosLocoAddresses have no system prefix, so return input unchanged. 108 * 109 * @param s the input to make a system name 110 * @return the resultant system name 111 */ 112 @Override 113 @Nonnull 114 public String makeSystemName(@Nonnull String s) { 115 return s; 116 } 117 118 public void clearLocoToRoster() { 119 addLocoToRoster = false; 120 } 121 122 public void setLocoToRoster() { 123 addLocoToRoster = true; 124 } 125 126 public boolean getLocoToRoster() { 127 return addLocoToRoster; 128 } 129 130 public EcosLocoAddress provideEcosLoco(String EcosObject, int DCCAddress) { 131 EcosLocoAddress l = getByEcosObject(EcosObject); 132 if (l != null) { 133 return l; 134 } 135 l = new EcosLocoAddress(DCCAddress); 136 l.setEcosObject(EcosObject); 137 register(l); 138 return l; 139 } 140 141 public EcosLocoAddress provideByDccAddress(int dccAddress) { 142 EcosLocoAddress l = getByDccAddress(dccAddress); 143 //Loco doesn't exist, so we shall create it. 144 if (l != null) { 145 return l; 146 } 147 148 l = new EcosLocoAddress(dccAddress); 149 register(l); 150 return _tdcc.get(dccAddress); 151 } 152 153 public EcosLocoAddress provideByEcosObject(String ecosObject) { 154 EcosLocoAddress l = getByEcosObject(ecosObject); 155 //Loco doesn't exist, so we shall create it. 156 if (l != null) { 157 return l; 158 } 159 160 l = new EcosLocoAddress(ecosObject, p.getRosterAttribute()); 161 register(l); 162 163 EcosMessage m = new EcosMessage("request(" + ecosObject + ", view)"); 164 tc.sendEcosMessage(m, this); 165 m = new EcosMessage("get(" + ecosObject + ", speed)"); 166 tc.sendEcosMessage(m, this); 167 168 m = new EcosMessage("get(" + ecosObject + ", dir)"); 169 tc.sendEcosMessage(m, this); 170 return _tecos.get(ecosObject); 171 } 172 173 public EcosLocoAddress getByEcosObject(String ecosObject) { 174 return _tecos.get(ecosObject); 175 } 176 177 public EcosLocoAddress getByDccAddress(int dccAddress) { 178 return _tdcc.get(dccAddress); 179 } 180 181 public String[] getEcosObjectArray() { 182 String[] arr = new String[_tecos.size()]; 183 Enumeration<String> en = _tecos.keys(); 184 int i = 0; 185 while (en.hasMoreElements()) { 186 arr[i] = en.nextElement(); 187 i++; 188 } 189 java.util.Arrays.sort(arr); 190 return arr; 191 } 192 193 public List<String> getEcosObjectList() { 194 String[] arr = new String[_tecos.size()]; 195 List<String> out = new ArrayList<>(); 196 Enumeration<String> en = _tecos.keys(); 197 int i = 0; 198 while (en.hasMoreElements()) { 199 arr[i] = en.nextElement(); 200 i++; 201 } 202 java.util.Arrays.sort(arr); 203 for (i = 0; i < arr.length; i++) { 204 out.add(arr[i]); 205 } 206 return out; 207 } 208 209 private void loadEcosData() { 210 if (p.getPreferencesLoaded() && rcm.isInitialized(ProfileManager.getDefault().getActiveProfile())) { 211 loadData(); 212 } else { 213 /*as the loco address manager is called prior to the remainder of the 214 preferences being loaded, we add a thread which waits for the preferences 215 to be loaded prior to reading the Ecos Loco database. 216 */ 217 if (waitPrefLoad != null) { 218 waitPrefLoad.interrupt(); 219 waitPrefLoad = null; 220 } 221 waitPrefLoad = new Thread(new WaitPrefLoad()); 222 waitPrefLoad.setName("Wait for Preferences to be loaded"); 223 waitPrefLoad.start(); 224 } 225 } 226 227 private void loadData() { 228 tc.addEcosListener(this); 229 230 try { 231 232 Roster.getDefault().addPropertyChangeListener(this); 233 234 EcosMessage m = new EcosMessage("request(10, view)"); 235 tc.sendWaitMessage(m, this); 236 237 /*m = new EcosMessage("queryObjects(10)"); 238 tc.sendWaitMessage(m, this);*/ 239 m = new EcosMessage("queryObjects(10, addr, name, protocol)"); 240 tc.sendEcosMessage(m, this); 241 242 if (ecosLocoShutDownTask == null) { 243 // TODO: I cannot tell what actually syncs the ECoS with the Roster 244 // or what in this ShutDownTask triggers a sync 245 ecosLocoShutDownTask = new AbstractShutDownTask("Ecos Loco Database Shutdown") { 246 247 @Override 248 public Boolean call() { 249 return shutdownDispose(); 250 } 251 252 @Override 253 public void run() { 254 disposefinal(); 255 } 256 }; 257 } 258 InstanceManager.getDefault(ShutDownManager.class).register(ecosLocoShutDownTask); 259 } catch (java.lang.NullPointerException npe) { 260 log.debug("Delayed initialization of EcosLocoAddressManager failed, no roster information available"); 261 } 262 } 263 264 public void monitorLocos(boolean monitor) { 265 monitorState = monitor; 266 List<String> objects = getEcosObjectList(); 267 268 for (String ecosObject : objects) { 269 EcosMessage m = new EcosMessage("get(" + ecosObject + ", speed)"); 270 tc.sendEcosMessage(m, this); 271 272 m = new EcosMessage("get(" + ecosObject + ", dir)"); 273 tc.sendEcosMessage(m, this); 274 } 275 } 276 277 public void deleteEcosLoco(EcosLocoAddress s) { 278 deregister(s); 279 } 280 281 public void register(EcosLocoAddress s) { 282 //We should always have at least a DCC address to register a loco. 283 //We may not always first time round on initial registration have the Ecos Object. 284 String ecosObject = s.getEcosObject(); 285 int oldsize; 286 if (ecosObject != null) { 287 oldsize = _tecos.size(); 288 _tecos.put(ecosObject, s); 289 firePropertyChange("length", oldsize, _tecos.size()); 290 } 291 292 oldsize = _tdcc.size(); 293 int dccAddress = s.getNumber(); 294 _tdcc.put(dccAddress, s); 295 firePropertyChange("length", oldsize, _tdcc.size()); 296 // listen for name and state changes to forward 297 s.addPropertyChangeListener(this); 298 } 299 300 /** 301 * Forget a NamedBean Object created outside the manager. 302 * <p> 303 * The non-system-specific RouteManager uses this method. 304 * @param s Ecos Loco Address to de-register. 305 */ 306 public void deregister(EcosLocoAddress s) { 307 s.removePropertyChangeListener(this); 308 String ecosObject = s.getEcosObject(); 309 int oldsize = _tecos.size(); 310 _tecos.remove(ecosObject); 311 firePropertyChange("length", Integer.valueOf(oldsize), Integer.valueOf(_tecos.size())); 312 313 int dccAddress = s.getNumber(); 314 oldsize = _tdcc.size(); 315 _tdcc.remove(dccAddress); 316 firePropertyChange("length", Integer.valueOf(oldsize), Integer.valueOf(_tdcc.size())); 317 EcosMessage m = new EcosMessage("release(" + ecosObject + ", view)"); 318 tc.sendEcosMessage(m, this); 319 // listen for name and state changes to forward 320 } 321 322 private boolean disposefinal() { 323 if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) { 324 InstanceManager.getDefault(ConfigureManager.class).deregister(this); 325 } 326 _tecos.clear(); 327 _tdcc.clear(); 328 return true; 329 } 330 331 /* Dispose is dealt with at shutdown */ 332 @Override 333 @SuppressFBWarnings(value = "OVERRIDING_METHODS_MUST_INVOKE_SUPER", 334 justification = "This method intentionally doesn't do anything") 335 public void dispose() { 336 } 337 338 public void terminateThreads() { 339 if (waitPrefLoad != null) { 340 waitPrefLoad.interrupt(); 341 } 342 } 343 344 protected boolean threadsRunning() { 345 return ( waitPrefLoad != null ? waitPrefLoad.isAlive() : false ); 346 } 347 348 public boolean shutdownDispose() { 349 boolean hasTempEntries = false; 350 Enumeration<String> en = _tecos.keys(); 351 _tdcc.clear(); 352 // This will remove/deregister non-temporary locos from the list. 353 while (en.hasMoreElements()) { 354 String ecosObject = en.nextElement(); 355 if (_tecos.get(ecosObject).getEcosTempEntry()) { 356 hasTempEntries = true; 357 } else { 358 deregister(getByEcosObject(ecosObject)); 359 _tecos.remove(ecosObject); 360 } 361 } 362 363 if (p.getAdhocLocoFromEcos() == 0x01) { 364 disposefinal(); 365 } else if (!hasTempEntries) { 366 disposefinal(); 367 } else if (p.getAdhocLocoFromEcos() == EcosPreferences.ASK) { 368 369 final JDialog dialog = new JDialog(); 370 dialog.setTitle(Bundle.getMessage("RemoveLocoTitle")); 371 dialog.setLocation(300, 200); 372 dialog.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE); 373 JPanel container = new JPanel(); 374 container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS)); 375 container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 376 377 JLabel question = new JLabel(Bundle.getMessage("RemoveLocoLine1")); 378 question.setAlignmentX(Component.CENTER_ALIGNMENT); 379 container.add(question); 380 question = new JLabel(Bundle.getMessage("RemoveLocoLine2")); 381 question.setAlignmentX(Component.CENTER_ALIGNMENT); 382 container.add(question); 383 final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting")); 384 remember.setFont(remember.getFont().deriveFont(10f)); 385 remember.setAlignmentX(Component.CENTER_ALIGNMENT); 386 // user preferences do not have the save option, but once complete the following line can be removed 387 // TODO get the method to save connection configuration. 388 remember.setVisible(true); 389 JButton yesButton = new JButton(Bundle.getMessage("ButtonYes")); 390 JButton noButton = new JButton(Bundle.getMessage("ButtonNo")); 391 JPanel button = new JPanel(); 392 button.setAlignmentX(Component.CENTER_ALIGNMENT); 393 button.add(yesButton); 394 button.add(noButton); 395 container.add(button); 396 397 noButton.addActionListener(new ActionListener() { 398 @Override 399 public void actionPerformed(ActionEvent e) { 400 if (remember.isSelected()) { 401 p.setAdhocLocoFromEcos(0x01); 402 } 403 dialog.dispose(); 404 } 405 }); 406 407 yesButton.addActionListener(new ActionListener() { 408 @Override 409 public void actionPerformed(ActionEvent e) { 410 if (remember.isSelected()) { 411 p.setAdhocLocoFromEcos(0x02); 412 } 413 dialog.dispose(); 414 } 415 }); 416 container.add(remember); 417 container.setAlignmentX(Component.CENTER_ALIGNMENT); 418 container.setAlignmentY(Component.CENTER_ALIGNMENT); 419 dialog.getContentPane().add(container); 420 dialog.pack(); 421 dialog.setModal(true); 422 dialog.setVisible(true); 423 } 424 return true; 425 } 426 427 /** 428 * The PropertyChangeListener interface in this class is intended to keep 429 * track of roster entries and sync them up with the Ecos. 430 */ 431 @Override 432 @SuppressFBWarnings(value = "OVERRIDING_METHODS_MUST_INVOKE_SUPER", 433 justification = "Further investigation is needed to handle this correctly") 434 public void propertyChange(java.beans.PropertyChangeEvent e) { 435 //If we are adding the loco to the roster from the ecos, we don't want to be adding it back to the ecos! 436 if (getLocoToRoster()) { 437 return; 438 } 439 if (e.getPropertyName().equals("add")) { 440 _re = (RosterEntry) e.getNewValue(); 441 442 } else if (e.getPropertyName().equals("saved")) { 443 if (_re != null) { 444 if (_re.getAttribute(rosterAttribute) != null) { 445 _re = null; 446 return; 447 } 448 //if the ecosobject attribute exists this would then indicate that it has already been created on the ecos 449 if (p.getAddLocoToEcos() == EcosPreferences.ASK) { 450 final JDialog dialog = new JDialog(); 451 dialog.setTitle(Bundle.getMessage("AddLocoTitle")); 452 //test.setSize(300,130); 453 dialog.setLocation(300, 200); 454 dialog.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE); 455 JPanel container = new JPanel(); 456 container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS)); 457 container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 458 459 JLabel question = new JLabel(Bundle.getMessage("AddLocoXQuestion", _re.getId(), getMemo().getUserName())); 460 question.setAlignmentX(Component.CENTER_ALIGNMENT); 461 container.add(question); 462 final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting")); 463 remember.setFont(remember.getFont().deriveFont(10f)); 464 remember.setAlignmentX(Component.CENTER_ALIGNMENT); 465 //user preferences do not have the save option, but once complete the following line can be removed 466 //Need to get the method to save connection configuration. 467 remember.setVisible(true); 468 JButton yesButton = new JButton(Bundle.getMessage("ButtonYes")); 469 JButton noButton = new JButton(Bundle.getMessage("ButtonNo")); 470 JPanel button = new JPanel(); 471 button.setAlignmentX(Component.CENTER_ALIGNMENT); 472 button.add(yesButton); 473 button.add(noButton); 474 container.add(button); 475 476 noButton.addActionListener(new ActionListener() { 477 @Override 478 public void actionPerformed(ActionEvent e) { 479 if (remember.isSelected()) { 480 p.setAddLocoToEcos(0x01); 481 } 482 _re = null; 483 dialog.dispose(); 484 } 485 }); 486 487 yesButton.addActionListener(new ActionListener() { 488 @Override 489 public void actionPerformed(ActionEvent e) { 490 if (remember.isSelected()) { 491 p.setAddLocoToEcos(0x02); 492 } 493 RosterToEcos rosterToEcos = new RosterToEcos(getMemo()); 494 rosterToEcos.createEcosLoco(_re); 495 _re = null; 496 dialog.dispose(); 497 } 498 }); 499 container.add(remember); 500 container.setAlignmentX(Component.CENTER_ALIGNMENT); 501 container.setAlignmentY(Component.CENTER_ALIGNMENT); 502 dialog.getContentPane().add(container); 503 dialog.pack(); 504 dialog.setModal(true); 505 dialog.setVisible(true); 506 } 507 if (p.getAddLocoToEcos() == 0x02) { 508 RosterToEcos rosterToEcos = new RosterToEcos(getMemo()); 509 rosterToEcos.createEcosLoco(_re); 510 _re = null; 511 } 512 } 513 } else if (e.getPropertyName().equals("remove")) { 514 _re = (RosterEntry) e.getNewValue(); 515 if (_re.getAttribute(rosterAttribute) != null) { 516 if (p.getRemoveLocoFromEcos() == EcosPreferences.YES){ 517 RemoveObjectFromEcos removeObjectFromEcos = new RemoveObjectFromEcos(); 518 removeObjectFromEcos.removeObjectFromEcos(_re.getAttribute(p.getRosterAttribute()), tc); 519 deleteEcosLoco(provideByEcosObject(_re.getAttribute(p.getRosterAttribute()))); 520 } else if(p.getRemoveLocoFromEcos() == EcosPreferences.ASK ) { 521 final JDialog dialog = new JDialog(); 522 dialog.setTitle(Bundle.getMessage("RemoveLocoTitle")); 523 //test.setSize(300,130); 524 dialog.setLocation(300, 200); 525 dialog.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE); 526 JPanel container = new JPanel(); 527 container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS)); 528 container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 529 530 JLabel question = new JLabel(Bundle.getMessage("RemoveLocoXQuestion", getMemo().getUserName())); 531 question.setAlignmentX(Component.CENTER_ALIGNMENT); 532 container.add(question); 533 final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting")); 534 remember.setFont(remember.getFont().deriveFont(10f)); 535 remember.setAlignmentX(Component.CENTER_ALIGNMENT); 536 //user preferences do not have the save option, but once complete the following line can be removed 537 //Need to get the method to save connection configuration. 538 remember.setVisible(true); 539 JButton yesButton = new JButton(Bundle.getMessage("ButtonYes")); 540 JButton noButton = new JButton(Bundle.getMessage("ButtonNo")); 541 JPanel button = new JPanel(); 542 button.setAlignmentX(Component.CENTER_ALIGNMENT); 543 button.add(yesButton); 544 button.add(noButton); 545 container.add(button); 546 547 noButton.addActionListener(new ActionListener() { 548 @Override 549 public void actionPerformed(ActionEvent e) { 550 if (remember.isSelected()) { 551 p.setRemoveLocoFromEcos(0x01); 552 } 553 provideByEcosObject(_re.getAttribute(p.getRosterAttribute())).setRosterId(null); 554 dialog.dispose(); 555 } 556 }); 557 558 yesButton.addActionListener(new ActionListener() { 559 @Override 560 public void actionPerformed(ActionEvent e) { 561 if (remember.isSelected()) { 562 p.setRemoveLocoFromEcos(0x02); 563 } 564 RemoveObjectFromEcos removeObjectFromEcos = new RemoveObjectFromEcos(); 565 removeObjectFromEcos.removeObjectFromEcos(_re.getAttribute(p.getRosterAttribute()), tc); 566 deleteEcosLoco(provideByEcosObject(_re.getAttribute(p.getRosterAttribute()))); 567 dialog.dispose(); 568 } 569 }); 570 container.add(remember); 571 container.setAlignmentX(Component.CENTER_ALIGNMENT); 572 container.setAlignmentY(Component.CENTER_ALIGNMENT); 573 dialog.getContentPane().add(container); 574 dialog.pack(); 575 dialog.setModal(true); 576 dialog.setVisible(true); 577 } 578 } 579 _re = null; 580 } else if (e.getPropertyName().equals("throttleAssigned")) { 581 DccLocoAddress la = (DccLocoAddress) e.getNewValue(); 582 EcosLocoAddress ela = getByDccAddress(la.getNumber()); 583 EcosMessage m = new EcosMessage("get(" + ela.getEcosObject() + ", speed)"); 584 tc.sendEcosMessage(m, this); 585 m = new EcosMessage("get(" + ela.getEcosObject() + ", dir)"); 586 tc.sendEcosMessage(m, this); 587 } 588 } 589 590 @Override 591 public void reply(EcosReply m) { 592 String strde; 593 594 if (m.getResultCode() == 0) { 595 int ecosObjectId = m.getEcosObjectId(); 596 if ((ecosObjectId != 10) && ((ecosObjectId < 1000) || (ecosObjectId > 2000))) { 597 log.debug("message received that is not within the valid loco object range"); 598 return; 599 } 600 List<String> headerDetails = m.getReplyHeaderDetails(); 601 String[] msgDetails = m.getContents(); 602 if (m.isUnsolicited()) { 603 if (ecosObjectId == 10) { 604 log.debug("We have received notification of a change in the Loco list"); 605 if (msgDetails.length == 0) { 606 EcosMessage mout = new EcosMessage("queryObjects(10)"); 607 tc.sendEcosMessage(mout, this); 608 //Version 3.0.1 of the software has an issue in that it stops sending updates on the 609 //loco objects when a delete has happened, we therefore need to release the old view 610 //then re-request it. 611 mout = new EcosMessage("release(10, view)"); 612 tc.sendEcosMessage(mout, this); 613 mout = new EcosMessage("request(10, view)"); 614 tc.sendEcosMessage(mout, this); 615 } else if (msgDetails[0].contains("msg[LIST_CHANGED]")) { 616 EcosMessage mout = new EcosMessage("queryObjects(10)"); 617 tc.sendEcosMessage(mout, this); 618 } 619 } else { 620 EcosLocoAddress tmploco; 621 log.debug("Forwarding on State change for {}", ecosObjectId); 622 String strLocoObject = Integer.toString(ecosObjectId); 623 tmploco = _tecos.get(strLocoObject); 624 if (tmploco != null) { 625 tmploco.reply(m); 626 } 627 } 628 } else { 629 String replyType = m.getReplyType(); 630 631 if (replyType.equals("queryObjects")) { 632 if (ecosObjectId == 10) { 633 if (headerDetails.size() == 0 || (headerDetails.size() == 1 && headerDetails.get(0).equals(""))) { 634 checkLocoList(msgDetails); 635 } else { 636 processLocoToRosterQueue = false; 637 //Format of the reply details are ObjectId, followed by object ids. 638 for (String line : msgDetails) { 639 String[] objectdetail = line.split(" "); 640 EcosLocoAddress tmploco = null; 641 //The first part of the messages is always the object id. 642 strde = objectdetail[0]; 643 strde = strde.trim(); 644 int object = Integer.parseInt(strde); 645 if ((1000 <= object) && (object < 2000)) { 646 tmploco = provideByEcosObject(strde); 647 } 648 decodeLocoDetails(tmploco, line, true); 649 } 650 locoToRoster.processQueue(); 651 processLocoToRosterQueue = true; 652 } 653 } 654 } else if (replyType.equals("get")) { 655 EcosLocoAddress tmploco = provideByEcosObject(Integer.toString(ecosObjectId)); 656 for (String line : msgDetails) { 657 decodeLocoDetails(tmploco, line, false); 658 } 659 } 660 } 661 } 662 } 663 664 void decodeLocoDetails(EcosLocoAddress tmploco, String line, boolean addToRoster) { 665 if (tmploco == null) { 666 return; 667 } 668 if (line.contains("cv")) { 669 String cv = EcosReply.getContentDetails(line, "cv"); 670 cv = cv.replaceAll("\\s", ""); //remove all white spaces, as 4.1.0 version removed the space after the , 671 int cvnum = Integer.parseInt(cv.substring(0, cv.indexOf(","))); 672 int cvval = Integer.parseInt(cv.substring(cv.indexOf(",") + 1, cv.length())); 673 tmploco.setCV(cvnum, cvval); 674 if (cvnum == 8 && processLocoToRosterQueue) { 675 locoToRoster.processQueue(); 676 } 677 } 678 if (line.contains("addr")) { 679 tmploco.setLocoAddress(GetEcosObjectNumber.getEcosObjectNumber(line, "addr[", "]")); 680 if (tmploco.getCV(7) == -1) { 681 tmploco.setCV(7, 0); 682 getEcosCVs(tmploco); 683 } 684 } 685 if (line.contains("name")) { 686 String name = EcosReply.getContentDetails(line, "name").trim(); 687 name = name.substring(1, name.length() - 1); 688 tmploco.setEcosDescription(name); 689 } 690 if (line.contains("protocol")) { 691 tmploco.setProtocol(EcosReply.getContentDetails(line, "protocol")); 692 } 693 if (line.contains("speed")) { 694 tmploco.setSpeed(Integer.parseInt(EcosReply.getContentDetails(line, "speed"))); 695 } 696 697 if (line.contains("dir")) { 698 boolean newDirection = false; 699 if (EcosReply.getContentDetails(line, "dir").equals("0")) { 700 newDirection = true; 701 } 702 tmploco.setDirection(newDirection); 703 } 704 register(tmploco); 705 if (p.getAddLocoToJMRI() != EcosPreferences.NO && addToRoster) { 706 locoToRoster.addToQueue(tmploco); 707 } 708 } 709 710 /* This is used after an event update form the ecos informing us of a change in the 711 * loco list, we have to determine if it is an addition or delete. 712 * We should only ever do either a remove or an add in one go, if we are adding the loco 713 * to the roster otherwise this causes a problem with the roster list. 714 */ 715 void checkLocoList(String[] ecoslines) { 716 log.debug("Checking loco list"); 717 String loco; 718 for (String ecosline : ecoslines) { 719 loco = ecosline; 720 loco = loco.replaceAll("[\\n\\r]", ""); 721 if (getByEcosObject(loco) == null) { 722 log.debug("We are to add loco {} to the Ecos Loco List", loco); 723 EcosMessage mout = new EcosMessage("get(" + loco + ", addr, name, protocol)"); 724 tc.sendEcosMessage(mout, this); 725 } 726 } 727 728 String[] jmrilist = getEcosObjectArray(); 729 boolean nomatch = true; 730 for (String entry : jmrilist) { 731 nomatch = true; 732 for (String ecosline : ecoslines) { 733 loco = ecosline; 734 loco = loco.replaceAll("[\\n\\r]", ""); 735 if (loco.equals(entry)) { 736 nomatch = false; 737 break; 738 } 739 } 740 if (nomatch) { 741 // We do not have a match, therefore this should be deleted from the Ecos loco Manager " + jmrilist[i] 742 log.debug("Loco not found so need to remove from register"); 743 if (getByEcosObject(entry).getRosterId() != null) { 744 final String rosterid = getByEcosObject(entry).getRosterId(); 745 final Roster _roster = Roster.getDefault(); 746 final RosterEntry re = _roster.entryFromTitle(rosterid); 747 re.deleteAttribute(p.getRosterAttribute()); 748 re.writeFile(null, null); 749 Roster.getDefault().writeRoster(); 750 if (p.getRemoveLocoFromJMRI() == EcosPreferences.YES) { 751 _roster.removeEntry(re); 752 Roster.getDefault().writeRoster(); 753 } else if (p.getRemoveLocoFromJMRI() == EcosPreferences.ASK) { 754 try { 755 final JDialog dialog = new JDialog(); 756 dialog.setTitle(Bundle.getMessage("RemoveRosterEntryTitle")); 757 dialog.setLocation(300, 200); 758 dialog.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE); 759 JPanel container = new JPanel(); 760 container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS)); 761 container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 762 763 JLabel question = new JLabel(Bundle.getMessage("RemoveRosterEntryX", rosterid)); 764 question.setAlignmentX(Component.CENTER_ALIGNMENT); 765 container.add(question); 766 final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting")); 767 remember.setFont(remember.getFont().deriveFont(10f)); 768 remember.setAlignmentX(Component.CENTER_ALIGNMENT); 769 //user preferences do not have the save option, but once complete the following line can be removed 770 //Need to get the method to save connection configuration. 771 remember.setVisible(true); 772 JButton yesButton = new JButton(Bundle.getMessage("ButtonYes")); 773 JButton noButton = new JButton(Bundle.getMessage("ButtonNo")); 774 JPanel button = new JPanel(); 775 button.setAlignmentX(Component.CENTER_ALIGNMENT); 776 button.add(yesButton); 777 button.add(noButton); 778 container.add(button); 779 780 noButton.addActionListener(new ActionListener() { 781 @Override 782 public void actionPerformed(ActionEvent e) { 783 if (remember.isSelected()) { 784 p.setRemoveLocoFromJMRI(EcosPreferences.ASK); 785 } 786 dialog.dispose(); 787 } 788 }); 789 790 yesButton.addActionListener(new ActionListener() { 791 @Override 792 public void actionPerformed(ActionEvent e) { 793 if (remember.isSelected()) { 794 p.setRemoveLocoFromJMRI(EcosPreferences.YES); 795 } 796 setLocoToRoster(); 797 _roster.removeEntry(re); 798 Roster.getDefault().writeRoster(); 799 dialog.dispose(); 800 } 801 }); 802 container.add(remember); 803 container.setAlignmentX(Component.CENTER_ALIGNMENT); 804 container.setAlignmentY(Component.CENTER_ALIGNMENT); 805 dialog.getContentPane().add(container); 806 dialog.pack(); 807 dialog.setModal(true); 808 dialog.setVisible(true); 809 810 } catch (HeadlessException he) { 811 // silently ignore inability to display dialog 812 } 813 } 814 } 815 // Even if we do not delete the loco from the roster, we need to remove it from the ecos list. 816 deregister(getByEcosObject(entry)); 817 } 818 } 819 } 820 821 @Override 822 public void message(EcosMessage m) { 823 824 } 825 /** 826 * The purpose of this is to get some of the basic cv details that are required 827 * for selecting the decoder mfg and family in the roster file. 828 * This might work as sending a single request rather than multiple. 829 */ 830 private void getEcosCVs(EcosLocoAddress tmploco) { 831 tc.addEcosListener(this); 832 // ask to be notified 833 // We won't look to add new locos created on the ecos yet this can be added in at a later date. 834 835 EcosMessage m = new EcosMessage("get(" + tmploco.getEcosObject() + ", cv[7])"); 836 tc.sendEcosMessage(m, this); 837 838 m = new EcosMessage("get(" + tmploco.getEcosObject() + ", cv[8])"); 839 tc.sendEcosMessage(m, this); 840 841 } 842 843 private class WaitPrefLoad implements Runnable { 844 845 @Override 846 public void run() { 847 boolean result = true; 848 log.debug("Waiting for the Ecos preferences to be loaded before loading the loco database on the Ecos"); 849 while (!wait) { 850 result = waitForPrefLoad(); 851 } 852 if (result) { 853 loadData(); 854 } else { 855 log.debug("waitForPrefLoad requested skip loadData()"); 856 } 857 } 858 859 boolean wait = false; 860 int count = 0; 861 862 /** 863 * @return true if OK to proceed to load data, false if should abort 864 */ 865 private boolean waitForPrefLoad() { 866 try { 867 Thread.sleep(100); 868 } catch (InterruptedException e) { 869 log.trace("waitForPrefLoad received InterruptedException, honoring termination request"); 870 wait = true; 871 return false; 872 } 873 wait = p.getPreferencesLoaded() && rcm.isInitialized(ProfileManager.getDefault().getActiveProfile()); 874 if (count >= 1000) { 875 wait = true; 876 log.warn("Timeout {} occurred on waiting for the Ecos preferences to be loaded", count); 877 return false; 878 } 879 count++; 880 return true; 881 } 882 } 883 884 public void refreshItems() { 885 // ask to be notified about newly created locos on the layout. 886 EcosMessage m = new EcosMessage("request(10, view)"); 887 tc.sendEcosMessage(m, this); 888 if (monitorState) { 889 List<String> objects = getEcosObjectList(); 890 for (int x = 0; x < objects.size(); x++) { 891 // Do a release before anything else. 892 m = new EcosMessage("release(" + getByEcosObject(objects.get(x)) + ", view, control)"); 893 tc.sendEcosMessage(m, this); 894 } 895 for (int x = 0; x < objects.size(); x++) { 896 //Re-request view on loco 897 m = new EcosMessage("request(" + getByEcosObject(objects.get(x)) + ", view)"); 898 tc.sendEcosMessage(m, this); 899 900 m = new EcosMessage("get(" + getByEcosObject(objects.get(x)) + ", speed)"); 901 tc.sendEcosMessage(m, this); 902 903 m = new EcosMessage("get(" + getByEcosObject(objects.get(x)) + ", dir)"); 904 tc.sendEcosMessage(m, this); 905 } 906 } 907 //monitorLocos(monitorState); 908 } 909 910 @Override 911 @Nonnull 912 public String getBeanTypeHandled(boolean plural) { 913 return Bundle.getMessage("EcosLocoAddresses"); 914 } 915 916 private final static Logger log = LoggerFactory.getLogger(EcosLocoAddressManager.class); 917 918}