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