001package jmri.jmrix.lenz;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import jmri.*;
005import jmri.jmrix.roco.RocoXNetThrottleManager;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009import java.awt.event.ActionEvent;
010import java.lang.reflect.Constructor;
011import java.lang.reflect.InvocationTargetException;
012
013/**
014 * This class performs Command Station dependent initialization for XpressNet.
015 * It adds the appropriate Managers via the Initialization Manager based on the
016 * Command Station Type.
017 *
018 * @author Paul Bender Copyright (C) 2003-2010,2020
019 * @author Giorgio Terdina Copyright (C) 2007
020 */
021public class XNetInitializationManager {
022
023    /**
024     * Construct a memo using the defaults and a version check.  This Constructor
025     * is included for backwards compatability and should not be used for new code.
026     * @param memo The connectionmemo to initialize.
027     * @deprecated since 4.21.1. Use {@link #XNetInitializationManager()} and the builder
028     * interface instead.
029     */
030    @Deprecated
031    public XNetInitializationManager(XNetSystemConnectionMemo memo){
032        memo(memo);
033        setDefaults();
034        setTimeout(getInitTimeout());
035        versionCheck();
036        init();
037    }
038
039    public XNetInitializationManager() {
040    }
041
042    /**
043     * Define the default timeout used during initialization
044     * @return timeout value in milliseconds
045     * @deprecated since 4.21.1.  Use {@link #setTimeout(int)} instead.
046     */
047    @Deprecated
048    protected int getInitTimeout() {
049        return initTimeout;
050    }
051
052    private XNetSystemConnectionMemo systemMemo;
053    private Class<? extends XNetPowerManager> powerManagerClass;
054    private Class<? extends XNetThrottleManager> throttleManagerClass;
055    private Class<? extends RocoXNetThrottleManager> rocoThrottleManagerClass;
056    private Class<? extends XNetProgrammerManager> programmerManagerClass;
057    private Class<? extends XNetProgrammer> programmerClass;
058    private Class<? extends XNetConsistManager> consistManagerClass;
059    private Class<? extends XNetTurnoutManager> turnoutManagerClass;
060    private Class<? extends XNetLightManager> lightManagerClass;
061    private Class<? extends XNetSensorManager> sensorManagerClass;
062    private boolean versionCheck = false;
063    private boolean noCommandStation = false;
064    private int initTimeout = 30000;
065
066    /**
067     * Set the version check flag to true.
068     * @return this initializer
069     */
070    public XNetInitializationManager versionCheck(){
071        versionCheck = true;
072        return this;
073    }
074
075    /**
076     * Set the initialization timeout
077     * @param timeout value in ms.
078     * @return this initializer.
079     */
080    public XNetInitializationManager setTimeout(int timeout){
081        initTimeout = timeout;
082        return this;
083    }
084
085    /**
086     * Set the memo to initialize
087     * @param systemMemo the memo
088     * @return this initializer
089     */
090    public XNetInitializationManager memo(XNetSystemConnectionMemo systemMemo){
091        this.systemMemo = systemMemo;
092        return this;
093    }
094
095    /**
096     * Set the defaults to the default classes in jmri.jmrix.lenz.
097     * <p>
098     * This methods sets the default values for Lenz command stations
099     * and the Roco MultiMaus and LokMaus.  Use with {@link #versionCheck}
100     * and {@link #setTimeout} to automatically configure these systems.
101     * </p>
102     * @return this initializer
103     */
104    public XNetInitializationManager setDefaults(){
105        powerManagerClass = XNetPowerManager.class;
106        throttleManagerClass = XNetThrottleManager.class;
107        rocoThrottleManagerClass = RocoXNetThrottleManager.class;
108        programmerManagerClass = XNetProgrammerManager.class;
109        programmerClass = XNetProgrammer.class;
110        consistManagerClass = XNetConsistManager.class;
111        turnoutManagerClass = XNetTurnoutManager.class;
112        lightManagerClass = XNetLightManager.class;
113        sensorManagerClass = XNetSensorManager.class;
114        return this;
115    }
116
117    /**
118     * Set the power Manager class
119     * @param powerManagerClass the power manager class to use
120     * @return this initializer
121     */
122    public XNetInitializationManager powerManager(Class<? extends XNetPowerManager> powerManagerClass){
123        this.powerManagerClass = powerManagerClass;
124        return this;
125    }
126
127    private void initPowerManager(){
128        if(powerManagerClass != null){
129            try {
130                Constructor<? extends XNetPowerManager> ctor = powerManagerClass.getConstructor(XNetSystemConnectionMemo.class);
131                XNetPowerManager pm = ctor.newInstance(systemMemo);
132                systemMemo.setPowerManager(pm);
133                InstanceManager.store(pm,PowerManager.class);
134            } catch (NoSuchMethodException | InstantiationException |
135                    IllegalAccessException | InvocationTargetException e){
136                log.warn("Unable to construct power manager for XPressNet connection {}", systemMemo.getSystemPrefix(),e);
137            }
138        }
139    }
140
141    /**
142     * Set the Programmer class to use with the XNetProgrammerManager.
143     * @param programmerClass the programmer class to use
144     * @return this initializer.
145     */
146    public XNetInitializationManager programmer(Class<? extends XNetProgrammer> programmerClass){
147        this.programmerClass = programmerClass;
148        return this;
149    }
150
151    private XNetProgrammer initProgrammer(){
152        XNetProgrammer prog = null;
153        if(programmerClass != null){
154            try {
155                Constructor<? extends XNetProgrammer> ctor = programmerClass.getConstructor(XNetTrafficController.class);
156                prog = ctor.newInstance(systemMemo.getXNetTrafficController());
157            } catch (NoSuchMethodException | InstantiationException |
158                    IllegalAccessException | InvocationTargetException e){
159                log.warn("Unable to construct programmer for XPressNet connection {}", systemMemo.getSystemPrefix(),e);
160            }
161        }
162        return prog;
163    }
164
165    public XNetInitializationManager noCommandStation(){
166        this.noCommandStation = true;
167        return this;
168    }
169
170    private void initCommandStation(){
171        if(!noCommandStation) {
172            /* The "raw" Command Station only works on systems that support Ops Mode Programming */
173            systemMemo.setCommandStation(systemMemo.getXNetTrafficController().getCommandStation());
174            if(systemMemo.getCommandStation()!= null) {
175                InstanceManager.store(systemMemo.getCommandStation(), jmri.CommandStation.class);
176            }
177        }
178    }
179
180    /**
181     * Set the programmer manager to initialize
182     * @param programmerManagerClass the programmer class to use.
183     * @return this initializer.
184     */
185    public XNetInitializationManager programmerManager(Class<? extends XNetProgrammerManager> programmerManagerClass){
186        this.programmerManagerClass = programmerManagerClass;
187        return this;
188    }
189
190    private void initProgrammerManager() {
191        XNetProgrammer programmer = initProgrammer();
192        if (programmerManagerClass != null && programmer != null) {
193            try {
194                Constructor<? extends XNetProgrammerManager> ctor = programmerManagerClass.getConstructor(Programmer.class, XNetSystemConnectionMemo.class);
195                XNetProgrammerManager pm = ctor.newInstance(programmer, systemMemo);
196                systemMemo.setProgrammerManager(pm);
197                if (pm.isAddressedModePossible()) {
198                    InstanceManager.store(pm, jmri.AddressedProgrammerManager.class);
199                    initCommandStation();
200                }
201                if (pm.isGlobalProgrammerAvailable()) {
202                    InstanceManager.store(pm, GlobalProgrammerManager.class);
203                }
204            } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
205                log.warn("Unable to construct programmer manager for XPressNet connection {}", systemMemo.getSystemPrefix(),e);
206            }
207        }
208    }
209
210    /**
211     * Set the Throttle Manager Class
212     * @param throttleManagerClass the Throttle Manager Class to use.
213     * @return this initializer
214     */
215    public XNetInitializationManager throttleManager(Class<? extends XNetThrottleManager> throttleManagerClass){
216        this.throttleManagerClass = throttleManagerClass;
217        return this;
218    }
219
220    private void initThrottleManager(){
221        if(throttleManagerClass != null){
222            try {
223                Constructor<? extends XNetThrottleManager> ctor = throttleManagerClass.getConstructor(XNetSystemConnectionMemo.class);
224                XNetThrottleManager tm = ctor.newInstance(systemMemo);
225                systemMemo.setThrottleManager(tm);
226                InstanceManager.store(tm, ThrottleManager.class);
227            } catch (NoSuchMethodException | InstantiationException |
228                    IllegalAccessException | InvocationTargetException e){
229                log.warn("Unable to construct throttle manager for XPressNet connection {}", systemMemo.getSystemPrefix());
230            }
231        }
232    }
233
234    /**
235     * Set the Roco Throttle Manager Class
236     * @param rocoThrottleManagerClass the Roco Throttle Manager Class to use.
237     * @return this initializer
238     */
239    public XNetInitializationManager rocoThrottleManager(Class<? extends RocoXNetThrottleManager> rocoThrottleManagerClass){
240        this.rocoThrottleManagerClass = rocoThrottleManagerClass;
241        return this;
242    }
243
244    private void initRocoThrottleManager(){
245        if(rocoThrottleManagerClass != null){
246            try {
247                Constructor<? extends RocoXNetThrottleManager> ctor = rocoThrottleManagerClass.getConstructor(XNetSystemConnectionMemo.class);
248                RocoXNetThrottleManager tm = ctor.newInstance(systemMemo);
249                systemMemo.setThrottleManager(tm);
250                InstanceManager.store(tm, ThrottleManager.class);
251            } catch (NoSuchMethodException | InstantiationException |
252                    IllegalAccessException | InvocationTargetException e){
253                log.warn("Unable to construct throttle manager for XPressNet connection {}", systemMemo.getSystemPrefix());
254            }
255        }
256    }
257
258    /**
259     * Set the Turnout Manager Class
260     * @param turnoutManagerClass the Turnout Manager Class to use.
261     * @return this initializer
262     */
263    public XNetInitializationManager turnoutManager(Class<? extends XNetTurnoutManager> turnoutManagerClass){
264        this.turnoutManagerClass = turnoutManagerClass;
265        return this;
266    }
267
268    private void initTurnoutManager(){
269        if(turnoutManagerClass != null){
270            try {
271                Constructor<? extends XNetTurnoutManager> ctor = turnoutManagerClass.getConstructor(XNetSystemConnectionMemo.class);
272                XNetTurnoutManager tm = ctor.newInstance(systemMemo);
273                systemMemo.setTurnoutManager(tm);
274                InstanceManager.setTurnoutManager(tm);
275            } catch (NoSuchMethodException | InstantiationException |
276                    IllegalAccessException | InvocationTargetException e){
277                log.warn("Unable to construct turnout manager for XPressNet connection {}", systemMemo.getSystemPrefix());
278            }
279        }
280    }
281
282    /**
283     * Set the Sensor Manager Class
284     * @param sensorManagerClass the Sensor Manager Class to use.
285     * @return this initializer
286     */
287    public XNetInitializationManager sensorManager(Class<? extends XNetSensorManager> sensorManagerClass){
288        this.sensorManagerClass = sensorManagerClass;
289        return this;
290    }
291
292    private void initSensorManager(){
293        if(sensorManagerClass != null){
294            try {
295                Constructor<? extends XNetSensorManager> ctor = sensorManagerClass.getConstructor(XNetSystemConnectionMemo.class);
296                XNetSensorManager sm = ctor.newInstance(systemMemo);
297                systemMemo.setSensorManager(sm);
298                InstanceManager.setSensorManager(sm);
299            } catch (NoSuchMethodException | InstantiationException |
300                    IllegalAccessException | InvocationTargetException e){
301                log.warn("Unable to construct sensor manager for XPressNet connection {}", systemMemo.getSystemPrefix());
302            }
303        }
304    }
305
306    /**
307     * Set the Light Manager Class
308     * @param lightManagerClass the Light Manager Class to use.
309     * @return this initializer
310     */
311    public XNetInitializationManager lightManager(Class<? extends XNetLightManager> lightManagerClass){
312        this.lightManagerClass = lightManagerClass;
313        return this;
314    }
315
316    private void initLightManager(){
317        if(lightManagerClass != null){
318            try {
319                Constructor<? extends XNetLightManager> ctor = lightManagerClass.getConstructor(XNetSystemConnectionMemo.class);
320                XNetLightManager lm = ctor.newInstance(systemMemo);
321                systemMemo.setLightManager(lm);
322                InstanceManager.setLightManager(lm);
323            } catch (NoSuchMethodException | InstantiationException |
324                    IllegalAccessException | InvocationTargetException e){
325                log.warn("Unable to construct light manager for XPressNet connection {}", systemMemo.getSystemPrefix());
326            }
327        }
328    }
329
330    /**
331     * Set the Consist Manager Class
332     * @param consistManagerClass the Consist Manager Class to use.
333     * @return this initializer
334     */
335    public XNetInitializationManager consistManager(Class<? extends XNetConsistManager> consistManagerClass){
336        this.consistManagerClass = consistManagerClass;
337        return this;
338    }
339
340    private void initConsistManager(){
341        if(consistManagerClass != null){
342            try {
343                Constructor<? extends XNetConsistManager> ctor = consistManagerClass.getConstructor(XNetSystemConnectionMemo.class);
344                XNetConsistManager tm = ctor.newInstance(systemMemo);
345                systemMemo.setConsistManager(tm);
346                InstanceManager.store(tm, ConsistManager.class);
347            } catch (NoSuchMethodException | InstantiationException |
348                    IllegalAccessException | InvocationTargetException e){
349                log.warn("Unable to construct consist manager for XPressNet connection {}", systemMemo.getSystemPrefix());
350            }
351        }
352    }
353
354    public void init() {
355        if (log.isDebugEnabled()) {
356            log.debug("Init called");
357        }
358        /* Load managers that should work on all systems */
359        initPowerManager();
360        if (versionCheck) {
361            checkVersionAndInit();
362        } else {
363            initServices();
364        }
365    }
366
367    private void checkVersionAndInit() {
368        /* spawn a thread to request version information and wait for the
369         command station to respond */
370        log.debug("Starting XpressNet Initialization Process");
371        new XNetInitializer(this);
372
373        // Since we can't currently reconfigure the user interface after
374        // initialization, We need to wait for the initialization thread
375        // to finish before we can continue.  The wait  can be removed IF
376        // we revisit the GUI initialization process.
377        synchronized (this) {
378            log.debug("start wait");
379            new jmri.util.WaitHandler(this);
380            log.debug("end wait");
381        }
382        float CSSoftwareVersion = systemMemo.getXNetTrafficController()
383                .getCommandStation()
384                .getCommandStationSoftwareVersion();
385        int CSType = systemMemo.getXNetTrafficController()
386                .getCommandStation()
387                .getCommandStationType();
388
389        if (CSSoftwareVersion < 0) {
390            log.warn("Command Station disconnected, or powered down assuming LZ100/LZV100 V3.x");
391            initServices();
392        } else if (CSSoftwareVersion < 3.0) {
393            log.error("Command Station does not support XpressNet Version 3 Command Set");
394            initThrottleManager();
395        } else {
396            /* Next we check the command station type, and add the
397             appropriate managers */
398            if (CSType == 0x02) {
399                log.debug("Command Station is Compact/Commander/Other");
400                initThrottleManager();
401                initTurnoutManager();
402                initLightManager();
403                initConsistManager();
404            } else if (CSType == 0x01) {
405                log.debug("Command Station is LH200");
406                initThrottleManager();
407            } else if (CSType == 0x00) {
408                log.debug("Command Station is LZ100/LZV100");
409                initServices();
410            } else if (CSType == 0x04) {
411                log.debug("Command Station is LokMaus II");
412                initRocoThrottleManager();
413                initTurnoutManager();
414                initLightManager();
415                initSensorManager();
416                initProgrammerManager();
417                // LokMaus does not support XpressNET consist commands. Let's the default consist manager be loaded.
418            } else if (CSType == 0x10 ) {
419                log.debug("Command Station is multiMaus");
420                initRocoThrottleManager();
421                initTurnoutManager();
422                initLightManager();
423                initSensorManager();
424                initProgrammerManager();
425                // multMaus does not support XpressNET consist commands. Let's the default consist manager be loaded.
426            } else {
427                /* If we still don't  know what we have, load everything */
428                log.debug("Command Station is Unknown type");
429                initServices();
430            }
431        }
432        log.debug("XpressNet Initialization Complete");
433    }
434
435    private void initServices(){
436        initThrottleManager();
437        initProgrammerManager();
438        initConsistManager();
439        initTurnoutManager();
440        initLightManager();
441        initSensorManager();
442    }
443
444    /* Internal class to retrieve version Information */
445    protected class XNetInitializer implements XNetListener {
446
447        private final javax.swing.Timer initTimer; // Timer used to let he
448        // command station response time
449        // out, and configure the defaults.
450
451        private final Object parent;
452
453        public XNetInitializer(Object Parent) {
454
455            parent = Parent;
456
457            initTimer = setupInitTimer();
458
459            // Register as an XpressNet Listener
460            systemMemo.getXNetTrafficController().addXNetListener(XNetInterface.CS_INFO, this);
461
462            //Send Information request to LI100/LI100
463         /* First, we need to send a request for the Command Station
464             hardware and software version */
465            XNetMessage msg = XNetMessage.getCSVersionRequestMessage();
466            //Then Send the version request to the controller
467            systemMemo.getXNetTrafficController().sendXNetMessage(msg, this);
468        }
469
470        protected javax.swing.Timer setupInitTimer() {
471            // Initialize and start initialization timeout timer.
472            javax.swing.Timer retVal = new javax.swing.Timer(initTimeout,
473                    (ActionEvent e) -> {
474                                    /* If the timer times out, notify any
475                                     waiting objects, and dispose of
476                                     this thread */
477                        if (log.isDebugEnabled()) {
478                            log.debug("Timeout waiting for Command Station Response");
479                        }
480                        finish();
481                    });
482            retVal.setInitialDelay(initTimeout);
483            retVal.start();
484            return retVal;
485        }
486
487        @SuppressFBWarnings(value = "NO_NOTIFY_NOT_NOTIFYALL", justification = "There should only ever be one thread waiting for this method (the designated parent, which started the thread).")
488        private void finish() {
489            initTimer.stop();
490            // Notify the parent
491            try {
492                synchronized (parent) {
493                    parent.notify();
494                }
495            } catch (Exception e) {
496                log.error("Exception {] while notifying initialization thread.",e);
497            }
498            if (log.isDebugEnabled()) {
499                log.debug("Notification Sent");
500            }
501            // Then dispose of this object
502            dispose();
503        }
504
505        // listen for the responses from the LI100/LI101
506        @Override
507        public void message(XNetReply l) {
508            // Check to see if this is a response with the Command Station
509            // Version Info
510            if (l.getElement(0) == XNetConstants.CS_SERVICE_MODE_RESPONSE &&
511                    l.getElement(1) == XNetConstants.CS_SOFTWARE_VERSION) {
512                // This is the Command Station Software Version Response
513                systemMemo.getXNetTrafficController()
514                        .getCommandStation()
515                        .setCommandStationSoftwareVersion(l);
516                systemMemo.getXNetTrafficController()
517                        .getCommandStation()
518                        .setCommandStationType(l);
519                finish();
520            }
521        }
522
523        // listen for the messages to the LI100/LI101
524        @Override
525        public void message(XNetMessage l) {
526            // we aren't concerned with incoming messages in this class.
527        }
528
529        // Handle a timeout notification
530        @Override
531        public void notifyTimeout(XNetMessage msg) {
532            if (log.isDebugEnabled()) {
533                log.debug("Notified of timeout on message {}",msg);
534            }
535        }
536
537        public void dispose() {
538            systemMemo.getXNetTrafficController().removeXNetListener(XNetInterface.CS_INFO, this);
539        }
540    }
541
542    private static final Logger log = LoggerFactory.getLogger(XNetInitializationManager.class);
543}