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 edits/additions 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, 85); // supports 85 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[] getFnValueArray(int number) {
055                
056            if (number < 0) return new long[]{};
057            
058            if (getSpeedStepMode() == jmri.SpeedStepMode.TMCC1_32 || getSpeedStepMode() == jmri.SpeedStepMode.TMCC1_100) {
059                if (number < SERIAL_FUNCTION_CODES_TMCC1.length) {
060                    return SERIAL_FUNCTION_CODES_TMCC1[number];
061                } else {
062                    return new long[]{};
063                }
064            } else if (getSpeedStepMode() == jmri.SpeedStepMode.TMCC2_32) {
065                 if (number < SERIAL_FUNCTION_CODES_TMCC2_32.length) {
066                     return SERIAL_FUNCTION_CODES_TMCC2_32[number];
067                } else {
068                    return new long[]{};
069                }
070            } else if (getSpeedStepMode() == jmri.SpeedStepMode.TMCC2_200) {
071                 if (number < SERIAL_FUNCTION_CODES_TMCC2_200.length) {
072                     return SERIAL_FUNCTION_CODES_TMCC2_200[number];
073                } else {
074                    return new long[]{};
075                }
076            }
077            return new long[]{};
078        }
079
080
081    /**
082     * {@inheritDoc}
083     */
084    @Override
085    public void setFunction(int func, boolean value) {
086        updateFunction(func, value);
087        
088        int numberOfTriple = 0; // ordinal number of triple being processed, e.g. 1, 2, 3, ...
089            checksumAccumulator = 0; // zeroes out accumulated checksum after each instance
090        
091        if (getFnValueArray(func).length >0) {
092            for (long triple : getFnValueArray(func)) {
093                numberOfTriple = numberOfTriple+1;
094                
095                // process each returned command
096                if (func>=0 && func < SERIAL_FUNCTION_CODES_TMCC1.length) {
097                    if ( triple > 0xFFFF ) {
098                        // TMCC 2 format
099                        if (triple > 0xFFFFFF ) {
100                            int first =  (int)(triple >> 24);
101                            int second = (int)(triple & 0xFFFFFF);
102                            accumulateChecksum(first);
103                            accumulateChecksum(second);
104                            
105                            // if this is the third triple, place the 
106                            // checksum in its lowest byte
107                            if (numberOfTriple == 3) {
108                                second = (second &0xFFFFFF00) | ( (~checksumAccumulator) & 0xFF);
109                            }
110                            
111                            // doubles are only sent once, not repeating
112                            sendOneWordOnce(first  + address.getNumber() * 512);
113                            sendOneWordOnce(second + address.getNumber() * 512);
114                            
115                        } else {
116                            // single message
117                            int content = (int)triple + address.getNumber() * 512;
118                            accumulateChecksum(content);
119                            
120                            // if this is the third triple, place the 
121                            // checksum in its lowest byte
122                            if (numberOfTriple == 3) {
123                                content = (content &0xFFFFFF00) | ( (~checksumAccumulator) & 0xFF);
124                            }
125
126                            sendFnToLayout(content, func);
127                        }
128                    } else {
129                        // TMCC 1 format
130                        sendFnToLayout((int)triple + address.getNumber() * 128, func);
131                    }
132                } else {
133                    super.setFunction(func, value);
134                }
135            }
136        } else {
137            super.setFunction(func, value);
138        }
139    }
140
141    // accumulate the checksum values into
142    // the checksumAccumulator variable
143    //
144    // Takes the two bottom bytes _only_ and adds them to the accumulation.
145    int checksumAccumulator = 0;
146    private void accumulateChecksum(int input) {
147        int byte1 = input&0xFF;
148        int byte2 = (input >> 8)&0xFF;
149        checksumAccumulator = (checksumAccumulator + byte1+byte2) & 0xFF;
150    }
151    
152    // the argument is a long containing 3 bytes. 
153    // The first byte is the message opcode
154    private void sendOneWordOnce(int word) {
155        SerialMessage m = new SerialMessage(word);
156        tc.sendSerialMessage(m, null);
157    }
158
159    // TMCC 1 Function Keys to trigger with TMCC1_32 and TMCC1_100 speed steps.
160    private final static long[][] SERIAL_FUNCTION_CODES_TMCC1 = new long[][] {
161
162        // TMCC1 Remote - Defined FnKeys
163        {0x00000D}, // Fn0 (Headlamp)
164        {0x00001D}, // Fn1 (Bell)
165        {0x00001C}, // Fn2 (Horn/Whistle)
166        {0x000005}, // Fn3 (F - Open Front Coupler)
167        {0x000006}, // Fn4 (R - Open Rear Coupler)
168
169        // TMCC1 Remote - Defined KeyPad FnKeys
170        {0x000011}, {0x000012}, {0x000013}, /* Fn5-7 */ // 1-2-3
171        {0x000014}, {0x000015}, {0x000016}, /* Fn8-10 */ // 4-5-6
172        {0x000017}, {0x000018}, {0x000019}, /* Fn11-13 */ // 7-8-9
173                    {0x000010},             /* Fn14 */ // 0
174
175        // TMCC1 Remote - Defined FnKeys
176        {0x000009}, // Fn15 (Aux1)
177        {0x00001E}, // Fn16 (Letoff Sound)
178        {0x000004}, // Fn17 (Boost)
179        {0x000007}, // Fn18 (Brake)
180        {0x000028}, // Fn19 (Momentum Low)
181        {0x000029}, // Fn20 (Momentum Medium)
182        {0x00002A}, // Fn21 (Momentum High)
183        {0x00002B}, // Fn22 (Set)
184        {0x00001F}, // Fn23 (Horn 2)
185
186        // TMCC1 RR Speed FnKeys
187        {0x000064}, // Fn24 ( 4)   5mph
188        {0x00006A}, // Fn25 (10)  20mph
189        {0x00006E}, // Fn26 (14)  35mph
190        {0x000072}, // Fn27 (18)  50mph
191        {0x000078}, // Fn28 (24)  70mph
192        {0x00007F}, // Fn29 (31)   Full
193
194        // TMCC1 ERR - Set SpeedSteps
195        {0x000009, 0x000010, 0x000009, 0x000010, 0x000004}, // Fn30 (Set ERR 100 SpeedSteps)
196        {0x000009, 0x000010, 0x000009, 0x000010, 0x000007}, // Fn31 (Set ERR  32 SpeedSteps)
197
198        // TMCC1 Acela/Subway FnKeys
199        {0x000009, 0x000005}, // Fn32 (Open Doors - Left)
200        {0x00000D, 0x000005, 0x00000D}, // Fn33 (Close Doors - Left)
201        {0x000009, 0x000006}, // Fn34 (Open Doors - Right)
202        {0x00000D, 0x000006, 0x00000D}, // Fn35 (Close Doors - Right)
203        {0x000009, 0x000013}, // Fn36 (Pantagraph - Automatic/Prototypical)
204        {0x000009, 0x000015}, // Fn37 (Pantagraph - Down)
205        {0x000009, 0x000016}, // Fn38 (Pantagraph - Manual Mode/Cycles Through Positions)
206        {0x000009, 0x00001C}, // Fn39 (Toggle Horn - City/Country)
207        {0x000009, 0x000018}, // Fn40 (Cab Light - Off)
208        {0x000009, 0x000019}, // Fn41 (Cab Light - On)
209        {0x000009, 0x00000D, 0x000018, 0x00000D}, // Fn42 (Interior Lights - Off)
210        {0x000009, 0x00000D, 0x000019, 0x00000D}, // Fn43 (Interior Lights - On)
211
212        // TMCC1 Break-Down B Unit
213        {0x000009, 0x000004}, // Fn44 Start Breakdown Sequence
214        {0x000009, 0x000015}, // Fn45 Made It Back to the Yard
215        {0x000009, 0x000013}, // Fn46 Restart Unit/Repairs Complete
216
217        // TMCC1 Boxcar/LiveStock Car
218        {0x000009, 0x000013}, // Fn47 Load
219        {0x000009, 0x000012}, // Fn48 Flat Wheel Sound
220
221        // TMCC1 Passenger/Dining Cars
222        {0x000009, 0x000017}, // Fn49 Station PA Arrival Dialog
223        {0x000009, 0x000012}, // Fn50 Conductor Arrival Dialog
224
225        // TMCC1 Crane/Boom Car
226        {0x000009, 0x000011, 0x000007}, // Fn51 Lower the Boom
227        {0x000009, 0x000011, 0x000004}, // Fn52 Raises the Boom
228        {0x000009, 0x000012, 0x000007}, // Fn53 Lowers Main/Large Hook
229        {0x000009, 0x000012, 0x000004}, // Fn54 Raises Main/Large Hook
230        {0x000017, 0x000013, 0x000007}, // Fn55 Lowers Small Hook
231        {0x000017, 0x000013, 0x000004}, // Fn56 Raises Small Hook
232        {0x000004, 0x000014}, // Fn57 Front Work Lights (Toggles On/Off)
233        {0x000004, 0x000015}, // Fn58 Rear Work Lights (Toggles On/Off)
234        {0x000004, 0x000016}, // Fn59 Launches Outriggers
235        {0x000009, 0x000017}, // Fn60 Crew Dialog Off
236        {0x000009, 0x000018}, // Fn61 All Sounds Off
237        {0x000009, 0x000019}, // Fn62 All Sounds On
238
239        // TMCC1 Unassigned FnKeys
240        {0x00002E}, // Fn63 Code to Trigger SerialMonFrame Message/Unassigned FnKey
241        {0x00002E}, // Fn64 Code to Trigger SerialMonFrame Message/Unassigned FnKey
242        {0x00002E}, // Fn65 Code to Trigger SerialMonFrame Message/Unassigned FnKey
243        {0x00002E}, // Fn66 Code to Trigger SerialMonFrame Message/Unassigned FnKey
244        {0x00002E}, // Fn67 Code to Trigger SerialMonFrame Message/Unassigned FnKey
245        {0x00002E}, // Fn68 Code to Trigger SerialMonFrame Message/Unassigned FnKey
246
247        // TMCC1 Aux FnKeys
248        {0x000008}, // Fnxx (Aux1 Off)
249        {0x000009}, // Fnxx (Aux1 Option 1 - On While Held)
250        {0x00000A}, // Fnxx (Aux1 Option 2 - Toggle On/Toggle Off)
251        {0x00000B}, // Fnxx (Aux1 On)
252        {0x00000C}, // Fnxx (Aux2 Off)
253        {0x00000D}, // Fnxx (Aux2 Option 1 - Toggle On/Toggle Off)
254        {0x00000E}, // Fnxx (Aux2 Option 2 - On While Held)
255        {0x00000F}, // Fnxx (Aux2 On)
256
257    };
258
259    /**
260    * Translate TMCC1 function numbers to line characters.
261    * If the upper byte is zero, it will be replaced by 0xF8
262    * and the address will be set in the low position.
263    * If the upper byte is non-zero, that value will be sent,
264    * and the address will be set in the upper (TMCC2) position.
265    * If six bytes are specified (with the upper one non-zero), 
266    * this will be interpreted as two commands to be sequentially sent,
267    * with the upper bytes sent first.
268    */
269
270    // TMCC 2 Legacy Function Keys to trigger with TMCC2_32 speed steps.
271    private final static long[][] SERIAL_FUNCTION_CODES_TMCC2_32 = new long[][] {
272
273        // TMCC2_32 Remote - Defined FnKeys
274        {0xF8010D}, // Fn0 (Headlamp)
275        {0xF8011D}, // Fn1 (Bell)
276        {0xF8011C}, // Fn2 (Horn/Whistle)
277        {0xF80105}, // Fn3 (F - Open Front Coupler)
278        {0xF80106}, // Fn4 (R - Open Rear Coupler)
279
280        // TMCC2_32 Remote - Defined KeyPad FnKeys
281        {0xF80111}, {0xF80112}, {0xF80113}, /* Fn5-7 */ // 1-2-3
282        {0xF80114}, {0xF80115}, {0xF80116}, /* Fn8-10 */ // 4-5-6
283        {0xF80117}, {0xF80118}, {0xF80119}, /* Fn11-13 */ // 7-8-9
284                    {0xF80110},             /* Fn14 */ // 0
285
286        // TMCC2_32 Remote - Defined FnKeys
287        {0xF80109}, // Fn15 (Aux1)
288        {0xF8011E}, // Fn16 (Letoff Sound)
289        {0xF80104}, // Fn17 (Boost)
290        {0xF80107}, // Fn18 (Brake)
291        {0xF80128}, // Fn19 (Momentum Low)
292        {0xF80129}, // Fn20 (Momentum Medium)
293        {0xF8012A}, // Fn21 (Momentum High)
294        {0xF8012B}, // Fn22 (Set)
295        {0xF8011F}, // Fn23 (Horn 2)
296
297        // TMCC2_32 RR Speed FnKeys
298        {0xF80164}, // Fn24 ( 4)   5mph
299        {0xF8016A}, // Fn25 (10)  20mph
300        {0xF8016E}, // Fn26 (14)  35mph
301        {0xF80172}, // Fn27 (18)  50mph
302        {0xF80178}, // Fn28 (24)  70mph
303        {0xF8017F}, // Fn29 (31)   Full
304
305        // TMCC2_32 Extended Lighting FnKeys
306        {0xF8017D, 0xFB00E8, 0xFB0000}, // Fn30 (Mars Lt On)
307        {0xF8017D, 0xFB00E9, 0xFB0000}, // Fn31 (Mars Lt Off)
308
309        {0xF8017D, 0xFB00D0, 0xFB0000}, // Fn32 (Ground Lt On)
310        {0xF8017D, 0xFB00D1, 0xFB0000}, // Fn33 (Ground Lt Off)
311        {0xF8017D, 0xFB00D2, 0xFB0000}, // Fn34 (Ground Lt Auto)
312
313        {0xF8017D, 0xFB00A0, 0xFB0000}, // Fn35 (DogHouse On)
314        {0xF8017D, 0xFB00A1, 0xFB0000}, // Fn36 (DogHouse Off)
315
316        {0xF8017D, 0xFB00CC, 0xFB0000}, // Fn37 (Tender Marker On)
317        {0xF8017D, 0xFB00CD, 0xFB0000}, // Fn38 (Tender Marker Off)
318
319        {0xF8017D, 0xFB00F4, 0xFB0000}, // Fn39 (Rule 17 On)
320        {0xF8017D, 0xFB00F5, 0xFB0000}, // Fn40 (Rule 17 Off)
321        {0xF8017D, 0xFB00F6, 0xFB0000}, // Fn41 (Rule 17 Auto)
322
323        {0xF8017D, 0xFB00C0, 0xFB0000}, // Fn42 (Ditch Lt On)
324        {0xF8017D, 0xFB00C1, 0xFB0000}, // Fn43 (Ditch Lt On; Pulse Off with Horn)
325        {0xF8017D, 0xFB00C2, 0xFB0000}, // Fn44 (Ditch Lt Off; Pulse On with Horn)
326        {0xF8017D, 0xFB00C3, 0xFB0000}, // Fn45 (Ditch Lt Off)
327
328        {0xF8017D, 0xFB00F0, 0xFB0000}, // Fn46 (Cab Lt On)
329        {0xF8017D, 0xFB00F1, 0xFB0000}, // Fn47 (Cab Lt Off)
330        {0xF8017D, 0xFB00F2, 0xFB0000}, // Fn48 (Cab Lt Auto)
331
332        {0xF8017D, 0xFB00C8, 0xFB0000}, // Fn49 (Loco Marker On)
333        {0xF8017D, 0xFB00C9, 0xFB0000}, // Fn50 (Loco Marker Off)
334
335        {0xF8017D, 0xFB00B0, 0xFB0000}, // Fn51 (Hazard Lt On)
336        {0xF8017D, 0xFB00B1, 0xFB0000}, // Fn52 (Hazard Lt Off)
337        {0xF8017D, 0xFB00B2, 0xFB0000}, // Fn53 (Hazard Lt Auto)
338
339        {0xF8017D, 0xFB00E0, 0xFB0000}, // Fn54 (Strobe Lt On - Single Flash)
340        {0xF8017D, 0xFB00E1, 0xFB0000}, // Fn55 (Strobe Lt On - Double Flash)
341        {0xF8017D, 0xFB00E2, 0xFB0000}, // Fn56 (Strobe Lt Off)
342
343        {0xF8017D, 0xFB00F8, 0xFB0000}, // Fn57 (Car Cabin Lt On)
344        {0xF8017D, 0xFB00F9, 0xFB0000}, // Fn58 (Car Cabin Lt Off)
345        {0xF8017D, 0xFB00FA, 0xFB0000}, // Fn59 (Car Cabin Lt Auto)
346
347        // TMCC2 Acela/Subway FnKeys
348        {0xF80109, 0xF80112}, // Fn60 (Crew: Report Speed - Moving)
349        {0xF80109, 0xF80115}, // Fn61 (Tower: Emergency Stop/Crew: Ack - Moving)
350        {0xF8017C, 0xFB0020, 0xFB0000}, // Fn62 (Open Doors - Left)
351        {0xF8017C, 0xFB0021, 0xFB0000}, // Fn63 (Close Doors - Left)
352        {0xF8017C, 0xFB0022, 0xFB0000}, // Fn64 (Open Doors - Right)
353        {0xF8017C, 0xFB0023, 0xFB0000}, // Fn65 (Close Doors - Right)
354        {0xF8017C, 0xFB0010, 0xFB0000}, // Fn66 (Pantagraph - Up/F)
355        {0xF8017C, 0xFB0011, 0xFB0000}, // Fn67 (Pantagraph - Down/F)
356        {0xF8017C, 0xFB0012, 0xFB0000}, // Fn68 (Pantagraph - Up/R)
357        {0xF8017C, 0xFB0013, 0xFB0000}, // Fn69 (Pantagraph - Down/R)
358        
359        // Only TMCC1 Break-Down B Unit
360
361        // TMCC2 Boxcar/Livestock Car
362        {0xF8017C, 0xFB0030, 0xFB0000}, // Fn70 (Option1 On)
363        {0xF8017C, 0xFB0031, 0xFB0000}, // Fn71 (Opiton1 Off)
364        {0xF8017C, 0xFB0032, 0xFB0000}, // Fn72 (Option2 On)
365        {0xF8017C, 0xFB0033, 0xFB0000}, // Fn73 (Option2 Off)
366        {0xF8017C, 0xFB0034, 0xFB0000}, // Fn74 (Load)
367        {0xF8017C, 0xFB0035, 0xFB0000}, // Fn75 (Unload)
368        {0xF8017C, 0xFB0036, 0xFB0000}, // Fn76 (FRED On)
369        {0xF8017C, 0xFB0037, 0xFB0000}, // Fn77 (FRED Off)
370        {0xF8017C, 0xFB0038, 0xFB0000}, // Fn78 (Flat Wheel On)
371        {0xF8017C, 0xFB0039, 0xFB0000}, // Fn79 (Flat Wheel Off)
372        {0xF8017C, 0xFB003A, 0xFB0000}, // Fn80 (Game On)
373        {0xF8017C, 0xFB003B, 0xFB0000}, // Fn81 (Game Off)
374
375        // Only TMCC1 Passenger/Dining Cars
376
377        // Only TMCC1 Crane/Boom Car
378
379        // TMCC2 Smoke System
380        {0xF8017C, 0xFB0000, 0xFB0000}, // Fn82 (Smoke System Off)
381        {0xF8017C, 0xFB0001, 0xFB0000}, // Fn83 (Smoke System Low)
382        {0xF8017C, 0xFB0002, 0xFB0000}, // Fn84 (Smoke System Med)
383        {0xF8017C, 0xFB0003, 0xFB0000}, // Fn85 (Smoke System High)
384
385        // TRMCC2_32 Unassigned FnKeys
386        {0xF8012E}, // Fnxx Code to Trigger SerialMonFrame Message/Unassigned FnKey
387        {0xF8012E}, // Fnxx Code to Trigger SerialMonFrame Message/Unassigned FnKey
388
389        // TRMCC2_32 Aux FnKeys
390        {0xF80108}, // Fnxx (Aux1 Off)
391        {0xF80109}, // Fnxx (Aux1 Option 1 - On While Held) 
392        {0xF8010A}, // Fnxx (Aux1 Option 2 - Toggle On/Toggle Off)
393        {0xF8010B}, // Fnxx (Aux1 On)
394        {0xF8010C}, // Fnxx (Aux2 Off)
395        {0xF8010D}, // Fnxx (Aux2 Option 1 - Toggle On/Toggle Off) 
396        {0xF8010E}, // Fnxx (Aux2 Option 2 - On While Held)
397        {0xF8010F}, // Fnxx (Aux2 On)
398
399};
400
401    // TMCC 2 Legacy Function Keys to trigger with TMCC2_200 speed steps.
402    private final static long[][] SERIAL_FUNCTION_CODES_TMCC2_200 = new long[][] {
403
404        // TMCC2_200 Remote - Defined FnKeys
405        {0xF8010D}, // Fn0 (Headlamp)
406        {0xF8011D}, // Fn1 (Bell)
407        {0xF8011C}, // Fn2 (Horn/Whistle)
408        {0xF80105}, // Fn3 (F - Open Front Coupler)
409        {0xF80106}, // Fn4 (R - Open Rear Coupler)
410
411        // TMCC2_200 Remote - Defined KeyPad FnKeys
412        {0xF80111}, {0xF80112}, {0xF80113}, /* Fn5-7 */ // 1-2-3
413        {0xF80114}, {0xF80115}, {0xF80116}, /* Fn8-10 */ // 4-5-6
414        {0xF80117}, {0xF80118}, {0xF80119}, /* Fn11-13 */ // 7-8-9
415                    {0xF80110},             /* Fn14 */ // 0
416
417        // TMCC2_200 Remote - Defined FnKeys
418        {0xF80109}, // Fn15 (Aux1)
419        {0xF8011E}, // Fn16 (Letoff Sound)
420        {0xF80104}, // Fn17 (Boost)
421        {0xF80107}, // Fn18 (Brake)
422        {0xF80128}, // Fn19 (Momentum Low)
423        {0xF80129}, // Fn20 (Momentum Medium)
424        {0xF8012A}, // Fn21 (Momentum High)
425        {0xF8012B}, // Fn22 (Set)
426        {0xF8011F}, // Fn23 (Horn 2)
427
428        // TMCC2_200 RR Speed FnKeys
429        {0xF8000A}, // Fn24 ( 10)   5mph
430        {0xF80028}, // Fn25 ( 40)  20mph
431        {0xF80046}, // Fn26 ( 70)  35mph
432        {0xF80064}, // Fn27 (100)  50mph
433        {0xF8008C}, // Fn28 (140)  70mph
434        {0xF800C7}, // Fn29 (199)   Full
435
436        // TMCC2_200 Extended Lighting FnKeys
437        {0xF8017D, 0xFB00E8, 0xFB0000}, // Fn30 (Mars Lt On)
438        {0xF8017D, 0xFB00E9, 0xFB0000}, // Fn31 (Mars Lt Off)
439
440        {0xF8017D, 0xFB00D0, 0xFB0000}, // Fn32 (Ground Lt On)
441        {0xF8017D, 0xFB00D1, 0xFB0000}, // Fn33 (Ground Lt Off)
442        {0xF8017D, 0xFB00D2, 0xFB0000}, // Fn34 (Ground Lt Auto)
443
444        {0xF8017D, 0xFB00A0, 0xFB0000}, // Fn35 (DogHouse On)
445        {0xF8017D, 0xFB00A1, 0xFB0000}, // Fn36 (DogHouse Off)
446
447        {0xF8017D, 0xFB00CC, 0xFB0000}, // Fn37 (Tender Marker On)
448        {0xF8017D, 0xFB00CD, 0xFB0000}, // Fn38 (Tender Marker Off)
449
450        {0xF8017D, 0xFB00F4, 0xFB0000}, // Fn39 (Rule 17 On)
451        {0xF8017D, 0xFB00F5, 0xFB0000}, // Fn40 (Rule 17 Off)
452        {0xF8017D, 0xFB00F6, 0xFB0000}, // Fn41 (Rule 17 Auto)
453
454        {0xF8017D, 0xFB00C0, 0xFB0000}, // Fn42 (Ditch Lt On)
455        {0xF8017D, 0xFB00C1, 0xFB0000}, // Fn43 (Ditch Lt On; Pulse Off with Horn)
456        {0xF8017D, 0xFB00C2, 0xFB0000}, // Fn44 (Ditch Lt Off; Pulse On with Horn)
457        {0xF8017D, 0xFB00C3, 0xFB0000}, // Fn45 (Ditch Lt Off)
458
459        {0xF8017D, 0xFB00F0, 0xFB0000}, // Fn46 (Cab Lt On)
460        {0xF8017D, 0xFB00F1, 0xFB0000}, // Fn47 (Cab Lt Off)
461        {0xF8017D, 0xFB00F2, 0xFB0000}, // Fn48 (Cab Lt Auto)
462
463        {0xF8017D, 0xFB00C8, 0xFB0000}, // Fn49 (Loco Marker On)
464        {0xF8017D, 0xFB00C9, 0xFB0000}, // Fn50 (Loco Marker Off)
465
466        {0xF8017D, 0xFB00B0, 0xFB0000}, // Fn51 (Hazard Lt On)
467        {0xF8017D, 0xFB00B1, 0xFB0000}, // Fn52 (Hazard Lt Off)
468        {0xF8017D, 0xFB00B2, 0xFB0000}, // Fn53 (Hazard Lt Auto)
469
470        {0xF8017D, 0xFB00E0, 0xFB0000}, // Fn54 (Strobe Lt On - SingleFlash)
471        {0xF8017D, 0xFB00E1, 0xFB0000}, // Fn55 (Strobe Lt On - DoubleFlash)
472        {0xF8017D, 0xFB00E2, 0xFB0000}, // Fn56 (Strobe Lt Off)
473
474        {0xF8017D, 0xFB00F8, 0xFB0000}, // Fn57 (Car Cabin Lt On)
475        {0xF8017D, 0xFB00F9, 0xFB0000}, // Fn58 (Car Cabin Lt Off)
476        {0xF8017D, 0xFB00FA, 0xFB0000}, // Fn59 (Car Cabin Lt Auto)
477
478        // TMCC2 Acela/Subway FnKeys
479        {0xF80109, 0xF80112}, // Fn60 (Crew: Report Speed - Moving)
480        {0xF80109, 0xF80115}, // Fn61 (Tower: Emergency Stop/Crew: Ack - Moving)
481        {0xF8017C, 0xFB0020, 0xFB0000}, // Fn62 (Open Doors - Left)
482        {0xF8017C, 0xFB0021, 0xFB0000}, // Fn63 (Close Doors - Left)
483        {0xF8017C, 0xFB0022, 0xFB0000}, // Fn64 (Open Doors - Right)
484        {0xF8017C, 0xFB0023, 0xFB0000}, // Fn65 (Close Doors - Right)
485        {0xF8017C, 0xFB0010, 0xFB0000}, // Fn66 (Pantagraph - Up/F)
486        {0xF8017C, 0xFB0011, 0xFB0000}, // Fn67 (Pantagraph - Down/F)
487        {0xF8017C, 0xFB0012, 0xFB0000}, // Fn68 (Pantagraph - Up/R)
488        {0xF8017C, 0xFB0013, 0xFB0000}, // Fn69 (Pantagraph - Down/R)
489
490        // Only TMCC1 Break-Down B Unit
491
492        // TMCC2 Boxcar/Livestock Car
493        {0xF8017C, 0xFB0030, 0xFB0000}, // Fn70 (Option1 On)
494        {0xF8017C, 0xFB0031, 0xFB0000}, // Fn71 (Opiton1 Off)
495        {0xF8017C, 0xFB0032, 0xFB0000}, // Fn72 (Option2 On)
496        {0xF8017C, 0xFB0033, 0xFB0000}, // Fn73 (Option2 Off)
497        {0xF8017C, 0xFB0034, 0xFB0000}, // Fn74 (Load)
498        {0xF8017C, 0xFB0035, 0xFB0000}, // Fn75 (Unload)
499        {0xF8017C, 0xFB0036, 0xFB0000}, // Fn76 (FRED On)
500        {0xF8017C, 0xFB0037, 0xFB0000}, // Fn77 (FRED Off)
501        {0xF8017C, 0xFB0038, 0xFB0000}, // Fn78 (Flat Wheel On)
502        {0xF8017C, 0xFB0039, 0xFB0000}, // Fn79 (Flat Wheel Off)
503        {0xF8017C, 0xFB003A, 0xFB0000}, // Fn80 (Game On)
504        {0xF8017C, 0xFB003B, 0xFB0000}, // Fn81 (Game Off)
505
506        // Only TMCC1 Passenger/Dining Cars
507
508        // Only TMCC1 Crane/Boom Car
509
510        // TMCC2 Smoke System
511        {0xF8017C, 0xFB0000, 0xFB0000}, // Fn82 (Smoke System Off)
512        {0xF8017C, 0xFB0001, 0xFB0000}, // Fn83 (Smoke System Low)
513        {0xF8017C, 0xFB0002, 0xFB0000}, // Fn84 (Smoke System Med)
514        {0xF8017C, 0xFB0003, 0xFB0000}, // Fn85 (Smoke System High)
515
516        // TRMCC2_200 Unassigned FnKeys
517        {0xF8012E}, // Fnxx Code to Trigger SerialMonFrame Message/Unassigned FnKey
518        {0xF8012E}, // Fnxx Code to Trigger SerialMonFrame Message/Unassigned FnKey
519
520        // TMCC2_200 Aux FnKeys
521        {0xF80108}, // Fnxx (Aux1 Off)
522        {0xF80109}, // Fnxx (Aux1 Option 1 - On While Held) 
523        {0xF8010A}, // Fnxx (Aux1 Option 2 - Toggle On/Toggle Off)
524        {0xF8010B}, // Fnxx (Aux1 On)
525        {0xF8010C}, // Fnxx (Aux2 Off)
526        {0xF8010D}, // Fnxx (Aux2 Option 1 - Toggle On/Toggle Off) 
527        {0xF8010E}, // Fnxx (Aux2 Option 2 - On While Held)
528        {0xF8010F}, // Fnxx (Aux2 On)
529
530    };
531
532
533     int previousValue;
534     int newValue;
535
536    /**
537     * Set the speed.
538     *
539     * @param speed Number from 0 to 1; less than zero is emergency stop
540     */
541
542    @Override
543    public void setSpeedSetting(float speed) {
544        float oldSpeed;
545        synchronized(this) {
546            oldSpeed = this.speedSetting;
547            this.speedSetting = speed;
548        }
549        
550        // Option TMCC2_200 "Absolute" speed steps
551        if (speedStepMode == jmri.SpeedStepMode.TMCC2_200) {
552
553            // TMCC2 Legacy 200 speed step mode
554            int value = (int) (199 * speed); // max value to send is 199 in 200 step mode
555            if (value > 199) {
556                // max possible speed
557                value = 199;
558            }
559            SerialMessage m = new SerialMessage();
560            m.setOpCode(0xF8);
561    
562            if (value < 0) {
563                // System HALT (immediate stop; ALL)
564                m.putAsWord(0xFF8B);
565
566                // send to layout (send 4 times to ensure received)
567                tc.sendSerialMessage(m, null);
568                tc.sendSerialMessage(m, null);
569                tc.sendSerialMessage(m, null);
570                tc.sendSerialMessage(m, null);
571
572            } else {
573                // normal speed setting
574                m.putAsWord(0x0000 + (address.getNumber() << 9) + value);
575
576                // send to command station (send twice is set, but number of sends may need to be adjusted depending on efficiency)
577                tc.sendSerialMessage(m, null);
578                tc.sendSerialMessage(m, null);
579            }
580        }
581
582        // Option TMCC1_100 "Relative" speed steps
583        if (speedStepMode == jmri.SpeedStepMode.TMCC1_100) {
584            
585          /** 
586            * TMCC1 MedMomentum, HighMomentum and ERR 100 speed step mode
587            * purpose is to increase resolution of 32 bits
588            * across 100 throttle 'clicks' by dividing value by 3            
589            * and setting top speed at 32
590          */
591
592            int value = (int) (99 * speed); // max value to send is 99 in 100 step mode
593            if (value > 99) {
594                // max possible speed step
595                value = 99;
596            }
597            SerialMessage m = new SerialMessage();
598            m.setOpCode(0xFE);
599
600            if (value < 0) {
601                // System HALT (immediate stop; ALL)
602                m.putAsWord(0xFFFF);
603
604                // send to layout (send 4 times to ensure received)
605                tc.sendSerialMessage(m, null);
606                tc.sendSerialMessage(m, null);
607                tc.sendSerialMessage(m, null);
608                tc.sendSerialMessage(m, null);
609            }
610            if (value == 0) {
611                // normal speed step setting
612                m.putAsWord(0x0060 + address.getNumber() * 128 + value);
613
614                // send to layout (send twice is set, but number of sends may need to be adjusted depending on efficiency)
615                tc.sendSerialMessage(m, null);
616                tc.sendSerialMessage(m, null);
617            }
618
619            if (value > 0) {
620                 newValue = value;
621                 if (newValue > previousValue) {
622                    // increase TMCC "Relative" speed +1 (repeat * valueChange46)
623                    int valueChange46 = (newValue - previousValue);
624                    for (int i = 0x0000; i < valueChange46; i++) {
625                        m.putAsWord(0x0046 + address.getNumber() * 128);
626                        tc.sendSerialMessage(m, null);
627                    }
628                }
629                if (newValue < previousValue) {
630                    // decrease TMCC "Relative" speed -1 (repeat * valueChange44)
631                    int valueChange44 = (previousValue - newValue);
632                    for (int j = 0x0000; j < valueChange44; j++) {
633                        m.putAsWord(0x0044 + address.getNumber() * 128);
634                        tc.sendSerialMessage(m, null);
635                    }
636                }
637            previousValue = newValue;                
638            }
639        }
640
641        // Option TMCC2_32 "Absolute" speed steps
642        if (speedStepMode == jmri.SpeedStepMode.TMCC2_32) {
643
644            // TMCC2 Legacy 32 speed step mode
645            int value = (int) (32 * speed);
646            if (value > 31) {
647                // max possible speed
648                value = 31;
649            }
650            SerialMessage m = new SerialMessage();
651            m.setOpCode(0xF8);
652    
653            if (value < 0) {
654                // System HALT (immediate stop; ALL)
655                m.putAsWord(0xFF8B);
656
657                // send to layout (send 4 times to ensure received)
658                tc.sendSerialMessage(m, null);
659                tc.sendSerialMessage(m, null);
660                tc.sendSerialMessage(m, null);
661                tc.sendSerialMessage(m, null);
662
663            } else {
664                // normal speed setting
665                m.putAsWord(0x0160 + address.getNumber() * 512 + value);
666
667                // send to command station (send twice is set, but number of sends may need to be adjusted depending on efficiency)
668                tc.sendSerialMessage(m, null);
669                tc.sendSerialMessage(m, null);
670            }
671        }
672
673        // Option TMCC1_32 "Absolute" speed steps
674        if (speedStepMode == jmri.SpeedStepMode.TMCC1_32) {
675
676            // TMCC1 32 speed step mode
677            int value = (int) (32 * speed);
678            if (value > 31) {
679                // max possible speed
680                value = 31;
681            }
682            SerialMessage m = new SerialMessage();
683            m.setOpCode(0xFE);
684    
685            if (value < 0) {
686                // System HALT (immediate stop; ALL)
687                m.putAsWord(0xFFFF);
688
689                // send to layout (send 4 times to ensure received)
690                tc.sendSerialMessage(m, null);
691                tc.sendSerialMessage(m, null);
692                tc.sendSerialMessage(m, null);
693                tc.sendSerialMessage(m, null);
694
695            } else {
696                // normal speed setting
697                m.putAsWord(0x0060 + address.getNumber() * 128 + value);
698
699                // send to layout (send twice is set, but number of sends may need to be adjusted depending on efficiency)
700                tc.sendSerialMessage(m, null);
701                tc.sendSerialMessage(m, null);           
702            }
703        }
704                  
705        synchronized(this) {
706            firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting);
707        }
708        record(speed);
709    }
710
711    /**
712     * {@inheritDoc}
713     */
714    @Override
715    public void setIsForward(boolean forward) {
716        boolean old = isForward;
717        isForward = forward;
718
719        // notify layout
720        SerialMessage m = new SerialMessage();
721        if (speedStepMode == jmri.SpeedStepMode.TMCC1_32 || speedStepMode == jmri.SpeedStepMode.TMCC1_100) {
722            m.setOpCode(0xFE);
723            if (forward) {
724                m.putAsWord(0x0000 + address.getNumber() * 128);
725                setSpeedSetting(0.0f);
726            } else {
727                m.putAsWord(0x0003 + address.getNumber() * 128);
728                setSpeedSetting(0.0f);
729            }
730        }
731
732        if (speedStepMode == jmri.SpeedStepMode.TMCC2_32 || speedStepMode == jmri.SpeedStepMode.TMCC2_200) {
733            m.setOpCode(0xF8);
734            if (forward) {
735                m.putAsWord(0x0100 + address.getNumber() * 512);
736                setSpeedSetting(0.0f);
737            } else {
738                m.putAsWord(0x0103 + address.getNumber() * 512);
739                setSpeedSetting(0.0f);
740            }
741        }
742
743        // send to command station (send twice is set, but number of sends may need to be adjusted depending on efficiency)
744        tc.sendSerialMessage(m, null);
745        tc.sendSerialMessage(m, null);
746
747        firePropertyChange(ISFORWARD, old, isForward);
748    }
749
750    /**
751     * Send these messages to the layout and repeat
752     * while button is pressed/on.
753     * @param value Content of message to be sent in three bytes
754     * @param func  The number of the function being addressed
755     */
756    protected void sendFnToLayout(int value, int func) {
757        
758        if (speedStepMode == jmri.SpeedStepMode.TMCC2_200) {
759            if (func == 24) {
760                setSpeedSetting(0.055f);
761                return;
762            }
763            if (func == 25) {
764                setSpeedSetting(0.205f);
765                return;
766            }
767            if (func == 26) {
768                setSpeedSetting(0.355f);
769                return;
770            }
771            if (func == 27) {
772                setSpeedSetting(0.505f);
773                return;
774            }
775            if (func == 28) {
776                setSpeedSetting(0.705f);
777                return;
778            }
779            if (func == 29) {
780                setSpeedSetting(1.0f);
781                return;
782            }
783        }
784
785        if (speedStepMode == jmri.SpeedStepMode.TMCC1_32 || speedStepMode == jmri.SpeedStepMode.TMCC1_100 || speedStepMode == jmri.SpeedStepMode.TMCC2_32) {
786            if (func == 24) {
787                setSpeedSetting(0.130f);
788                return;
789            }
790            if (func == 25) {
791                setSpeedSetting(0.320f);
792                return;
793            }
794            if (func == 26) {
795                setSpeedSetting(0.450f);
796                return;
797            }
798            if (func == 27) {
799                setSpeedSetting(0.580f);
800                return;
801            }
802            if (func == 28) {
803                setSpeedSetting(0.775f);
804                return;
805            }
806            if (func == 29) {
807                setSpeedSetting(1.0f);
808                return;
809            }
810        }
811
812        /**
813        * This code sends FnKey presses to the command station. 
814        * Send once is set, per the need of TMCC multi-key commands that
815        * do not work when a specific command sequence is not followed.
816        * If these multi-key commands are integrated into single FnKeys,
817        * this "send" section can be converted back to "send twice" as
818        * the other send sequences througout tmcc\SerialThrottle.java.
819        */
820
821        repeatFunctionSendWhileOn(value, func); // Single FnKey Press, Single Send; FnKey Held, Repeats FnKey while pressed.
822    }
823
824    /**
825    * This code block is necessary to support the send repeats of
826    * the repeatFunctionSendWhileOn(value, func); code above.
827    * This code block "Sends Again" if FkKey is still pressed/on, and
828    * repeats per the interval set in static final int REPEAT_TIME.
829    */
830
831    static final int REPEAT_TIME = 150;
832
833    protected void repeatFunctionSendWhileOn(int value, int func) {
834        if (getFunction(func)) {
835            tc.sendSerialMessage(new SerialMessage(value), null);
836            jmri.util.ThreadingUtil.runOnLayoutDelayed(() -> {
837                repeatFunctionSendWhileOn(value, func);
838            }, REPEAT_TIME);
839        }
840    }
841
842    /*
843     * Set the speed step value.
844     * <p>
845     * The speed step range is from 32 steps, to 100 steps, to 200 steps
846     *
847     * @param mode only TMCC1_32, TMCC2_32, TMCC1_100 and TMCC2_200 are allowed
848     */
849    @Override
850    public void setSpeedStepMode(jmri.SpeedStepMode mode) {
851        if (mode == jmri.SpeedStepMode.TMCC1_32 || mode == jmri.SpeedStepMode.TMCC2_32 || mode == jmri.SpeedStepMode.TMCC1_100 || mode == jmri.SpeedStepMode.TMCC2_200) {
852            super.setSpeedStepMode(mode);
853        }
854    }
855
856    /**
857     * {@inheritDoc}
858     */
859    @Override
860    public void throttleDispose() {
861        finishRecord();
862    }
863
864}