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-&gt;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}