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