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