001package jmri.jmrix.loconet; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import jmri.DccLocoAddress; 005import jmri.LocoAddress; 006import jmri.SpeedStepMode; 007import jmri.jmrix.AbstractThrottle; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011/** 012 * An implementation of DccThrottle via AbstractThrottle with code specific to a 013 * PR2 connection. 014 * <p> 015 * Speed in the Throttle interfaces and AbstractThrottle is a float, but in 016 * LocoNet is an int with values from 0 to 127. 017 * 018 * @author Bob Jacobsen Copyright (C) 2006 019 */ 020public class Pr2Throttle extends AbstractThrottle { 021 022 private final int addr; 023 DccLocoAddress address; 024 025 /** 026 * Constructor 027 * @param memo a LocoNetSystemConnectionMemo to associate with this throttle 028 * @param address a DccLocoAddress to associate with this throttle 029 */ 030 public Pr2Throttle(LocoNetSystemConnectionMemo memo, DccLocoAddress address) { 031 super(memo); 032 this.address = address; 033 addr = address.getNumber(); 034 setSpeedStepMode(SpeedStepMode.NMRA_DCC_28); 035 } 036 037 /** 038 * Convert a LocoNet speed integer to a float speed value. 039 * 040 * @param lSpeed loconet speed value 041 * @return speed as float 0->1.0 042 */ 043 protected float floatSpeed(int lSpeed) { 044 if (lSpeed == 0) { 045 return 0.f; 046 } else if (lSpeed == 1) { 047 return -1.f; // estop 048 } 049 if (getSpeedStepMode() == SpeedStepMode.NMRA_DCC_28) { 050 if (lSpeed <= 15) //Value less than 15 is in the stop/estop range bracket 051 { 052 return 0.f; 053 } 054 return (((lSpeed - 12) / 4f) / 28.f); 055 } else if (getSpeedStepMode() == SpeedStepMode.NMRA_DCC_14) { 056 if (lSpeed <= 15) //Value less than 15 is in the stop/estop range bracket 057 { 058 return 0.f; 059 } 060 return ((lSpeed - 8) / 8f) / 14.f; 061 } else { 062 return ((lSpeed - 1) / 126.f); 063 } 064 } 065 066 /** 067 * {@inheritDoc} 068 * <p> 069 * This implementation does not support 128 speed steps. 070 */ 071 @Override 072 // This is a specific implementation for the PR2 that seems to 073 // return different values from the super class. This is an attempt at only 074 // outputting those speeds to the Pr2 that generate discrete DCC speeds 075 // 14/28 speed steps are the same as LocoNetThrottle.intSpeed 076 protected int intSpeed(float fSpeed) { 077 int speed; 078 if (fSpeed < 0.) { 079 return 1; 080 } // emergency stop 081 if (fSpeed == 0.) { 082 return 0; 083 } // idle (zero speed) 084 085 switch (this.getSpeedStepMode()) { 086 case NMRA_DCC_28: 087 case MOTOROLA_28: 088 speed = (int) ((fSpeed * 28) * 4) + 12; 089 // ensure we never send a non-zero speed to loconet 090 // that we reinterpret as 0 in floatSpeed() later 091 if (speed < 16) { 092 speed = 16; 093 } 094 return speed; 095 096 case NMRA_DCC_14: 097 speed = (int) ((fSpeed * 14) * 8) + 8; 098 // ensure we never send a non-zero speed to loconet 099 // that we reinterpret as 0 in floatSpeed() later 100 if (speed < 16) { 101 speed = 16; 102 } 103 return speed; 104 105 default: 106 // includes the 128 case 107 log.warn("Unhandled speed step mode: {}", this.getSpeedStepMode()); 108 return super.intSpeed(fSpeed); 109 } 110 } 111 112 public void writeData() { 113 // convert contents 114 int stat = 0; 115 int speed; 116 synchronized(this) { 117 speed = intSpeed(speedSetting); 118 } 119 int dirf = 0; // contains dir, f0, f4-1 120 if (getFunction(0)) { 121 dirf |= (1 << 4); 122 } 123 if (getFunction(1)) { 124 dirf |= (1 << 0); 125 } 126 if (getFunction(2)) { 127 dirf |= (1 << 1); 128 } 129 if (getFunction(3)) { 130 dirf |= (1 << 2); 131 } 132 if (getFunction(4)) { 133 dirf |= (1 << 3); 134 } 135 if (!getIsForward()) { 136 dirf |= (1 << 5); // note sign of bit 137 } 138 // contains f11-5 139 int f11 = 0; 140 if (getFunction(5)) { 141 f11 |= (1 << 0); 142 } 143 if (getFunction(6)) { 144 f11 |= (1 << 1); 145 } 146 if (getFunction(7)) { 147 f11 |= (1 << 2); 148 } 149 if (getFunction(8)) { 150 f11 |= (1 << 3); 151 } 152 if (getFunction(9)) { 153 f11 |= (1 << 4); 154 } 155 if (getFunction(10)) { 156 f11 |= (1 << 5); 157 } 158 if (getFunction(11)) { 159 f11 |= (1 << 6); 160 } 161 162 // contains F19-F13 163 int f19 = 0; 164 165 // contains F27-F21 166 int f27 = 0; 167 168 // contains F28, F20, F12 169 int f28 = 0; 170 if (getFunction(12)) { 171 f28 |= (1 << 4); 172 } 173 174 LocoNetMessage l = new LocoNetMessage(21); 175 l.setOpCode(LnConstants.OPC_EXP_WR_SL_DATA); 176 int i = 1; 177 l.setElement(i++, 21); // length 178 l.setElement(i++, 0); // EXP_MAST 179 l.setElement(i++, 1); // EXP_SLOT 180 l.setElement(i++, stat & 0x7F); // EXPD_STAT 181 l.setElement(i++, addr & 0x7F); // EXPD_ADRL 182 l.setElement(i++, (addr / 128) & 0x7F); // EXPD_ADRH 183 l.setElement(i++, 0); // EXPD_FLAGS 184 l.setElement(i++, speed & 0x7F); // EXPD_SPD 185 l.setElement(i++, f28 & 0x7F); // EXPD_F28F20F12 186 l.setElement(i++, dirf & 0x7F); // EXPD_DIR_F0F4_F1 187 l.setElement(i++, f11 & 0x7F); // EXPD_F11_F5 188 l.setElement(i++, f19 & 0x7F); // EXPD_F19_F13 189 l.setElement(i++, f27 & 0x7F); // EXPD_F27_F21 190 // rest are zero 191 192 ((LocoNetSystemConnectionMemo) adapterMemo).getLnTrafficController().sendLocoNetMessage(l); 193 } 194 195 /** 196 * Send the LocoNet message to set the state of locomotive direction and 197 * functions F0, F1, F2, F3, F4. Invoked by AbstractThrottle when needed. 198 */ 199 @Override 200 protected void sendFunctionGroup1() { 201 writeData(); 202 } 203 204 /** 205 * Send the LocoNet message to set the state of functions F5, F6, F7, F8. 206 * Invoked by AbstractThrottle when needed. 207 */ 208 @Override 209 protected void sendFunctionGroup2() { 210 writeData(); 211 } 212 213 /** 214 * {@inheritDoc} 215 */ 216 @Override 217 protected void sendFunctionGroup3() { 218 writeData(); 219 } 220 221 /** 222 * Set the speed. 223 * <p> 224 * This intentionally skips the emergency stop value of 1. 225 * 226 * @param speed Number from 0 to 1; less than zero is emergency stop 227 */ 228 @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY") // OK to compare floating point, notify on any change 229 @Override 230 public synchronized void setSpeedSetting(float speed) { 231 float oldSpeed = this.speedSetting; 232 this.speedSetting = speed; 233 if (speed < 0) { 234 this.speedSetting = -1.f; 235 } 236 237 writeData(); 238 firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting); // NOI18N 239 record(speed); 240 } 241 242 /** 243 * LocoNet actually puts forward and backward in the same message as the 244 * first function group. 245 */ 246 @Override 247 public void setIsForward(boolean forward) { 248 boolean old = isForward; 249 isForward = forward; 250 sendFunctionGroup1(); 251 firePropertyChange(ISFORWARD, old, isForward); // NOI18N 252 } 253 254 /** 255 * {@inheritDoc} 256 */ 257 @Override 258 public String toString() { 259 return getLocoAddress().toString(); 260 } 261 262 /** 263 * {@inheritDoc} 264 */ 265 @Override 266 public LocoAddress getLocoAddress() { 267 return address; 268 } 269 270 /** 271 * {@inheritDoc} 272 */ 273 @Override 274 public void throttleDispose() { 275 finishRecord(); 276 } 277 278 // initialize logging 279 private final static Logger log = LoggerFactory.getLogger(Pr2Throttle.class); 280 281}