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 * with additions and edits by
016 * @author Timothy Jump Copyright (C) 2025
017 */
018public class SerialThrottle extends AbstractThrottle {
019
020    /**
021     * Constructor.
022     *
023     * @param memo the connected SerialTrafficController
024     * @param address Loco ID
025     */
026    public SerialThrottle(TmccSystemConnectionMemo memo, DccLocoAddress address) {
027        super(memo, 69); // supports 69 functions
028        tc = memo.getTrafficController();
029
030        // cache settings. It would be better to read the
031        // actual state, but I don't know how to do this
032        synchronized(this) {
033            this.speedSetting = 0;
034        }
035        // Functions default to false
036        this.address = address;
037        this.isForward = true;
038        this.speedStepMode = SpeedStepMode.TMCC1_32;
039    }
040
041    private final DccLocoAddress address;
042    private final SerialTrafficController tc;
043
044    /**
045     * {@inheritDoc}
046     */
047    @Override
048    public LocoAddress getLocoAddress() {
049        return address;
050    }
051
052    // Relating SERIAL_FUNCTION_CODES_TMCC1 to SpeedStepMode.TMCC1_32 and TMCC1_100;
053    //    and SERIAL_FUNCTION_CODES_TMCC2 to SpeedStepMode.TMCC2_32 and TMCC2_200.
054    private long getFnValue(int number) {
055                if (getSpeedStepMode() == jmri.SpeedStepMode.TMCC1_32 || getSpeedStepMode() == jmri.SpeedStepMode.TMCC1_100) {
056                    if (number < SERIAL_FUNCTION_CODES_TMCC1.length) {
057                        return SERIAL_FUNCTION_CODES_TMCC1[number];
058                    } else {
059                        return 0;
060                    }
061                } else if (getSpeedStepMode() == jmri.SpeedStepMode.TMCC2_32) {
062                     if (number < SERIAL_FUNCTION_CODES_TMCC2_32.length) {
063                         return SERIAL_FUNCTION_CODES_TMCC2_32[number];
064                    } else {
065                        return 0;
066                    }
067                } else if (getSpeedStepMode() == jmri.SpeedStepMode.TMCC2_200) {
068                     if (number < SERIAL_FUNCTION_CODES_TMCC2_200.length) {
069                         return SERIAL_FUNCTION_CODES_TMCC2_200[number];
070                    } else {
071                        return 0;
072                    }
073                }
074            return 0;
075        }
076
077
078    /**
079     * {@inheritDoc}
080     */
081    @Override
082    public void setFunction(int func, boolean value) {
083        updateFunction(func, value);
084        if (func>=0 && func < SERIAL_FUNCTION_CODES_TMCC1.length) {
085            if ( getFnValue(func) > 0xFFFF ) {
086                // TMCC 2 format
087                if (getFnValue(func) > 0xFFFFFF ) {
088                    int first =  (int)(getFnValue(func) >> 24);
089                    int second = (int)(getFnValue(func) & 0xFFFFFF);
090                    // doubles are only sent once, not repeating
091                    sendOneWordOnce(first  + address.getNumber() * 512);
092                    sendOneWordOnce(second + address.getNumber() * 512);
093                } else {
094                    // single message
095                    sendFnToLayout((int)getFnValue(func) + address.getNumber() * 512, func);
096                    }
097            } else {
098                // TMCC 1 format
099                sendFnToLayout((int)getFnValue(func) + address.getNumber() * 128, func);
100                }
101        } else {
102            super.setFunction(func, value);
103            }
104    }
105
106    // the argument is a long containing 3 bytes. 
107    // The first byte is the message opcode
108    private void sendOneWordOnce(int word) {
109        SerialMessage m = new SerialMessage(word);
110        tc.sendSerialMessage(m, null);
111    }
112
113    // TMCC 1 Function Keys to trigger with TMCC1_32 and TMCC1_100 speed steps.
114    private final static long[] SERIAL_FUNCTION_CODES_TMCC1 = new long[] {
115
116        // TMCC1 Remote - Buttons
117        0x00000D, 0x00001D, 0x00001C, 0x000005, 0x000006, /* Fn0-4 */
118
119        // TMCC1 Remote - KeyPad Buttons
120        0x000011, 0x000012, 0x000013, /* Fn5-7 */
121        0x000014, 0x000015, 0x000016, /* Fn8-10 */
122        0x000017, 0x000018, 0x000019, /* Fn11-13 */
123                  0x000010,           /* Fn14 */
124
125        // TMCC1 Remote - Buttons
126        0x000009, 0x00001E, 0x000004, 0x000007, 0x000028, /* Fn15-19 */
127        0x000029, 0x00002A, 0x00002B, 0x00001F,           /* 20-23 */
128
129        // TMCC1 RR Speed FnKeys
130        0x000064, // Fn24 ( 4)
131        0x00006A, // Fn25 (10)
132        0x00006E, // Fn26 (14)
133        0x000072, // Fn27 (18)
134        0x000078, // Fn28 (24)
135        0x00007F, // Fn29 (31)
136
137        // TMCC1 Aux FnKeys 
138        0x00002E, // Fn30
139        0x00002E, // Fn31
140        0x00002E, // Fn32
141        0x00002E, // Fn33
142        0x00002E, // Fn34
143        0x00002E, // Fn35
144
145        // TMCC1 Unused FnKeys
146        0x00002E, // Fn36
147        0x00002E, // Fn37
148        0x00002E, // Fn38
149        0x00002E, // Fn39
150        0x00002E, // Fn40
151        0x00002E, // Fn41
152        0x00002E, // Fn42
153        0x00002E, // Fn43
154        0x00002E, // Fn44
155        0x00002E, // Fn45
156        0x00002E, // Fn46
157        0x00002E, // Fn47
158        0x00002E, // Fn48
159        0x00002E, // Fn49
160        0x00002E, // Fn50
161        0x00002E, // Fn51
162        0x00002E, // Fn52
163        0x00002E, // Fn53
164        0x00002E, // Fn54
165        0x00002E, // Fn55
166        0x00002E, // Fn56
167        0x00002E, // Fn57
168        0x00002E, // Fn58
169        0x00002E, // Fn59
170        0x00002E, // Fn60
171        0x00002E, // Fn61
172        0x00002E, // Fn62
173        0x00002E, // Fn63
174        0x00002E, // Fn64
175        0x00002E, // Fn65
176        0x00002E, // Fn66
177        0x00002E, // Fn67
178        0x00002E, // Fn68        
179    };
180
181    // Translate TMCC1 function numbers to line characters.
182    // If the upper byte is zero, it will be replaced by 0xF8
183    //    and the address will be set in the low position.
184    // If the upper byte is non-zero, that value will be sent,
185    //    and the address will be set in the upper (TMCC2) position.
186    // If six bytes are specified (with the upper one non-zero), 
187    //    this will be interpreted as two commands to be sequentially sent,
188    //    with the upper bytes sent first.
189
190    // TMCC 2 Legacy Function Keys to trigger with TMCC2_32 speed steps.
191    private final static long[] SERIAL_FUNCTION_CODES_TMCC2_32 = new long[] {
192
193        // TMCC2_32 Remote - Buttons
194        0xF8010D, 0xF8011D, 0xF8011C, 0xF80105, 0xF80106, /* Fn0-4 */
195
196        // TMCC2_32 Remote - Keypad Buttons
197        0xF80111, 0xF80112, 0xF80113, /* Fn5-7 */
198        0xF80114, 0xF80115, 0xF80116, /* Fn8-10 */
199        0xF80117, 0xF80118, 0xF80119, /* Fn11-13 */
200                  0xF80110,           /* Fn14 */
201
202        // TMCC2_32 Remote - Buttons
203        0xF80109, 0xF8011E, 0xF80104, 0xF80107, 0xF80128, /* Fn15-19 */
204        0xF80129, 0xF8012A, 0xF8012B, 0xF8011F,/* 20-23 */
205
206        // TMCC2_32 RR Speed FnKeys
207        0xF80164, // Fn24
208        0xF8016A, // Fn25
209        0xF8016E, // Fn26
210        0xF80172, // Fn27
211        0xF80178, // Fn28
212        0xF8017F, // Fn29
213
214        // TMCC2_32 Extended Lighting FnKeys
215
216        //0xF8017DFB01F2FB0189L, // Fn35 Set Cab Light Auto
217
218
219        // Extended Sound Effects FnKeys
220        //0xF801FBF801FCL, // Fn35 Start Up Sequence 1 (Delayed Prime Mover, then Immediate Start Up)
221        //0xF801FC, // Fn36 Start Up Sequence 2 (Immediate Start Up)
222        //0xF801FDF801FEL, // Fn37 Shut Down Sequence 1 (Delay w/ Announcement then Immediate Shut Down)
223        //0xF801FE, // Fn38 Shut down Sequence 2 (Immediate Shut Down)
224
225
226        // TRMCC2_32 Aux FnKeys
227        0xF80108, // Fn30
228        0xF8010A, // Fn31
229        0xF8010B, // Fn32
230        0xF8010C, // Fn33
231        0xF8010E, // Fn34
232        0xF8010F, // Fn35
233
234        // TRMCC2_32 Unused FnKeys
235        0xF8012E, // Fn36
236        0xF8012E, // Fn37
237        0xF8012E, // Fn38
238        0xF8012E, // Fn39
239        0xF8012E, // Fn40
240        0xF8012E, // Fn41
241        0xF8012E, // Fn42
242        0xF8012E, // Fn43
243        0xF8012E, // Fn44
244        0xF8012E, // Fn45
245        0xF8012E, // Fn46
246        0xF8012E, // Fn47
247        0xF8012E, // Fn48
248        0xF8012E, // Fn49
249        0xF8012E, // Fn50
250        0xF8012E, // Fn51
251        0xF8012E, // Fn52
252        0xF8012E, // Fn53
253        0xF8012E, // Fn54
254        0xF8012E, // Fn55
255        0xF8012E, // Fn56
256        0xF8012E, // Fn57
257        0xF8012E, // Fn58
258        0xF8012E, // Fn59
259        0xF8012E, // Fn60
260        0xF8012E, // Fn61
261        0xF8012E, // Fn62
262        0xF8012E, // Fn63
263    };
264
265    // TMCC 2 Legacy Function Keys to trigger with TMCC2_200 speed steps.
266    private final static long[] SERIAL_FUNCTION_CODES_TMCC2_200 = new long[] {
267
268        // TMCC2_200 Remote - Buttons
269        0xF8010D, 0xF8011D, 0xF8011C, 0xF80105, 0xF80106, /* Fn0-4 */
270
271        // TMCC2_200 Remote - Keypad Buttons
272        0xF80111, 0xF80112, 0xF80113, /* Fn5-7 */
273        0xF80114, 0xF80115, 0xF80116, /* Fn8-10 */
274        0xF80117, 0xF80118, 0xF80119, /* Fn11-13 */
275                  0xF80110,           /* Fn14 */
276
277        // TMCC2_200 Remote - Buttons
278        0xF80109, 0xF8011E, 0xF80104, 0xF80107, 0xF80128, /* Fn15-19 */
279        0xF80129, 0xF8012A, 0xF8012B, 0xF8011F,/* 20-23 */
280
281        // TMCC2_200 RR Speed FnKeys
282        0xF8000A, // Fn24
283        0xF80028, // Fn25
284        0xF80046, // Fn26
285        0xF80064, // Fn27
286        0xF8008C, // Fn28
287        0xF800C7, // Fn29
288
289        // TMCC2_200 Extended Lighting FnKeys
290
291        //0xF8017DFB01F2FB0189L, // Fn35 Set Cab Light Auto
292
293
294        // Extended Sound Effects FnKeys
295        //0xF801FBF801FCL, // Fn35 Start Up Sequence 1 (Delayed Prime Mover, then Immediate Start Up)
296        //0xF801FC, // Fn36 Start Up Sequence 2 (Immediate Start Up)
297        //0xF801FDF801FEL, // Fn37 Shut Down Sequence 1 (Delay w/ Announcement then Immediate Shut Down)
298        //0xF801FE, // Fn38 Shut down Sequence 2 (Immediate Shut Down)
299
300
301        // TMCC2_200 Aux FnKeys
302        0xF80108, // Fn30
303        0xF8010A, // Fn31
304        0xF8010B, // Fn32
305        0xF8010C, // Fn33
306        0xF8010E, // Fn34
307        0xF8010F, // Fn35
308
309        // TMCC2_200 Unused FnKeys
310        0xF8012E, // Fn36
311        0xF8012E, // Fn37
312        0xF8012E, // Fn38
313        0xF8012E, // Fn39
314        0xF8012E, // Fn40
315        0xF8012E, // Fn41
316        0xF8012E, // Fn42
317        0xF8012E, // Fn43
318        0xF8012E, // Fn44
319        0xF8012E, // Fn45
320        0xF8012E, // Fn46
321        0xF8012E, // Fn47
322        0xF8012E, // Fn48
323        0xF8012E, // Fn49
324        0xF8012E, // Fn50
325        0xF8012E, // Fn51
326        0xF8012E, // Fn52
327        0xF8012E, // Fn53
328        0xF8012E, // Fn54
329        0xF8012E, // Fn55
330        0xF8012E, // Fn56
331        0xF8012E, // Fn57
332        0xF8012E, // Fn58
333        0xF8012E, // Fn59
334        0xF8012E, // Fn60
335        0xF8012E, // Fn61
336        0xF8012E, // Fn62
337        0xF8012E, // Fn63
338    };
339
340    /**
341     * Set the speed.
342     *
343     * @param speed Number from 0 to 1; less than zero is emergency stop
344     */
345    @Override
346    public void setSpeedSetting(float speed) {
347        float oldSpeed;
348        synchronized(this) {
349            oldSpeed = this.speedSetting;
350            this.speedSetting = speed;
351        }
352        
353        // send to layout option 200 speed steps
354        if (speedStepMode == jmri.SpeedStepMode.TMCC2_200) {
355
356            // TMCC2 Legacy 200 speed step mode
357            int value = (int) (199 * speed); // max value to send is 199 in 200 step mode
358            if (value > 199) {
359                // max possible speed
360                value = 199;
361            }
362            SerialMessage m = new SerialMessage();
363            m.setOpCode(0xF8);
364    
365            if (value < 0) {
366                // System HALT (immediate stop; ALL)
367                m.putAsWord(0xFF8B);
368            } else {
369                // normal speed setting
370                m.putAsWord(0x0000 + (address.getNumber() << 9) + value);
371            }
372            // send to command station (send twice is set, but number of sends may need to be adjusted depending on efficiency)
373            tc.sendSerialMessage(m, null);
374            tc.sendSerialMessage(m, null);
375        }
376
377        // send to layout option 100 speed steps
378        if (speedStepMode == jmri.SpeedStepMode.TMCC1_100) {
379            
380          /** 
381            * TMCC1 ERR 100 speed step mode
382            * purpose is to increase resolution of 32 bits
383            * across 100 throttle 'clicks' by dividing value by 3            
384            * and setting top speed at 32
385          */
386            int value = (int) (99 * speed); // max value to send is 99 in 100 step mode
387            if (value > 93) {
388                // max possible speed step
389                value = 93;
390            }
391            SerialMessage m = new SerialMessage();
392            m.setOpCode(0xFE);
393
394            if (value < 0) {
395                // System HALT (immediate stop; ALL)
396                m.putAsWord(0xFFFF);
397            }
398            if (value >= 0) {
399                // normal speed step setting
400                m.putAsWord(0x0060 + address.getNumber() * 128 + value / 3);
401            }
402                            
403            // send to command station (send once for maximum efficiency; add extra sends if layout not responding with only one send)
404            tc.sendSerialMessage(m, null);
405            tc.sendSerialMessage(m, null);
406        }
407
408        // send to layout option TMCC2 32 speed steps
409        if (speedStepMode == jmri.SpeedStepMode.TMCC2_32) {
410
411            // TMCC2 Legacy 32 speed step mode
412            int value = (int) (32 * speed);
413            if (value > 31) {
414                // max possible speed
415                value = 31;
416            }
417            SerialMessage m = new SerialMessage();
418            m.setOpCode(0xF8);
419    
420            if (value < 0) {
421                // System HALT (immediate stop; ALL)
422                m.putAsWord(0xFF8B);
423            } else {
424                // normal speed setting
425                m.putAsWord(0x0160 + address.getNumber() * 512 + value);
426            }
427    
428            // send to command station (send twice is set, but number of sends may need to be adjusted depending on efficiency)
429            tc.sendSerialMessage(m, null);
430            tc.sendSerialMessage(m, null);           
431        }
432
433        // send to layout option TMCC1 32 speed steps
434        if (speedStepMode == jmri.SpeedStepMode.TMCC1_32) {
435
436            // TMCC1 32 speed step mode
437            int value = (int) (32 * speed);
438            if (value > 31) {
439                // max possible speed
440                value = 31;
441            }
442            SerialMessage m = new SerialMessage();
443            m.setOpCode(0xFE);
444    
445            if (value < 0) {
446                // System HALT (immediate stop; ALL)
447                m.putAsWord(0xFFFF);
448            } else {
449                // normal speed setting
450                m.putAsWord(0x0060 + address.getNumber() * 128 + value);
451            }
452    
453            // send to command station (send twice is set, but number of sends may need to be adjusted depending on efficiency)
454            tc.sendSerialMessage(m, null);
455            tc.sendSerialMessage(m, null);           
456        }
457                  
458        synchronized(this) {
459            firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting);
460        }
461        record(speed);
462    }
463
464    /**
465     * {@inheritDoc}
466     */
467    @Override
468    public void setIsForward(boolean forward) {
469        boolean old = isForward;
470        isForward = forward;
471
472        // notify layout
473        SerialMessage m = new SerialMessage();
474        if (speedStepMode == jmri.SpeedStepMode.TMCC1_32 || speedStepMode == jmri.SpeedStepMode.TMCC1_100) {
475            m.setOpCode(0xFE);
476            if (forward) {
477                m.putAsWord(0x0000 + address.getNumber() * 128);
478                setSpeedSetting(0.0f);
479            } else {
480                m.putAsWord(0x0003 + address.getNumber() * 128);
481                setSpeedSetting(0.0f);
482            }
483        }
484
485        if (speedStepMode == jmri.SpeedStepMode.TMCC2_32 || speedStepMode == jmri.SpeedStepMode.TMCC2_200) {
486            m.setOpCode(0xF8);
487            if (forward) {
488                m.putAsWord(0x0100 + address.getNumber() * 512);
489                setSpeedSetting(0.0f);
490            } else {
491                m.putAsWord(0x0103 + address.getNumber() * 512);
492                setSpeedSetting(0.0f);
493            }
494        }
495
496        tc.sendSerialMessage(m, null);
497        tc.sendSerialMessage(m, null);
498        // tc.sendSerialMessage(m, null);
499        // tc.sendSerialMessage(m, null);
500
501        firePropertyChange(ISFORWARD, old, isForward);
502    }
503
504    /**
505     * Send these messages to the layout and repeat
506     * while button is on.
507     * @param value Content of message to be sent in three bytes
508     * @param func  The number of the function being addressed
509     */
510    protected void sendFnToLayout(int value, int func) {
511        
512        if (speedStepMode == jmri.SpeedStepMode.TMCC2_200) {
513            if (func == 24) {
514                setSpeedSetting(0.055f);
515            }
516            if (func == 25) {
517                setSpeedSetting(0.205f);
518            }
519            if (func == 26) {
520                setSpeedSetting(0.355f);
521            }
522            if (func == 27) {
523                setSpeedSetting(0.505f);
524            }
525            if (func == 28) {
526                setSpeedSetting(0.705f);
527            }
528            if (func == 29) {
529                setSpeedSetting(1.0f);
530            }
531        }
532
533        if (speedStepMode == jmri.SpeedStepMode.TMCC1_32 || speedStepMode == jmri.SpeedStepMode.TMCC1_100 || speedStepMode == jmri.SpeedStepMode.TMCC2_32) {
534            if (func == 24) {
535                setSpeedSetting(0.130f);
536            }
537            if (func == 25) {
538                setSpeedSetting(0.320f);
539            }
540            if (func == 26) {
541                setSpeedSetting(0.450f);
542            }
543            if (func == 27) {
544                setSpeedSetting(0.580f);
545            }
546            if (func == 28) {
547                setSpeedSetting(0.775f);
548            }
549            if (func == 29) {
550                setSpeedSetting(1.0f);
551            }
552        }
553
554        /**
555        * Commenting out these repeat send lines in case it is
556        * necessary to reinstate them after testing. These are
557        * holdovers from the original "repeat 4 times to make
558        * sure they're accepted" instructions.
559        */
560
561        // tc.sendSerialMessage(new SerialMessage(value), null);
562        // tc.sendSerialMessage(new SerialMessage(value), null);
563        // tc.sendSerialMessage(new SerialMessage(value), null);     
564    
565        repeatFunctionSendWhileOn(value, func); // 4th send is here
566    }
567
568    static final int REPEAT_TIME = 150;
569
570    protected void repeatFunctionSendWhileOn(int value, int func) {
571        // Send again if function is still on and repeat in a short while
572        if (getFunction(func)) {
573            tc.sendSerialMessage(new SerialMessage(value), null);
574            jmri.util.ThreadingUtil.runOnLayoutDelayed(() -> {
575                repeatFunctionSendWhileOn(value, func);
576            }, REPEAT_TIME);
577        }
578    }
579
580    /*
581     * Set the speed step value.
582     * <p>
583     * Only 32 steps is available
584     *
585     * @param mode only TMCC1 32, TMCC2 32, TMCC1 100 and TMCC2 200 are allowed
586     */
587    @Override
588    public void setSpeedStepMode(jmri.SpeedStepMode mode) {
589        if (mode == jmri.SpeedStepMode.TMCC1_32 || mode == jmri.SpeedStepMode.TMCC2_32 || mode == jmri.SpeedStepMode.TMCC1_100 || mode == jmri.SpeedStepMode.TMCC2_200) {
590            super.setSpeedStepMode(mode);
591        }
592    }
593
594    /**
595     * {@inheritDoc}
596     */
597    @Override
598    public void throttleDispose() {
599        finishRecord();
600    }
601
602}