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