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