001package jmri.jmrix.tams; 002 003import java.util.LinkedList; 004import java.util.Queue; 005import jmri.DccLocoAddress; 006import jmri.LocoAddress; 007import jmri.jmrix.AbstractThrottle; 008import jmri.util.StringUtil; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012/** 013 * An implementation of DccThrottle with code specific to a TAMS connection. 014 * <p> 015 * Based on Glen Oberhauser's original LnThrottle implementation and work by 016 * Kevin Dickerson 017 * 018 * @author Jan Boen 019 */ 020public class TamsThrottle extends AbstractThrottle implements TamsListener { 021 022 //Create a local TamsMessage Queue which we will use in combination with TamsReplies 023 private final Queue<TamsMessage> tmq = new LinkedList<>(); 024 025 //This dummy message is used in case we expect a reply from polling 026 static private TamsMessage myDummy() { 027 log.trace("*** myDummy ***"); 028 TamsMessage m = new TamsMessage(2); 029 m.setElement(0, TamsConstants.POLLMSG & TamsConstants.MASKFF); 030 m.setElement(1, TamsConstants.XEVTLOK & TamsConstants.MASKFF); 031 m.setBinary(true); 032 m.setReplyOneByte(false); 033 m.setReplyType('L'); 034 return m; 035 } 036 037 public TamsThrottle(TamsSystemConnectionMemo memo, DccLocoAddress address) { 038 super(memo); 039 super.speedStepMode = jmri.SpeedStepMode.NMRA_DCC_128; 040 tc = memo.getTrafficController(); 041 042 // cache settings. It would be better to read the 043 // actual state, but I don't know how to do this 044 synchronized(this) { 045 this.speedSetting = 0; 046 } 047 // Functions default to false 048 this.address = address; 049 this.isForward = true; 050 051 //get the status if known of the current loco 052 TamsMessage tm = new TamsMessage("xL " + address.getNumber()); 053 tm.setTimeout(10000); 054 tm.setBinary(false); 055 tm.setReplyType('L'); 056 tc.sendTamsMessage(tm, this); 057 tmq.add(tm); 058 //tc.addPollMessage(m, this); 059 060 tm = new TamsMessage("xF " + address.getNumber()); 061 tm.setBinary(false); 062 tm.setReplyType('L'); 063 tc.sendTamsMessage(tm, this); 064 tmq.add(tm); 065 //tc.addPollMessage(tm, this); 066 067 tm = new TamsMessage("xFX " + address.getNumber()); 068 tm.setBinary(false); 069 tm.setReplyType('L'); 070 tc.sendTamsMessage(tm, this); 071 tmq.add(tm); 072 //tc.addPollMessage(tm, this); 073 074 //Add binary polling message 075 tm = TamsMessage.getXEvtLok(); 076 tc.sendTamsMessage(tm, this); 077 tmq.add(tm); 078 tc.addPollMessage(tm, this); 079 080 } 081 082 /** 083 * Send the message to set the state of functions F0, F1, F2, F3, F4. To 084 * send function group 1 we have to also send speed, direction etc. 085 */ 086 @Override 087 protected void sendFunctionGroup1() { 088 089 StringBuilder sb = new StringBuilder(); 090 sb.append("xL "); 091 sb.append(address.getNumber()); 092 sb.append(","); 093 sb.append(","); 094 sb.append((getFunction(0) ? "1" : "0")); 095 sb.append(","); 096 sb.append(","); 097 sb.append((getFunction(1) ? "1" : "0")); 098 sb.append(","); 099 sb.append((getFunction(2) ? "1" : "0")); 100 sb.append(","); 101 sb.append((getFunction(3) ? "1" : "0")); 102 sb.append(","); 103 sb.append((getFunction(4) ? "1" : "0")); 104 TamsMessage tm = new TamsMessage(sb.toString()); 105 tm.setBinary(false); 106 tm.setReplyType('L'); 107 tc.sendTamsMessage(tm, this); 108 tmq.add(tm); 109 } 110 111 /** 112 * Send the message to set the state of functions F5, F6, F7, F8. 113 */ 114 @Override 115 protected void sendFunctionGroup2() { 116 StringBuilder sb = new StringBuilder(); 117 sb.append("xF "); 118 sb.append(address.getNumber()); 119 sb.append(","); 120 sb.append(","); 121 sb.append(","); 122 sb.append(","); 123 sb.append(","); 124 sb.append((getFunction(5) ? "1" : "0")); 125 sb.append(","); 126 sb.append((getFunction(6) ? "1" : "0")); 127 sb.append(","); 128 sb.append((getFunction(7) ? "1" : "0")); 129 sb.append(","); 130 sb.append((getFunction(8) ? "1" : "0")); 131 132 TamsMessage tm = new TamsMessage(sb.toString()); 133 tm.setBinary(false); 134 tm.setReplyType('T'); 135 tc.sendTamsMessage(tm, this); 136 tmq.add(tm); 137 } 138 139 @Override 140 protected void sendFunctionGroup3() { 141 StringBuilder sb = new StringBuilder(); 142 sb.append("xFX "); 143 sb.append(address.getNumber()); 144 sb.append(","); 145 sb.append((getFunction(9) ? "1" : "0")); 146 sb.append(","); 147 sb.append((getFunction(10) ? "1" : "0")); 148 sb.append(","); 149 sb.append((getFunction(11) ? "1" : "0")); 150 sb.append(","); 151 sb.append((getFunction(12) ? "1" : "0")); 152 153 TamsMessage tm = new TamsMessage(sb.toString()); 154 tm.setBinary(false); 155 tm.setReplyType('L'); 156 tc.sendTamsMessage(tm, this); 157 tmq.add(tm); 158 } 159 160 /** 161 * Set the speed and direction. 162 * <p> 163 * This intentionally skips the emergency stop value of 1. 164 * 165 * @param speed Number from 0 to 1; less than zero is emergency stop 166 */ 167 @Override 168 public synchronized void setSpeedSetting(float speed) { 169 float oldSpeed = this.speedSetting; 170 this.speedSetting = speed; 171 172 int value = Math.round((127 - 1) * this.speedSetting); // -1 for rescale to avoid estop 173 if (this.speedSetting > 0 && value == 0) { 174 value = 1; // ensure non-zero input results in non-zero output 175 } 176 if (value > 0) { 177 value = value + 1; // skip estop 178 } 179 if (value > 127) { 180 value = 127; // max possible speed 181 } 182 if (value < 0) { 183 value = 1; // emergency stop 184 } 185 StringBuilder sb = new StringBuilder(); 186 sb.append("xL "); 187 sb.append(address.getNumber()); 188 sb.append(","); 189 sb.append(value); 190 sb.append(","); 191 sb.append(","); 192 sb.append((isForward ? "f" : "r")); 193 sb.append(","); 194 sb.append(","); 195 sb.append(","); 196 sb.append(","); 197 198 TamsMessage tm = new TamsMessage(sb.toString()); 199 tm.setBinary(false); 200 tm.setReplyType('L'); 201 tc.sendTamsMessage(tm, this); 202 tmq.add(tm); 203 204 firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting); 205 record(speed); 206 } 207 208 @Override 209 public void setIsForward(boolean forward) { 210 boolean old = isForward; 211 isForward = forward; 212 synchronized(this) { 213 setSpeedSetting(speedSetting); // send the command 214 } 215 firePropertyChange(ISFORWARD, old, isForward); 216 } 217 218 private final DccLocoAddress address; 219 220 TamsTrafficController tc; 221 222 @Override 223 public LocoAddress getLocoAddress() { 224 return address; 225 } 226 227 @Override 228 public void throttleDispose() { 229 active = false; 230 TamsMessage tm = TamsMessage.getXEvtLok(); 231 tc.removePollMessage(tm, this); 232 finishRecord(); 233 } 234 235 @Override 236 public void message(TamsMessage m) { 237 // messages are ignored 238 } 239 240 /** 241 * Convert a Tams speed integer to a float speed value. 242 * 243 * @param lSpeed Tams speed 244 * @return speed as -1 or number between 0 and 1, inclusive 245 */ 246 protected float floatSpeed(int lSpeed) { 247 if (lSpeed == 0) { 248 return 0.f; 249 } else if (lSpeed == 1) { 250 return -1.f; // estop 251 } else if (super.speedStepMode == jmri.SpeedStepMode.NMRA_DCC_128) { 252 return ((lSpeed - 1) / 126.f); 253 } else { 254 // pretty sure this is wrong, it's definitely never going to be < 1. 255 return (int) (lSpeed * 27.f + 0.5) + 1; 256 } 257 } 258 259 @Override 260 public void reply(TamsReply tr) { 261 log.trace("*** Loco reply ***"); 262 TamsMessage tm = tmq.isEmpty() ? myDummy() : tmq.poll(); 263 if (tm.isBinary()) {//Binary reply 264 //The binary logic as created by Jan 265 //Complete Loco status is given by: 266 // element(0) = Speed: 0..127, 0 = Stop, 1 = not used, 2 = min. Speed, 127 = max. Speed 267 // element(1) = F1..F8 (bit #0..7) 268 // element(2) = low byte of Loco# (A7..A0) 269 // element(3) = high byte of Loco#, plus Dir and Light status as in: 270 // bit# 7 6 5 4 3 2 1 0 271 // +-----+-----+-----+-----+-----+-----+-----+-----+ 272 // | Dir | FL | A13 | A12 | A11 | A10 | A9 | A8 | 273 // +-----+-----+-----+-----+-----+-----+-----+-----+ 274 // where: 275 // Dir Loco direction (1 = forward) 276 // FL Light status 277 // A13..8 high bits of Loco# 278 // element(4) = 'real' Loco speed (in terms of the Loco type/configuration) 279 // (please check XLokSts in P50X_LT.TXT for doc on 'real' speed) 280 //Decode address 281 int msb = tr.getElement(3) & 0x3F; 282 int lsb = tr.getElement(2) & 0xFF; 283 int receivedAddress = msb * 256 + lsb; 284 if (log.isTraceEnabled()) { // avoid overhead of StringUtil calls 285 log.trace("reply for loco = {}", receivedAddress); 286 log.trace("reply = {} {} {} {} {}", StringUtil.appendTwoHexFromInt(tr.getElement(4) & 0xFF, ""), StringUtil.appendTwoHexFromInt(tr.getElement(3) & 0xFF, ""), StringUtil.appendTwoHexFromInt(tr.getElement(2) & 0xFF, ""), StringUtil.appendTwoHexFromInt(tr.getElement(1) & 0xFF, ""), StringUtil.appendTwoHexFromInt(tr.getElement(0) & 0xFF, "")); 287 } 288 if (receivedAddress == address.getNumber()) {//If correct address then decode the content 289 log.trace("Is my address"); 290 try { 291 StringBuilder sb = new StringBuilder(); 292 Float newSpeed = floatSpeed(tr.getElement(0)); 293 super.setSpeedSetting(newSpeed); 294 log.trace("f0 = {}", tr.getElement(3) & 0x40); 295 296 appendFuncString(0,sb,((tr.getElement(3) & 0x40) == 64)); 297 298 if (((tr.getElement(3) & 0x80) == 0) && isForward) { 299 isForward = false; 300 firePropertyChange(ISFORWARD, true, isForward); 301 } 302 if (((tr.getElement(3) & 0x80) == 128) && !isForward) { 303 isForward = true; 304 firePropertyChange(ISFORWARD, false, isForward); 305 } 306 307 appendFuncString(1,sb,((tr.getElement(1) & 0x01) == 0x01)); 308 appendFuncString(2,sb,((tr.getElement(1) & 0x02) == 0x02)); 309 appendFuncString(3,sb,((tr.getElement(1) & 0x04) == 0x04)); 310 appendFuncString(4,sb,((tr.getElement(1) & 0x08) == 0x08)); 311 appendFuncString(5,sb,((tr.getElement(1) & 0x10) == 0x10)); 312 appendFuncString(6,sb,((tr.getElement(1) & 0x20) == 0x20)); 313 appendFuncString(7,sb,((tr.getElement(1) & 0x40) == 0x40)); 314 appendFuncString(8,sb,((tr.getElement(1) & 0x80) == 0x80)); 315 316 log.trace("Functions: {}", sb ); 317 } catch (RuntimeException ex) { 318 log.error("Error handling reply from MC", ex); 319 } 320 } 321 322 } else {//ASCII reply 323 //The original logic as provided by Kevin 324 if (tr.match("WARNING") >= 0) { 325 return; 326 } 327 if (tr.match("L " + address.getNumber()) >= 0) { 328 try { 329 log.trace("ASCII address = {}", address.getNumber()); 330 String[] lines = tr.toString().split(" "); 331 Float newSpeed = floatSpeed(Integer.parseInt(lines[2])); 332 super.setSpeedSetting(newSpeed); 333 updateFunction(0,lines[3].equals("1")); 334 335 if (lines[4].equals("r") && isForward) { 336 isForward = false; 337 firePropertyChange(ISFORWARD, true, isForward); 338 } else if (lines[4].equals("f") && !isForward) { 339 isForward = true; 340 firePropertyChange(ISFORWARD, false, isForward); 341 } 342 343 updateFunction(1,lines[5].equals("1")); 344 updateFunction(2,lines[6].equals("1")); 345 updateFunction(3,lines[7].equals("1")); 346 updateFunction(4,lines[8].equals("1")); 347 } catch (NumberFormatException ex) { 348 log.error("Error phrasing reply from MC", ex); 349 } 350 } else if (tr.match("FX " + address.getNumber()) >= 0) { 351 String[] lines = tr.toString().split(" "); 352 try { 353 updateFunction(9,lines[2].equals("1")); 354 updateFunction(10,lines[3].equals("1")); 355 updateFunction(11,lines[4].equals("1")); 356 updateFunction(12,lines[5].equals("1")); 357 updateFunction(13,lines[6].equals("1")); 358 updateFunction(14,lines[7].equals("1")); 359 } catch (RuntimeException ex) { 360 log.error("Error phrasing reply from MC", ex); 361 } 362 } else if (tr.match("F " + address.getNumber()) >= 0) { 363 String[] lines = tr.toString().split(" "); 364 try { 365 updateFunction(1,lines[2].equals("1")); 366 updateFunction(2,lines[3].equals("1")); 367 updateFunction(3,lines[4].equals("1")); 368 updateFunction(4,lines[5].equals("1")); 369 updateFunction(5,lines[6].equals("1")); 370 updateFunction(6,lines[7].equals("1")); 371 updateFunction(7,lines[8].equals("1")); 372 updateFunction(8,lines[9].equals("1")); 373 } catch (RuntimeException ex) { 374 log.error("Error phrasing reply from MC", ex); 375 } 376 } else if (tr.toString().equals("ERROR: no data.")) { 377 log.debug("Loco has no data"); 378 } 379 } 380 } 381 382 private void appendFuncString(int Fn, StringBuilder sb, boolean value){ 383 updateFunction(Fn,value); 384 if (getFunction(Fn)){ 385 sb.append("f"); 386 sb.append(String.valueOf(Fn)); 387 } else { 388 sb.append(String.valueOf(Fn)); 389 sb.append("f"); 390 } 391 if (Fn<8){ 392 sb.append(" "); 393 } 394 } 395 396 // initialize logging 397 private final static Logger log = LoggerFactory.getLogger(TamsThrottle.class); 398 399}