001package jmri.jmrix.tmcc;
002
003import jmri.DccLocoAddress;
004import jmri.LocoAddress;
005import jmri.SpeedStepMode;
006import jmri.jmrix.AbstractThrottle;
007
008/**
009 * An implementation of DccThrottle.
010 * <p>
011 * Addresses of 99 and below are considered short addresses, and over 100 are
012 * considered long addresses.
013 *
014 * @author Bob Jacobsen Copyright (C) 2001, 2006
015 */
016public class SerialThrottle extends AbstractThrottle {
017
018    /**
019     * Constructor.
020     *
021     * @param memo the connected SerialTrafficController
022     * @param address Loco ID
023     */
024    public SerialThrottle(TmccSystemConnectionMemo memo, DccLocoAddress address) {
025        super(memo, 69); // supports 69 functions
026        tc = memo.getTrafficController();
027
028        // cache settings. It would be better to read the
029        // actual state, but I don't know how to do this
030        synchronized(this) {
031            this.speedSetting = 0;
032        }
033        // Functions default to false
034        this.address = address;
035        this.isForward = true;
036        this.speedStepMode = SpeedStepMode.TMCC_32;
037    }
038
039    private final DccLocoAddress address;
040    private final SerialTrafficController tc;
041
042    /**
043     * {@inheritDoc}
044     */
045    @Override
046    public LocoAddress getLocoAddress() {
047        return address;
048    }
049
050    /**
051     * {@inheritDoc}
052     */
053    @Override
054    public void setFunction(int func, boolean value) {
055        updateFunction(func, value);
056        if (func>=0 && func < SERIAL_FUNCTION_CODES.length) {
057            if ( SERIAL_FUNCTION_CODES[func] > 0xFFFF ) {
058                // TMCC 2 format
059                if (SERIAL_FUNCTION_CODES[func] > 0xFFFFFF ) {
060                    int first =  (int)(SERIAL_FUNCTION_CODES[func] >> 24);
061                    int second = (int)(SERIAL_FUNCTION_CODES[func] & 0xFFFFFF);
062                    // doubles are only sent once, not repeating
063                    sendOneWordOnce(first  + address.getNumber() * 512);
064                    sendOneWordOnce(second + address.getNumber() * 512);           
065                } else {
066                    // single message
067                    sendFnToLayout((int)SERIAL_FUNCTION_CODES[func] + address.getNumber() * 512, func);
068                }
069            } else {
070                // TMCC 1 format
071                sendFnToLayout((int)SERIAL_FUNCTION_CODES[func] + address.getNumber() * 128, func);
072            }
073        }
074        else {
075            super.setFunction(func, value);
076        }
077    }
078
079    // the argument is a long containing 3 bytes. 
080    // The first byte is the message opcode
081    private void sendOneWordOnce(int word) {
082        SerialMessage m = new SerialMessage(word);
083        tc.sendSerialMessage(m, null);
084    }
085
086    // Translate function number to line characters.
087    // If the upper byte is zero, it will be replaces by 0xF8
088    //    and the address will be set in the low position.
089    // If the upper byte is non-zero, that value will be sent,
090    //    and the address will be set in the upper (TMCC2) position.
091    //    If six bytes are specified (with the upper one non-zero), 
092    //    this will be interpreted as two commands to be sequentially sent,
093    //    with the upper bytes sent first.
094    private final static long[] SERIAL_FUNCTION_CODES = new long[] {
095        0x00000D, 0x00001D, 0x00001C, 0x000005, 0x000006, /* Fn0-4 */
096        0x000010, 0x000011, 0x000012, 0x000013, 0x000014, /* Fn5-9 */
097        0x000015, 0x000016, 0x000017, 0x000018, 0x000019, /* Fn10-14 */
098        0x000009, 0x00001E, 0x000000, 0x000003, 0x000001, /* Fn15-19 */
099        0x000004, 0x000007, 0x000047, 0x000042, 0x000028, /* Fn20-24 */
100        0x000029, 0x00002A, 0x00002B, /* 25-27 */
101        // start of TMCC 2 functions
102        0xF801FBF801FCL, // Fn28 Start Up Sequence 1 (Delayed Prime Mover, then Immediate Start Up)
103        0xF801FC, // Fn29 Start Up Sequence 2 (Immediate Start Up)
104        0xF801FDF801FEL, // Fn30 Shut Down Sequence 1 (Delay w/ Announcement then Immediate Shut Down)
105        0xF801FE, // Fn31 Shut down Sequence 2 (Immediate Shut Down)
106        0xF90000, // Fn32
107        0xF90000, // Fn33
108        0xF90000, // Fn34
109        0xF90000, // Fn35
110        0xF90000, // Fn36
111        0xF90000, // Fn37
112        0xF90000, // Fn38
113        0xF90000, // Fn39
114        0xF90000, // Fn40
115        0xF90000, // Fn41
116        0xF90000, // Fn42
117        0xF90000, // Fn43
118        0xF90000, // Fn44
119        0xF90000, // Fn45
120        0xF90000, // Fn46
121        0xF90000, // Fn47
122        0xF90000, // Fn48
123        0xF90000, // Fn49
124        0xF90000, // Fn50
125        0xF90000, // Fn51
126        0xF90000, // Fn52
127        0xF90000, // Fn53
128        0xF90000, // Fn54
129        0xF90000, // Fn55
130        0xF90000, // Fn56
131        0xF90000, // Fn57
132        0xF90000, // Fn58
133        0xF90000, // Fn59
134        0xF90000, // Fn60
135        0xF90000, // Fn61
136        0xF90000, // Fn62
137        0xF90000, // Fn63
138        0xF90000, // Fn64
139        0xF90000, // Fn65
140        0xF90000, // Fn66
141        0xF90000, // Fn67
142    };
143
144    /**
145     * Set the speed.
146     *
147     * @param speed Number from 0 to 1; less than zero is emergency stop
148     */
149    @Override
150    public void setSpeedSetting(float speed) {
151        float oldSpeed;
152        synchronized(this) {
153            oldSpeed = this.speedSetting;
154            this.speedSetting = speed;
155        }
156        
157        // send to layout option 200 speed steps
158        if (speedStepMode == jmri.SpeedStepMode.TMCC_200) {
159
160            // TMCC2 Legacy 200 step mode
161            int value = (int) (199 * speed); // max value to send is 199 in 200 step mode
162            if (value > 199) {
163                value = 199;    // max possible speed
164            }
165            SerialMessage m = new SerialMessage();
166            m.setOpCode(0xF8);
167    
168            if (value < 0) {
169                // immediate stop
170                m.putAsWord(0x0000 + (address.getNumber() << 9) + 0);
171            } else {
172                // normal speed setting
173                m.putAsWord(0x0000 + (address.getNumber() << 9) + value);
174            }
175            // only send twice to advanced command station
176            tc.sendSerialMessage(m, null);
177            tc.sendSerialMessage(m, null);
178
179            // send to layout option 100 speed steps
180        } else if (speedStepMode == jmri.SpeedStepMode.TMCC_100) {
181
182            // TMCC1 ERR 100 speed step mode
183            int value = (int) (99 * speed); // max value to send is 99 in 100 step mode
184            if (value > 99) {
185                value = 99;    // max possible speed
186            }
187            SerialMessage m = new SerialMessage();
188            m.setOpCode(0xFE);
189    
190            if (value < 0) {
191                // immediate stop
192                m.putAsWord(0x0060 + address.getNumber() * 128 + 0);
193            } else {
194                // normal speed setting
195                m.putAsWord(0x0060 + address.getNumber() * 128 + value);
196            }
197            // only send twice to advanced command station
198            tc.sendSerialMessage(m, null);
199            tc.sendSerialMessage(m, null);
200
201                // Send to layout 32 speed steps
202        } else {
203
204            // assume TMCC 32 step mode
205            int value = (int) (32 * speed);
206            if (value > 31) {
207                value = 31;    // max possible speed
208            }
209            SerialMessage m = new SerialMessage();
210    
211            if (value < 0) {
212                // immediate stop
213                m.putAsWord(0x0060 + address.getNumber() * 128 + 0);
214            } else {
215                // normal speed setting
216                m.putAsWord(0x0060 + address.getNumber() * 128 + value);
217            }
218    
219            tc.sendSerialMessage(m, null);
220            tc.sendSerialMessage(m, null);
221            tc.sendSerialMessage(m, null);
222            tc.sendSerialMessage(m, null);
223        }
224            
225        synchronized(this) {
226            firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting);
227        }
228        record(speed);
229    }
230
231    /**
232     * {@inheritDoc}
233     */
234    @Override
235    public void setIsForward(boolean forward) {
236        boolean old = isForward;
237        isForward = forward;
238
239        // notify layout
240        SerialMessage m = new SerialMessage();
241        if (forward) {
242            m.putAsWord(0x0000 + address.getNumber() * 128);
243        } else {
244            m.putAsWord(0x0003 + address.getNumber() * 128);
245        }
246        tc.sendSerialMessage(m, null);
247        tc.sendSerialMessage(m, null);
248        tc.sendSerialMessage(m, null);
249        tc.sendSerialMessage(m, null);
250        firePropertyChange(ISFORWARD, old, isForward);
251    }
252
253    /**
254     * Send these messages to the layout and repeat
255     * while button is on.
256     * @param value Content of message to be sent in three bytes
257     * @param func  The number of the function being addressed
258     */
259    protected void sendFnToLayout(int value, int func) {
260        tc.sendSerialMessage(new SerialMessage(value), null);
261
262    /**
263     * Commenting out these repeat send lines in case it is
264     * necessary to reinstate them after testing. These are
265     * holdovers from the original "repeat 4 times to make
266     * sure they're accepted" instructions.
267     */
268        // tc.sendSerialMessage(new SerialMessage(value), null);
269        // tc.sendSerialMessage(new SerialMessage(value), null);
270     
271        repeatFunctionSendWhileOn(value, func); // 4th send is here
272    }
273
274    static final int REPEAT_TIME = 150;
275
276    protected void repeatFunctionSendWhileOn(int value, int func) {
277        // Send again if function is still on and repeat in a short while
278        if (getFunction(func)) {
279            tc.sendSerialMessage(new SerialMessage(value), null);
280            jmri.util.ThreadingUtil.runOnLayoutDelayed(() -> {
281                repeatFunctionSendWhileOn(value, func);
282            }, REPEAT_TIME);
283        }
284    }
285
286    /*
287     * Set the speed step value.
288     * <p>
289     * Only 32 steps is available
290     *
291     * @param mode only TMCC 32, TMCC 100 and TMCC 200 are allowed
292     */
293    @Override
294    public void setSpeedStepMode(jmri.SpeedStepMode mode) {
295        if (mode == jmri.SpeedStepMode.TMCC_32 || mode == jmri.SpeedStepMode.TMCC_100 || mode == jmri.SpeedStepMode.TMCC_200) {
296            super.setSpeedStepMode(mode);
297        }
298    }
299
300    /**
301     * {@inheritDoc}
302     */
303    @Override
304    public void throttleDispose() {
305        finishRecord();
306    }
307
308}