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}