001package jmri.jmrit.logix;
002
003import jmri.InstanceManager;
004import jmri.JmriException;
005import jmri.NamedBean;
006import jmri.NamedBeanHandle;
007import jmri.NamedBeanHandleManager;
008import jmri.Memory;
009import jmri.Sensor;
010import jmri.SpeedStepMode;
011
012import java.text.NumberFormat;
013
014import org.slf4j.Logger;
015import org.slf4j.LoggerFactory;
016
017/**
018 * Oct 2020 - change formats to allow I18N of parameters
019 * Jul 2024 - Add the SET_MEMORY action.  This makes it possible to trigger external actions beyond just a sensor.  DAS
020 * @author  Pete Cressman Copyright (C) 2009, 2020
021 */
022public class ThrottleSetting {
023
024    static final int CMD_SPEED = 1;
025    static final int CMD_SPEEDSTEP = 2;
026    static final int CMD_FORWARD = 3;
027    static final int CMD_FTN = 4;
028    static final int CMD_LATCH = 5;
029    static final int CMD_NOOP = 6;
030    static final int CMD_SET_SENSOR = 7;
031    static final int CMD_WAIT_SENSOR = 8;
032    static final int CMD_RUN_WARRANT = 9;
033    static final int CMD_SET_MEMORY = 10;
034
035    public enum Command {
036        SPEED(CMD_SPEED, true, "speed"),
037        FORWARD(CMD_FORWARD, true, "forward"),
038        FKEY(CMD_FTN, true, "setFunction"),
039        LATCHF(CMD_LATCH, true, "setKeyMomentary"),
040        SET_SENSOR(CMD_SET_SENSOR, false, "SetSensor"),
041        WAIT_SENSOR(CMD_WAIT_SENSOR, false, "WaitSensor"),
042        RUN_WARRANT(CMD_RUN_WARRANT, false, "runWarrant"),
043        NOOP(CMD_NOOP, true, "NoOp"),
044        SPEEDSTEP(CMD_SPEEDSTEP, true, "speedstep"),
045        SET_MEMORY(CMD_SET_MEMORY, false, "SetMemory");
046
047        int _command;
048        boolean _hasBlock; // when bean is an OBlock.
049        String _bundleKey; // key to get command display name
050
051        Command(int cmd, boolean hasBlock, String bundleName) {
052            _command = cmd;
053            _hasBlock = hasBlock;
054            _bundleKey = bundleName;
055        }
056
057        public int getIntId() {
058            return _command;
059        }
060
061        boolean hasBlockName() {
062            return _hasBlock;
063        }
064
065        @Override
066        public String toString() {
067            return Bundle.getMessage(_bundleKey);
068        }
069    }
070
071    static final int VALUE_FLOAT = 1;
072    static final int VALUE_NOOP = 2;
073    static final int VALUE_INT = 3;
074    static final int VALUE_STEP = 4;
075    static final int VALUE_TEXT = 5;
076    static final int VALUE_TRUE = 10;
077    static final int VALUE_FALSE = 11;
078    static final int VALUE_ON = 20;
079    static final int VALUE_OFF = 21;
080    static final int VALUE_ACTIVE = 30;
081    static final int VALUE_INACTIVE = 31;
082
083    static final int IS_TRUE_FALSE = 1;
084    static final int IS_ON_OFF = 2;
085    static final int IS_SENSOR_STATE = 3;
086
087    public enum ValueType {
088        VAL_FLOAT(VALUE_FLOAT, "NumData"),
089        VAL_NOOP(VALUE_NOOP, "Mark"),
090        VAL_INT(VALUE_INT, "NumData"),
091        VAL_STEP(VALUE_STEP, "speedstep"),
092        VAL_TEXT(VALUE_TEXT, "TextData"),
093        VAL_TRUE(VALUE_TRUE, "StateTrue"),
094        VAL_FALSE(VALUE_FALSE, "StateFalse"),
095        VAL_ON(VALUE_ON, "StateOn"),
096        VAL_OFF(VALUE_OFF, "StateOff"),
097        VAL_ACTIVE(VALUE_ACTIVE, "SensorStateActive"),
098        VAL_INACTIVE(VALUE_INACTIVE, "SensorStateInactive");
099
100        int _valueId;  // state id
101        String _bundleKey; // key to get state display name
102
103        ValueType(int id, String bundleName) {
104            _valueId = id;
105            _bundleKey = bundleName;
106        }
107
108        public int getIntId() {
109            return _valueId;
110        }
111
112        @Override
113        public String toString() {
114            return Bundle.getMessage(_bundleKey);
115        }
116    }
117
118    public static class CommandValue {
119        ValueType _type;
120        SpeedStepMode  _stepMode;
121        float   _floatValue;
122        String  _textValue;
123        NumberFormat formatter = NumberFormat.getNumberInstance();
124        NumberFormat intFormatter = NumberFormat.getIntegerInstance();
125
126        public CommandValue(ValueType t, SpeedStepMode s, float f, String tx) {
127            _type = t;
128            _stepMode = s;
129            _floatValue = f;
130            _textValue = tx;
131        }
132
133        @Override
134        protected CommandValue clone() {
135            return new CommandValue(_type, _stepMode, _floatValue, _textValue);
136        }
137
138        public ValueType getType() {
139            return _type;
140        }
141
142        public SpeedStepMode getMode() {
143            return _stepMode;
144        }
145
146        void setFloat(float f) {
147            _floatValue = f;
148        }
149
150        public float getFloat() {
151            return _floatValue;
152        }
153
154        public String getText() {
155            return _textValue;
156        }
157
158        public String showValue() {
159            if (_type == ValueType.VAL_FLOAT) {
160                return formatter.format(_floatValue);
161            } else if (_type == ValueType.VAL_INT) {
162                return intFormatter.format(_floatValue);
163            } else if (_type == ValueType.VAL_STEP) {
164                return _stepMode.name;
165            } else if (_type == ValueType.VAL_TEXT) {
166                return _textValue;
167            } else {
168                return _type.toString();
169            }
170        }
171
172        @Override
173        public String toString() {
174            return "CommandValue type "+_type.getIntId();
175        }
176    }
177
178    public static Command getCommandTypeFromInt(int typeInt) {
179        for (Command type : Command.values()) {
180            if (type.getIntId() == typeInt) {
181                return type;
182            }
183        }
184        throw new IllegalArgumentException(typeInt + " Command type ID is unknown");
185    }
186
187    public static ValueType getValueTypeFromInt(int typeInt) {
188        for (ValueType type : ValueType.values()) {
189            if (type.getIntId() == typeInt) {
190                return type;
191            }
192        }
193        throw new IllegalArgumentException(typeInt + " ValueType ID is unknown");
194    }
195
196    //====================== THrottleSteeing Class ====================
197    private long    _time;
198    private Command _command;
199    private int     _keyNum; // function key number
200    private CommandValue _value;
201    // _namedHandle may be of 3 different types
202    private NamedBeanHandle<? extends NamedBean> _namedHandle = null;
203    private float _trackSpeed; // track speed of the train (millimeters per second)
204
205    public ThrottleSetting() {
206        _keyNum = -1;
207    }
208
209    public ThrottleSetting(long time, Command command, int key, ValueType vType, SpeedStepMode ss, float f, String tx, String beanName) {
210        _time = time;
211        _command = command;
212        _keyNum = key;
213        setValue(vType, ss, f, tx);
214        setNamedBean(command, beanName);
215        _trackSpeed = 0.0f;
216    }
217
218    public ThrottleSetting(long time, Command command, int key, ValueType vType, SpeedStepMode ss, float f, String tx, String beanName, float trkSpd) {
219        _time = time;
220        _command = command;
221        _keyNum = key;
222        setValue(vType, ss, f, tx);
223        setNamedBean(command, beanName);
224        _trackSpeed = trkSpd;
225    }
226
227    // pre 4.21.3
228    public ThrottleSetting(long time, String cmdStr, String value, String beanName) {
229        _time = time;
230        setCommand(cmdStr);
231        setValue(value);    // must follow setCommand()
232        setNamedBean(_command, beanName);
233        _trackSpeed = 0.0f;
234    }
235
236    // pre 4.21.3
237    public ThrottleSetting(long time, String cmdStr, String value, String beanName, float trkSpd) {
238        _time = time;
239        setCommand(cmdStr);
240        setValue(value);
241        setNamedBean(_command, beanName);
242        _trackSpeed = trkSpd;
243    }
244
245    public ThrottleSetting(long time, Command command, int key, String value, String beanName, float trkSpd) {
246        _time = time;
247        _command = command;
248        _keyNum = key;
249        setValue(value);    // must follow setCommand()
250        _namedHandle = null;
251        _trackSpeed = trkSpd;
252    }
253
254    public ThrottleSetting(ThrottleSetting ts) {
255        _time = ts.getTime();
256        _command = ts.getCommand();
257        _keyNum = ts.getKeyNum();
258        setValue(ts.getValue());
259        _namedHandle = ts.getNamedBeanHandle();
260        _trackSpeed = ts.getTrackSpeed();
261    }
262
263    /**
264     * Convert old format. (former Strings for Command enums)
265     * @param cmdStr old style description string
266     * @return enum Command
267     * @throws JmriException in case of a non-integer Function or Fn lock/latch value
268     */
269    private Command getCommandFromString(String cmdStr) throws JmriException {
270        Command command;
271        String cmd = cmdStr.trim().toUpperCase();
272        if ("SPEED".equals(cmd) || Bundle.getMessage("speed").toUpperCase().equals(cmd)) {
273            command = Command.SPEED;
274            _keyNum = -1;
275        } else if ("SPEEDSTEP".equals(cmd) || Bundle.getMessage("speedstep").toUpperCase().equals(cmd)) {
276            command = Command.SPEEDSTEP;
277            _keyNum = -1;
278        } else if ("FORWARD".equals(cmd) || Bundle.getMessage("forward").toUpperCase().equals(cmd)) {
279            command = Command.FORWARD;
280            _keyNum = -1;
281        } else if (cmd.startsWith("F") || Bundle.getMessage("setFunction").toUpperCase().equals(cmd)) {
282            command = Command.FKEY;
283            try {
284                _keyNum = Integer.parseInt(cmd.substring(1));
285            } catch (NumberFormatException nfe) {
286                throw new JmriException(Bundle.getMessage("badFunctionNum"), nfe);
287            }
288        } else if (cmd.startsWith("LOCKF") || Bundle.getMessage("setKeyMomentary").toUpperCase().equals(cmd)) {
289            command = Command.LATCHF;
290            try {
291                _keyNum = Integer.parseInt(cmd.substring(5));
292            } catch (NumberFormatException nfe) {
293                throw new JmriException(Bundle.getMessage("badLockFNum"), nfe);
294            }
295        } else if ("NOOP".equals(cmd) || Bundle.getMessage("NoOp").toUpperCase().equals(cmd)) {
296            command = Command.NOOP;
297            _keyNum = -1;
298        } else if ("SENSOR".equals(cmd) || "SET SENSOR".equals(cmd) || "SET".equals(cmd)
299                || Bundle.getMessage("SetSensor").toUpperCase().equals(cmd)) {
300            command = Command.SET_SENSOR;
301            _keyNum = -1;
302        } else if ("WAIT SENSOR".equals(cmd) || "WAIT".equals(cmd)
303                || Bundle.getMessage("WaitSensor").toUpperCase().equals(cmd)) {
304            command = Command.WAIT_SENSOR;
305            _keyNum = -1;
306        } else if ("RUN WARRANT".equals(cmd) || Bundle.getMessage("runWarrant").toUpperCase().equals(cmd)) {
307            command = Command.RUN_WARRANT;
308            _keyNum = -1;
309        } else if (Bundle.getMessage("SetMemory").toUpperCase().equals(cmd)) {
310            command = Command.SET_MEMORY;
311            _keyNum = -1;
312        } else {
313            throw new jmri.JmriException(Bundle.getMessage("badCommand", cmdStr));
314        }
315        return command;
316    }
317
318    static protected CommandValue getValueFromString(Command command, String valueStr) throws JmriException {
319        if (command == null) {
320            throw new jmri.JmriException(Bundle.getMessage("badCommand", "Command missing "+valueStr));
321        }
322        ValueType type;
323        SpeedStepMode mode = SpeedStepMode.UNKNOWN;
324        float speed = 0.0f;
325        String text = "";
326        String val = valueStr.trim().toUpperCase();
327        if ("ON".equals(val) || Bundle.getMessage("StateOn").toUpperCase().equals(val)) {
328            switch (command) {
329                case FKEY:
330                case LATCHF:
331                type = ValueType.VAL_ON;
332                    break;
333                default:
334                    throw new jmri.JmriException(Bundle.getMessage("badValue", valueStr, command));
335            }
336        } else if ("OFF".equals(val) || Bundle.getMessage("StateOff").toUpperCase().equals(val)) {
337            switch (command) {
338                case FKEY:
339                case LATCHF:
340                type = ValueType.VAL_OFF;
341                    break;
342                default:
343                    throw new jmri.JmriException(Bundle.getMessage("badValue", valueStr, command));
344            }
345        } else  if ("TRUE".equals(val) || Bundle.getMessage("StateTrue").toUpperCase().equals(val)) {
346            switch (command) {
347                case FORWARD:
348                    type = ValueType.VAL_TRUE;
349                    break;
350                case FKEY:
351                case LATCHF:
352                    type = ValueType.VAL_ON;
353                    break;
354                default:
355                    throw new jmri.JmriException(Bundle.getMessage("badValue", valueStr, command));
356            }
357        } else if ("FALSE".equals(val) || Bundle.getMessage("StateFalse").toUpperCase().equals(val)) {
358            switch (command) {
359                case FORWARD:
360                    type = ValueType.VAL_FALSE;
361                    break;
362                case FKEY:
363                case LATCHF:
364                    type = ValueType.VAL_OFF;
365                    break;
366                default:
367                    throw new jmri.JmriException(Bundle.getMessage("badValue", valueStr, command));
368            }
369        } else if ("ACTIVE".equals(val) || Bundle.getMessage("SensorStateActive").toUpperCase().equals(val)) {
370            switch (command) {
371                case SET_SENSOR:
372                case WAIT_SENSOR:
373                    type = ValueType.VAL_ACTIVE;
374                    break;
375                default:
376                    throw new jmri.JmriException(Bundle.getMessage("badValue", valueStr, command));
377            }
378        } else if ("INACTIVE".equals(val) || Bundle.getMessage("SensorStateInactive").toUpperCase().equals(val)) {
379            switch (command) {
380                case SET_SENSOR:
381                case WAIT_SENSOR:
382                    type = ValueType.VAL_INACTIVE;
383                    break;
384                default:
385                    throw new jmri.JmriException(Bundle.getMessage("badValue", valueStr, command));
386            }
387        } else {
388            try {
389                switch (command) {
390                    case SPEED:
391                        speed = Float.parseFloat(valueStr.replace(',', '.'));
392                        type = ValueType.VAL_FLOAT;
393                        break;
394                    case NOOP:
395                        type = ValueType.VAL_NOOP;
396                        break;
397                    case RUN_WARRANT:
398                        speed = Float.parseFloat(valueStr.replace(',', '.'));
399                        type = ValueType.VAL_INT;
400                        break;
401                    case SPEEDSTEP:
402                        mode = SpeedStepMode.getByName(val);
403                        type = ValueType.VAL_STEP;
404                        break;
405                    case SET_MEMORY:
406                        type = ValueType.VAL_TEXT;
407                        text = valueStr.trim();
408                        break;
409                    default:
410                        throw new JmriException(Bundle.getMessage("badValue", valueStr, command));
411                }
412            } catch (IllegalArgumentException | NullPointerException ex) { // NumberFormatException is sublass of iae
413                throw new JmriException(Bundle.getMessage("badValue", valueStr, command), ex);
414            }
415        }
416        return new CommandValue(type, mode, speed, text);
417    }
418
419    /**
420     * Time is an object so that a "synch to block entry" notation can be used
421     * rather than elapsed time.
422     *
423     * @param time the time in some unit
424     */
425    public void setTime(long time) {
426        _time = time;
427    }
428
429    public long getTime() {
430        return _time;
431    }
432
433    public void setCommand(String cmdStr) {
434        try {
435            _command = getCommandFromString(cmdStr);
436        } catch (JmriException je) {
437            log.error("Cannot set command from string \"{}\" {}", cmdStr, je.toString());
438        }
439    }
440
441    public void setCommand(Command command) {
442        _command = command;
443    }
444
445    public Command getCommand() {
446        return _command;
447    }
448
449    public void setKeyNum(int key) {
450        _keyNum = key;
451    }
452
453    public int getKeyNum() {
454        return _keyNum;
455    }
456
457    public void setValue(String valueStr) {
458        try {
459            _value = getValueFromString(_command, valueStr);
460        } catch (JmriException je) {
461            log.error("Cannot set value for command {}. {}",
462                   (_command!=null?_command.toString():"null"), je.toString());
463        }
464    }
465
466    public void setValue(CommandValue value) {
467        _value = value.clone();
468    }
469
470    public void setValue(ValueType t, SpeedStepMode s, float f, String tx) {
471        _value = new CommandValue(t, s, f, tx);
472    }
473
474    public CommandValue getValue() {
475        return _value;
476    }
477
478    public void setTrackSpeed(float s) {
479        _trackSpeed = s;
480    }
481
482    public float getTrackSpeed() {
483        return _trackSpeed;
484    }
485
486    // _namedHandle may be of 3 different types
487    public String setNamedBean(Command cmd, String name) {
488        if (log.isDebugEnabled()) {
489            log.debug("setNamedBean({}, {})", cmd, name);
490        }
491        String msg = WarrantFrame.checkBeanName(cmd, name);
492        if (msg != null) {
493            _namedHandle = null;
494            return msg;
495        }
496        try {
497            if (cmd.equals(Command.SET_SENSOR) || cmd.equals(Command.WAIT_SENSOR)) {
498                Sensor s = InstanceManager.sensorManagerInstance().provideSensor(name);
499                _namedHandle = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, s);
500            } else if (cmd.equals(Command.RUN_WARRANT)) {
501                Warrant w = InstanceManager.getDefault(jmri.jmrit.logix.WarrantManager.class).provideWarrant(name);
502                _namedHandle = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, w);
503            } else if (cmd.equals(Command.SET_MEMORY)) {
504                Memory m = InstanceManager.getDefault(jmri.MemoryManager.class).provideMemory(name);
505                _namedHandle = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, m);
506            } else {
507                OBlock b = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getOBlock(name);
508                if (b != null) {
509                    _namedHandle = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, b);
510                }
511            }
512        } catch (IllegalArgumentException iae) {
513            return Bundle.getMessage("badCommand", cmd+iae.toString());
514        }
515        return null;
516    }
517
518    // _namedHandle may be of 3 different types
519    public <T extends NamedBean> void setNamedBeanHandle(NamedBeanHandle<T> bh) {
520        _namedHandle = bh;
521    }
522
523    // _namedHandle may be of 3 different types
524    public NamedBeanHandle<? extends NamedBean> getNamedBeanHandle() {
525        return _namedHandle;
526    }
527
528    public NamedBean getBean() {
529        if (_namedHandle == null) {
530            return null;
531        }
532        return _namedHandle.getBean();
533    }
534
535    public String getBeanDisplayName() {
536        if (_namedHandle == null) {
537            return null;
538        }
539        return _namedHandle.getBean().getDisplayName();
540    }
541
542    public String getBeanSystemName() {
543        if (_namedHandle == null) {
544            return null;
545        }
546        return _namedHandle.getBean().getSystemName();
547    }
548
549    @Override
550    public String toString() {
551        return "ThrottleSetting: wait " + _time + "ms then " + _command.toString()
552                + " with value " + _value.showValue() + " for bean \"" + getBeanDisplayName()
553                + "\" at trackSpeed " + getTrackSpeed() + "\"";
554    }
555
556    private static final Logger log = LoggerFactory.getLogger(ThrottleSetting.class);
557}