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
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        } else {
180
181            // assume TMCC 32 step mode
182            int value = (int) (32 * speed);
183            if (value > 31) {
184                value = 31;    // max possible speed
185            }
186            SerialMessage m = new SerialMessage();
187    
188            if (value < 0) {
189                // immediate stop
190                m.putAsWord(0x0060 + address.getNumber() * 128 + 0);
191            } else {
192                // normal speed setting
193                m.putAsWord(0x0060 + address.getNumber() * 128 + value);
194            }
195    
196            tc.sendSerialMessage(m, null);
197            tc.sendSerialMessage(m, null);
198            tc.sendSerialMessage(m, null);
199            tc.sendSerialMessage(m, null);
200        }
201            
202        synchronized(this) {
203            firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting);
204        }
205        record(speed);
206    }
207
208    /**
209     * {@inheritDoc}
210     */
211    @Override
212    public void setIsForward(boolean forward) {
213        boolean old = isForward;
214        isForward = forward;
215
216        // notify layout
217        SerialMessage m = new SerialMessage();
218        if (forward) {
219            m.putAsWord(0x0000 + address.getNumber() * 128);
220        } else {
221            m.putAsWord(0x0003 + address.getNumber() * 128);
222        }
223        tc.sendSerialMessage(m, null);
224        tc.sendSerialMessage(m, null);
225        tc.sendSerialMessage(m, null);
226        tc.sendSerialMessage(m, null);
227        firePropertyChange(ISFORWARD, old, isForward);
228    }
229
230    /**
231     * Send these messages to the layout four times
232     * to make sure they're accepted.
233     * @param value Content of message to be sent in three bytes
234     * @param func  The number of the function being addressed
235     */
236    protected void sendFnToLayout(int value, int func) {
237        tc.sendSerialMessage(new SerialMessage(value), null);
238        tc.sendSerialMessage(new SerialMessage(value), null);
239        tc.sendSerialMessage(new SerialMessage(value), null);
240     
241        repeatFunctionSendWhileOn(value, func); // 4th send is here
242    }
243
244    static final int REPEAT_TIME = 150;
245
246    protected void repeatFunctionSendWhileOn(int value, int func) {
247        // Send again if function is still on and repeat in a short while
248        if (getFunction(func)) {
249            tc.sendSerialMessage(new SerialMessage(value), null);
250            jmri.util.ThreadingUtil.runOnLayoutDelayed(() -> {
251                repeatFunctionSendWhileOn(value, func);
252            }, REPEAT_TIME);
253        }
254    }
255
256    /*
257     * Set the speed step value.
258     * <p>
259     * Only 32 steps is available
260     *
261     * @param mode only TMCC 30 and TMCC 200 are allowed
262     */
263    @Override
264    public void setSpeedStepMode(jmri.SpeedStepMode mode) {
265        if (mode == jmri.SpeedStepMode.TMCC_32 || mode == jmri.SpeedStepMode.TMCC_200) {
266            super.setSpeedStepMode(mode);
267        }
268    }
269
270    /**
271     * {@inheritDoc}
272     */
273    @Override
274    public void throttleDispose() {
275        finishRecord();
276    }
277
278}