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 @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY") // OK to compare floating point 241 @Override 242 public void reply(EcosReply m) { 243 int resultCode = m.getResultCode(); 244 if (resultCode == 0) { 245 String replyType = m.getReplyType(); 246 if (replyType.equals("create")) { 247 String[] msgDetails = m.getContents(); 248 for (String line : msgDetails) { 249 if (line.startsWith("10 id[")) { 250 String EcosAddr = EcosReply.getContentDetail(line); 251 objEcosLoco.setEcosObject(EcosAddr); 252 objEcosLocoManager.deregister(objEcosLoco); 253 objEcosLocoManager.register(objEcosLoco); 254 objEcosLoco.setEcosTempEntry(true); 255 objEcosLoco.doNotAddToRoster(); 256 this.objectNumber = EcosAddr; 257 getControl(); 258 } 259 } 260 return; 261 } 262 263 /*if (lines[lines.length-1].contains("<END 0 (NERROR_OK)>")){ 264 //Need to investigate this a bit futher to see what the significance of the message is 265 //we may not have to worry much about it. 266 log.info("Loco has been created on the ECoS Sucessfully."); 267 return; 268 }*/ 269 if (m.getEcosObjectId() != objEcosLoco.getEcosObjectAsInt()) { 270 log.debug("message is not for us"); 271 return; 272 } 273 if (replyType.equals("set")) { 274 //This might need to use speedstep, rather than speed 275 //This is for standard response to set and request. 276 String[] msgDetails = m.getContents(); 277 for (String line : msgDetails) { 278 if (line.contains("speed") && !line.contains("speedstep")) { 279 speedMessageSent--; 280 if (speedMessageSent <= 0) { 281 Float newSpeed = floatSpeed(Integer.parseInt(EcosReply.getContentDetails(line, "speed"))); 282 super.setSpeedSetting(newSpeed); 283 } 284 } 285 if (line.contains("dir")) { 286 boolean newDirection = false; 287 if (EcosReply.getContentDetails(line, "dir").equals("0")) { 288 newDirection = true; 289 } 290 super.setIsForward(newDirection); 291 } 292 } 293 if (msgDetails.length == 0) { 294 //For some reason in recent ECOS software releases we do not get the contents, only a header and End State 295 if (m.toString().contains("speed") && !m.toString().contains("speedstep")) { 296 speedMessageSent--; 297 if (speedMessageSent <= 0) { 298 Float newSpeed = floatSpeed(Integer.parseInt(EcosReply.getContentDetails(m.toString(), "speed"))); 299 super.setSpeedSetting(newSpeed); 300 } 301 } 302 if (m.toString().contains("dir")) { 303 boolean newDirection = false; 304 if (EcosReply.getContentDetails(m.toString(), "dir").equals("0")) { 305 newDirection = true; 306 } 307 super.setIsForward(newDirection); 308 } 309 } 310 } //Treat gets and events as the same. 311 else if ((replyType.equals("get")) || (m.isUnsolicited())) { 312 //log.debug("The last command was accepted by the ecos"); 313 String[] msgDetails = m.getContents(); 314 for (String line : msgDetails) { 315 if (speedMessageSent > 0 && m.isUnsolicited() && line.contains("speed")) { 316 //We want to ignore these messages. 317 } else if (speedMessageSent <= 0 && line.contains("speed") && !line.contains("speedstep")) { 318 Float newSpeed = floatSpeed(Integer.parseInt(EcosReply.getContentDetails(line, "speed"))); 319 super.setSpeedSetting(newSpeed); 320 } else if (line.contains("dir")) { 321 boolean newDirection = false; 322 if (EcosReply.getContentDetails(line, "dir").equals("0")) { 323 newDirection = true; 324 } 325 super.setIsForward(newDirection); 326 } else if (line.contains("protocol")) { 327 String pro = EcosReply.getContentDetails(line, "protocol"); 328 if (pro.equals("DCC128")) { 329 setSpeedStepMode(SpeedStepMode.NMRA_DCC_128); 330 } else if (pro.equals("DCC28")) { 331 setSpeedStepMode(SpeedStepMode.NMRA_DCC_28); 332 } else if (pro.equals("DCC14")) { 333 setSpeedStepMode(SpeedStepMode.NMRA_DCC_14); 334 } 335 } else if (line.contains("func[")) { 336 String funcStr = EcosReply.getContentDetails(line, "func"); 337 int function = Integer.parseInt(funcStr.substring(0, funcStr.indexOf(",")).trim()); 338 int functionValue = Integer.parseInt(funcStr.substring((funcStr.indexOf(",") + 1), funcStr.length()).trim()); 339 updateFunction(function,functionValue == 1); 340 341 } else if (line.contains("msg")) { 342 //We get this lost control error because we have registered as a viewer. 343 if (line.contains("CONTROL_LOST")) { 344 retryControl(); 345 log.debug("We have no control over the ecos object, but will retry."); 346 } 347 348 } 349 350 } 351 } else if (replyType.equals("release")) { 352 log.debug("Released {} from the Ecos", this.objectNumber); 353 _haveControl = false; 354 } else if (replyType.equals("request")) { 355 log.debug("We have control over {} from the Ecos", this.objectNumber); 356 ecosretry = 0; 357 if (_control) { 358 _haveControl = true; 359 } 360 if (!_hadControl) { 361 ((EcosDccThrottleManager) adapterMemo.get(jmri.ThrottleManager.class)).throttleSetup(this, this.address, true); 362 getInitialStates(); 363 } 364 } 365 } else if (resultCode == 35) { 366 /** 367 * This message occurs when have already created a loco, but have 368 * not appended it to the database. The Ecos will not allow another 369 * loco to be created until the previous entry has been appended. 370 */ 371 372 //Potentially need to deal with this error better. 373 log.info("Another loco create operation is already taking place unable to create another."); 374 375 } else if (resultCode == 25) { 376 /** 377 * This section deals with no longer having control over the ecos 378 * loco object. we try three times to request control, on the fourth 379 * attempt we try a forced control, if that fails we inform the user 380 * and reset the counter to zero. 381 */ 382 retryControl(); 383 } else if (resultCode == 15) { 384 log.info("Loco can not be accessed via the Ecos Object Id {}", this.objectNumber); 385 try { 386 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("UnknownLocoDialog", this.address), 387 Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE); 388 } catch (HeadlessException he) { 389 // silently ignore inability to display dialog 390 } 391 jmri.InstanceManager.throttleManagerInstance().releaseThrottle(this, null); 392 } else { 393 log.debug("Last Message resulted in an END code we do not understand {}", resultCode); 394 } 395 } 396 397 /** 398 * Messages ignored. 399 * {@inheritDoc} 400 */ 401 @Override 402 public void message(EcosMessage m) { 403 } 404 405 public void forceControl() { 406 String message = "request(" + this.objectNumber + ", control, force)"; 407 EcosMessage ms = new EcosMessage(message); 408 tc.sendEcosMessage(ms, this); 409 } 410 411 //Converts the int value of the protocol to the ESU protocol string 412 private String protocol(LocoAddress.Protocol protocol) { 413 switch (protocol) { 414 case MOTOROLA: 415 return "MM28"; 416 case SELECTRIX: 417 return "SX28"; 418 case MFX: 419 return "MMFKT"; 420 case LGB: 421 return "LGB"; 422 default: 423 return "DCC128"; 424 } 425 } 426 427 private void createEcosLoco() { 428 objEcosLoco.setEcosDescription(Bundle.getMessage("CreatedByJMRI")); 429 objEcosLoco.setProtocol(protocol(address.getProtocol())); 430 String message = "create(10, addr[" + objEcosLoco.getNumber() + "], name[\"Created By JMRI\"], protocol[" + objEcosLoco.getECOSProtocol() + "], append)"; 431 EcosMessage m = new EcosMessage(message); 432 tc.sendEcosMessage(m, this); 433 } 434 435 private void retryControl() { 436 if (_haveControl) { 437 _hadControl = true; 438 } 439 _haveControl = false; 440 if (ecosretry < 3) { 441 //It might be worth adding in a sleep/pause of discription between retries. 442 ecosretry++; 443 444 String message = "request(" + this.objectNumber + ", view, control)"; 445 EcosMessage ms = new EcosMessage(message); 446 tc.sendEcosMessage(ms, this); 447 log.error("We have no control over the ecos object {} Retrying Attempt {}", this.objectNumber, ecosretry); 448 } else if (ecosretry == 3) { 449 ecosretry++; 450 int val = 0; 451 if (p.getForceControlFromEcos() == 0x00) { 452 try { 453 val = JmriJOptionPane.showConfirmDialog(null, "UnableToGainDialog", 454 Bundle.getMessage("WarningTitle"), 455 JmriJOptionPane.YES_NO_OPTION, JmriJOptionPane.QUESTION_MESSAGE); 456 } catch (HeadlessException he) { 457 val = 1; 458 } 459 } else { 460 if (p.getForceControlFromEcos() == 0x01) { 461 val = 1; 462 } 463 } 464 if (val == 0) { 465 String message = "request(" + this.objectNumber + ", view, control, force)"; 466 EcosMessage ms = new EcosMessage(message); 467 tc.sendEcosMessage(ms, this); 468 log.error("We have no control over the ecos object {}Trying a forced control", this.objectNumber); 469 } else { 470 if (_hadControl) { 471 firePropertyChange("LostControl", 0, 0); 472 _hadControl = false; 473 ecosretry = 0; 474 } else { 475 ((EcosDccThrottleManager) adapterMemo.get(jmri.ThrottleManager.class)).throttleSetup(this, this.address, false); 476 } 477 } 478 } else { 479 ecosretry = 0; 480 if (_hadControl) { 481 firePropertyChange("LostControl", 0, 0); 482 } else { 483 ((EcosDccThrottleManager) adapterMemo.get(jmri.ThrottleManager.class)).throttleSetup(this, this.address, false); 484 } 485 ((EcosDccThrottleManager) adapterMemo.get(jmri.ThrottleManager.class)).releaseThrottle(this, null); 486 } 487 } 488 489 void getInitialStates() { 490 String message = "get(" + this.objectNumber + ", speed)"; 491 EcosMessage m = new EcosMessage(message); 492 tc.sendEcosMessage(m, this); 493 message = "get(" + this.objectNumber + ", dir)"; 494 m = new EcosMessage(message); 495 tc.sendEcosMessage(m, this); 496 for (int i = 0; i <= 28; i++) { 497 message = "get(" + this.objectNumber + ", func[" + i + "])"; 498 m = new EcosMessage(message); 499 tc.sendEcosMessage(m, this); 500 } 501 } 502 503 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EcosDccThrottle.class); 504 505}