001package jmri.jmrix.dccpp;
002
003import java.util.concurrent.LinkedBlockingQueue;
004import jmri.DccLocoAddress;
005import jmri.LocoAddress;
006import jmri.SpeedStepMode;
007import jmri.jmrix.AbstractThrottle;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * An implementation of DccThrottle with code specific to a DCC++
013 * connection.
014 *
015 * @author Paul Bender (C) 2002-2010
016 * @author Giorgio Terdina (C) 2007
017 * @author Mark Underwood (C) 2015
018 *
019 * Based on XNetThrottle by Paul Bender and Giorgio Terdina
020 */
021public class DCCppThrottle extends AbstractThrottle implements DCCppListener {
022
023    protected DCCppTrafficController tc;
024
025    // status of the throttle
026    protected static final int THROTTLEIDLE = 0;  // Idle Throttle
027    protected static final int THROTTLESPEEDSENT = 2;  // Sent speed/dir command to locomotive
028    protected static final int THROTTLEFUNCSENT = 4;   // Sent a function command to locomotive.
029    private final float speedMultiplier = 1.0f / 126.0f; //used to convert from integer speed to what JMRI expects
030
031    public int requestState = THROTTLEIDLE;
032
033    protected int address;
034
035    /**
036     * Constructor.
037     * @param memo system connection.
038     * @param controller system connection traffic controller.
039     */
040    public DCCppThrottle(DCCppSystemConnectionMemo memo, DCCppTrafficController controller) {
041        super(memo, 69); // supports up to F68
042        tc = controller;
043        requestList = new LinkedBlockingQueue<RequestMessage>();
044        this.isForward = true; //loco should default to forward
045        log.debug("DCCppThrottle constructor");
046    }
047
048    /**
049     * Constructor.
050     * @param memo system connection.
051     * @param address loco address to set on throttle
052     * @param controller system connection traffic controller.
053     */
054    public DCCppThrottle(DCCppSystemConnectionMemo memo, LocoAddress address, DCCppTrafficController controller) {
055        super(memo, 69); // supports up to F68
056        tc = controller;
057        if (address instanceof DccLocoAddress) {
058            this.setDccAddress(address.getNumber());
059        }
060        else {
061            log.error("LocoAddress {} is not a DccLocoAddress",address);
062        }
063        this.speedStepMode = SpeedStepMode.NMRA_DCC_128;
064
065        requestList = new LinkedBlockingQueue<RequestMessage>();
066        this.isForward = true; //loco should default to forward
067        log.debug("DCCppThrottle constructor called for address {}", address);
068    }
069
070    /*
071     *  Set the traffic controller used with this throttle
072     */
073    public void setDCCppTrafficController(DCCppTrafficController controller) {
074        tc = controller;
075    }
076
077    /**
078     * Get the Register Number for this Throttle's assigned address
079     * @return register number currently
080     */
081    int getRegisterNum() {
082        return (tc.getCommandStation().getRegisterNum(this.getDccAddress()));
083    }
084
085    /**
086     * {@inheritDoc}
087     */
088    @Override
089    public void setFunction(int functionNum, boolean newState) {
090        if (tc.getCommandStation().isFunctionV4Supported()) {
091            //send the newer <F CAB FUNC STATE> message
092            DCCppMessage msg = DCCppMessage.makeFunctionV4Message(this.getDccAddress(), functionNum, newState);
093            queueMessage(msg, THROTTLEIDLE);
094            updateFunction(functionNum, newState); //update throttle and broadcast change
095        } else {
096            //or send the older <f ADDR BYTE1 (BYTE2)> message
097            super.setFunction(functionNum, newState);
098        }
099    }
100
101   
102    /**
103     * Send the DCC++  message to set the state of locomotive direction and
104     * functions F0, F1, F2, F3, F4
105     */
106    @Override
107    protected void sendFunctionGroup1() {
108        log.debug("sendFunctionGroup1(): f0 {} f1 {} f2 {} f3 {} f4 {}",
109            getFunction(0), getFunction(1), getFunction(2), getFunction(3), getFunction(4));
110        DCCppMessage msg = DCCppMessage.makeFunctionGroup1OpsMsg(this.getDccAddress(),
111            getFunction(0), getFunction(1), getFunction(2), getFunction(3), getFunction(4));
112        log.debug("sendFunctionGroup1(): Message: {}", msg);
113        // now, queue the message for sending to the command station
114        //queueMessage(msg, THROTTLEFUNCSENT);
115        queueMessage(msg, THROTTLEIDLE);
116    }
117
118    /**
119     * Send the DCC++ message to set the state of functions F5, F6, F7, F8
120     */
121    @Override
122    protected void sendFunctionGroup2() {
123        DCCppMessage msg = DCCppMessage.makeFunctionGroup2OpsMsg(this.getDccAddress(),
124            getFunction(5), getFunction(6), getFunction(7), getFunction(8));
125        // now, queue the message for sending to the command station
126        //queueMessage(msg, THROTTLEFUNCSENT);
127        queueMessage(msg, THROTTLEIDLE);
128    }
129
130    /**
131     * Send the DCC++ message to set the state of functions F9, F10, F11,
132     * F12
133     */
134    @Override
135    protected void sendFunctionGroup3() {
136        DCCppMessage msg = DCCppMessage.makeFunctionGroup3OpsMsg(this.getDccAddress(),
137            getFunction(9), getFunction(10), getFunction(11), getFunction(12));
138        // now, queue the message for sending to the command station
139        //queueMessage(msg, THROTTLEFUNCSENT);
140        queueMessage(msg, THROTTLEIDLE);
141    }
142
143    /**
144     * Send the DCC++ message to set the state of functions F13, F14, F15,
145     * F16, F17, F18, F19, F20
146     */
147    @Override
148    protected void sendFunctionGroup4() {
149        DCCppMessage msg = DCCppMessage.makeFunctionGroup4OpsMsg(this.getDccAddress(),
150            getFunction(13), getFunction(14), getFunction(15), getFunction(16),
151            getFunction(17), getFunction(18), getFunction(19), getFunction(20));
152        // now, queue the message for sending to the command station
153        //queueMessage(msg, THROTTLEFUNCSENT);
154        queueMessage(msg, THROTTLEIDLE);
155    }
156
157    /**
158     * Send the DCC++ message to set the state of functions F21, F22, F23,
159     * F24, F25, F26, F27, F28
160     */
161    @Override
162    protected void sendFunctionGroup5() {
163        log.debug("sendFunctionGroup5(): f21 {} f22 {} f23 {} f24 {} f25 {} f26 {} f27 {} f28 {}",
164            getFunction(21), getFunction(22), getFunction(23), getFunction(24),
165            getFunction(25), getFunction(26), getFunction(27), getFunction(28));
166        DCCppMessage msg = DCCppMessage.makeFunctionGroup5OpsMsg(this.getDccAddress(),
167            getFunction(21), getFunction(22), getFunction(23), getFunction(24),
168            getFunction(25), getFunction(26), getFunction(27), getFunction(28));
169        log.debug("sendFunctionGroup5(): Message: '{}'", msg);
170        // now, queue the message for sending to the command station
171        //queueMessage(msg, THROTTLEFUNCSENT);
172        queueMessage(msg, THROTTLEIDLE);
173    }
174
175    /* 
176     * setSpeedSetting - notify listeners and send the new speed to the
177     * command station.
178     */
179    @Override
180    synchronized public void setSpeedSetting(float speed) {
181        if (log.isDebugEnabled()) {
182            log.debug("set Speed to: {} Current step mode is: {}", speed, this.speedStepMode);
183        }
184        super.setSpeedSetting(speed);
185        if (speed < 0) {
186            /* we're sending an emergency stop to this locomotive only */
187            sendEmergencyStop();
188        } else {
189            if (speed > 1) {
190                speed = (float) 1.0;
191            }
192            /* we're sending a speed to the locomotive */
193            DCCppMessage msg;
194            //older version includes register
195            if (tc.getCommandStation().isThrottleRegisterRequired()) {
196                msg = DCCppMessage.makeSpeedAndDirectionMsg(
197                getRegisterNum(),
198                getDccAddress(),
199                speed,
200                this.isForward);
201            } else {
202                //newer version does not need register passed
203                msg = DCCppMessage.makeSpeedAndDirectionMsg(
204                getDccAddress(),
205                speed,
206                this.isForward);               
207            }
208            // now, queue the message for sending to the command station
209            //queueMessage(msg, THROTTLESPEEDSENT);
210            queueMessage(msg, THROTTLEIDLE);
211        }
212    }
213
214    /* Since DCC++ has a separate Opcode for emergency stop,
215     * We're setting this up as a separate protected function
216     */
217    protected void sendEmergencyStop() {
218        /* Emergency stop sent */
219        DCCppMessage msg;
220        if (tc.getCommandStation().isThrottleRegisterRequired()) {
221            msg = DCCppMessage.makeAddressedEmergencyStop(this.getRegisterNum(), this.getDccAddress());
222        } else {
223            msg = DCCppMessage.makeAddressedEmergencyStop(this.getDccAddress());            
224        }
225        // now, queue the message for sending to the command station
226        //queueMessage(msg, THROTTLESPEEDSENT);
227        queueMessage(msg, THROTTLEIDLE);
228    }
229
230    /* Since there is only one "throttle" command to the DCC++ base station,
231     * when we change the direction, we must also re-set the speed.
232     */
233    @Override
234    public void setIsForward(boolean forward) {
235        super.setIsForward(forward);
236        synchronized(this) {
237            setSpeedSetting(this.speedSetting);
238        }
239    }
240
241    /*
242     * setSpeedStepMode - set the speed step value and the related
243     *                    speedIncrement value.
244     *
245     * @param Mode  the current speed step mode - default should be 128
246     *              speed step mode in most cases
247     *
248     * NOTE: DCC++ only supports 128-step mode.  So we ignore the speed
249     * setting, even though we store it.
250     */
251    @Override
252    public void setSpeedStepMode(SpeedStepMode Mode) {
253        super.setSpeedStepMode(Mode);
254    }
255
256    /**
257     * Dispose when finished with this object. After this, further usage of this
258     * Throttle object will result in a JmriException.
259     *
260     * This is quite problematic, because a using object doesn't know when it's
261     * the last user.
262     */
263    @Override
264    protected void throttleDispose() {
265        active = false;
266        finishRecord();
267    }
268
269    public int setDccAddress(int newaddress) {
270        address = newaddress;
271        return address;
272    }
273
274    public int getDccAddress() {
275        return address;
276    }
277
278
279    protected int getDccAddressHigh() {
280        return DCCppCommandStation.getDCCAddressHigh(this.address);
281    }
282
283    protected int getDccAddressLow() {
284        return DCCppCommandStation.getDCCAddressLow(this.address);
285    }
286
287    // Handle incoming messages for This throttle.
288    @Override
289    public void message(DCCppReply l) {
290        // First, we want to see if this throttle is waiting for a message 
291        //or not.
292        if (log.isDebugEnabled()) {
293            log.trace("Throttle {} - received message '{}'", getDccAddress(), l);
294        }
295        if (requestState == THROTTLEIDLE) {
296            log.trace("Current throttle status is THROTTLEIDLE");
297            // We haven't sent anything, but we might be told someone else 
298            // has taken over this address
299            // For now, do nothing.
300        } else if ((requestState & THROTTLESPEEDSENT) == THROTTLESPEEDSENT) {
301            log.debug("Current throttle status is THROTTLESPEEDSENT");
302            // This is a reply to a Throttle message, or to a Status message.
303            if (l.isThrottleReply()) {
304                // Update our state with the register's information.
305                handleThrottleReply(l);
306            }
307            // For a Throttle command ("t") we get back a Throttle Status.
308
309            log.debug("Last Command processed successfully.");
310
311            requestState = THROTTLEIDLE;
312            sendQueuedMessage();
313  
314        }
315        if ((requestState & THROTTLEFUNCSENT) == THROTTLEFUNCSENT) {
316            log.debug("Current throttle status is THROTTLEFUNCSENT. Ignoring Reply: '{}'", l);
317        }
318        requestState=THROTTLEIDLE;
319        sendQueuedMessage();
320    }
321
322    //check for any changes needed based on incoming LocoState reply for this throttle
323    //then make those changes directly to the parent throttle to avoid a message loop
324    protected void handleLocoState(DCCppReply r) {
325        int locoId = r.getLocoIdInt();
326        //insure this message belongs to this throttle (really shouldn't happen)        
327        if (this.address != locoId) {
328            log.error("throttle {} incorrectly called for locoId {}", this.address, locoId);
329            return;
330        }
331
332        boolean newForward = r.getIsForward();
333        float newSpeedSetting = r.getSpeedInt() * speedMultiplier;
334        String newFunctionsString = r.getFunctionsString();
335        
336        if (this.getIsForward() != newForward) {
337            if (log.isDebugEnabled()) log.debug("changing forward from {} to {} for {}", this.getIsForward(), newForward, locoId);
338            super.setIsForward(newForward);
339        }
340        if (Math.abs(this.getSpeedSetting() - newSpeedSetting) > 0.0001) { //avoid possible float precision errors
341            if (log.isDebugEnabled()) log.debug("changing speed from {} to {} for {}", this.getSpeedSetting(), newSpeedSetting, locoId);
342            super.setSpeedSetting(newSpeedSetting);
343        }
344        //check each function value for any changes, and update if so
345        for (int i = 0; i <= 28; i++) {
346            boolean newState = (newFunctionsString.charAt(i)=='1');
347            if (this.getFunction(i) != newState) {
348//                log.debug(r.toMonitorString());
349                if (log.isDebugEnabled()) log.debug("changing F{} from {} to {} for {}", i, this.getFunction(i), newState, locoId);                
350                super.updateFunction(i,newState);
351            }
352        }
353    }
354
355    private void handleThrottleReply(DCCppReply l) {
356        int reg, speed, dir;
357        reg = l.getRegisterInt();
358        speed = l.getSpeedInt();
359        dir = l.getDirectionInt();
360
361        // Check to see if register matches MY throttle.
362        // If so, update my values to match the returned values.
363        // Make (relatively) direct writes to the memories, so we don't
364        // cause looped throttle messages.
365        int regaddr = tc.getCommandStation().getRegisterAddress(reg);
366        if ((regaddr == DCCppConstants.REGISTER_UNALLOCATED) ||
367            (regaddr != this.address)) {
368            // This register doesn't match anything.
369            // Or the assigned address doesn't match mine.
370        } else {
371            // The assigned address matches mine.  Update my info 
372            // to match the returned register info.
373            synchronized(this) {
374                if (speed < 0) {
375                    //this.setSpeedSetting(0.0f);
376                    this.speedSetting = 0.0f;
377                } else {
378                    //this.setSpeedSetting((speed * 1.0f)/126.0f);
379                    this.speedSetting = (speed * 1.0f) / 126.0f;
380                }
381            }
382            this.isForward = (dir == 1);
383         }
384    }
385 
386    // Listen for the outgoing messages (to the command station)
387    @Override
388    public void message(DCCppMessage l) {
389    }
390
391    // Handle a timeout notification
392    @Override
393    public void notifyTimeout(DCCppMessage msg) {
394        log.debug("Notified of timeout on message '{}' , {} retries available.", msg, msg.getRetries());
395        if (msg.getRetries() > 0) {
396            // If the message still has retries available, send it back to 
397            // the traffic controller.
398            tc.sendDCCppMessage(msg, this);
399        } else {
400            // Try to send the next queued message,  if one is available.
401            sendQueuedMessage();
402        }
403    }
404
405    @Override
406    public LocoAddress getLocoAddress() {
407        return new DccLocoAddress(address, DCCppThrottleManager.isLongAddress(address));
408    }
409
410    //A queue to hold outstanding messages
411    protected LinkedBlockingQueue<RequestMessage> requestList;
412
413    // function to send message from queue.
414    synchronized protected void sendQueuedMessage() {
415        RequestMessage msg;
416        // check to see if the queue has a message in it, and if it does,
417        // remove the first message
418        if (!requestList.isEmpty()) {
419            log.trace("sending message to traffic controller");
420            // if the queue is not empty, remove the first message
421            // from the queue, send the message, and set the state machine 
422            // to the requeried state.
423            try {
424                msg = requestList.take();
425            } catch (java.lang.InterruptedException ie) {
426                return; // if there was an error, exit.
427            }
428            requestState = msg.getState();
429            tc.sendDCCppMessage(msg.getMsg(), this);
430        } else {
431            log.trace("message queue empty");
432            // if the queue is empty, set the state to idle.
433            requestState = THROTTLEIDLE;
434        }
435    }
436
437    //function to queue a message
438    synchronized protected void queueMessage(DCCppMessage m, int s) {
439        log.trace("adding message '{}' to message queue", m);
440        // put the message in the queue
441        RequestMessage msg = new RequestMessage(m, s);
442        try {
443            requestList.put(msg);
444        } catch (java.lang.InterruptedException ignore) {
445        }
446        // if the state is idle, trigger the message send
447        if (requestState == THROTTLEIDLE) {
448            sendQueuedMessage();
449        }
450    }
451
452    // internal class to hold a request message, along with the associated
453    // throttle state.
454    protected static class RequestMessage {
455
456        private final int state;
457        private final DCCppMessage msg;
458
459        RequestMessage(DCCppMessage m, int s) {
460            state = s;
461            msg = m;
462        }
463
464        int getState() {
465            return state;
466        }
467
468        DCCppMessage getMsg() {
469            return msg;
470        }
471
472    }
473
474    // register for notification
475    private final static Logger log = LoggerFactory.getLogger(DCCppThrottle.class);
476
477}