001package jmri.jmrit.logixng.actions;
002
003import java.util.Locale;
004import java.util.Map;
005
006import jmri.*;
007import jmri.jmrit.logixng.*;
008import jmri.util.ThreadingUtil;
009
010/**
011 * Runs an engine.
012 * This action reads an analog expression with the loco address and sets its
013 * speed according to an alaog expression and the direction according to a
014 * digital expression.
015 *
016 * @author Daniel Bergqvist Copyright 2019
017 */
018public final class ActionThrottle extends AbstractDigitalAction
019        implements FemaleSocketListener {
020
021    public static final int LOCO_ADDRESS_SOCKET = 0;
022    public static final int LOCO_SPEED_SOCKET = LOCO_ADDRESS_SOCKET + 1;
023    public static final int LOCO_DIRECTION_SOCKET = LOCO_SPEED_SOCKET + 1;
024    public static final int LOCO_FUNCTION_SOCKET = LOCO_DIRECTION_SOCKET + 1;
025    public static final int LOCO_FUNCTION_ONOFF_SOCKET = LOCO_FUNCTION_SOCKET + 1;
026    public static final int NUM_LOCO_SOCKETS = LOCO_FUNCTION_ONOFF_SOCKET + 1;
027
028    private SystemConnectionMemo _memo;
029    private ThrottleManager _throttleManager;
030    private ThrottleManager _oldThrottleManager;
031
032    // The throttle if we have one or if a request is sent, null otherwise
033    private DccThrottle _throttle;
034    private ThrottleListener _throttleListener;
035
036    private String _locoAddressSocketSystemName;
037    private String _locoSpeedSocketSystemName;
038    private String _locoDirectionSocketSystemName;
039    private String _locoFunctionSocketSystemName;
040    private String _locoFunctionOnOffSocketSystemName;
041    private final FemaleAnalogExpressionSocket _locoAddressSocket;
042    private final FemaleAnalogExpressionSocket _locoSpeedSocket;
043    private final FemaleDigitalExpressionSocket _locoDirectionSocket;
044    private final FemaleAnalogExpressionSocket _locoFunctionSocket;
045    private final FemaleDigitalExpressionSocket _locoFunctionOnOffSocket;
046    private boolean _stopLocoWhenSwitchingLoco = true;
047    private boolean _waitForThrottle = false;
048
049
050    public ActionThrottle(String sys, String user) {
051        super(sys, user);
052        _locoAddressSocket = InstanceManager.getDefault(AnalogExpressionManager.class)
053                .createFemaleSocket(this, this, Bundle.getMessage("ActionThrottle_SocketName_Address"));
054        _locoSpeedSocket = InstanceManager.getDefault(AnalogExpressionManager.class)
055                .createFemaleSocket(this, this, Bundle.getMessage("ActionThrottle_SocketName_Speed"));
056        _locoDirectionSocket = InstanceManager.getDefault(DigitalExpressionManager.class)
057                .createFemaleSocket(this, this, Bundle.getMessage("ActionThrottle_SocketName_Direction"));
058        _locoFunctionSocket = InstanceManager.getDefault(AnalogExpressionManager.class)
059                .createFemaleSocket(this, this, Bundle.getMessage("ActionThrottle_SocketName_Function"));
060        _locoFunctionOnOffSocket = InstanceManager.getDefault(DigitalExpressionManager.class)
061                .createFemaleSocket(this, this, Bundle.getMessage("ActionThrottle_SocketName_FunctionOnOff"));
062
063        // Set the _throttleManager variable
064        setMemo(null);
065    }
066
067    @Override
068    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
069        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
070        String sysName = systemNames.get(getSystemName());
071        String userName = userNames.get(getSystemName());
072        if (sysName == null) sysName = manager.getAutoSystemName();
073        ActionThrottle copy = new ActionThrottle(sysName, userName);
074        copy.setComment(getComment());
075        copy.setStopLocoWhenSwitchingLoco(_stopLocoWhenSwitchingLoco);
076        copy.setWaitForThrottle(_waitForThrottle);
077        copy.setMemo(_memo);
078        return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames);
079    }
080
081    public void setMemo(SystemConnectionMemo memo) {
082        assertListenersAreNotRegistered(log, "setMemo");
083        _memo = memo;
084        if (_memo != null) {
085            _throttleManager = _memo.get(jmri.ThrottleManager.class);
086            if (_throttleManager == null) {
087                throw new IllegalArgumentException("Memo "+memo.getUserName()+" doesn't have a ThrottleManager");
088            }
089        } else {
090            _throttleManager = InstanceManager.getDefault(ThrottleManager.class);
091        }
092    }
093
094    public SystemConnectionMemo getMemo() {
095        return _memo;
096    }
097
098    /** {@inheritDoc} */
099    @Override
100    public LogixNG_Category getCategory() {
101        return LogixNG_Category.ITEM;
102    }
103
104    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("WA_NOT_IN_LOOP")
105    /** {@inheritDoc} */
106    @Override
107    public void execute() throws JmriException {
108
109        final Object lock = new Object();
110        Reference<Boolean> throttleAquired = new Reference<>(false);
111
112        int currentLocoAddress = -1;
113        int newLocoAddress = -1;
114
115        if (_throttle != null) {
116            currentLocoAddress = _throttle.getLocoAddress().getNumber();
117        }
118
119        if (_locoAddressSocket.isConnected()) {
120            newLocoAddress =
121                    (int) ((MaleAnalogExpressionSocket)_locoAddressSocket.getConnectedSocket())
122                            .evaluate();
123        }
124
125        if (_throttleManager != _oldThrottleManager) {
126            currentLocoAddress = -1;    // Force request of new throttle
127            _oldThrottleManager = _throttleManager;
128        }
129
130        if (newLocoAddress != currentLocoAddress) {
131
132            if (_throttle != null) {
133                if (_stopLocoWhenSwitchingLoco) {
134                    // Stop the loco
135                    _throttle.setSpeedSetting(0);
136                }
137                // Release the loco
138                ThreadingUtil.runOnGUI(() -> {
139                    _throttleManager.releaseThrottle(_throttle, _throttleListener); });
140                _throttle = null;
141            }
142
143            if (newLocoAddress != -1) {
144
145                _throttleListener =  new ThrottleListener() {
146                    @Override
147                    public void notifyThrottleFound(DccThrottle t) {
148                        _throttle = t;
149                        if (_waitForThrottle) {
150                            synchronized(lock) {
151                                throttleAquired.set(true);
152                                lock.notifyAll();
153                            }
154                        } else {
155                            executeConditionalNG();
156                        }
157                    }
158
159                    @Override
160                    public void notifyFailedThrottleRequest(LocoAddress address, String reason) {
161                        log.warn("loco {} cannot be aquired", address.getNumber());
162                    }
163
164                    @Override
165                    public void notifyDecisionRequired(LocoAddress address, ThrottleListener.DecisionType question) {
166                        log.warn("Loco {} cannot be aquired. Decision required.", address.getNumber());
167                    }
168                };
169
170                final int locoAddr = newLocoAddress;
171                boolean result = ThreadingUtil.runOnGUIwithReturn(
172                        () -> { return _throttleManager.requestThrottle(locoAddr, _throttleListener); });
173
174                if (!result) {
175                    log.warn("loco {} cannot be aquired", newLocoAddress);
176                }
177
178                if (_waitForThrottle) {
179                    synchronized(lock) {
180                        try {
181                            if (! throttleAquired.get()) {
182                                lock.wait();
183                            }
184                        } catch (InterruptedException e) {
185                            log.warn("Action Throttle was interrupted during wait for throttle to be aquired");
186                        }
187                    }
188                }
189            }
190
191        }
192
193        // We have a throttle if _throttle is not null
194        if (_throttle != null) {
195
196            double speed = 0;
197            boolean isForward = true;
198            int function = 0;
199            boolean isFunctionOn = true;
200
201            if (_locoSpeedSocket.isConnected()) {
202                speed =
203                        ((MaleAnalogExpressionSocket)_locoSpeedSocket.getConnectedSocket())
204                                .evaluate();
205            }
206
207            if (_locoDirectionSocket.isConnected()) {
208                isForward =
209                        ((MaleDigitalExpressionSocket)_locoDirectionSocket.getConnectedSocket())
210                                .evaluate();
211            }
212
213            if (_locoFunctionSocket.isConnected()) {
214                function = (int) Math.round(
215                        ((MaleAnalogExpressionSocket)_locoFunctionSocket.getConnectedSocket())
216                                .evaluate());
217            }
218
219            if (_locoFunctionOnOffSocket.isConnected()) {
220                isFunctionOn =
221                        ((MaleDigitalExpressionSocket)_locoFunctionOnOffSocket.getConnectedSocket())
222                                .evaluate();
223            }
224
225            DccThrottle throttle = _throttle;
226            float spd = (float) speed;
227            boolean fwd = isForward;
228            int func = function;
229            boolean funcState = isFunctionOn;
230            jmri.util.ThreadingUtil.runOnLayoutWithJmriException(() -> {
231                if (_locoSpeedSocket.isConnected()) throttle.setSpeedSetting(spd);
232                if (_locoDirectionSocket.isConnected()) throttle.setIsForward(fwd);
233                if (_locoFunctionSocket.isConnected() && _locoFunctionOnOffSocket.isConnected()) {
234                    throttle.setFunction(func, funcState);
235                }
236            });
237
238            if (_waitForThrottle) {
239                ThreadingUtil.runOnGUI(() -> {
240                    _throttleManager.releaseThrottle(_throttle, _throttleListener); });
241                _throttle = null;
242            }
243        }
244    }
245
246    @Override
247    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
248        switch (index) {
249            case LOCO_ADDRESS_SOCKET:
250                return _locoAddressSocket;
251
252            case LOCO_SPEED_SOCKET:
253                return _locoSpeedSocket;
254
255            case LOCO_DIRECTION_SOCKET:
256                return _locoDirectionSocket;
257
258            case LOCO_FUNCTION_SOCKET:
259                return _locoFunctionSocket;
260
261            case LOCO_FUNCTION_ONOFF_SOCKET:
262                return _locoFunctionOnOffSocket;
263
264            default:
265                throw new IllegalArgumentException(
266                        String.format("index has invalid value: %d", index));
267        }
268    }
269
270    @Override
271    public int getChildCount() {
272        return NUM_LOCO_SOCKETS;
273    }
274
275    @Override
276    public void connected(FemaleSocket socket) {
277        if (socket == _locoAddressSocket) {
278            _locoAddressSocketSystemName = socket.getConnectedSocket().getSystemName();
279            executeConditionalNG();
280        } else if (socket == _locoSpeedSocket) {
281            _locoSpeedSocketSystemName = socket.getConnectedSocket().getSystemName();
282            executeConditionalNG();
283        } else if (socket == _locoDirectionSocket) {
284            _locoDirectionSocketSystemName = socket.getConnectedSocket().getSystemName();
285            executeConditionalNG();
286        } else if (socket == _locoFunctionSocket) {
287            _locoFunctionSocketSystemName = socket.getConnectedSocket().getSystemName();
288            executeConditionalNG();
289        } else if (socket == _locoFunctionOnOffSocket) {
290            _locoFunctionOnOffSocketSystemName = socket.getConnectedSocket().getSystemName();
291            executeConditionalNG();
292        } else {
293            throw new IllegalArgumentException("unkown socket");
294        }
295    }
296
297    @Override
298    public void disconnected(FemaleSocket socket) {
299        if (socket == _locoAddressSocket) {
300            if (_throttle != null) {
301                // Stop the loco
302                _throttle.setSpeedSetting(0);
303                // Release the loco
304                ThreadingUtil.runOnGUI(() -> {
305                    _throttleManager.releaseThrottle(_throttle, _throttleListener); });
306            }
307            _locoAddressSocketSystemName = null;
308            executeConditionalNG();
309        } else if (socket == _locoSpeedSocket) {
310            _locoSpeedSocketSystemName = null;
311            executeConditionalNG();
312        } else if (socket == _locoDirectionSocket) {
313            _locoDirectionSocketSystemName = null;
314            executeConditionalNG();
315        } else if (socket == _locoFunctionSocket) {
316            _locoFunctionSocketSystemName = null;
317            executeConditionalNG();
318        } else if (socket == _locoFunctionOnOffSocket) {
319            _locoFunctionOnOffSocketSystemName = null;
320            executeConditionalNG();
321        } else {
322            throw new IllegalArgumentException("unkown socket");
323        }
324    }
325
326    private void executeConditionalNG() {
327        if (_listenersAreRegistered) {
328            ConditionalNG c = getConditionalNG();
329            if (c != null) {
330                c.execute();
331            }
332        }
333    }
334
335    @Override
336    public String getShortDescription(Locale locale) {
337        return Bundle.getMessage(locale, "ActionThrottle_Short");
338    }
339
340    @Override
341    public String getLongDescription(Locale locale) {
342        String stopLocoWhenSwitchingLocoStr = _stopLocoWhenSwitchingLoco
343                ? Bundle.getMessage("ActionThrottle_StopLocoWhenSwitchingLoco")
344                : Bundle.getMessage("ActionThrottle_DontStopLocoWhenSwitchingLoco");
345        String waitForThrottleStr = _waitForThrottle
346                ? Bundle.getMessage("ActionThrottle_WaitForThrottle")
347                : Bundle.getMessage("ActionThrottle_DontWaitForThrottle");
348
349        if (_memo != null) {
350            if (_waitForThrottle) {
351                return Bundle.getMessage(locale, "ActionThrottle_LongConnection1",
352                        _memo.getUserName(), waitForThrottleStr);
353            } else {
354                return Bundle.getMessage(locale, "ActionThrottle_LongConnection2",
355                        _memo.getUserName(), waitForThrottleStr, stopLocoWhenSwitchingLocoStr);
356            }
357        } else {
358            if (_waitForThrottle) {
359                return Bundle.getMessage(locale, "ActionThrottle_Long1",
360                        waitForThrottleStr);
361            } else {
362                return Bundle.getMessage(locale, "ActionThrottle_Long2",
363                        waitForThrottleStr, stopLocoWhenSwitchingLocoStr);
364            }
365        }
366    }
367
368    public FemaleAnalogExpressionSocket getLocoAddressSocket() {
369        return _locoAddressSocket;
370    }
371
372    public String getLocoAddressSocketSystemName() {
373        return _locoAddressSocketSystemName;
374    }
375
376    public void setLocoAddressSocketSystemName(String systemName) {
377        _locoAddressSocketSystemName = systemName;
378    }
379
380    public FemaleAnalogExpressionSocket getLocoSpeedSocket() {
381        return _locoSpeedSocket;
382    }
383
384    public String getLocoSpeedSocketSystemName() {
385        return _locoSpeedSocketSystemName;
386    }
387
388    public void setLocoSpeedSocketSystemName(String systemName) {
389        _locoSpeedSocketSystemName = systemName;
390    }
391
392    public FemaleDigitalExpressionSocket getLocoDirectionSocket() {
393        return _locoDirectionSocket;
394    }
395
396    public String getLocoDirectionSocketSystemName() {
397        return _locoDirectionSocketSystemName;
398    }
399
400    public void setLocoDirectionSocketSystemName(String systemName) {
401        _locoDirectionSocketSystemName = systemName;
402    }
403
404    public FemaleAnalogExpressionSocket getLocoFunctionSocket() {
405        return _locoFunctionSocket;
406    }
407
408    public String getLocoFunctionSocketSystemName() {
409        return _locoFunctionSocketSystemName;
410    }
411
412    public void setLocoFunctionSocketSystemName(String systemName) {
413        _locoFunctionSocketSystemName = systemName;
414    }
415
416    public FemaleDigitalExpressionSocket getLocoFunctionOnOffSocket() {
417        return _locoFunctionOnOffSocket;
418    }
419
420    public String getLocoFunctionOnOffSocketSystemName() {
421        return _locoFunctionOnOffSocketSystemName;
422    }
423
424    public void setLocoFunctionOnOffSocketSystemName(String systemName) {
425        _locoFunctionOnOffSocketSystemName = systemName;
426    }
427
428    /** {@inheritDoc} */
429    @Override
430    public void setup() {
431        try {
432            if ( !_locoAddressSocket.isConnected()
433                    || !_locoAddressSocket.getConnectedSocket().getSystemName()
434                            .equals(_locoAddressSocketSystemName)) {
435
436                String socketSystemName = _locoAddressSocketSystemName;
437                _locoAddressSocket.disconnect();
438                if (socketSystemName != null) {
439                    MaleSocket maleSocket =
440                            InstanceManager.getDefault(AnalogExpressionManager.class)
441                                    .getBySystemName(socketSystemName);
442                    _locoAddressSocket.disconnect();
443                    if (maleSocket != null) {
444                        _locoAddressSocket.connect(maleSocket);
445                        maleSocket.setup();
446                    } else {
447                        log.error("cannot load analog expression {}", socketSystemName);
448                    }
449                }
450            } else {
451                _locoAddressSocket.getConnectedSocket().setup();
452            }
453
454            if ( !_locoSpeedSocket.isConnected()
455                    || !_locoSpeedSocket.getConnectedSocket().getSystemName()
456                            .equals(_locoSpeedSocketSystemName)) {
457
458                String socketSystemName = _locoSpeedSocketSystemName;
459                _locoSpeedSocket.disconnect();
460                if (socketSystemName != null) {
461                    MaleSocket maleSocket =
462                            InstanceManager.getDefault(AnalogExpressionManager.class)
463                                    .getBySystemName(socketSystemName);
464                    _locoSpeedSocket.disconnect();
465                    if (maleSocket != null) {
466                        _locoSpeedSocket.connect(maleSocket);
467                        maleSocket.setup();
468                    } else {
469                        log.error("cannot load analog expression {}", socketSystemName);
470                    }
471                }
472            } else {
473                _locoSpeedSocket.getConnectedSocket().setup();
474            }
475
476            if ( !_locoDirectionSocket.isConnected()
477                    || !_locoDirectionSocket.getConnectedSocket().getSystemName()
478                            .equals(_locoDirectionSocketSystemName)) {
479
480                String socketSystemName = _locoDirectionSocketSystemName;
481                _locoDirectionSocket.disconnect();
482                if (socketSystemName != null) {
483                    MaleSocket maleSocket =
484                            InstanceManager.getDefault(DigitalExpressionManager.class)
485                                    .getBySystemName(socketSystemName);
486                    _locoDirectionSocket.disconnect();
487                    if (maleSocket != null) {
488                        _locoDirectionSocket.connect(maleSocket);
489                        maleSocket.setup();
490                    } else {
491                        log.error("cannot load digital expression {}", socketSystemName);
492                    }
493                }
494            } else {
495                _locoDirectionSocket.getConnectedSocket().setup();
496            }
497
498            if ( !_locoFunctionSocket.isConnected()
499                    || !_locoFunctionSocket.getConnectedSocket().getSystemName()
500                            .equals(_locoFunctionSocketSystemName)) {
501
502                String socketSystemName = _locoFunctionSocketSystemName;
503                _locoFunctionSocket.disconnect();
504                if (socketSystemName != null) {
505                    MaleSocket maleSocket =
506                            InstanceManager.getDefault(AnalogExpressionManager.class)
507                                    .getBySystemName(socketSystemName);
508                    _locoFunctionSocket.disconnect();
509                    if (maleSocket != null) {
510                        _locoFunctionSocket.connect(maleSocket);
511                        maleSocket.setup();
512                    } else {
513                        log.error("cannot load analog expression {}", socketSystemName);
514                    }
515                }
516            } else {
517                _locoFunctionSocket.getConnectedSocket().setup();
518            }
519
520            if ( !_locoFunctionOnOffSocket.isConnected()
521                    || !_locoFunctionOnOffSocket.getConnectedSocket().getSystemName()
522                            .equals(_locoFunctionOnOffSocketSystemName)) {
523
524                String socketSystemName = _locoFunctionOnOffSocketSystemName;
525                _locoFunctionOnOffSocket.disconnect();
526                if (socketSystemName != null) {
527                    MaleSocket maleSocket =
528                            InstanceManager.getDefault(DigitalExpressionManager.class)
529                                    .getBySystemName(socketSystemName);
530                    _locoFunctionOnOffSocket.disconnect();
531                    if (maleSocket != null) {
532                        _locoFunctionOnOffSocket.connect(maleSocket);
533                        maleSocket.setup();
534                    } else {
535                        log.error("cannot load digital expression {}", socketSystemName);
536                    }
537                }
538            } else {
539                _locoFunctionOnOffSocket.getConnectedSocket().setup();
540            }
541        } catch (SocketAlreadyConnectedException ex) {
542            // This shouldn't happen and is a runtime error if it does.
543            throw new RuntimeException("socket is already connected");
544        }
545    }
546
547    public boolean isStopLocoWhenSwitchingLoco() {
548        return _stopLocoWhenSwitchingLoco;
549    }
550
551    public void setStopLocoWhenSwitchingLoco(boolean value) {
552        _stopLocoWhenSwitchingLoco = value;
553    }
554
555    public boolean isWaitForThrottle() {
556        return _waitForThrottle;
557    }
558
559    public void setWaitForThrottle(boolean value) {
560        _waitForThrottle = value;
561    }
562
563    /** {@inheritDoc} */
564    @Override
565    public void registerListenersForThisClass() {
566        _listenersAreRegistered = true;
567    }
568
569    /** {@inheritDoc} */
570    @Override
571    public void unregisterListenersForThisClass() {
572        _listenersAreRegistered = false;
573    }
574
575    /** {@inheritDoc} */
576    @Override
577    public void disposeMe() {
578        if (_throttle != null) {
579            ThreadingUtil.runOnGUI(() -> {
580                _throttleManager.releaseThrottle(_throttle, _throttleListener); });
581        }
582    }
583
584    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionThrottle.class);
585
586}