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