001package jmri.jmrix.easydcc; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import jmri.DccLocoAddress; 005import jmri.LocoAddress; 006import jmri.SpeedStepMode; 007import jmri.jmrix.AbstractThrottle; 008 009/** 010 * An implementation of DccThrottle with code specific to an EasyDCC connection. 011 * <p> 012 * Addresses of 99 and below are considered short addresses, and over 100 are 013 * considered long addresses. 014 * <p> 015 * Based on Glen Oberhauser's original LnThrottleManager implementation and NCEThrottle 016 * 017 * @author Bob Jacobsen Copyright (C) 2001, modified 2004 by Kelly Loyd 018 */ 019public class EasyDccThrottle extends AbstractThrottle { 020 021 /** 022 * Constructor. 023 * 024 * @param memo the connected EasyDccTrafficController 025 * @param address Loco ID 026 */ 027 public EasyDccThrottle(EasyDccSystemConnectionMemo memo, DccLocoAddress address) { 028 super(memo); 029 super.speedStepMode = SpeedStepMode.NMRA_DCC_128; 030 tc = memo.getTrafficController(); 031 032 // cache settings. It would be better to read the 033 // actual state, but I don't know how to do this 034 synchronized (this) { 035 this.speedSetting = 0; 036 } 037 // Functions default to false 038 this.address = address; 039 this.isForward = true; 040 } 041 042 /** 043 * Send the message to set the state of functions F0, F1, F2, F3, F4. 044 */ 045 @Override 046 protected void sendFunctionGroup1() { 047 byte[] result = jmri.NmraPacket.function0Through4Packet(address.getNumber(), 048 address.isLongAddress(), 049 getFunction(0), getFunction(1), getFunction(2), getFunction(3), getFunction(4)); 050 051 /* Format of EasyDcc 'send' command 052 * S nn xx yy 053 * nn = number of times to send - usually 01 is sufficient. 054 * xx = Cx for 4 digit or 00 for 2 digit addresses 055 * yy = LSB of address for 4 digit, or just 2 digit address 056 */ 057 EasyDccMessage m = new EasyDccMessage(4 + 3 * result.length); 058 int i = 0; // message index counter 059 m.setElement(i++, 'S'); 060 m.setElement(i++, ' '); 061 m.setElement(i++, '0'); 062 m.setElement(i++, '1'); 063 064 for (int j = 0; j < result.length; j++) { 065 m.setElement(i++, ' '); 066 m.addIntAsTwoHex(result[j] & 0xFF, i); 067 i = i + 2; 068 } 069 tc.sendEasyDccMessage(m, null); 070 } 071 072 /** 073 * Send the message to set the state of functions F5, F6, F7, F8. 074 */ 075 @Override 076 protected void sendFunctionGroup2() { 077 078 byte[] result = jmri.NmraPacket.function5Through8Packet(address.getNumber(), 079 address.isLongAddress(), 080 getFunction(5), getFunction(6), getFunction(7), getFunction(8)); 081 082 EasyDccMessage m = new EasyDccMessage(4 + 3 * result.length); 083 int i = 0; // message index counter 084 m.setElement(i++, 'S'); 085 m.setElement(i++, ' '); 086 m.setElement(i++, '0'); 087 m.setElement(i++, '1'); 088 089 for (int j = 0; j < result.length; j++) { 090 m.setElement(i++, ' '); 091 m.addIntAsTwoHex(result[j] & 0xFF, i); 092 i = i + 2; 093 } 094 tc.sendEasyDccMessage(m, null); 095 } 096 097 /** 098 * Send the message to set the state of functions F9, F10, F11, F12. 099 */ 100 @Override 101 protected void sendFunctionGroup3() { 102 103 byte[] result = jmri.NmraPacket.function9Through12Packet(address.getNumber(), 104 address.isLongAddress(), 105 getFunction(9), getFunction(10), getFunction(11), getFunction(12)); 106 107 EasyDccMessage m = new EasyDccMessage(4 + 3 * result.length); 108 int i = 0; // message index counter 109 m.setElement(i++, 'S'); 110 m.setElement(i++, ' '); 111 m.setElement(i++, '0'); 112 m.setElement(i++, '1'); 113 114 for (int j = 0; j < result.length; j++) { 115 m.setElement(i++, ' '); 116 m.addIntAsTwoHex(result[j] & 0xFF, i); 117 i = i + 2; 118 } 119 tc.sendEasyDccMessage(m, null); 120 } 121 122 /** 123 * Set the speed and direction. 124 * <p> 125 * This intentionally skips the emergency stop value of 1. 126 * 127 * @param speed Number from 0 to 1; less than zero is emergency stop 128 */ 129 @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY") // OK to compare floating point, notify on any change 130 @Override 131 public void setSpeedSetting(float speed) { 132 float oldSpeed; 133 synchronized (this) { 134 oldSpeed = this.speedSetting; 135 this.speedSetting = speed; 136 } 137 byte[] result; 138 139 if (super.speedStepMode == SpeedStepMode.NMRA_DCC_128) { 140 int value = (int) ((127 - 1) * speed); // -1 for rescale to avoid estop 141 if (value > 0) { 142 value = value + 1; // skip estop 143 } 144 if (value > 127) { 145 value = 127; // max possible speed 146 } 147 if (value < 0) { 148 value = 1; // emergency stop 149 } 150 result = jmri.NmraPacket.speedStep128Packet(address.getNumber(), 151 address.isLongAddress(), value, isForward); 152 } else { 153 154 /* [A Crosland 05Feb12] There is a potential issue in the way 155 * the float speed value is converted to integer speed step. 156 * A max speed value of 1 is first converted to int 28 then incremented 157 * to 29 which is too large. The next highest speed value also 158 * results in a value of 28. So two discrete throttle steps 159 * both map to speed step 28. 160 * 161 * This is compounded by the bug in speedStep28Packet() which 162 * cannot generate a DCC packet with speed step 28. 163 * 164 * Suggested correct code is 165 * value = (int) ((31-3) * speed); // -3 for rescale to avoid stop and estop x2 166 * if (value > 0) value = value + 3; // skip stop and estop x2 167 * if (value > 31) value = 31; // max possible speed 168 * if (value < 0) value = 2; // emergency stop 169 * bl = jmri.NmraPacket.speedStep28Packet(true, address.getNumber(), 170 * address.isLongAddress(), value, isForward); 171 */ 172 int value = (int) ((28) * speed); // -1 for rescale to avoid estop 173 if (value > 0) { 174 value = value + 1; // skip estop 175 } 176 if (value > 28) { 177 value = 28; // max possible speed 178 } 179 if (value < 0) { 180 value = 1; // emergency stop 181 } 182 result = jmri.NmraPacket.speedStep28Packet(address.getNumber(), 183 address.isLongAddress(), value, isForward); 184 } 185 186 EasyDccMessage m = new EasyDccMessage(1 + 3 * result.length); 187 // for EasyDCC, sending a speed command involves: 188 // Q place in Queue 189 // Cx xx (address) 190 // yy (speed) 191 int i = 0; // message index counter 192 m.setElement(i++, 'Q'); 193 194 for (int j = 0; j < result.length; j++) { 195 m.setElement(i++, ' '); 196 m.addIntAsTwoHex(result[j] & 0xFF, i); 197 i = i + 2; 198 } 199 200 tc.sendEasyDccMessage(m, null); 201 synchronized (this) { 202 firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting); 203 } 204 record(speed); 205 } 206 207 @Override 208 public void setIsForward(boolean forward) { 209 boolean old = isForward; 210 isForward = forward; 211 synchronized (this) { 212 setSpeedSetting(speedSetting); // send the command 213 } 214 firePropertyChange(ISFORWARD, old, isForward); 215 } 216 217 private final DccLocoAddress address; 218 EasyDccTrafficController tc; 219 220 @Override 221 public LocoAddress getLocoAddress() { 222 return address; 223 } 224 225 @Override 226 public void throttleDispose() { 227 active = false; 228 finishRecord(); 229 } 230 231}