001package jmri.jmrix.nce;
002
003import org.slf4j.Logger;
004import org.slf4j.LoggerFactory;
005
006import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
007import jmri.DccLocoAddress;
008import jmri.LocoAddress;
009import jmri.jmrix.AbstractThrottle;
010
011/**
012 * An implementation of DccThrottle with code specific to an NCE connection.
013 * <p>
014 * Based on Glen Oberhauser's original LnThrottleManager implementation
015 *
016 * @author Bob Jacobsen Copyright (C) 2001
017 */
018public class NceThrottle extends AbstractThrottle {
019
020    /* Note the NCE USB doesn't support the NMRA packet format.
021     * Before April 2010, this code would send NMRA packets if connected
022     * to the NCE command station.  Now it always sends the A2 loco
023     * commands if the command station eprom was built after 2004.
024     */
025    private boolean sendA2command = true;
026
027    private NceTrafficController tc = null;
028
029    /**
030     * Constructor.
031     * @param memo system connection memo to use
032     * @param address DCC loco address
033     */
034    public NceThrottle(NceSystemConnectionMemo memo, DccLocoAddress address) {
035        super(memo);
036        this.tc = memo.getNceTrafficController();
037        setSpeedStepMode(jmri.SpeedStepMode.NMRA_DCC_128);
038
039        // cache settings. It would be better to read the
040        // actual state, but I don't know how to do this
041        synchronized (this) {
042            this.speedSetting = 0;
043        }
044        // Functions default to false
045        this.address = address;
046        this.isForward = true;
047
048        // send NMRA style packets to old versions of NCE eprom
049        if (tc.getCommandOptions() <= NceTrafficController.OPTION_2004) {
050            sendA2command = false;
051        }
052    }
053
054    DccLocoAddress address;
055
056    @Override
057    public LocoAddress getLocoAddress() {
058        return address;
059    }
060
061    /**
062     * Send the message to set the state of functions F0, F1, F2, F3, F4.
063     */
064    @Override
065    protected void sendFunctionGroup1() {
066        // The NCE USB doesn't support the NMRA packet format
067        // Always need speed command before function group command to reset consist pointer
068        synchronized (this) {
069            setSpeedSetting(this.speedSetting);
070        }
071        if (sendA2command) {
072            int locoAddr = address.getNumber();
073            if (address.isLongAddress()) {
074                locoAddr += 0xC000;
075            }
076
077            int data = 0x00
078                    | (getFunction(0) ? 0x10 : 0)
079                    | (getFunction(1) ? 0x01 : 0)
080                    | (getFunction(2) ? 0x02 : 0)
081                    | (getFunction(3) ? 0x04 : 0)
082                    | (getFunction(4) ? 0x08 : 0);
083
084            NceMessage m = NceMessage.sendLocoCmd(tc, locoAddr, NceMessage.LOCO_CMD_FG1, (byte) data);
085            tc.sendNceMessage(m, null);
086
087        } else {
088            byte[] result = jmri.NmraPacket.function0Through4Packet(address
089                    .getNumber(), address.isLongAddress(), getFunction(0), getFunction(1),
090                    getFunction(2), getFunction(3), getFunction(4));
091            NceMessage m = NceMessage.sendPacketMessage(tc, result);
092            tc.sendNceMessage(m, null);
093        }
094    }
095
096    /**
097     * Send the message to set the state of functions F5, F6, F7, F8.
098     */
099    @Override
100    protected void sendFunctionGroup2() {
101        // The NCE USB doesn't support the NMRA packet format
102        // Always need speed command before function group command to reset consist pointer
103        synchronized (this) {
104            setSpeedSetting(this.speedSetting);
105        }
106        if (sendA2command) {
107            int locoAddr = address.getNumber();
108            if (address.isLongAddress()) {
109                locoAddr += 0xC000;
110            }
111
112            int data = 0x00
113                    | (getFunction(8) ? 0x08 : 0)
114                    | (getFunction(7) ? 0x04 : 0)
115                    | (getFunction(6) ? 0x02 : 0)
116                    | (getFunction(5) ? 0x01 : 0);
117
118            NceMessage m = NceMessage.sendLocoCmd(tc, locoAddr, NceMessage.LOCO_CMD_FG2, (byte) data);
119            tc.sendNceMessage(m, null);
120
121        } else {
122            byte[] result = jmri.NmraPacket.function5Through8Packet(address
123                    .getNumber(), address.isLongAddress(), getFunction(5), getFunction(6),
124                    getFunction(7), getFunction(8));
125            NceMessage m = NceMessage.sendPacketMessage(tc, result);
126            tc.sendNceMessage(m, null);
127        }
128    }
129
130    /**
131     * Send the message to set the state of functions F9, F10, F11, F12.
132     */
133    @Override
134    protected void sendFunctionGroup3() {
135        // The NCE USB doesn't support the NMRA packet format
136        // Always need speed command before function group command to reset consist pointer
137        synchronized (this) {
138            setSpeedSetting(this.speedSetting);
139        }
140        if (sendA2command) {
141            int locoAddr = address.getNumber();
142            if (address.isLongAddress()) {
143                locoAddr += 0xC000;
144            }
145
146            int data = 0x00
147                    | (getFunction(12) ? 0x08 : 0)
148                    | (getFunction(11) ? 0x04 : 0)
149                    | (getFunction(10) ? 0x02 : 0)
150                    | (getFunction(9) ? 0x01 : 0);
151
152            NceMessage m = NceMessage.sendLocoCmd(tc, locoAddr, NceMessage.LOCO_CMD_FG3, (byte) data);
153            tc.sendNceMessage(m, null);
154
155        } else {
156            byte[] result = jmri.NmraPacket.function9Through12Packet(address
157                    .getNumber(), address.isLongAddress(), getFunction(9), getFunction(10),
158                    getFunction(11), getFunction(12));
159            NceMessage m = NceMessage.sendPacketMessage(tc, result);
160            tc.sendNceMessage(m, null);
161        }
162    }
163
164    /**
165     * Send the message to set the state of functions F13, F14, F15, F16, F17,
166     * F18, F19, F20
167     */
168    @Override
169    protected void sendFunctionGroup4() {
170        // The NCE USB doesn't support the NMRA packet format
171        // Always need speed command before function group command to reset consist pointer
172        synchronized (this) {
173            setSpeedSetting(this.speedSetting);
174        }
175        if (sendA2command) {
176            int locoAddr = address.getNumber();
177            if (address.isLongAddress()) {
178                locoAddr += 0xC000;
179            }
180
181            int data = 0x00
182                    | (getFunction(20) ? 0x80 : 0)
183                    | (getFunction(19) ? 0x40 : 0)
184                    | (getFunction(18) ? 0x20 : 0)
185                    | (getFunction(17) ? 0x10 : 0)
186                    | (getFunction(16) ? 0x08 : 0)
187                    | (getFunction(15) ? 0x04 : 0)
188                    | (getFunction(14) ? 0x02 : 0)
189                    | (getFunction(13) ? 0x01 : 0);
190
191            NceMessage m = NceMessage.sendLocoCmd(tc, locoAddr, NceMessage.LOCO_CMD_FG4, (byte) data);
192            tc.sendNceMessage(m, null);
193
194        } else {
195            // Note NCE EPROM 2004 doesn't support LOCO_CMD_FG4
196            byte[] result = jmri.NmraPacket.function13Through20Packet(address
197                    .getNumber(), address.isLongAddress(), getFunction(13), getFunction(14),
198                    getFunction(15), getFunction(16), getFunction(17), getFunction(18), getFunction(19), getFunction(20));
199            NceMessage m = NceMessage.sendPacketMessage(tc, result);
200            tc.sendNceMessage(m, null);
201        }
202    }
203
204    /**
205     * Send the message to set the state of functions F21, F22, F23, F24, F25,
206     * F26, F27, F28
207     */
208    @Override
209    protected void sendFunctionGroup5() {
210        // The NCE USB doesn't support the NMRA packet format
211        // Always need speed command before function group command to reset consist pointer
212        synchronized (this) {
213            setSpeedSetting(this.speedSetting);
214        }
215        if (sendA2command) {
216            int locoAddr = address.getNumber();
217            if (address.isLongAddress()) {
218                locoAddr += 0xC000;
219            }
220
221            int data = 0x00
222                    | (getFunction(28) ? 0x80 : 0)
223                    | (getFunction(27) ? 0x40 : 0)
224                    | (getFunction(26) ? 0x20 : 0)
225                    | (getFunction(25) ? 0x10 : 0)
226                    | (getFunction(24) ? 0x08 : 0)
227                    | (getFunction(23) ? 0x04 : 0)
228                    | (getFunction(22) ? 0x02 : 0)
229                    | (getFunction(21) ? 0x01 : 0);
230
231            NceMessage m = NceMessage.sendLocoCmd(tc, locoAddr, NceMessage.LOCO_CMD_FG5, (byte) data);
232            tc.sendNceMessage(m, null);
233
234        } else {
235            // Note NCE EPROM 2004 doesn't support LOCO_CMD_FG5
236            byte[] result = jmri.NmraPacket.function21Through28Packet(address
237                    .getNumber(), address.isLongAddress(), getFunction(21), getFunction(22),
238                    getFunction(23), getFunction(24), getFunction(25), getFunction(26), getFunction(27), getFunction(28));
239            NceMessage m = NceMessage.sendPacketMessage(tc, result);
240            tc.sendNceMessage(m, null);
241        }
242    }
243
244    /**
245     * Set the speed and direction.
246     *
247     * @param speed Number from 0 to 1; less than zero is emergency stop
248     */
249    @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY") // OK to compare floating point, notify on any change
250    @Override
251    public void setSpeedSetting(float speed) {
252        float oldSpeed;
253        synchronized (this) {
254            oldSpeed = this.speedSetting;
255            this.speedSetting = speed;
256        }
257        log.debug("setSpeedSetting= {}", speed);
258
259        // The NCE USB doesn't support the NMRA packet format
260        if (sendA2command) {
261            byte[] bl;
262            int value;
263            int locoAddr = address.getNumber();
264            if (address.isLongAddress()) {
265                locoAddr += 0xC000;
266            }
267            value = Math.round((127 - 1) * speed);     // -1 for rescale to avoid estop
268            if (speed > 0 && value == 0) {
269                value = 1;          // ensure non-zero input results in non-zero output
270            }
271            if (speed < 0) {
272                value = -1;         // ensure small negative speeds don't get caught by Math.round above
273            }
274            if (value > 126) {
275                value = 126;    // max possible speed, 127 can crash PowerCab!
276            }   // emergency stop?
277            if (value < 0) {
278
279                bl = NceBinaryCommand.nceLocoCmd(locoAddr,
280                        (isForward ? NceMessage.LOCO_CMD_FWD_ESTOP
281                                : NceMessage.LOCO_CMD_REV_ESTOP),
282                        (byte) 0);
283
284            } else if (super.speedStepMode == jmri.SpeedStepMode.NMRA_DCC_128) {
285                bl = NceBinaryCommand.nceLocoCmd(locoAddr,
286                        (isForward ? NceMessage.LOCO_CMD_FWD_128SPEED
287                                : NceMessage.LOCO_CMD_REV_128SPEED),
288                        (byte) value);
289            } else {
290                // 28 speed step mode
291                bl = NceBinaryCommand.nceLocoCmd(locoAddr,
292                        (isForward ? NceMessage.LOCO_CMD_FWD_28SPEED
293                                : NceMessage.LOCO_CMD_REV_28SPEED),
294                        (byte) value);
295            }
296            NceMessage m = NceMessage.createBinaryMessage(tc, bl);
297            tc.sendNceMessage(m, null);
298
299        } else {
300            byte[] bl;
301            int value;
302
303            if (super.speedStepMode == jmri.SpeedStepMode.NMRA_DCC_128) {
304                value = Math.round((127 - 1) * speed);     // -1 for rescale to avoid estop
305                if (speed > 0 && value == 0) {
306                    value = 1;          // ensure non-zero input results in non-zero output
307                }
308                if (value > 0) {
309                    value = value + 1;  // skip estop
310                }
311                if (value > 127) {
312                    value = 127;    // max possible speed
313                }
314                if (speed < 0) {
315                    value = 1;      // emergency stop
316                                    // ensure small negative speeds don't get caught by Math.round above
317                }
318                bl = jmri.NmraPacket.speedStep128Packet(address.getNumber(),
319                        address.isLongAddress(), value, isForward);
320            } else {
321
322                /* [A Crosland 05Feb12] There is a potential issue in the way
323                 * the float speed value is converted to integer speed step.
324                 * A max speed value of 1 is first converted to int 28 then incremented
325                 * to 29 which is too large. The next highest speed value also
326                 * results in a value of 28. So two discrete throttle steps
327                 * both map to speed step 28.
328                 *
329                 * This is compounded by the bug in speedStep28Packet() which
330                 * cannot generate a DCC packet with speed step 28.
331                 *
332                 * Suggested correct code is
333                 *   value = (int) ((31-3) * speed); // -3 for rescale to avoid stop and estop x2
334                 *   if (value > 0) value = value + 3; // skip stop and estop x2
335                 *   if (value > 31) value = 31; // max possible speed
336                 *   if (value < 0) value = 2; // emergency stop
337                 *   bl = jmri.NmraPacket.speedStep28Packet(true, address.getNumber(),
338                 *     address.isLongAddress(), value, isForward);
339                 */
340                value = Math.round((28-1) * speed); // -1 for rescale to avoid estop
341                if (speed > 0 && value == 0) {
342                    value = 1;          // ensure non-zero input results in non-zero output
343                }
344                if (value > 0) {
345                    value = value + 1; // skip estop
346                }
347                if (value > 28) {
348                    value = 28; // max possible speed
349                }
350                if (speed < 0) {
351                    value = 1;      // emergency stop
352                                    // ensure small negative speeds don't get caught by Math.round above
353                }
354                bl = jmri.NmraPacket.speedStep28Packet(address.getNumber(),
355                        address.isLongAddress(), value, isForward);
356            }
357            NceMessage m = NceMessage.queuePacketMessage(tc, bl);
358            tc.sendNceMessage(m, null);
359
360        }
361        synchronized (this) {
362            firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting);
363        }
364        record(speed);
365    }
366
367    @Override
368    public void setIsForward(boolean forward) {
369        boolean old = isForward;
370        isForward = forward;
371        synchronized (this) {
372            setSpeedSetting(speedSetting);  // send the command
373        }
374        log.debug("setIsForward= {}", forward);
375        firePropertyChange(ISFORWARD, old, isForward);
376    }
377
378    @Override
379    public void throttleDispose() {
380        finishRecord();
381    }
382
383    // initialize logging
384    private final static Logger log = LoggerFactory.getLogger(NceThrottle.class);
385
386}