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