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}