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