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    boolean _isActive = false;
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                // Stop the loco
126                _throttle.setSpeedSetting(0);
127                // Release the loco
128                _throttleManager.releaseThrottle(_throttle, _throttleListener);
129                _throttle = null;
130            }
131
132            if (newLocoAddress != -1) {
133
134                _throttleListener =  new ThrottleListener() {
135                    @Override
136                    public void notifyThrottleFound(DccThrottle t) {
137                        _throttle = t;
138                        executeConditionalNG();
139                    }
140
141                    @Override
142                    public void notifyFailedThrottleRequest(LocoAddress address, String reason) {
143                        log.warn("loco {} cannot be aquired", address.getNumber());
144                    }
145
146                    @Override
147                    public void notifyDecisionRequired(LocoAddress address, ThrottleListener.DecisionType question) {
148                        log.warn("Loco {} cannot be aquired. Decision required.", address.getNumber());
149                    }
150                };
151
152                boolean result = _throttleManager.requestThrottle(newLocoAddress, _throttleListener);
153
154                if (!result) {
155                    log.warn("loco {} cannot be aquired", newLocoAddress);
156                }
157            }
158
159        }
160
161        // We have a throttle if _throttle is not null
162        if (_throttle != null) {
163
164            double speed = 0;
165            boolean isForward = true;
166            int function = 0;
167            boolean isFunctionOn = true;
168
169            if (_locoSpeedSocket.isConnected()) {
170                speed =
171                        ((MaleAnalogExpressionSocket)_locoSpeedSocket.getConnectedSocket())
172                                .evaluate();
173            }
174
175            if (_locoDirectionSocket.isConnected()) {
176                isForward =
177                        ((MaleDigitalExpressionSocket)_locoDirectionSocket.getConnectedSocket())
178                                .evaluate();
179            }
180
181            if (_locoFunctionSocket.isConnected()) {
182                function = (int) Math.round(
183                        ((MaleAnalogExpressionSocket)_locoFunctionSocket.getConnectedSocket())
184                                .evaluate());
185            }
186
187            if (_locoFunctionOnOffSocket.isConnected()) {
188                isFunctionOn =
189                        ((MaleDigitalExpressionSocket)_locoFunctionOnOffSocket.getConnectedSocket())
190                                .evaluate();
191            }
192
193            DccThrottle throttle = _throttle;
194            float spd = (float) speed;
195            boolean fwd = isForward;
196            int func = function;
197            boolean funcState = isFunctionOn;
198            jmri.util.ThreadingUtil.runOnLayoutWithJmriException(() -> {
199                if (_locoSpeedSocket.isConnected()) throttle.setSpeedSetting(spd);
200                if (_locoDirectionSocket.isConnected()) throttle.setIsForward(fwd);
201                if (_locoFunctionSocket.isConnected() && _locoFunctionOnOffSocket.isConnected()) {
202                    throttle.setFunction(func, funcState);
203                }
204            });
205        }
206    }
207
208    @Override
209    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
210        switch (index) {
211            case LOCO_ADDRESS_SOCKET:
212                return _locoAddressSocket;
213
214            case LOCO_SPEED_SOCKET:
215                return _locoSpeedSocket;
216
217            case LOCO_DIRECTION_SOCKET:
218                return _locoDirectionSocket;
219
220            case LOCO_FUNCTION_SOCKET:
221                return _locoFunctionSocket;
222
223            case LOCO_FUNCTION_ONOFF_SOCKET:
224                return _locoFunctionOnOffSocket;
225
226            default:
227                throw new IllegalArgumentException(
228                        String.format("index has invalid value: %d", index));
229        }
230    }
231
232    @Override
233    public int getChildCount() {
234        return NUM_LOCO_SOCKETS;
235    }
236
237    @Override
238    public void connected(FemaleSocket socket) {
239        if (socket == _locoAddressSocket) {
240            _locoAddressSocketSystemName = socket.getConnectedSocket().getSystemName();
241            executeConditionalNG();
242        } else if (socket == _locoSpeedSocket) {
243            _locoSpeedSocketSystemName = socket.getConnectedSocket().getSystemName();
244            executeConditionalNG();
245        } else if (socket == _locoDirectionSocket) {
246            _locoDirectionSocketSystemName = socket.getConnectedSocket().getSystemName();
247            executeConditionalNG();
248        } else if (socket == _locoFunctionSocket) {
249            _locoFunctionSocketSystemName = socket.getConnectedSocket().getSystemName();
250            executeConditionalNG();
251        } else if (socket == _locoFunctionOnOffSocket) {
252            _locoFunctionOnOffSocketSystemName = socket.getConnectedSocket().getSystemName();
253            executeConditionalNG();
254        } else {
255            throw new IllegalArgumentException("unkown socket");
256        }
257    }
258
259    @Override
260    public void disconnected(FemaleSocket socket) {
261        if (socket == _locoAddressSocket) {
262            if (_throttle != null) {
263                // Stop the loco
264                _throttle.setSpeedSetting(0);
265                // Release the loco
266                _throttleManager.releaseThrottle(_throttle, _throttleListener);
267            }
268            _locoAddressSocketSystemName = null;
269            executeConditionalNG();
270        } else if (socket == _locoSpeedSocket) {
271            _locoSpeedSocketSystemName = null;
272            executeConditionalNG();
273        } else if (socket == _locoDirectionSocket) {
274            _locoDirectionSocketSystemName = null;
275            executeConditionalNG();
276        } else if (socket == _locoFunctionSocket) {
277            _locoFunctionSocketSystemName = null;
278            executeConditionalNG();
279        } else if (socket == _locoFunctionOnOffSocket) {
280            _locoFunctionOnOffSocketSystemName = null;
281            executeConditionalNG();
282        } else {
283            throw new IllegalArgumentException("unkown socket");
284        }
285    }
286
287    private void executeConditionalNG() {
288        if (_listenersAreRegistered) {
289            ConditionalNG c = getConditionalNG();
290            if (c != null) {
291                c.execute();
292            }
293        }
294    }
295
296    @Override
297    public String getShortDescription(Locale locale) {
298        return Bundle.getMessage(locale, "ActionThrottle_Short");
299    }
300
301    @Override
302    public String getLongDescription(Locale locale) {
303        if (_memo != null) {
304            return Bundle.getMessage(locale, "ActionThrottle_LongConnection",
305                    _memo.getUserName());
306        } else {
307            return Bundle.getMessage(locale, "ActionThrottle_Long");
308        }
309    }
310
311    public FemaleAnalogExpressionSocket getLocoAddressSocket() {
312        return _locoAddressSocket;
313    }
314
315    public String getLocoAddressSocketSystemName() {
316        return _locoAddressSocketSystemName;
317    }
318
319    public void setLocoAddressSocketSystemName(String systemName) {
320        _locoAddressSocketSystemName = systemName;
321    }
322
323    public FemaleAnalogExpressionSocket getLocoSpeedSocket() {
324        return _locoSpeedSocket;
325    }
326
327    public String getLocoSpeedSocketSystemName() {
328        return _locoSpeedSocketSystemName;
329    }
330
331    public void setLocoSpeedSocketSystemName(String systemName) {
332        _locoSpeedSocketSystemName = systemName;
333    }
334
335    public FemaleDigitalExpressionSocket getLocoDirectionSocket() {
336        return _locoDirectionSocket;
337    }
338
339    public String getLocoDirectionSocketSystemName() {
340        return _locoDirectionSocketSystemName;
341    }
342
343    public void setLocoDirectionSocketSystemName(String systemName) {
344        _locoDirectionSocketSystemName = systemName;
345    }
346
347    public FemaleAnalogExpressionSocket getLocoFunctionSocket() {
348        return _locoFunctionSocket;
349    }
350
351    public String getLocoFunctionSocketSystemName() {
352        return _locoFunctionSocketSystemName;
353    }
354
355    public void setLocoFunctionSocketSystemName(String systemName) {
356        _locoFunctionSocketSystemName = systemName;
357    }
358
359    public FemaleDigitalExpressionSocket getLocoFunctionOnOffSocket() {
360        return _locoFunctionOnOffSocket;
361    }
362
363    public String getLocoFunctionOnOffSocketSystemName() {
364        return _locoFunctionOnOffSocketSystemName;
365    }
366
367    public void setLocoFunctionOnOffSocketSystemName(String systemName) {
368        _locoFunctionOnOffSocketSystemName = systemName;
369    }
370
371    /** {@inheritDoc} */
372    @Override
373    public void setup() {
374        try {
375            if ( !_locoAddressSocket.isConnected()
376                    || !_locoAddressSocket.getConnectedSocket().getSystemName()
377                            .equals(_locoAddressSocketSystemName)) {
378
379                String socketSystemName = _locoAddressSocketSystemName;
380                _locoAddressSocket.disconnect();
381                if (socketSystemName != null) {
382                    MaleSocket maleSocket =
383                            InstanceManager.getDefault(AnalogExpressionManager.class)
384                                    .getBySystemName(socketSystemName);
385                    _locoAddressSocket.disconnect();
386                    if (maleSocket != null) {
387                        _locoAddressSocket.connect(maleSocket);
388                        maleSocket.setup();
389                    } else {
390                        log.error("cannot load analog expression {}", socketSystemName);
391                    }
392                }
393            } else {
394                _locoAddressSocket.getConnectedSocket().setup();
395            }
396
397            if ( !_locoSpeedSocket.isConnected()
398                    || !_locoSpeedSocket.getConnectedSocket().getSystemName()
399                            .equals(_locoSpeedSocketSystemName)) {
400
401                String socketSystemName = _locoSpeedSocketSystemName;
402                _locoSpeedSocket.disconnect();
403                if (socketSystemName != null) {
404                    MaleSocket maleSocket =
405                            InstanceManager.getDefault(AnalogExpressionManager.class)
406                                    .getBySystemName(socketSystemName);
407                    _locoSpeedSocket.disconnect();
408                    if (maleSocket != null) {
409                        _locoSpeedSocket.connect(maleSocket);
410                        maleSocket.setup();
411                    } else {
412                        log.error("cannot load analog expression {}", socketSystemName);
413                    }
414                }
415            } else {
416                _locoSpeedSocket.getConnectedSocket().setup();
417            }
418
419            if ( !_locoDirectionSocket.isConnected()
420                    || !_locoDirectionSocket.getConnectedSocket().getSystemName()
421                            .equals(_locoDirectionSocketSystemName)) {
422
423                String socketSystemName = _locoDirectionSocketSystemName;
424                _locoDirectionSocket.disconnect();
425                if (socketSystemName != null) {
426                    MaleSocket maleSocket =
427                            InstanceManager.getDefault(DigitalExpressionManager.class)
428                                    .getBySystemName(socketSystemName);
429                    _locoDirectionSocket.disconnect();
430                    if (maleSocket != null) {
431                        _locoDirectionSocket.connect(maleSocket);
432                        maleSocket.setup();
433                    } else {
434                        log.error("cannot load digital expression {}", socketSystemName);
435                    }
436                }
437            } else {
438                _locoDirectionSocket.getConnectedSocket().setup();
439            }
440
441            if ( !_locoFunctionSocket.isConnected()
442                    || !_locoFunctionSocket.getConnectedSocket().getSystemName()
443                            .equals(_locoFunctionSocketSystemName)) {
444
445                String socketSystemName = _locoFunctionSocketSystemName;
446                _locoFunctionSocket.disconnect();
447                if (socketSystemName != null) {
448                    MaleSocket maleSocket =
449                            InstanceManager.getDefault(AnalogExpressionManager.class)
450                                    .getBySystemName(socketSystemName);
451                    _locoFunctionSocket.disconnect();
452                    if (maleSocket != null) {
453                        _locoFunctionSocket.connect(maleSocket);
454                        maleSocket.setup();
455                    } else {
456                        log.error("cannot load analog expression {}", socketSystemName);
457                    }
458                }
459            } else {
460                _locoFunctionSocket.getConnectedSocket().setup();
461            }
462
463            if ( !_locoFunctionOnOffSocket.isConnected()
464                    || !_locoFunctionOnOffSocket.getConnectedSocket().getSystemName()
465                            .equals(_locoFunctionOnOffSocketSystemName)) {
466
467                String socketSystemName = _locoFunctionOnOffSocketSystemName;
468                _locoFunctionOnOffSocket.disconnect();
469                if (socketSystemName != null) {
470                    MaleSocket maleSocket =
471                            InstanceManager.getDefault(DigitalExpressionManager.class)
472                                    .getBySystemName(socketSystemName);
473                    _locoFunctionOnOffSocket.disconnect();
474                    if (maleSocket != null) {
475                        _locoFunctionOnOffSocket.connect(maleSocket);
476                        maleSocket.setup();
477                    } else {
478                        log.error("cannot load digital expression {}", socketSystemName);
479                    }
480                }
481            } else {
482                _locoFunctionOnOffSocket.getConnectedSocket().setup();
483            }
484        } catch (SocketAlreadyConnectedException ex) {
485            // This shouldn't happen and is a runtime error if it does.
486            throw new RuntimeException("socket is already connected");
487        }
488    }
489
490    /** {@inheritDoc} */
491    @Override
492    public void registerListenersForThisClass() {
493        _listenersAreRegistered = true;
494    }
495
496    /** {@inheritDoc} */
497    @Override
498    public void unregisterListenersForThisClass() {
499        _listenersAreRegistered = false;
500    }
501
502    /** {@inheritDoc} */
503    @Override
504    public void disposeMe() {
505        if (_throttle != null) {
506            _throttleManager.releaseThrottle(_throttle, _throttleListener);
507        }
508    }
509
510    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionThrottle.class);
511
512}