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