001package jmri.jmrix.nce; 002 003import org.slf4j.Logger; 004import org.slf4j.LoggerFactory; 005 006import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 007import jmri.DccLocoAddress; 008import jmri.LocoAddress; 009import jmri.jmrix.AbstractThrottle; 010 011/** 012 * An implementation of DccThrottle with code specific to an NCE connection. 013 * <p> 014 * Based on Glen Oberhauser's original LnThrottleManager implementation 015 * 016 * @author Bob Jacobsen Copyright (C) 2001 017 */ 018public class NceThrottle extends AbstractThrottle { 019 020 /* Note the NCE USB doesn't support the NMRA packet format. 021 * Before April 2010, this code would send NMRA packets if connected 022 * to the NCE command station. Now it always sends the A2 loco 023 * commands if the command station eprom was built after 2004. 024 */ 025 private boolean sendA2command = true; 026 027 private NceTrafficController tc = null; 028 029 /** 030 * Constructor. 031 * @param memo system connection memo to use 032 * @param address DCC loco address 033 */ 034 public NceThrottle(NceSystemConnectionMemo memo, DccLocoAddress address) { 035 super(memo); 036 this.tc = memo.getNceTrafficController(); 037 setSpeedStepMode(jmri.SpeedStepMode.NMRA_DCC_128); 038 039 // cache settings. It would be better to read the 040 // actual state, but I don't know how to do this 041 synchronized (this) { 042 this.speedSetting = 0; 043 } 044 // Functions default to false 045 this.address = address; 046 this.isForward = true; 047 048 // send NMRA style packets to old versions of NCE eprom 049 if (tc.getCommandOptions() <= NceTrafficController.OPTION_2004) { 050 sendA2command = false; 051 } 052 } 053 054 DccLocoAddress address; 055 056 @Override 057 public LocoAddress getLocoAddress() { 058 return address; 059 } 060 061 /** 062 * Send the message to set the state of functions F0, F1, F2, F3, F4. 063 */ 064 @Override 065 protected void sendFunctionGroup1() { 066 // The NCE USB doesn't support the NMRA packet format 067 // Always need speed command before function group command to reset consist pointer 068 synchronized (this) { 069 setSpeedSetting(this.speedSetting); 070 } 071 if (sendA2command) { 072 int locoAddr = address.getNumber(); 073 if (address.isLongAddress()) { 074 locoAddr += 0xC000; 075 } 076 077 int data = 0x00 078 | (getFunction(0) ? 0x10 : 0) 079 | (getFunction(1) ? 0x01 : 0) 080 | (getFunction(2) ? 0x02 : 0) 081 | (getFunction(3) ? 0x04 : 0) 082 | (getFunction(4) ? 0x08 : 0); 083 084 NceMessage m = NceMessage.sendLocoCmd(tc, locoAddr, NceMessage.LOCO_CMD_FG1, (byte) data); 085 tc.sendNceMessage(m, null); 086 087 } else { 088 byte[] result = jmri.NmraPacket.function0Through4Packet(address 089 .getNumber(), address.isLongAddress(), getFunction(0), getFunction(1), 090 getFunction(2), getFunction(3), getFunction(4)); 091 NceMessage m = NceMessage.sendPacketMessage(tc, result); 092 tc.sendNceMessage(m, null); 093 } 094 } 095 096 /** 097 * Send the message to set the state of functions F5, F6, F7, F8. 098 */ 099 @Override 100 protected void sendFunctionGroup2() { 101 // The NCE USB doesn't support the NMRA packet format 102 // Always need speed command before function group command to reset consist pointer 103 synchronized (this) { 104 setSpeedSetting(this.speedSetting); 105 } 106 if (sendA2command) { 107 int locoAddr = address.getNumber(); 108 if (address.isLongAddress()) { 109 locoAddr += 0xC000; 110 } 111 112 int data = 0x00 113 | (getFunction(8) ? 0x08 : 0) 114 | (getFunction(7) ? 0x04 : 0) 115 | (getFunction(6) ? 0x02 : 0) 116 | (getFunction(5) ? 0x01 : 0); 117 118 NceMessage m = NceMessage.sendLocoCmd(tc, locoAddr, NceMessage.LOCO_CMD_FG2, (byte) data); 119 tc.sendNceMessage(m, null); 120 121 } else { 122 byte[] result = jmri.NmraPacket.function5Through8Packet(address 123 .getNumber(), address.isLongAddress(), getFunction(5), getFunction(6), 124 getFunction(7), getFunction(8)); 125 NceMessage m = NceMessage.sendPacketMessage(tc, result); 126 tc.sendNceMessage(m, null); 127 } 128 } 129 130 /** 131 * Send the message to set the state of functions F9, F10, F11, F12. 132 */ 133 @Override 134 protected void sendFunctionGroup3() { 135 // The NCE USB doesn't support the NMRA packet format 136 // Always need speed command before function group command to reset consist pointer 137 synchronized (this) { 138 setSpeedSetting(this.speedSetting); 139 } 140 if (sendA2command) { 141 int locoAddr = address.getNumber(); 142 if (address.isLongAddress()) { 143 locoAddr += 0xC000; 144 } 145 146 int data = 0x00 147 | (getFunction(12) ? 0x08 : 0) 148 | (getFunction(11) ? 0x04 : 0) 149 | (getFunction(10) ? 0x02 : 0) 150 | (getFunction(9) ? 0x01 : 0); 151 152 NceMessage m = NceMessage.sendLocoCmd(tc, locoAddr, NceMessage.LOCO_CMD_FG3, (byte) data); 153 tc.sendNceMessage(m, null); 154 155 } else { 156 byte[] result = jmri.NmraPacket.function9Through12Packet(address 157 .getNumber(), address.isLongAddress(), getFunction(9), getFunction(10), 158 getFunction(11), getFunction(12)); 159 NceMessage m = NceMessage.sendPacketMessage(tc, result); 160 tc.sendNceMessage(m, null); 161 } 162 } 163 164 /** 165 * Send the message to set the state of functions F13, F14, F15, F16, F17, 166 * F18, F19, F20 167 */ 168 @Override 169 protected void sendFunctionGroup4() { 170 // The NCE USB doesn't support the NMRA packet format 171 // Always need speed command before function group command to reset consist pointer 172 synchronized (this) { 173 setSpeedSetting(this.speedSetting); 174 } 175 if (sendA2command) { 176 int locoAddr = address.getNumber(); 177 if (address.isLongAddress()) { 178 locoAddr += 0xC000; 179 } 180 181 int data = 0x00 182 | (getFunction(20) ? 0x80 : 0) 183 | (getFunction(19) ? 0x40 : 0) 184 | (getFunction(18) ? 0x20 : 0) 185 | (getFunction(17) ? 0x10 : 0) 186 | (getFunction(16) ? 0x08 : 0) 187 | (getFunction(15) ? 0x04 : 0) 188 | (getFunction(14) ? 0x02 : 0) 189 | (getFunction(13) ? 0x01 : 0); 190 191 NceMessage m = NceMessage.sendLocoCmd(tc, locoAddr, NceMessage.LOCO_CMD_FG4, (byte) data); 192 tc.sendNceMessage(m, null); 193 194 } else { 195 // Note NCE EPROM 2004 doesn't support LOCO_CMD_FG4 196 byte[] result = jmri.NmraPacket.function13Through20Packet(address 197 .getNumber(), address.isLongAddress(), getFunction(13), getFunction(14), 198 getFunction(15), getFunction(16), getFunction(17), getFunction(18), getFunction(19), getFunction(20)); 199 NceMessage m = NceMessage.sendPacketMessage(tc, result); 200 tc.sendNceMessage(m, null); 201 } 202 } 203 204 /** 205 * Send the message to set the state of functions F21, F22, F23, F24, F25, 206 * F26, F27, F28 207 */ 208 @Override 209 protected void sendFunctionGroup5() { 210 // The NCE USB doesn't support the NMRA packet format 211 // Always need speed command before function group command to reset consist pointer 212 synchronized (this) { 213 setSpeedSetting(this.speedSetting); 214 } 215 if (sendA2command) { 216 int locoAddr = address.getNumber(); 217 if (address.isLongAddress()) { 218 locoAddr += 0xC000; 219 } 220 221 int data = 0x00 222 | (getFunction(28) ? 0x80 : 0) 223 | (getFunction(27) ? 0x40 : 0) 224 | (getFunction(26) ? 0x20 : 0) 225 | (getFunction(25) ? 0x10 : 0) 226 | (getFunction(24) ? 0x08 : 0) 227 | (getFunction(23) ? 0x04 : 0) 228 | (getFunction(22) ? 0x02 : 0) 229 | (getFunction(21) ? 0x01 : 0); 230 231 NceMessage m = NceMessage.sendLocoCmd(tc, locoAddr, NceMessage.LOCO_CMD_FG5, (byte) data); 232 tc.sendNceMessage(m, null); 233 234 } else { 235 // Note NCE EPROM 2004 doesn't support LOCO_CMD_FG5 236 byte[] result = jmri.NmraPacket.function21Through28Packet(address 237 .getNumber(), address.isLongAddress(), getFunction(21), getFunction(22), 238 getFunction(23), getFunction(24), getFunction(25), getFunction(26), getFunction(27), getFunction(28)); 239 NceMessage m = NceMessage.sendPacketMessage(tc, result); 240 tc.sendNceMessage(m, null); 241 } 242 } 243 244 /** 245 * Set the speed and direction. 246 * 247 * @param speed Number from 0 to 1; less than zero is emergency stop 248 */ 249 @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY") // OK to compare floating point, notify on any change 250 @Override 251 public void setSpeedSetting(float speed) { 252 float oldSpeed; 253 synchronized (this) { 254 oldSpeed = this.speedSetting; 255 this.speedSetting = speed; 256 } 257 log.debug("setSpeedSetting= {}", speed); 258 259 // The NCE USB doesn't support the NMRA packet format 260 if (sendA2command) { 261 byte[] bl; 262 int value; 263 int locoAddr = address.getNumber(); 264 if (address.isLongAddress()) { 265 locoAddr += 0xC000; 266 } 267 value = Math.round((127 - 1) * speed); // -1 for rescale to avoid estop 268 if (speed > 0 && value == 0) { 269 value = 1; // ensure non-zero input results in non-zero output 270 } 271 if (speed < 0) { 272 value = -1; // ensure small negative speeds don't get caught by Math.round above 273 } 274 if (value > 126) { 275 value = 126; // max possible speed, 127 can crash PowerCab! 276 } // emergency stop? 277 if (value < 0) { 278 279 bl = NceBinaryCommand.nceLocoCmd(locoAddr, 280 (isForward ? NceMessage.LOCO_CMD_FWD_ESTOP 281 : NceMessage.LOCO_CMD_REV_ESTOP), 282 (byte) 0); 283 284 } else if (super.speedStepMode == jmri.SpeedStepMode.NMRA_DCC_128) { 285 bl = NceBinaryCommand.nceLocoCmd(locoAddr, 286 (isForward ? NceMessage.LOCO_CMD_FWD_128SPEED 287 : NceMessage.LOCO_CMD_REV_128SPEED), 288 (byte) value); 289 } else { 290 // 28 speed step mode 291 bl = NceBinaryCommand.nceLocoCmd(locoAddr, 292 (isForward ? NceMessage.LOCO_CMD_FWD_28SPEED 293 : NceMessage.LOCO_CMD_REV_28SPEED), 294 (byte) value); 295 } 296 NceMessage m = NceMessage.createBinaryMessage(tc, bl); 297 tc.sendNceMessage(m, null); 298 299 } else { 300 byte[] bl; 301 int value; 302 303 if (super.speedStepMode == jmri.SpeedStepMode.NMRA_DCC_128) { 304 value = Math.round((127 - 1) * speed); // -1 for rescale to avoid estop 305 if (speed > 0 && value == 0) { 306 value = 1; // ensure non-zero input results in non-zero output 307 } 308 if (value > 0) { 309 value = value + 1; // skip estop 310 } 311 if (value > 127) { 312 value = 127; // max possible speed 313 } 314 if (speed < 0) { 315 value = 1; // emergency stop 316 // ensure small negative speeds don't get caught by Math.round above 317 } 318 bl = jmri.NmraPacket.speedStep128Packet(address.getNumber(), 319 address.isLongAddress(), value, isForward); 320 } else { 321 322 /* [A Crosland 05Feb12] There is a potential issue in the way 323 * the float speed value is converted to integer speed step. 324 * A max speed value of 1 is first converted to int 28 then incremented 325 * to 29 which is too large. The next highest speed value also 326 * results in a value of 28. So two discrete throttle steps 327 * both map to speed step 28. 328 * 329 * This is compounded by the bug in speedStep28Packet() which 330 * cannot generate a DCC packet with speed step 28. 331 * 332 * Suggested correct code is 333 * value = (int) ((31-3) * speed); // -3 for rescale to avoid stop and estop x2 334 * if (value > 0) value = value + 3; // skip stop and estop x2 335 * if (value > 31) value = 31; // max possible speed 336 * if (value < 0) value = 2; // emergency stop 337 * bl = jmri.NmraPacket.speedStep28Packet(true, address.getNumber(), 338 * address.isLongAddress(), value, isForward); 339 */ 340 value = Math.round((28-1) * speed); // -1 for rescale to avoid estop 341 if (speed > 0 && value == 0) { 342 value = 1; // ensure non-zero input results in non-zero output 343 } 344 if (value > 0) { 345 value = value + 1; // skip estop 346 } 347 if (value > 28) { 348 value = 28; // max possible speed 349 } 350 if (speed < 0) { 351 value = 1; // emergency stop 352 // ensure small negative speeds don't get caught by Math.round above 353 } 354 bl = jmri.NmraPacket.speedStep28Packet(address.getNumber(), 355 address.isLongAddress(), value, isForward); 356 } 357 NceMessage m = NceMessage.queuePacketMessage(tc, bl); 358 tc.sendNceMessage(m, null); 359 360 } 361 synchronized (this) { 362 firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting); 363 } 364 record(speed); 365 } 366 367 @Override 368 public void setIsForward(boolean forward) { 369 boolean old = isForward; 370 isForward = forward; 371 synchronized (this) { 372 setSpeedSetting(speedSetting); // send the command 373 } 374 log.debug("setIsForward= {}", forward); 375 firePropertyChange(ISFORWARD, old, isForward); 376 } 377 378 @Override 379 public void throttleDispose() { 380 finishRecord(); 381 } 382 383 // initialize logging 384 private final static Logger log = LoggerFactory.getLogger(NceThrottle.class); 385 386}