001package jmri.jmrix.mrc;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.util.Date;
005import jmri.DccLocoAddress;
006import jmri.LocoAddress;
007import jmri.jmrix.AbstractThrottle;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * An implementation of DccThrottle with code specific to an MRC connection.
013 * <p>
014 * Addresses of 99 and below are considered short addresses, and over 100 are
015 * considered long addresses. This is not the MRC system standard, but is used
016 * as an expedient here.
017 * <p>
018 * Based on Glen Oberhauser's original LnThrottleManager implementation
019 *
020 * @author Bob Jacobsen Copyright (C) 2001
021 */
022public class MrcThrottle extends AbstractThrottle implements MrcTrafficListener {
023
024    private MrcTrafficController tc = null;
025
026    /**
027     * Throttle Constructor.
028     * @param memo system connection memo
029     * @param address DCC loco address for throttle
030     */
031    public MrcThrottle(MrcSystemConnectionMemo memo, DccLocoAddress address) {
032        super(memo);
033        this.tc = memo.getMrcTrafficController();
034        super.speedStepMode = jmri.SpeedStepMode.NMRA_DCC_128;
035
036        // cache settings. It would be better to read the
037        // actual state, but I don't know how to do this
038        synchronized(this) {
039            this.speedSetting = 0;
040        }
041        // Functions default to false
042        this.address = address;
043        this.isForward = true;
044        if (address.isLongAddress()) {
045            addressLo = address.getNumber();
046            addressHi = address.getNumber() >> 8;
047            addressHi = addressHi + 0xc0; //We add 0xc0 to the high byte.
048        } else {
049            addressLo = address.getNumber();
050        }
051        tc.addTrafficListener(MrcInterface.THROTTLEINFO, this);
052    }
053
054    DccLocoAddress address;
055
056    int addressLo = 0x00;
057    int addressHi = 0x00;
058
059    @Override
060    public LocoAddress getLocoAddress() {
061        return address;
062    }
063
064    @Override
065    protected void sendFunctionGroup1() {
066
067        int data = 0x00
068                | (getFunction(0) ? 0x10 : 0)
069                | (getFunction(1) ? 0x01 : 0)
070                | (getFunction(2) ? 0x02 : 0)
071                | (getFunction(3) ? 0x04 : 0)
072                | (getFunction(4) ? 0x08 : 0);
073
074        data = data + 0x80;
075        MrcMessage m = MrcMessage.getSendFunction(1, addressLo, addressHi, data);
076        if (m != null) {
077            tc.sendMrcMessage(m);
078        }
079    }
080
081    /**
082     * Send the message to set the state of functions F5, F6, F7, F8.
083     */
084    @Override
085    protected void sendFunctionGroup2() {
086        int data = 0x00
087                | (getFunction(8) ? 0x08 : 0)
088                | (getFunction(7) ? 0x04 : 0)
089                | (getFunction(6) ? 0x02 : 0)
090                | (getFunction(5) ? 0x01 : 0);
091
092        data = data + 0xB0;
093
094        MrcMessage m = MrcMessage.getSendFunction(2, addressLo, addressHi, data);
095        if (m != null) {
096            tc.sendMrcMessage(m);
097        }
098    }
099
100    /**
101     * Send the message to set the state of functions F9, F12, F11, F12.
102     */
103    @Override
104    protected void sendFunctionGroup3() {
105
106        int data = 0x00
107                | (getFunction(9) ? 0x01 : 0)
108                | (getFunction(10) ? 0x02 : 0)
109                | (getFunction(11) ? 0x04 : 0)
110                | (getFunction(12) ? 0x08 : 0);
111
112        data = data + 0xA0;
113        MrcMessage m = MrcMessage.getSendFunction(3, addressLo, addressHi, data);
114        if (m != null) {
115            tc.sendMrcMessage(m);
116        }
117    }
118
119    /**
120     * Send the message to set the state of functions F13 to F20. MRC Group 4 and 5
121     */
122    @Override
123    protected void sendFunctionGroup4() {
124        int data = 0x00
125                | (getFunction(16) ? 0x08 : 0)
126                | (getFunction(15) ? 0x04 : 0)
127                | (getFunction(14) ? 0x02 : 0)
128                | (getFunction(13) ? 0x01 : 0);
129
130        data = data + 0xD0;
131
132        MrcMessage m = MrcMessage.getSendFunction(4, addressLo, addressHi, data);
133        if (m != null) {
134            tc.sendMrcMessage(m);
135        }
136
137        data = 0x00
138                | (getFunction(20) ? 0x08 : 0)
139                | (getFunction(19) ? 0x04 : 0)
140                | (getFunction(18) ? 0x02 : 0)
141                | (getFunction(17) ? 0x01 : 0);
142        data = data + 0xC0;
143
144        m = MrcMessage.getSendFunction(5, addressLo, addressHi, data);
145        if (m != null) {
146            tc.sendMrcMessage(m);
147        }
148    }
149
150    /**
151     * Send the message to set the state of functions F21 to F28. MRC Group 6
152     */
153    @Override
154    protected void sendFunctionGroup5() {
155        int data = 0x00
156                | (getFunction(28) ? 0x80 : 0)
157                | (getFunction(27) ? 0x40 : 0)
158                | (getFunction(26) ? 0x20 : 0)
159                | (getFunction(25) ? 0x10 : 0)
160                | (getFunction(24) ? 0x08 : 0)
161                | (getFunction(23) ? 0x04 : 0)
162                | (getFunction(22) ? 0x02 : 0)
163                | (getFunction(21) ? 0x01 : 0);
164
165        MrcMessage m = MrcMessage.getSendFunction(6, addressLo, addressHi, data);
166        if (m != null) {
167            tc.sendMrcMessage(m);
168        }
169    }
170
171    /**
172     * Set the speed and direction.
173     *
174     * @param speed Number from 0 to 1, or less than zero for emergency stop
175     */
176    @Override
177    public void setSpeedSetting(float speed) {
178        float oldSpeed;
179        synchronized(this) {
180            oldSpeed = this.speedSetting;
181            this.speedSetting = speed;
182        }
183        MrcMessage m;
184        int value;
185        if (super.speedStepMode == jmri.SpeedStepMode.NMRA_DCC_128) {
186            log.debug("setSpeedSetting= {}", speed); // NOI18N
187            //MRC use a value between 0-127 no matter what the controller is set to
188            value = (int) ((127 - 1) * speed);     // -1 for rescale to avoid estop
189            if (value > 0) {
190                value = value + 1;  // skip estop
191            }
192            if (value > 127) {
193                value = 127;    // max possible speed
194            }
195            if (value < 0) {
196                value = 1;        // emergency stop
197            }
198            if (isForward) {
199                value = value + 128;
200            }
201            m = MrcMessage.getSendSpeed128(addressLo, addressHi, value);
202        } else {
203            value = (int) ((28) * speed); // -1 for rescale to avoid estop
204            if (value > 0) {
205                value = value + 3; // skip estop
206            }
207            if (value > 32) {
208                value = 31; // max possible speed
209            }
210            if (value < 0) {
211                value = 2; // emergency stop
212            }
213            m = MrcMessage.getSendSpeed28(addressLo, addressHi, value, isForward);
214        }
215        tc.sendMrcMessage(m);
216        synchronized(this) {
217            firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting);
218        }
219        record(speed);
220    }
221
222    @Override
223    public void setIsForward(boolean forward) {
224        boolean old = isForward;
225        isForward = forward;
226        synchronized(this) {
227            setSpeedSetting(speedSetting);  // send the command
228        }
229        log.debug("setIsForward= {}", forward);
230        firePropertyChange(ISFORWARD, old, isForward);
231    }
232
233    @Override
234    public void throttleDispose() {
235        finishRecord();
236    }
237
238    @Override
239    public String toString() {
240        return getLocoAddress().toString();
241    }
242
243    //Might need to look at other packets from handsets to see if they also have control of our loco and adjust from that.
244    @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY", justification = "fixed number of possible values")
245    @Override
246    public void notifyRcv(Date timestamp, MrcMessage m) {
247        if (m.getMessageClass() != MrcInterface.THROTTLEINFO
248                || (m.getMessageClass() == MrcInterface.THROTTLEINFO && (m.getElement(0) == MrcPackets.LOCOSOLECONTROLCODE
249                || m.getElement(0) == MrcPackets.LOCODBLCONTROLCODE))) {
250            return;
251        }
252        if (m.getLocoAddress() == address.getNumber()) {
253            if (MrcPackets.startsWith(m, MrcPackets.THROTTLEPACKETHEADER)) {
254                synchronized(this) {
255                    if (m.getElement(10) == 0x02) {
256                        //128
257                        log.debug("speed Packet from another controller for our loco");
258                        int speed = m.getElement(8);
259                        if ((m.getElement(8) & 0x80) == 0x80) {
260                            //Forward
261                            if (!this.isForward) {
262                                this.isForward = true;
263                                firePropertyChange(ISFORWARD, !isForward, isForward);
264                            }
265                            //speed = m.getElement(8);
266                        } else if (this.isForward) {
267                            //reverse
268                            this.isForward = false;
269                            firePropertyChange(ISFORWARD, !isForward, isForward);
270                            //speed = m.getElement(8);
271                        }
272                        // does this handle emergency stop in any way?
273                        speed = (speed & 0x7f) - 1;
274                        if (speed < 0) {
275                            speed = 0;
276                        }
277                        float val = speed / 126.0f;
278
279                        // next line is the FE_FLOATING_POINT_EQUALITY annotated above
280                        if (val != this.speedSetting) {
281                            float old = this.speedSetting;
282                            this.speedSetting = val;
283                            firePropertyChange(SPEEDSETTING, old, this.speedSetting);
284                            record(val);
285                        }
286                    } else if (m.getElement(10) == 0x00) {
287                        int value = m.getElement(8) & 0xff;
288                        //28 Speed Steps
289                        if ((m.getElement(8) & 0x60) == 0x60) {
290                            //Forward
291                            value = value - 0x60;
292                        } else {
293                            value = value - 0x40;
294                        }
295                        if (((value >> 4) & 0x01) == 0x01) {
296                            value = value - 0x10;
297                            value = (value << 1) + 1;
298                        } else {
299                            value = value << 1;
300                        }
301                        value = value - 3; //Turn into user expected 0-28
302                        float val = -1;
303                        if (value != -1) {
304                            if (value < 1) {
305                                value = 0;
306                            }
307                            val = value / 28.0f;
308                        }
309
310                        if (val != this.speedSetting) {
311                            firePropertyChange(SPEEDSETTING, this.speedSetting, val);
312                            this.speedSetting = val;
313                            record(val);
314                        }
315                    }
316                }
317            } else if (MrcPackets.startsWith(m, MrcPackets.FUNCTIONGROUP1PACKETHEADER)) {
318                int data = m.getElement(8) & 0xff;
319                updateFunction(0,((data & 0x10) == 0x10));
320                updateFunction(1,((data & 0x01) == 0x01));
321                updateFunction(2,((data & 0x02) == 0x02));
322                updateFunction(3,((data & 0x04) == 0x04));
323                updateFunction(4,((data & 0x08) == 0x08));
324                
325            } else if (MrcPackets.startsWith(m, MrcPackets.FUNCTIONGROUP2PACKETHEADER)) {
326                int data = m.getElement(8) & 0xff;
327                updateFunction(5,((data & 0x01) == 0x01));
328                updateFunction(6,((data & 0x02) == 0x02));
329                updateFunction(7,((data & 0x04) == 0x04));
330                updateFunction(8,((data & 0x08) == 0x08));
331                
332            } else if (MrcPackets.startsWith(m, MrcPackets.FUNCTIONGROUP3PACKETHEADER)) {
333                int data = m.getElement(8) & 0xff;
334                updateFunction(9,((data & 0x01) == 0x01));
335                updateFunction(10,((data & 0x02) == 0x02));
336                updateFunction(11,((data & 0x04) == 0x04));
337                updateFunction(12,((data & 0x08) == 0x08));
338                
339            } else if (MrcPackets.startsWith(m, MrcPackets.FUNCTIONGROUP4PACKETHEADER)) {
340                int data = m.getElement(8) & 0xff;
341                updateFunction(13,((data & 0x01) == 0x01));
342                updateFunction(14,((data & 0x02) == 0x02));
343                updateFunction(15,((data & 0x04) == 0x04));
344                updateFunction(16,((data & 0x08) == 0x08));
345                
346            } else if (MrcPackets.startsWith(m, MrcPackets.FUNCTIONGROUP5PACKETHEADER)) {
347                int data = m.getElement(8) & 0xff;
348                updateFunction(17,((data & 0x01) == 0x01));
349                updateFunction(18,((data & 0x02) == 0x02));
350                updateFunction(19,((data & 0x04) == 0x04));
351                updateFunction(20,((data & 0x08) == 0x08));
352                
353            } else if (MrcPackets.startsWith(m, MrcPackets.FUNCTIONGROUP6PACKETHEADER)) {
354                int data = m.getElement(8) & 0xff;
355                updateFunction(21,((data & 0x01) == 0x01));
356                updateFunction(22,((data & 0x02) == 0x02));
357                updateFunction(23,((data & 0x04) == 0x04));
358                updateFunction(24,((data & 0x08) == 0x08));
359                
360                updateFunction(25,((data & 0x10) == 0x10));
361                updateFunction(26,((data & 0x20) == 0x20));
362                updateFunction(27,((data & 0x40) == 0x40));
363                updateFunction(28,((data & 0x80) == 0x80));
364                
365            }
366        }
367    }
368
369    @Override
370    public void notifyXmit(Date timestamp, MrcMessage m) {/* message(m); */
371
372    }
373
374    @Override
375    public void notifyFailedXmit(Date timestamp, MrcMessage m) { /*message(m);*/ }
376
377    // initialize logging
378    private final static Logger log = LoggerFactory.getLogger(MrcThrottle.class);
379
380}