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    @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY") // OK to compare floating point, notify on any change
177    @Override
178    public void setSpeedSetting(float speed) {
179        float oldSpeed;
180        synchronized(this) {
181            oldSpeed = this.speedSetting;
182            this.speedSetting = speed;
183        }
184        MrcMessage m;
185        int value;
186        if (super.speedStepMode == jmri.SpeedStepMode.NMRA_DCC_128) {
187            log.debug("setSpeedSetting= {}", speed); // NOI18N
188            //MRC use a value between 0-127 no matter what the controller is set to
189            value = (int) ((127 - 1) * speed);     // -1 for rescale to avoid estop
190            if (value > 0) {
191                value = value + 1;  // skip estop
192            }
193            if (value > 127) {
194                value = 127;    // max possible speed
195            }
196            if (value < 0) {
197                value = 1;        // emergency stop
198            }
199            if (isForward) {
200                value = value + 128;
201            }
202            m = MrcMessage.getSendSpeed128(addressLo, addressHi, value);
203        } else {
204            value = (int) ((28) * speed); // -1 for rescale to avoid estop
205            if (value > 0) {
206                value = value + 3; // skip estop
207            }
208            if (value > 32) {
209                value = 31; // max possible speed
210            }
211            if (value < 0) {
212                value = 2; // emergency stop
213            }
214            m = MrcMessage.getSendSpeed28(addressLo, addressHi, value, isForward);
215        }
216        tc.sendMrcMessage(m);
217        synchronized(this) {
218            firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting);
219        }
220        record(speed);
221    }
222
223    @Override
224    public void setIsForward(boolean forward) {
225        boolean old = isForward;
226        isForward = forward;
227        synchronized(this) {
228            setSpeedSetting(speedSetting);  // send the command
229        }
230        log.debug("setIsForward= {}", forward);
231        firePropertyChange(ISFORWARD, old, isForward);
232    }
233
234    @Override
235    public void throttleDispose() {
236        finishRecord();
237    }
238
239    @Override
240    public String toString() {
241        return getLocoAddress().toString();
242    }
243
244    //Might need to look at other packets from handsets to see if they also have control of our loco and adjust from that.
245    @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY", justification = "fixed number of possible values")
246    @Override
247    public void notifyRcv(Date timestamp, MrcMessage m) {
248        if (m.getMessageClass() != MrcInterface.THROTTLEINFO
249                || (m.getMessageClass() == MrcInterface.THROTTLEINFO && (m.getElement(0) == MrcPackets.LOCOSOLECONTROLCODE
250                || m.getElement(0) == MrcPackets.LOCODBLCONTROLCODE))) {
251            return;
252        }
253        if (m.getLocoAddress() == address.getNumber()) {
254            if (MrcPackets.startsWith(m, MrcPackets.THROTTLEPACKETHEADER)) {
255                synchronized(this) {
256                    if (m.getElement(10) == 0x02) {
257                        //128
258                        log.debug("speed Packet from another controller for our loco");
259                        int speed = m.getElement(8);
260                        if ((m.getElement(8) & 0x80) == 0x80) {
261                            //Forward
262                            if (!this.isForward) {
263                                this.isForward = true;
264                                firePropertyChange(ISFORWARD, !isForward, isForward);
265                            }
266                            //speed = m.getElement(8);
267                        } else if (this.isForward) {
268                            //reverse
269                            this.isForward = false;
270                            firePropertyChange(ISFORWARD, !isForward, isForward);
271                            //speed = m.getElement(8);
272                        }
273                        // does this handle emergency stop in any way?
274                        speed = (speed & 0x7f) - 1;
275                        if (speed < 0) {
276                            speed = 0;
277                        }
278                        float val = speed / 126.0f;
279
280                        // next line is the FE_FLOATING_POINT_EQUALITY annotated above
281                        if (val != this.speedSetting) {
282                            float old = this.speedSetting;
283                            this.speedSetting = val;
284                            firePropertyChange(SPEEDSETTING, old, this.speedSetting);
285                            record(val);
286                        }
287                    } else if (m.getElement(10) == 0x00) {
288                        int value = m.getElement(8) & 0xff;
289                        //28 Speed Steps
290                        if ((m.getElement(8) & 0x60) == 0x60) {
291                            //Forward
292                            value = value - 0x60;
293                        } else {
294                            value = value - 0x40;
295                        }
296                        if (((value >> 4) & 0x01) == 0x01) {
297                            value = value - 0x10;
298                            value = (value << 1) + 1;
299                        } else {
300                            value = value << 1;
301                        }
302                        value = value - 3; //Turn into user expected 0-28
303                        float val = -1;
304                        if (value != -1) {
305                            if (value < 1) {
306                                value = 0;
307                            }
308                            val = value / 28.0f;
309                        }
310
311                        if (val != this.speedSetting) {
312                            firePropertyChange(SPEEDSETTING, this.speedSetting, val);
313                            this.speedSetting = val;
314                            record(val);
315                        }
316                    }
317                }
318            } else if (MrcPackets.startsWith(m, MrcPackets.FUNCTIONGROUP1PACKETHEADER)) {
319                int data = m.getElement(8) & 0xff;
320                updateFunction(0,((data & 0x10) == 0x10));
321                updateFunction(1,((data & 0x01) == 0x01));
322                updateFunction(2,((data & 0x02) == 0x02));
323                updateFunction(3,((data & 0x04) == 0x04));
324                updateFunction(4,((data & 0x08) == 0x08));
325                
326            } else if (MrcPackets.startsWith(m, MrcPackets.FUNCTIONGROUP2PACKETHEADER)) {
327                int data = m.getElement(8) & 0xff;
328                updateFunction(5,((data & 0x01) == 0x01));
329                updateFunction(6,((data & 0x02) == 0x02));
330                updateFunction(7,((data & 0x04) == 0x04));
331                updateFunction(8,((data & 0x08) == 0x08));
332                
333            } else if (MrcPackets.startsWith(m, MrcPackets.FUNCTIONGROUP3PACKETHEADER)) {
334                int data = m.getElement(8) & 0xff;
335                updateFunction(9,((data & 0x01) == 0x01));
336                updateFunction(10,((data & 0x02) == 0x02));
337                updateFunction(11,((data & 0x04) == 0x04));
338                updateFunction(12,((data & 0x08) == 0x08));
339                
340            } else if (MrcPackets.startsWith(m, MrcPackets.FUNCTIONGROUP4PACKETHEADER)) {
341                int data = m.getElement(8) & 0xff;
342                updateFunction(13,((data & 0x01) == 0x01));
343                updateFunction(14,((data & 0x02) == 0x02));
344                updateFunction(15,((data & 0x04) == 0x04));
345                updateFunction(16,((data & 0x08) == 0x08));
346                
347            } else if (MrcPackets.startsWith(m, MrcPackets.FUNCTIONGROUP5PACKETHEADER)) {
348                int data = m.getElement(8) & 0xff;
349                updateFunction(17,((data & 0x01) == 0x01));
350                updateFunction(18,((data & 0x02) == 0x02));
351                updateFunction(19,((data & 0x04) == 0x04));
352                updateFunction(20,((data & 0x08) == 0x08));
353                
354            } else if (MrcPackets.startsWith(m, MrcPackets.FUNCTIONGROUP6PACKETHEADER)) {
355                int data = m.getElement(8) & 0xff;
356                updateFunction(21,((data & 0x01) == 0x01));
357                updateFunction(22,((data & 0x02) == 0x02));
358                updateFunction(23,((data & 0x04) == 0x04));
359                updateFunction(24,((data & 0x08) == 0x08));
360                
361                updateFunction(25,((data & 0x10) == 0x10));
362                updateFunction(26,((data & 0x20) == 0x20));
363                updateFunction(27,((data & 0x40) == 0x40));
364                updateFunction(28,((data & 0x80) == 0x80));
365                
366            }
367        }
368    }
369
370    @Override
371    public void notifyXmit(Date timestamp, MrcMessage m) {/* message(m); */
372
373    }
374
375    @Override
376    public void notifyFailedXmit(Date timestamp, MrcMessage m) { /*message(m);*/ }
377
378    // initialize logging
379    private final static Logger log = LoggerFactory.getLogger(MrcThrottle.class);
380
381}