001package jmri.jmrix.ecos; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.awt.HeadlessException; 006 007import jmri.DccLocoAddress; 008import jmri.LocoAddress; 009import jmri.SpeedStepMode; 010import jmri.jmrix.AbstractThrottle; 011import jmri.util.swing.JmriJOptionPane; 012 013/** 014 * An implementation of DccThrottle with code specific to an ECoS connection. 015 * 016 * Based on Glen Oberhauser's original LnThrottleManager implementation 017 * 018 * @author Bob Jacobsen Copyright (C) 2001, modified 2009 by Kevin Dickerson 019 */ 020public class EcosDccThrottle extends AbstractThrottle implements EcosListener { 021 022 023 String objectNumber; 024 int ecosretry = 0; 025 private EcosLocoAddress objEcosLoco; 026 private EcosLocoAddressManager objEcosLocoManager; 027 final EcosPreferences p; 028 //This boolean is used to prevent un-necessary commands from being sent to the ECOS if we have already lost 029 //control of the object 030 private boolean _haveControl = false; 031 private boolean _hadControl = false; 032 private boolean _control = true; 033 034 /** 035 * Create a new EcosDccThrottle. 036 * @param address Throttle Address 037 * @param memo System Connection 038 * @param control sets _control flag which NEEDS CLARIFICATION. 039 */ 040 public EcosDccThrottle(DccLocoAddress address, EcosSystemConnectionMemo memo, boolean control) { 041 super(memo,32); 042 super.speedStepMode = SpeedStepMode.NMRA_DCC_128; 043 p = memo.getPreferenceManager(); 044 tc = memo.getTrafficController(); 045 objEcosLocoManager = memo.getLocoAddressManager(); 046 //The script will go through and read the values from the Ecos 047 synchronized (this) { 048 this.speedSetting = 0; 049 } 050 // Functions 0-31 default to false 051 this.address = address; 052 this.isForward = true; 053 this._control = control; 054 055 ecosretry = 0; 056 057 log.debug("EcosDccThrottle constructor {}", address); 058 059 //We go on a hunt to find an object with the dccaddress sent by our controller. 060 if (address.getNumber() < EcosLocoAddress.MFX_DCCAddressOffset) { 061 objEcosLoco = objEcosLocoManager.provideByDccAddress(address.getNumber()); 062 } else { 063 int ecosID = address.getNumber()-EcosLocoAddress.MFX_DCCAddressOffset; 064 objEcosLoco = objEcosLocoManager.provideByEcosObject(String.valueOf(ecosID)); 065 } 066 067 this.objectNumber = objEcosLoco.getEcosObject(); 068 if (this.objectNumber == null) { 069 createEcosLoco(); 070 } else { 071 getControl(); 072 } 073 } 074 075 private void getControl() { 076 String message; 077 setSpeedStepMode(objEcosLoco.getSpeedStepMode()); 078 message = "get(" + this.objectNumber + ", speed)"; 079 EcosMessage m = new EcosMessage(message); 080 tc.sendEcosMessage(m, this); 081 082 message = "get(" + this.objectNumber + ", dir)"; 083 m = new EcosMessage(message); 084 tc.sendEcosMessage(m, this); 085 086 if (_control) { 087 if (p.getLocoControl()) { 088 message = "request(" + this.objectNumber + ", view, control, force)"; 089 } else { 090 message = "request(" + this.objectNumber + ", view, control)"; 091 } 092 } else { 093 message = "request(" + this.objectNumber + ", view)"; 094 } 095 096 m = new EcosMessage(message); 097 tc.sendEcosMessage(m, this); 098 } 099 100 //The values here might need a bit of re-working 101 102 /** 103 * Convert an Ecos speed integer to a float speed value. 104 * @param lSpeed speed value as an integer 105 * @return speed value as a float 106 */ 107 protected float floatSpeed(int lSpeed) { 108 if (lSpeed == 0) { 109 return 0.0f; 110 } 111 if (getSpeedStepMode() == SpeedStepMode.NMRA_DCC_28) { 112 int step = (int) Math.ceil(lSpeed / 4.65); 113 return step * getSpeedIncrement(); 114 } 115 return ((lSpeed) / 126.f); 116 } 117 118 /** 119 * {@inheritDoc} 120 */ 121 @Override 122 public void setFunction(int functionNum, boolean newState){ 123 updateFunction(functionNum,newState); 124 if (_haveControl) { 125 EcosMessage m = new EcosMessage("set(" + this.objectNumber + ", func[" + 126 String.valueOf(functionNum) + ", " + (newState ? 1 : 0) + "])"); 127 tc.sendEcosMessage(m, this); 128 } 129 } 130 131 /** 132 * Set the speed and direction. 133 * <p> 134 * This intentionally skips the emergency stop value of 1. 135 * 136 * @param speed Number from 0 to 1; less than zero is emergency stop 137 */ 138 //The values here might need a bit of re-working 139 @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY") // OK to compare floating point 140 @Override 141 public void setSpeedSetting(float speed) { 142 if (!_haveControl) { 143 return; 144 } 145 synchronized (this) { 146 if (speed == this.speedSetting && speedMessageSent <= 0) { 147 return; 148 } 149 } 150 int value = Math.round((127 - 1) * speed); // -1 for rescale to avoid estop 151 if (value == 0 && speed > 0) { // make sure to output non-zero speed for non-zero input speed 152 value = 1; 153 } 154 if (value > 126) { 155 value = 126; // max possible speed 156 } 157 if ((value > 0) || (value == 0)) { 158 String message = "set(" + this.objectNumber + ", speed[" + value + "])"; 159 EcosMessage m = new EcosMessage(message); 160 tc.sendEcosMessage(m, this); 161 if (speedMessageSent != 0) { 162 if (System.currentTimeMillis() - lastSpeedMessageTime > 500 || speedMessageSent < 0) { 163 speedMessageSent = 0; 164 } 165 } 166 lastSpeedMessageTime = System.currentTimeMillis(); 167 speedMessageSent++; 168 } else { 169 //Not sure if this performs an emergency stop or a normal one. 170 String message = "set(" + this.objectNumber + ", stop)"; 171 synchronized (this) { 172 this.speedSetting = 0.0f; 173 } 174 EcosMessage m = new EcosMessage(message); 175 tc.sendEcosMessage(m, this); 176 177 } 178 record(speed); 179 } 180 181 long lastSpeedMessageTime = 0L; 182 183 EcosTrafficController tc; 184 185 int speedMessageSent = 0; 186 187 /** 188 * {@inheritDoc} 189 */ 190 @Override 191 public void setIsForward(boolean forward) { 192 if (!_haveControl) { 193 return; 194 } 195 196 String message; 197 synchronized (this) { 198 if (this.speedSetting > 0.0f) { 199 // Need to send current speed as well as direction, otherwise 200 // speed will be set to zero on direction change 201 int speedValue = (int) ((127 - 1) * this.speedSetting); // -1 for rescale to avoid estop 202 if (speedValue > 128) { 203 speedValue = 126; // max possible speed 204 } 205 message = "set(" + this.objectNumber + ", dir[" + (forward ? 0 : 1) + "], speed[" + speedValue + "])"; 206 } else { 207 message = "set(" + this.objectNumber + ", dir[" + (forward ? 0 : 1) + "])"; 208 } 209 } 210 EcosMessage m = new EcosMessage(message); 211 tc.sendEcosMessage(m, this); 212 } 213 214 private DccLocoAddress address; 215 216 /** 217 * {@inheritDoc} 218 */ 219 @Override 220 public LocoAddress getLocoAddress() { 221 return address; 222 } 223 224 /** 225 * {@inheritDoc} 226 */ 227 @Override 228 public void throttleDispose() { 229 String message = "release(" + this.objectNumber + ", control)"; 230 EcosMessage m = new EcosMessage(message); 231 tc.sendEcosMessage(m, this); 232 _haveControl = false; 233 _hadControl = false; 234 finishRecord(); 235 } 236 237 /** 238 * {@inheritDoc} 239 */ 240 @Override 241 public void reply(EcosReply m) { 242 int resultCode = m.getResultCode(); 243 if (resultCode == 0) { 244 String replyType = m.getReplyType(); 245 if (replyType.equals("create")) { 246 String[] msgDetails = m.getContents(); 247 for (String line : msgDetails) { 248 if (line.startsWith("10 id[")) { 249 String EcosAddr = EcosReply.getContentDetail(line); 250 objEcosLoco.setEcosObject(EcosAddr); 251 objEcosLocoManager.deregister(objEcosLoco); 252 objEcosLocoManager.register(objEcosLoco); 253 objEcosLoco.setEcosTempEntry(true); 254 objEcosLoco.doNotAddToRoster(); 255 this.objectNumber = EcosAddr; 256 getControl(); 257 } 258 } 259 return; 260 } 261 262 /*if (lines[lines.length-1].contains("<END 0 (NERROR_OK)>")){ 263 //Need to investigate this a bit futher to see what the significance of the message is 264 //we may not have to worry much about it. 265 log.info("Loco has been created on the ECoS Sucessfully."); 266 return; 267 }*/ 268 if (m.getEcosObjectId() != objEcosLoco.getEcosObjectAsInt()) { 269 log.debug("message is not for us"); 270 return; 271 } 272 if (replyType.equals("set")) { 273 //This might need to use speedstep, rather than speed 274 //This is for standard response to set and request. 275 String[] msgDetails = m.getContents(); 276 for (String line : msgDetails) { 277 if (line.contains("speed") && !line.contains("speedstep")) { 278 speedMessageSent--; 279 if (speedMessageSent <= 0) { 280 Float newSpeed = floatSpeed(Integer.parseInt(EcosReply.getContentDetails(line, "speed"))); 281 super.setSpeedSetting(newSpeed); 282 } 283 } 284 if (line.contains("dir")) { 285 boolean newDirection = false; 286 if (EcosReply.getContentDetails(line, "dir").equals("0")) { 287 newDirection = true; 288 } 289 super.setIsForward(newDirection); 290 } 291 } 292 if (msgDetails.length == 0) { 293 //For some reason in recent ECOS software releases we do not get the contents, only a header and End State 294 if (m.toString().contains("speed") && !m.toString().contains("speedstep")) { 295 speedMessageSent--; 296 if (speedMessageSent <= 0) { 297 Float newSpeed = floatSpeed(Integer.parseInt(EcosReply.getContentDetails(m.toString(), "speed"))); 298 super.setSpeedSetting(newSpeed); 299 } 300 } 301 if (m.toString().contains("dir")) { 302 boolean newDirection = false; 303 if (EcosReply.getContentDetails(m.toString(), "dir").equals("0")) { 304 newDirection = true; 305 } 306 super.setIsForward(newDirection); 307 } 308 } 309 } //Treat gets and events as the same. 310 else if ((replyType.equals("get")) || (m.isUnsolicited())) { 311 //log.debug("The last command was accepted by the ecos"); 312 String[] msgDetails = m.getContents(); 313 for (String line : msgDetails) { 314 if (speedMessageSent > 0 && m.isUnsolicited() && line.contains("speed")) { 315 //We want to ignore these messages. 316 } else if (speedMessageSent <= 0 && line.contains("speed") && !line.contains("speedstep")) { 317 Float newSpeed = floatSpeed(Integer.parseInt(EcosReply.getContentDetails(line, "speed"))); 318 super.setSpeedSetting(newSpeed); 319 } else if (line.contains("dir")) { 320 boolean newDirection = false; 321 if (EcosReply.getContentDetails(line, "dir").equals("0")) { 322 newDirection = true; 323 } 324 super.setIsForward(newDirection); 325 } else if (line.contains("protocol")) { 326 String pro = EcosReply.getContentDetails(line, "protocol"); 327 if (pro.equals("DCC128")) { 328 setSpeedStepMode(SpeedStepMode.NMRA_DCC_128); 329 } else if (pro.equals("DCC28")) { 330 setSpeedStepMode(SpeedStepMode.NMRA_DCC_28); 331 } else if (pro.equals("DCC14")) { 332 setSpeedStepMode(SpeedStepMode.NMRA_DCC_14); 333 } 334 } else if (line.contains("func[")) { 335 String funcStr = EcosReply.getContentDetails(line, "func"); 336 int function = Integer.parseInt(funcStr.substring(0, funcStr.indexOf(",")).trim()); 337 int functionValue = Integer.parseInt(funcStr.substring((funcStr.indexOf(",") + 1), funcStr.length()).trim()); 338 updateFunction(function,functionValue == 1); 339 340 } else if (line.contains("msg")) { 341 //We get this lost control error because we have registered as a viewer. 342 if (line.contains("CONTROL_LOST")) { 343 retryControl(); 344 log.debug("We have no control over the ecos object, but will retry."); 345 } 346 347 } 348 349 } 350 } else if (replyType.equals("release")) { 351 log.debug("Released {} from the Ecos", this.objectNumber); 352 _haveControl = false; 353 } else if (replyType.equals("request")) { 354 log.debug("We have control over {} from the Ecos", this.objectNumber); 355 ecosretry = 0; 356 if (_control) { 357 _haveControl = true; 358 } 359 if (!_hadControl) { 360 ((EcosDccThrottleManager) adapterMemo.get(jmri.ThrottleManager.class)).throttleSetup(this, this.address, true); 361 getInitialStates(); 362 } 363 } 364 } else if (resultCode == 35) { 365 /** 366 * This message occurs when have already created a loco, but have 367 * not appended it to the database. The Ecos will not allow another 368 * loco to be created until the previous entry has been appended. 369 */ 370 371 //Potentially need to deal with this error better. 372 log.info("Another loco create operation is already taking place unable to create another."); 373 374 } else if (resultCode == 25) { 375 /** 376 * This section deals with no longer having control over the ecos 377 * loco object. we try three times to request control, on the fourth 378 * attempt we try a forced control, if that fails we inform the user 379 * and reset the counter to zero. 380 */ 381 retryControl(); 382 } else if (resultCode == 15) { 383 log.info("Loco can not be accessed via the Ecos Object Id {}", this.objectNumber); 384 try { 385 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("UnknownLocoDialog", this.address), 386 Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE); 387 } catch (HeadlessException he) { 388 // silently ignore inability to display dialog 389 } 390 jmri.InstanceManager.throttleManagerInstance().releaseThrottle(this, null); 391 } else { 392 log.debug("Last Message resulted in an END code we do not understand {}", resultCode); 393 } 394 } 395 396 /** 397 * Messages ignored. 398 * {@inheritDoc} 399 */ 400 @Override 401 public void message(EcosMessage m) { 402 } 403 404 public void forceControl() { 405 String message = "request(" + this.objectNumber + ", control, force)"; 406 EcosMessage ms = new EcosMessage(message); 407 tc.sendEcosMessage(ms, this); 408 } 409 410 //Converts the int value of the protocol to the ESU protocol string 411 private String protocol(LocoAddress.Protocol protocol) { 412 switch (protocol) { 413 case MOTOROLA: 414 return "MM28"; 415 case SELECTRIX: 416 return "SX28"; 417 case MFX: 418 return "MMFKT"; 419 case LGB: 420 return "LGB"; 421 default: 422 return "DCC128"; 423 } 424 } 425 426 private void createEcosLoco() { 427 objEcosLoco.setEcosDescription(Bundle.getMessage("CreatedByJMRI")); 428 objEcosLoco.setProtocol(protocol(address.getProtocol())); 429 String message = "create(10, addr[" + objEcosLoco.getNumber() + "], name[\"Created By JMRI\"], protocol[" + objEcosLoco.getECOSProtocol() + "], append)"; 430 EcosMessage m = new EcosMessage(message); 431 tc.sendEcosMessage(m, this); 432 } 433 434 private void retryControl() { 435 if (_haveControl) { 436 _hadControl = true; 437 } 438 _haveControl = false; 439 if (ecosretry < 3) { 440 //It might be worth adding in a sleep/pause of discription between retries. 441 ecosretry++; 442 443 String message = "request(" + this.objectNumber + ", view, control)"; 444 EcosMessage ms = new EcosMessage(message); 445 tc.sendEcosMessage(ms, this); 446 log.error("We have no control over the ecos object {} Retrying Attempt {}", this.objectNumber, ecosretry); 447 } else if (ecosretry == 3) { 448 ecosretry++; 449 int val = 0; 450 if (p.getForceControlFromEcos() == 0x00) { 451 try { 452 val = JmriJOptionPane.showConfirmDialog(null, "UnableToGainDialog", 453 Bundle.getMessage("WarningTitle"), 454 JmriJOptionPane.YES_NO_OPTION, JmriJOptionPane.QUESTION_MESSAGE); 455 } catch (HeadlessException he) { 456 val = 1; 457 } 458 } else { 459 if (p.getForceControlFromEcos() == 0x01) { 460 val = 1; 461 } 462 } 463 if (val == 0) { 464 String message = "request(" + this.objectNumber + ", view, control, force)"; 465 EcosMessage ms = new EcosMessage(message); 466 tc.sendEcosMessage(ms, this); 467 log.error("We have no control over the ecos object {}Trying a forced control", this.objectNumber); 468 } else { 469 if (_hadControl) { 470 firePropertyChange("LostControl", 0, 0); 471 _hadControl = false; 472 ecosretry = 0; 473 } else { 474 ((EcosDccThrottleManager) adapterMemo.get(jmri.ThrottleManager.class)).throttleSetup(this, this.address, false); 475 } 476 } 477 } else { 478 ecosretry = 0; 479 if (_hadControl) { 480 firePropertyChange("LostControl", 0, 0); 481 } else { 482 ((EcosDccThrottleManager) adapterMemo.get(jmri.ThrottleManager.class)).throttleSetup(this, this.address, false); 483 } 484 ((EcosDccThrottleManager) adapterMemo.get(jmri.ThrottleManager.class)).releaseThrottle(this, null); 485 } 486 } 487 488 void getInitialStates() { 489 String message = "get(" + this.objectNumber + ", speed)"; 490 EcosMessage m = new EcosMessage(message); 491 tc.sendEcosMessage(m, this); 492 message = "get(" + this.objectNumber + ", dir)"; 493 m = new EcosMessage(message); 494 tc.sendEcosMessage(m, this); 495 for (int i = 0; i <= 28; i++) { 496 message = "get(" + this.objectNumber + ", func[" + i + "])"; 497 m = new EcosMessage(message); 498 tc.sendEcosMessage(m, this); 499 } 500 } 501 502 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EcosDccThrottle.class); 503 504}