001package jmri.jmrit.vsdecoder; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.util.ArrayList; 006import java.util.Collection; 007import java.util.HashMap; 008import java.util.LinkedHashMap; 009import java.util.Iterator; 010import java.awt.geom.Point2D; 011import jmri.Audio; 012import jmri.LocoAddress; 013import jmri.Throttle; 014import jmri.jmrit.display.layoutEditor.*; 015import jmri.jmrit.operations.locations.Location; 016import jmri.jmrit.operations.routes.RouteLocation; 017import jmri.jmrit.operations.routes.Route; 018import jmri.jmrit.operations.trains.Train; 019import jmri.jmrit.operations.trains.TrainManager; 020import jmri.jmrit.roster.RosterEntry; 021import jmri.jmrit.vsdecoder.swing.VSDControl; 022import jmri.jmrit.vsdecoder.swing.VSDManagerFrame; 023import jmri.util.PhysicalLocation; 024 025import org.jdom2.Element; 026 027/** 028 * Implements a software "decoder" that responds to throttle inputs and 029 * generates sounds in responds to them. 030 * <p> 031 * Each VSDecoder implements exactly one Sound Profile (describes a particular 032 * type of locomotive, say, an EMD GP7). 033 * <hr> 034 * This file is part of JMRI. 035 * <p> 036 * JMRI is free software; you can redistribute it and/or modify it under the 037 * terms of version 2 of the GNU General Public License as published by the Free 038 * Software Foundation. See the "COPYING" file for a copy of this license. 039 * <p> 040 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 041 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 042 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 043 * 044 * @author Mark Underwood Copyright (C) 2011 045 * @author Klaus Killinger Copyright (C) 2018-2023, 2025 046 */ 047public class VSDecoder implements PropertyChangeListener { 048 049 boolean initialized = false; // This decoder has been initialized 050 private boolean enabled = false; // This decoder is enabled 051 private boolean create_xy_series = false; // Create xy coordinates in console 052 053 private VSDConfig config; 054 055 // For use in VSDecoderManager 056 int dirfn = 1; 057 PhysicalLocation posToSet; 058 PhysicalLocation lastPos; 059 PhysicalLocation startPos; 060 int topspeed; 061 int topspeed_rev; 062 int setup_index; // Can be set by a Route 063 boolean is_muted; 064 065 double distanceOnTrack; 066 float distanceMeter; 067 double distance; // how far to travel this frame 068 private double returnDistance; // used by a direction change 069 private Point2D location; 070 private LayoutTrack lastTrack; // the layout track we were on previously 071 private LayoutTrack layoutTrack; // which layout track we're on 072 private LayoutTrack returnTrack; 073 private LayoutTrack returnLastTrack; 074 LayoutTrack nextLayoutTrack; 075 private double directionRAD; // directionRAD we're headed (in radians) 076 private LayoutEditor models; 077 private VSDNavigation navigation; 078 079 HashMap<String, VSDSound> sound_list; // list of sounds 080 LinkedHashMap<String, SoundEvent> event_list; // list of events 081 082 /** 083 * Construct a VSDecoder with the given system name (id) and configuration 084 * (config) 085 * 086 * @param cfg (VSDConfig) Configuration 087 */ 088 public VSDecoder(VSDConfig cfg) { 089 config = cfg; 090 091 sound_list = new HashMap<>(); 092 event_list = new LinkedHashMap<>(); 093 094 // Force re-initialization 095 initialized = _init(); 096 097 try { 098 VSDFile vsdfile = new VSDFile(config.getVSDPath()); 099 if (vsdfile.isInitialized()) { 100 log.debug("Constructor: vsdfile init OK, loading XML..."); 101 this.setXml(vsdfile, config.getProfileName()); 102 } else { 103 log.debug("Constructor: vsdfile init FAILED."); 104 initialized = false; 105 } 106 } catch (java.util.zip.ZipException e) { 107 log.error("ZipException loading VSDecoder from {}", config.getVSDPath()); 108 // would be nice to pop up a dialog here... 109 } catch (java.io.IOException ioe) { 110 log.error("IOException loading VSDecoder from {}", config.getVSDPath()); 111 // would be nice to pop up a dialog here... 112 } 113 114 if (this.getEngineSound().getBuffersFreeState()) { 115 // Since the Config already has the address set, we need to call 116 // our own setAddress() to register the throttle listener 117 this.setAddress(config.getLocoAddress()); 118 this.enable(); 119 120 // Handle Advanced Location Following (if the parameter file is OK) 121 if (VSDecoderManager.instance().geofile_ok) { 122 // ALF1 needs this 123 this.setup_index = 0; 124 // create a navigator for this VSDecoder 125 if (VSDecoderManager.instance().alf_version == 2) { 126 navigation = new VSDNavigation(this); 127 } 128 } 129 130 if (log.isDebugEnabled()) { 131 log.debug("VSDecoder Init Complete. Audio Objects Created:"); 132 jmri.InstanceManager.getDefault(jmri.AudioManager.class).getNamedBeanSet(Audio.SOURCE).forEach((s) -> { 133 log.debug("\tSource: {}", s); 134 }); 135 jmri.InstanceManager.getDefault(jmri.AudioManager.class).getNamedBeanSet(Audio.BUFFER).forEach((s) -> { 136 log.debug("\tBuffer: {}", s); 137 }); 138 } 139 140 log.info("Number of used buffers: {}, max: {}", 141 jmri.InstanceManager.getDefault(jmri.AudioManager.class).getNamedBeanSet(Audio.BUFFER).size(), 142 jmri.AudioManager.MAX_BUFFERS); 143 } else { 144 this.disable(); // not a valid VSDecoder 145 } 146 } 147 148 /** 149 * Construct a VSDecoder with the given system name (id), profile name and 150 * VSD file path 151 * 152 * @param id (String) System name for this VSDecoder 153 * @param name (String) Profile name 154 * @param path (String) Path to a VSD file to pull the given Profile from 155 */ 156 public VSDecoder(String id, String name, String path) { 157 158 config = new VSDConfig(); 159 config.setProfileName(name); 160 config.setId(id); 161 162 sound_list = new HashMap<>(); 163 event_list = new LinkedHashMap<>(); 164 165 // Force re-initialization 166 initialized = _init(); 167 168 config.setVSDPath(path); 169 170 try { 171 VSDFile vsdfile = new VSDFile(path); 172 if (vsdfile.isInitialized()) { 173 log.debug("Constructor: vsdfile init OK, loading XML..."); 174 this.setXml(vsdfile, name); 175 } else { 176 log.debug("Constructor: vsdfile init FAILED."); 177 initialized = false; 178 } 179 } catch (java.util.zip.ZipException e) { 180 log.error("ZipException loading VSDecoder from {}", path); 181 // would be nice to pop up a dialog here... 182 } catch (java.io.IOException ioe) { 183 log.error("IOException loading VSDecoder from {}", path); 184 // would be nice to pop up a dialog here... 185 } 186 } 187 188 private boolean _init() { 189 // Do nothing for now 190 this.enable(); 191 return true; 192 } 193 194 /** 195 * Get the ID (System Name) of this VSDecoder 196 * 197 * @return (String) system name of this VSDecoder 198 */ 199 public String getId() { 200 return config.getId(); 201 } 202 203 /** 204 * Check whether this VSDecoder has completed initialization 205 * 206 * @return (boolean) true if initialization is complete. 207 */ 208 public boolean isInitialized() { 209 return initialized; 210 } 211 212 /** 213 * Set the VSD File path for this VSDecoder to use 214 * 215 * @param p (String) path to VSD File 216 */ 217 public void setVSDFilePath(String p) { 218 config.setVSDPath(p); 219 } 220 221 /** 222 * Get the current VSD File path for this VSDecoder 223 * 224 * @return (String) path to VSD file 225 */ 226 public String getVSDFilePath() { 227 return config.getVSDPath(); 228 } 229 230 /** 231 * Shut down this VSDecoder and all of its associated sounds. 232 */ 233 public void shutdown() { 234 log.debug("Shutting down sounds..."); 235 for (VSDSound vs : sound_list.values()) { 236 log.debug("Stopping sound: {}", vs.getName()); 237 vs.shutdown(); 238 } 239 } 240 241 /** 242 * Handle the details of responding to a PropertyChangeEvent from a 243 * throttle. 244 * 245 * @param event (PropertyChangeEvent) Throttle event to respond to 246 */ 247 protected void throttlePropertyChange(PropertyChangeEvent event) { 248 // WARNING: FRAGILE CODE 249 // This will break if the return type of the event.getOld/NewValue() changes. 250 251 String eventName = event.getPropertyName(); 252 253 // Skip this if disabled 254 if (!enabled) { 255 log.debug("VSDecoder disabled. Take no action."); 256 return; 257 } 258 259 log.debug("VSDecoder throttle property change: {}", eventName); 260 261 if (eventName.equals("throttleAssigned")) { 262 Float s = (Float) jmri.InstanceManager.throttleManagerInstance().getThrottleInfo(config.getDccAddress(), Throttle.SPEEDSETTING); 263 if (s != null) { 264 this.getEngineSound().setFirstSpeed(true); // Auto-start needs this 265 // Mimic a throttlePropertyChange to propagate the current (init) speed setting of the throttle. 266 log.debug("Existing DCC Throttle found. Speed: {}", s); 267 this.throttlePropertyChange(new PropertyChangeEvent(this, Throttle.SPEEDSETTING, null, s)); 268 } 269 270 // Check for an existing throttle and get loco direction if it exists. 271 Boolean b = (Boolean) jmri.InstanceManager.throttleManagerInstance().getThrottleInfo(config.getDccAddress(), Throttle.ISFORWARD); 272 if (b != null) { 273 dirfn = b ? 1 : -1; 274 log.debug("Existing DCC Throttle found. IsForward is {}", b); 275 log.debug("Initial dirfn: {} for {}", dirfn, config.getDccAddress()); 276 this.throttlePropertyChange(new PropertyChangeEvent(this, Throttle.ISFORWARD, null, b)); 277 } else { 278 log.warn("No existing DCC throttle found."); 279 } 280 281 // Check for an existing throttle and get ENGINE throttle function key status if it exists. 282 // For all function keys used in config.xml (sound-event name="ENGINE") this will send an initial value! This could be ON or OFF. 283 if (event_list.get("ENGINE") != null) { 284 for (Trigger t : event_list.get("ENGINE").trigger_list.values()) { 285 log.debug("ENGINE trigger Name: {}, Event: {}, t: {}", t.getName(), t.getEventName(), t); 286 if (t.getEventName().startsWith("F")) { 287 log.debug("F-Key trigger found: {}, name: {}, event: {}", t, t.getName(), t.getEventName()); 288 // Don't send an initial value if trigger is ENGINE_STARTSTOP, because that would work against auto-start; BRAKE_KEY would play a sound 289 if (!t.getName().equals("ENGINE_STARTSTOP") && !t.getName().equals("BRAKE_KEY")) { 290 b = (Boolean) jmri.InstanceManager.throttleManagerInstance().getThrottleInfo(config.getDccAddress(), t.getEventName()); 291 if (b != null) { 292 this.throttlePropertyChange(new PropertyChangeEvent(this, t.getEventName(), null, b)); 293 } 294 } 295 } 296 } 297 } 298 } 299 300 // Iterate through the list of sound events, forwarding the propertyChange event. 301 for (SoundEvent t : event_list.values()) { 302 t.propertyChange(event); 303 } 304 305 if (eventName.equals(Throttle.ISFORWARD)) { 306 dirfn = (Boolean) event.getNewValue() ? 1 : -1; 307 } 308 } 309 310 /** 311 * Set this VSDecoder's LocoAddress, and register to follow events from the 312 * throttle with this address. 313 * 314 * @param l (LocoAddress) LocoAddress to be followed 315 */ 316 public void setAddress(LocoAddress l) { 317 // Hack for ThrottleManager Dcc dependency 318 config.setLocoAddress(l); 319 jmri.InstanceManager.throttleManagerInstance().attachListener(config.getDccAddress(), 320 new PropertyChangeListener() { 321 @Override 322 public void propertyChange(PropertyChangeEvent event) { 323 log.debug("property change name: {}, old: {}, new: {}", event.getPropertyName(), event.getOldValue(), event.getNewValue()); 324 throttlePropertyChange(event); 325 } 326 }); 327 log.debug("VSDecoder: Address set to {}", config.getLocoAddress()); 328 } 329 330 /** 331 * Get the currently assigned LocoAddress 332 * 333 * @return the currently assigned LocoAddress 334 */ 335 public LocoAddress getAddress() { 336 return config.getLocoAddress(); 337 } 338 339 public RosterEntry getRosterEntry() { 340 return config.getRosterEntry(); 341 } 342 343 /** 344 * Get the current decoder volume setting for this VSDecoder 345 * 346 * @return (float) volume level (0.0 - 1.0) 347 */ 348 public float getDecoderVolume() { 349 return config.getVolume(); 350 } 351 352 private void forwardMasterVolume(float volume) { 353 log.debug("VSD config id: {}, Master volume: {}, Decoder volume: {}", getId(), volume, config.getVolume()); 354 for (VSDSound vs : sound_list.values()) { 355 vs.setVolume(volume * config.getVolume()); 356 } 357 } 358 359 /** 360 * Set the decoder volume for this VSDecoder 361 * 362 * @param decoder_volume (float) volume level (0.0 - 1.0) 363 */ 364 public void setDecoderVolume(float decoder_volume) { 365 config.setVolume(decoder_volume); 366 float master_vol = 0.01f * VSDecoderManager.instance().getMasterVolume(); 367 log.debug("config set decoder volume to {}, master volume adjusted: {}", decoder_volume, master_vol); 368 for (VSDSound vs : sound_list.values()) { 369 vs.setVolume(master_vol * decoder_volume); 370 } 371 } 372 373 /** 374 * Is this VSDecoder muted? 375 * 376 * @return true if muted 377 */ 378 public boolean isMuted() { 379 return getMuteState(); 380 } 381 382 /** 383 * Mute or un-mute this VSDecoder 384 * 385 * @param m (boolean) true to mute, false to un-mute 386 */ 387 public void mute(boolean m) { 388 for (VSDSound vs : sound_list.values()) { 389 vs.mute(m); 390 } 391 } 392 393 private void setMuteState(boolean m) { 394 is_muted = m; 395 } 396 397 private boolean getMuteState() { 398 return is_muted; 399 } 400 401 /** 402 * set the x/y/z position in the soundspace of this VSDecoder Translates the 403 * given position to a position relative to the listener for the component 404 * VSDSounds. 405 * <p> 406 * The idea is that the user-preference Listener Position (relative to the 407 * USER's chosen origin) is always the OpenAL Context's origin. 408 * 409 * @param p (PhysicalLocation) location relative to the user's chosen 410 * Origin. 411 */ 412 public void setPosition(PhysicalLocation p) { 413 // Store the actual position relative to the user's Origin locally. 414 config.setPhysicalLocation(p); 415 if (create_xy_series) { 416 log.info("setPosition {}: {}\t{}", this.getAddress(), (float) Math.round(p.x*10000)/10000, p.y); 417 } 418 log.debug("address {} set Position: {}", this.getAddress(), p); 419 420 this.lastPos = p; // save this position 421 422 // Give all of the VSDSound objects the position translated relative to the listener position. 423 // This is a workaround for OpenAL requiring the listener position to always be at (0,0,0). 424 /* 425 * PhysicalLocation ref = VSDecoderManager.instance().getVSDecoderPreferences().getListenerPhysicalLocation(); 426 * if (ref == null) ref = PhysicalLocation.Origin; 427 */ 428 for (VSDSound s : sound_list.values()) { 429 // s.setPosition(PhysicalLocation.translate(p, ref)); 430 s.setPosition(p); 431 } 432 433 // Set (relative) volume for this location (in case we're in a tunnel) 434 float tv = 0.01f * VSDecoderManager.instance().getMasterVolume() * getDecoderVolume(); 435 log.debug("current master volume: {}, decoder volume: {}", VSDecoderManager.instance().getMasterVolume(), getDecoderVolume()); 436 if (this.getEngineSound().getTunnel()) { 437 tv *= VSDSound.tunnel_volume; 438 log.debug("VSD: In tunnel, volume: {}", tv); 439 } else { 440 log.debug("VSD: Not in tunnel, volume: {}", tv); 441 } 442 if (! getMuteState()) { 443 for (VSDSound vs : sound_list.values()) { 444 vs.setVolume(tv); 445 } 446 } 447 } 448 449 // Forward tunnel state to the VSDSound of this VSDecoder's Engine Sound and all Configurable Sounds 450 void setTunnelState(boolean t) { 451 for (VSDSound vs : sound_list.values()) { 452 vs.setTunnel(t); 453 } 454 } 455 456 /** 457 * Get the current x/y/z position in the soundspace of this VSDecoder 458 * 459 * @return PhysicalLocation location of this VSDecoder 460 */ 461 public PhysicalLocation getPosition() { 462 return config.getPhysicalLocation(); 463 } 464 465 /** 466 * Respond to property change events from this VSDecoder's GUI 467 * 468 * @param evt (PropertyChangeEvent) event to respond to 469 */ 470 @Override 471 public void propertyChange(PropertyChangeEvent evt) { 472 String property = evt.getPropertyName(); 473 // Respond to events from the new GUI. 474 if (evt.getSource() instanceof VSDControl) { 475 if (property.equals(VSDControl.OPTION_CHANGE)) { 476 Train selected_train = jmri.InstanceManager.getDefault(TrainManager.class).getTrainByName((String) evt.getNewValue()); 477 if (selected_train != null) { 478 selected_train.addPropertyChangeListener(this); 479 // Handle Advanced Location Following (if the parameter file is OK) 480 if (VSDecoderManager.instance().geofile_ok) { 481 Route r = selected_train.getRoute(); 482 if (r != null) { 483 log.info("Train \"{}\" selected for {} - Route is now \"{}\"", selected_train, this.getAddress(), r.getName()); 484 if (r.getName().equals("VSDRoute1")) { 485 this.setup_index = 0; 486 } else if (r.getName().equals("VSDRoute2") && VSDecoderManager.instance().num_setups > 1) { 487 this.setup_index = 1; 488 } else if (r.getName().equals("VSDRoute3") && VSDecoderManager.instance().num_setups > 2) { 489 this.setup_index = 2; 490 } else if (r.getName().equals("VSDRoute4") && VSDecoderManager.instance().num_setups > 3) { 491 this.setup_index = 3; 492 } else { 493 log.warn("\"{}\" is not suitable for VSD Advanced Location Following", r.getName()); 494 } 495 } else { 496 log.warn("Train \"{}\" is without Route", selected_train); 497 } 498 } 499 } 500 } 501 return; 502 } 503 504 if (property.equals(VSDManagerFrame.MUTE)) { 505 // GUI Mute button 506 log.debug("VSD: Mute change. value: {}", evt.getNewValue()); 507 setMuteState((boolean) evt.getNewValue()); 508 this.mute(getMuteState()); 509 } else if (property.equals(VSDManagerFrame.VOLUME_CHANGE)) { 510 // GUI Volume slider (Master Volume) 511 log.debug("VSD: Volume change. value: {}", evt.getOldValue()); 512 // Slider gives integer 0-100. Need to change that to a float 0.0-1.0 513 this.forwardMasterVolume((0.01f * (Integer) evt.getOldValue())); 514 } else if (property.equals(Train.TRAIN_LOCATION_CHANGED_PROPERTY)) { 515 // Train Location Move 516 PhysicalLocation p = getTrainPosition((Train) evt.getSource()); 517 if (p != null) { 518 this.setPosition(getTrainPosition((Train) evt.getSource())); 519 } else { 520 log.debug("Train has null position"); 521 this.setPosition(new PhysicalLocation()); 522 } 523 } else if (property.equals(Train.STATUS_CHANGED_PROPERTY)) { 524 // Train Status change 525 String status = (String) evt.getOldValue(); 526 log.debug("Train status changed: {}", status); 527 log.debug("New Location: {}", getTrainPosition((Train) evt.getSource())); 528 if ((status.startsWith(Train.BUILT)) || (status.startsWith(Train.PARTIAL_BUILT))) { 529 log.debug("Train built. status: {}", status); 530 PhysicalLocation p = getTrainPosition((Train) evt.getSource()); 531 if (p != null) { 532 this.setPosition(getTrainPosition((Train) evt.getSource())); 533 } else { 534 log.debug("Train has null position"); 535 this.setPosition(new PhysicalLocation()); 536 } 537 } 538 } 539 } 540 541 // Methods for handling location tracking based on JMRI Operations 542 /** 543 * Get the physical location of the given Operations Train 544 * 545 * @param t (Train) the Train to interrogate 546 * @return PhysicalLocation location of the train 547 */ 548 protected PhysicalLocation getTrainPosition(Train t) { 549 if (t == null) { 550 log.debug("Train is null."); 551 return null; 552 } 553 RouteLocation rloc = t.getCurrentRouteLocation(); 554 if (rloc == null) { 555 log.debug("RouteLocation is null."); 556 return null; 557 } 558 Location loc = rloc.getLocation(); 559 if (loc == null) { 560 log.debug("Location is null."); 561 return null; 562 } 563 return loc.getPhysicalLocation(); 564 } 565 566 // Methods for handling the underlying sounds 567 /** 568 * Retrieve the VSDSound with the given system name 569 * 570 * @param name (String) System name of the requested VSDSound 571 * @return VSDSound the requested sound 572 */ 573 public VSDSound getSound(String name) { 574 return sound_list.get(name); 575 } 576 577 // Java Bean set/get Functions 578 /** 579 * Set the profile name to the given string 580 * 581 * @param pn (String) : name of the profile to set 582 */ 583 public void setProfileName(String pn) { 584 config.setProfileName(pn); 585 } 586 587 /** 588 * get the currently selected profile name 589 * 590 * @return (String) name of the currently selected profile 591 */ 592 public String getProfileName() { 593 return config.getProfileName(); 594 } 595 596 /** 597 * Enable this VSDecoder. 598 */ 599 void enable() { 600 enabled = true; 601 } 602 603 /** 604 * Disable this VSDecoder. 605 */ 606 void disable() { 607 enabled = false; 608 } 609 610 boolean isEnabled() { 611 return enabled; 612 } 613 614 /** 615 * Get a reference to the EngineSound associated with this VSDecoder 616 * 617 * @return EngineSound The EngineSound reference for this VSDecoder or null 618 */ 619 public EngineSound getEngineSound() { 620 return (EngineSound) sound_list.get("ENGINE"); 621 } 622 623 /** 624 * Get a Collection of SoundEvents associated with this VSDecoder 625 * 626 * @return {@literal Collection<SoundEvent>} collection of SoundEvents 627 */ 628 public Collection<SoundEvent> getEventList() { 629 return event_list.values(); 630 } 631 632 /** 633 * Get an XML representation of this VSDecoder Includes a subtree of 634 * Elements for all of the associated SoundEvents, Triggers, VSDSounds, etc. 635 * 636 * @return Element XML Element for this VSDecoder 637 */ 638 public Element getXml() { 639 Element me = new Element("vsdecoder"); 640 ArrayList<Element> le = new ArrayList<>(); 641 642 me.setAttribute("name", this.config.getProfileName()); 643 644 for (SoundEvent se : event_list.values()) { 645 le.add(se.getXml()); 646 } 647 648 for (VSDSound vs : sound_list.values()) { 649 le.add(vs.getXml()); 650 } 651 652 me.addContent(le); 653 654 // Need to add whatever else here. 655 return me; 656 } 657 658 /** 659 * Build this VSDecoder from an XML representation 660 * 661 * @param vf (VSDFile) : VSD File to pull the XML from 662 * @param pn (String) : Parameter Name to find within the VSD File. 663 */ 664 public void setXml(VSDFile vf, String pn) { 665 Iterator<Element> itr; 666 Element e = null; 667 Element el = null; 668 SoundEvent se; 669 String n; 670 671 if (vf == null) { 672 log.debug("Null VSD File Name"); 673 return; 674 } 675 676 log.debug("VSD File Name: {}, profile: {}", vf.getName(), pn); 677 // need to choose one. 678 this.setVSDFilePath(vf.getName()); 679 680 // Find the <profile/> element that matches the name pn 681 // List<Element> profiles = vf.getRoot().getChildren("profile"); 682 // java.util.Iterator i = profiles.iterator(); 683 java.util.Iterator<Element> i = vf.getRoot().getChildren("profile").iterator(); 684 while (i.hasNext()) { 685 e = i.next(); 686 if (e.getAttributeValue("name").equals(pn)) { 687 break; 688 } 689 } 690 // E is now the first <profile/> in vsdfile that matches pn. 691 692 if (e == null) { 693 // No matching profile name found. 694 return; 695 } 696 697 // Set this decoder's name. 698 this.setProfileName(e.getAttributeValue("name")); 699 log.debug("Decoder Name: {}", e.getAttributeValue("name")); 700 701 // Check for a flag element to create xy-position-coordinates. 702 n = e.getChildText("create-xy-series"); 703 if ((n != null) && (n.equals("yes"))) { 704 create_xy_series = true; 705 log.debug("Profile {}: xy-position-coordinates will be created in JMRI System Console", getProfileName()); 706 } else { 707 create_xy_series = false; 708 log.debug("Profile {}: xy-position-coordinates will NOT be created in JMRI System Console", getProfileName()); 709 } 710 711 // Check for an optional sound start-position. 712 n = e.getChildText("start-position"); 713 if (n != null) { 714 startPos = PhysicalLocation.parse(n); 715 } else { 716 startPos = null; 717 } 718 log.debug("Start position: {}", startPos); 719 720 // +++ DEBUG 721 // Log and print all of the child elements. 722 itr = (e.getChildren()).iterator(); 723 while (itr.hasNext()) { 724 // Pull each element from the XML file. 725 el = itr.next(); 726 log.debug("Element: {}", el); 727 if (el.getAttribute("name") != null) { 728 log.debug(" Name: {}", el.getAttributeValue("name")); 729 log.debug(" type: {}", el.getAttributeValue("type")); 730 } 731 } 732 // --- DEBUG 733 734 // First, the sounds. 735 String prefix = "" + this.getId() + ":"; 736 log.debug("VSDecoder {}, prefix: {}", this.getId(), prefix); 737 itr = (e.getChildren("sound")).iterator(); 738 while (itr.hasNext()) { 739 el = itr.next(); 740 if (el.getAttributeValue("type") == null) { 741 // Empty sound. Skip. 742 log.debug("Skipping empty Sound."); 743 continue; 744 } else if (el.getAttributeValue("type").equals("configurable")) { 745 // Handle configurable sounds. 746 ConfigurableSound cs = new ConfigurableSound(prefix + el.getAttributeValue("name")); 747 cs.setXml(el, vf); 748 sound_list.put(el.getAttributeValue("name"), cs); 749 } else if (el.getAttributeValue("type").equals("diesel")) { 750 // Handle a diesel Engine sound 751 DieselSound es = new DieselSound(prefix + el.getAttributeValue("name")); 752 es.setXml(el, vf); 753 sound_list.put(el.getAttributeValue("name"), es); 754 } else if (el.getAttributeValue("type").equals("diesel3")) { 755 // Handle a diesel3 Engine sound 756 Diesel3Sound es = new Diesel3Sound(prefix + el.getAttributeValue("name")); 757 es.setXml(el, vf); 758 sound_list.put(el.getAttributeValue("name"), es); 759 topspeed = es.top_speed; 760 topspeed_rev = topspeed; 761 } else if (el.getAttributeValue("type").equals("steam")) { 762 // Handle a steam Engine sound 763 SteamSound es = new SteamSound(prefix + el.getAttributeValue("name")); 764 es.setXml(el, vf); 765 sound_list.put(el.getAttributeValue("name"), es); 766 topspeed = es.top_speed; 767 topspeed_rev = topspeed; 768 } else if (el.getAttributeValue("type").equals("steam1")) { 769 // Handle a steam1 Engine sound 770 Steam1Sound es = new Steam1Sound(prefix + el.getAttributeValue("name")); 771 es.setXml(el, vf); 772 sound_list.put(el.getAttributeValue("name"), es); 773 topspeed = es.top_speed; 774 topspeed_rev = es.top_speed_reverse; 775 //} else { 776 // TODO: Some type other than configurable sound. Handle appropriately 777 } 778 } 779 780 // Next, grab all of the SoundEvents 781 // Have to do the sounds first because the SoundEvent's setXml() will 782 // expect to be able to look it up. 783 itr = (e.getChildren("sound-event")).iterator(); 784 while (itr.hasNext()) { 785 el = itr.next(); 786 switch (SoundEvent.ButtonType.valueOf(el.getAttributeValue("buttontype").toUpperCase())) { 787 case MOMENTARY: 788 se = new MomentarySoundEvent(el.getAttributeValue("name")); 789 break; 790 case TOGGLE: 791 se = new ToggleSoundEvent(el.getAttributeValue("name")); 792 break; 793 case ENGINE: 794 se = new EngineSoundEvent(el.getAttributeValue("name")); 795 break; 796 case NONE: 797 default: 798 se = new SoundEvent(el.getAttributeValue("name")); 799 } 800 se.setParent(this); 801 se.setXml(el, vf); 802 event_list.put(se.getName(), se); 803 } 804 // Handle other types of children similarly here. 805 } 806 807 // VSDNavigation accessors 808 // 809 // Code from George Warner's LENavigator 810 // 811 void setLocation(Point2D location) { 812 this.location = location; 813 } 814 815 Point2D getLocation() { 816 return location; 817 } 818 819 LayoutTrack getLastTrack() { 820 return lastTrack; 821 } 822 823 void setLastTrack(LayoutTrack lastTrack) { 824 this.lastTrack = lastTrack; 825 } 826 827 void setLayoutTrack(LayoutTrack layoutTrack) { 828 this.layoutTrack = layoutTrack; 829 } 830 831 LayoutTrack getLayoutTrack() { 832 return layoutTrack; 833 } 834 835 void setReturnTrack(LayoutTrack returnTrack) { 836 this.returnTrack = returnTrack; 837 } 838 839 LayoutTrack getReturnTrack() { 840 return returnTrack; 841 } 842 843 void setReturnLastTrack(LayoutTrack returnLastTrack) { 844 this.returnLastTrack = returnLastTrack; 845 } 846 847 LayoutTrack getReturnLastTrack() { 848 return returnLastTrack; 849 } 850 851 double getDistance() { 852 return distance; 853 } 854 855 void setDistance(double distance) { 856 this.distance = distance; 857 } 858 859 double getReturnDistance() { 860 return returnDistance; 861 } 862 863 void setReturnDistance(double returnDistance) { 864 this.returnDistance = returnDistance; 865 } 866 867 double getDirectionRAD() { 868 return directionRAD; 869 } 870 871 void setDirectionRAD(double directionRAD) { 872 this.directionRAD = directionRAD; 873 } 874 875 void setDirectionDEG(double directionDEG) { 876 this.directionRAD = Math.toRadians(directionDEG); 877 } 878 879 LayoutEditor getModels() { 880 return models; 881 } 882 883 void setModels(LayoutEditor models) { 884 this.models = models; 885 } 886 887 void navigate() { 888 boolean result = false; 889 do { 890 if (this.getLayoutTrack() instanceof TrackSegment) { 891 result = navigation.navigateTrackSegment(); 892 } else if (this.getLayoutTrack() instanceof LayoutSlip) { 893 result = navigation.navigateLayoutSlip(); 894 } else if (this.getLayoutTrack() instanceof LayoutTurnout) { 895 result = navigation.navigateLayoutTurnout(); 896 } else if (this.getLayoutTrack() instanceof PositionablePoint) { 897 result = navigation.navigatePositionalPoint(); 898 } else if (this.getLayoutTrack() instanceof LevelXing) { 899 result = navigation.navigateLevelXing(); 900 } else if (this.getLayoutTrack() instanceof LayoutTurntable) { 901 result = navigation.navigateLayoutTurntable(); 902 } else { 903 log.warn("Track type not supported"); 904 setReturnDistance(0); 905 setReturnTrack(getLastTrack()); 906 result = false; 907 } 908 } while (result); 909 } 910 911 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(VSDecoder.class); 912 913}