001package jmri.jmrix.marklin;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import jmri.LocoAddress;
005import jmri.SpeedStepMode;
006import jmri.jmrix.AbstractThrottle;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * An implementation of DccThrottle with code specific to an TAMS connection.
012 * <p>
013 * Based on Glen Oberhauser's original LnThrottle implementation
014 *
015 * @author Kevin Dickerson Copyright (C) 2012
016 */
017public class MarklinThrottle extends AbstractThrottle implements MarklinListener {
018
019    /**
020     * Constructor.
021     * @param memo system connection.
022     * @param address loco address.
023     */
024    public MarklinThrottle(MarklinSystemConnectionMemo memo, LocoAddress address) {
025        super(memo);
026        tc = memo.getTrafficController();
027
028        synchronized(this) {
029            this.speedSetting = 0;
030        }
031        // Functions default to false
032        this.address = address;
033        this.isForward = true;
034
035        setSpeedStepMode(jmri.SpeedStepMode.NMRA_DCC_128);
036        tc.addMarklinListener(this);
037        tc.sendMarklinMessage(MarklinMessage.getQryLocoSpeed(getCANAddress()), this);
038        tc.sendMarklinMessage(MarklinMessage.getQryLocoDirection(getCANAddress()), this);
039        for (int i = 0; i <= 28; i++) {
040            tc.sendMarklinMessage(MarklinMessage.getQryLocoFunction(getCANAddress(), i), this);
041        }
042    }
043
044    /**
045     * Send the message to set the state of functions F0, F1, F2, F3, F4. To
046     * send function group 1 we have to also send speed, direction etc.
047     */
048    @Override
049    protected void sendFunctionGroup1() {
050
051        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 0, (getFunction(0) ? 0x01 : 0x00)), this);
052        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 1, (getFunction(1) ? 0x01 : 0x00)), this);
053        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 2, (getFunction(2) ? 0x01 : 0x00)), this);
054        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 3, (getFunction(3) ? 0x01 : 0x00)), this);
055        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 4, (getFunction(4) ? 0x01 : 0x00)), this);
056
057    }
058
059    /**
060     * Send the message to set the state of functions F5, F6, F7, F8.
061     */
062    @Override
063    protected void sendFunctionGroup2() {
064
065        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 5, (getFunction(5) ? 0x01 : 0x00)), this);
066        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 6, (getFunction(6) ? 0x01 : 0x00)), this);
067        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 7, (getFunction(7) ? 0x01 : 0x00)), this);
068        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 8, (getFunction(8) ? 0x01 : 0x00)), this);
069
070    }
071
072    @Override
073    protected void sendFunctionGroup3() {
074
075        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 9, (getFunction(9) ? 0x01 : 0x00)), this);
076        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 10, (getFunction(10) ? 0x01 : 0x00)), this);
077        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 11, (getFunction(11) ? 0x01 : 0x00)), this);
078        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 12, (getFunction(12) ? 0x01 : 0x00)), this);
079
080    }
081
082    @Override
083    protected void sendFunctionGroup4() {
084
085        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 13, (getFunction(13) ? 0x01 : 0x00)), this);
086        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 14, (getFunction(14) ? 0x01 : 0x00)), this);
087        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 15, (getFunction(15) ? 0x01 : 0x00)), this);
088        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 16, (getFunction(16) ? 0x01 : 0x00)), this);
089        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 17, (getFunction(17) ? 0x01 : 0x00)), this);
090        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 18, (getFunction(18) ? 0x01 : 0x00)), this);
091        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 19, (getFunction(19) ? 0x01 : 0x00)), this);
092        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 20, (getFunction(20) ? 0x01 : 0x00)), this);
093
094    }
095
096    @Override
097    protected void sendFunctionGroup5() {
098
099        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 21, (getFunction(21) ? 0x01 : 0x00)), this);
100        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 22, (getFunction(22) ? 0x01 : 0x00)), this);
101        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 23, (getFunction(23) ? 0x01 : 0x00)), this);
102        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 24, (getFunction(24) ? 0x01 : 0x00)), this);
103        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 25, (getFunction(25) ? 0x01 : 0x00)), this);
104        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 26, (getFunction(26) ? 0x01 : 0x00)), this);
105        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 27, (getFunction(27) ? 0x01 : 0x00)), this);
106        tc.sendMarklinMessage(MarklinMessage.setLocoFunction(getCANAddress(), 28, (getFunction(28) ? 0x01 : 0x00)), this);
107
108    }
109
110    /**
111     * Set the speed and direction.
112     * <p>
113     * This intentionally skips the emergency stop value of 1.
114     *
115     * @param speed Number from 0 to 1; less than zero is emergency stop
116     */
117    @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY") // OK to compare floating point, notify on any change
118    @Override
119    public synchronized void setSpeedSetting(float speed) {
120        float oldSpeed = this.speedSetting;
121        this.speedSetting = speed;
122
123        int value = (int) ((1000) * this.speedSetting);
124        if (value > 1000) {
125            value = 1000;    // max possible speed
126        }
127        
128        if (this.speedSetting > 0 && value == 0) {
129            value = 1;      // ensure non-zero input results in non-zero output
130        }
131        
132        if (value < 0) {
133            //Emergency Stop
134            tc.sendMarklinMessage(MarklinMessage.setLocoEmergencyStop(getCANAddress()), this);
135        } else {
136            tc.sendMarklinMessage(MarklinMessage.setLocoSpeed(getCANAddress(), value), this);
137        }
138        log.debug("Float speed = {} Int speed = {}", speed, value);
139        firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting);
140        record(speed);
141    }
142
143    /**
144     * Convert a Marklin speed integer to a float speed value
145     * @param lSpeed Marklin-format speed value
146     * @return 0.0 - 1.0 speed value
147     */
148    protected float floatSpeed(int lSpeed) {
149        if (lSpeed == 0) {
150            return 0.f;
151        }
152        return ((lSpeed) / 1000.f);
153    }
154
155    @Override
156    public void setIsForward(boolean forward) {
157        boolean old = isForward;
158        isForward = forward;
159        setSpeedSetting(0.0f); //Stop the loco first before changing direction.
160        tc.sendMarklinMessage(MarklinMessage.setLocoDirection(getCANAddress(), (forward ? 0x01 : 0x02)), this);
161        firePropertyChange(ISFORWARD, old, isForward);
162    }
163
164    private LocoAddress address;
165
166    MarklinTrafficController tc;
167
168    @Override
169    public void setSpeedStepMode(SpeedStepMode Mode) {
170        if (log.isDebugEnabled()) {
171            log.debug("Speed Step Mode Change to Mode: {} Current mode is: {}", Mode, this.speedStepMode);
172        }
173        boolean isLong = adapterMemo.get(jmri.ThrottleManager.class).canBeLongAddress(address.getNumber());
174        switch (address.getProtocol()) {
175            case DCC:
176                if (Mode == SpeedStepMode.NMRA_DCC_28 && isLong) {
177                    tc.sendMarklinMessage(MarklinMessage.setLocoSpeedSteps(getCANAddress(), MarklinConstants.STEPLONG28), this);
178                } else if (Mode == SpeedStepMode.NMRA_DCC_28 && !isLong) {
179                    tc.sendMarklinMessage(MarklinMessage.setLocoSpeedSteps(getCANAddress(), MarklinConstants.STEPSHORT28), this);
180                } else if (Mode == SpeedStepMode.NMRA_DCC_128 && isLong) {
181                    tc.sendMarklinMessage(MarklinMessage.setLocoSpeedSteps(getCANAddress(), MarklinConstants.STEPLONG128), this);
182                } else if (Mode == SpeedStepMode.NMRA_DCC_128 && !isLong) {
183                    tc.sendMarklinMessage(MarklinMessage.setLocoSpeedSteps(getCANAddress(), MarklinConstants.STEPSHORT128), this);
184                }
185                break;
186            default:
187                Mode = SpeedStepMode.NMRA_DCC_28;
188                break;
189        }
190        super.setSpeedStepMode(Mode);
191    }
192
193    @Override
194    public LocoAddress getLocoAddress() {
195        return address;
196    }
197
198    @Override
199    public void throttleDispose() {
200        active = false;
201         finishRecord();
202    }
203
204    @Override
205    public void message(MarklinMessage m) {
206        // messages are ignored
207    }
208
209    @Override
210    public void reply(MarklinReply m) {
211        if (m.getPriority() == MarklinConstants.PRIO_1 && m.getCommand() >= MarklinConstants.MANCOMMANDSTART && m.getCommand() <= MarklinConstants.MANCOMMANDEND) {
212            if (m.getAddress() != getCANAddress()) {
213                if (log.isDebugEnabled()) {
214                    log.debug("Addressed packet is not for us {} {}", m.getAddress(), getCANAddress());
215                }
216                return;
217            }
218            if (m.getCommand() == MarklinConstants.LOCODIRECTION) {
219                if (log.isDebugEnabled()) {
220                    log.debug("Loco Direction {}", m.getElement(9));
221                }
222                //The CS2 sets the speed of the loco to Zero when changing direction, however it doesn't appear to broadcast it out.
223                synchronized(this) {
224                    switch (m.getElement(9)) {
225                        case 0x00:
226                            return; //No change
227                        case 0x01:
228                            if (!isForward) {
229                                speedSetting = 0.0f;
230                                super.setSpeedSetting(speedSetting);
231                                isForward = true;
232                                firePropertyChange(ISFORWARD, false, isForward);
233                            }
234                            return;
235                        case 0x02:
236                            if (isForward) {
237                                speedSetting = 0.0f;
238                                super.setSpeedSetting(speedSetting);
239                                isForward = false;
240                                firePropertyChange(ISFORWARD, true, isForward);
241                            }
242                            return;
243                        case 0x03:
244                            speedSetting = 0.0f;
245                            super.setSpeedSetting(speedSetting);
246                            isForward = !isForward;
247                            firePropertyChange(ISFORWARD, !isForward, isForward);
248                            return;
249                        default:
250                            log.error("No Match Found for loco direction {}", m.getElement(9));
251                            return;
252                    }
253                }
254            }
255            if (m.getCommand() == MarklinConstants.LOCOSPEED) {
256                int speed = m.getElement(9);
257                speed = (speed << 8) + (m.getElement(10));
258                float newSpeed = floatSpeed(speed);
259                log.debug("Speed raw {} float {}", speed, newSpeed);
260                super.setSpeedSetting(newSpeed);
261            }
262            if (m.getCommand() == MarklinConstants.LOCOFUNCTION) {
263                updateFunction(m.getElement(9),!(m.getElement(10)==0));
264            }
265        }
266    }
267
268    int getCANAddress() {
269        switch (address.getProtocol()) {
270            case DCC:
271                return MarklinConstants.DCCSTART + address.getNumber();
272            case MOTOROLA:
273                return address.getNumber();
274            case SELECTRIX:
275                return MarklinConstants.SX2START + address.getNumber();
276            case MFX:
277                return MarklinConstants.MFXSTART + address.getNumber();
278            default:
279                return MarklinConstants.DCCSTART + address.getNumber();
280        }
281    }
282
283    // initialize logging
284    private final static Logger log = LoggerFactory.getLogger(MarklinThrottle.class);
285
286}