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