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 speed step mode
161            int value = (int) (199 * speed); // max value to send is 199 in 200 step mode
162            if (value > 199) {
163                // max possible speed
164                value = 199;
165            }
166            SerialMessage m = new SerialMessage();
167            m.setOpCode(0xF8);
168    
169            if (value < 1) {
170                // immediate stop
171                m.putAsWord(0x0000 + (address.getNumber() << 9) + 0);
172            } else {
173                // normal speed setting
174                m.putAsWord(0x0000 + (address.getNumber() << 9) + value);
175            }
176            // send to command station (send twice is set, but number of sends may need to be adjusted depending on efficiency)
177            tc.sendSerialMessage(m, null);
178            tc.sendSerialMessage(m, null);
179
180            // send to layout option 100 speed steps
181        } else if (speedStepMode == jmri.SpeedStepMode.TMCC_100) {
182            
183          /** 
184            * TMCC1 ERR 100 speed step mode
185            * purpose is to increase resolution of 32 bits
186            * across 100 throttle 'clicks' by dividing value by 3            
187            * and setting top speed at 32
188          */
189            int value = (int) (99 * speed); // max value to send is 99 in 100 step mode
190            if (value > 93) {
191                // max possible speed step
192                value = 93;
193            }
194            SerialMessage m = new SerialMessage();
195            m.setOpCode(0xFE);
196    
197            if (value < 1) {
198                // immediate stop
199                m.putAsWord(0x0060 + address.getNumber() * 128 + 0);
200            }
201            if (value > 0) {
202                // normal speed step setting
203                m.putAsWord(0x0060 + address.getNumber() * 128 + value / 3);
204            }
205                            
206            // send to command station (send once for maximum efficiency; add extra sends if layout not responding with only one send)
207            tc.sendSerialMessage(m, null);
208  
209            // Send to layout 32 speed steps
210        } else {
211
212            // assume TMCC 32 step mode
213            int value = (int) (32 * speed);
214            if (value > 31) {
215                // max possible speed
216                value = 31;
217            }
218            SerialMessage m = new SerialMessage();
219            m.setOpCode(0xFE);
220    
221            if (value < 1) {
222                // immediate stop
223                m.putAsWord(0x0060 + address.getNumber() * 128 + 0);
224            } else {
225                // normal speed setting
226                m.putAsWord(0x0060 + address.getNumber() * 128 + value);
227            }
228    
229            // send to command station (send twice is set, but number of sends may need to be adjusted depending on efficiency)
230            tc.sendSerialMessage(m, null);
231            tc.sendSerialMessage(m, null);
232         }
233            
234        synchronized(this) {
235            firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting);
236        }
237        record(speed);
238    }
239
240    /**
241     * {@inheritDoc}
242     */
243    @Override
244    public void setIsForward(boolean forward) {
245        boolean old = isForward;
246        isForward = forward;
247
248        // notify layout
249        SerialMessage m = new SerialMessage();
250        if (forward) {
251            m.putAsWord(0x0000 + address.getNumber() * 128);
252        } else {
253            m.putAsWord(0x0003 + address.getNumber() * 128);
254        }
255        tc.sendSerialMessage(m, null);
256        tc.sendSerialMessage(m, null);
257        tc.sendSerialMessage(m, null);
258        tc.sendSerialMessage(m, null);
259        firePropertyChange(ISFORWARD, old, isForward);
260    }
261
262    /**
263     * Send these messages to the layout and repeat
264     * while button is on.
265     * @param value Content of message to be sent in three bytes
266     * @param func  The number of the function being addressed
267     */
268    protected void sendFnToLayout(int value, int func) {
269    /**
270     * Commenting out these repeat send lines in case it is
271     * necessary to reinstate them after testing. These are
272     * holdovers from the original "repeat 4 times to make
273     * sure they're accepted" instructions.
274     */
275        // tc.sendSerialMessage(new SerialMessage(value), null);
276        // tc.sendSerialMessage(new SerialMessage(value), null);
277        // tc.sendSerialMessage(new SerialMessage(value), null);     
278    
279        repeatFunctionSendWhileOn(value, func); // 4th send is here
280    }
281
282    static final int REPEAT_TIME = 150;
283
284    protected void repeatFunctionSendWhileOn(int value, int func) {
285        // Send again if function is still on and repeat in a short while
286        if (getFunction(func)) {
287            tc.sendSerialMessage(new SerialMessage(value), null);
288            jmri.util.ThreadingUtil.runOnLayoutDelayed(() -> {
289                repeatFunctionSendWhileOn(value, func);
290            }, REPEAT_TIME);
291        }
292    }
293
294    /*
295     * Set the speed step value.
296     * <p>
297     * Only 32 steps is available
298     *
299     * @param mode only TMCC 32, TMCC 100 and TMCC 200 are allowed
300     */
301    @Override
302    public void setSpeedStepMode(jmri.SpeedStepMode mode) {
303        if (mode == jmri.SpeedStepMode.TMCC_32 || mode == jmri.SpeedStepMode.TMCC_100 || mode == jmri.SpeedStepMode.TMCC_200) {
304            super.setSpeedStepMode(mode);
305        }
306    }
307
308    /**
309     * {@inheritDoc}
310     */
311    @Override
312    public void throttleDispose() {
313        finishRecord();
314    }
315
316}