001package jmri.jmrix.dccpp;
002
003import java.util.EnumSet;
004import java.util.HashMap;
005
006import jmri.DccLocoAddress;
007import jmri.DccThrottle;
008import jmri.LocoAddress;
009import jmri.SpeedStepMode;
010import jmri.ThrottleListener;
011import jmri.jmrix.AbstractThrottleManager;
012import org.slf4j.Logger;
013import org.slf4j.LoggerFactory;
014
015/**
016 * DCC++ implementation of a ThrottleManager based on the
017 * AbstractThrottleManager.
018 *
019 * @author Paul Bender Copyright (C) 2002-2004
020 * @author Mark Underwood Copyright (C) 2015
021 *
022 * Based on XNetThrottleManager by Paul Bender
023 */
024public class DCCppThrottleManager extends AbstractThrottleManager implements DCCppListener {
025
026    protected HashMap<LocoAddress, DCCppThrottle> throttles = new HashMap<LocoAddress, DCCppThrottle>(5);
027
028    protected DCCppTrafficController tc;
029    
030    /**
031     * Constructor.
032     * @param memo the memo for the connection this tm will use
033     */
034    public DCCppThrottleManager(DCCppSystemConnectionMemo memo) {
035        super(memo);
036        DCCppMessage msg;
037        // connect to the TrafficController manager
038        tc = memo.getDCCppTrafficController();
039
040        // Register to listen for throttle messages
041        tc.addDCCppListener(DCCppInterface.THROTTLE, this);
042        //Request number of available slots
043        msg = DCCppMessage.makeCSMaxNumSlotsMsg();
044        tc.sendDCCppMessage(msg, this);
045    }
046
047    /**
048     * Request a new throttle object be created for the address, and let the
049     * throttle listeners know about it.
050     *
051     */
052    @Override
053    public void requestThrottleSetup(LocoAddress address, boolean control) {
054        DCCppThrottle throttle;
055        log.debug("Requesting Throttle: {}", address);
056        if (throttles.containsKey(address)) {
057            notifyThrottleKnown(throttles.get(address), address);
058        } else {
059            if (tc.getCommandStation().requestNewRegister(address.getNumber()) == DCCppConstants.NO_REGISTER_FREE) {
060                failedThrottleRequest(address, "No Register available for Throttle. Address="+ address);
061                log.error("No Register available for Throttle. Address = {}", address);
062                return;
063            }
064            throttle = new DCCppThrottle((DCCppSystemConnectionMemo) adapterMemo, address, tc);
065            throttles.put(address, throttle);
066            notifyThrottleKnown(throttle, address);
067        }
068    }
069
070    /**
071     * DCC++ based systems DO NOT use the Dispatch Function
072     * (do they?)
073     */
074    @Override
075    public boolean hasDispatchFunction() {
076        return false;
077    }
078
079    /**
080     * DCC++ based systems can have multiple throttles for the same 
081     * device
082     * <p>
083     * {@inheritDoc}
084     */
085    @Override
086    protected boolean singleUse() {
087        return false;
088    }
089
090    /**
091     * Address 128 and above is a long address
092     *
093     */
094    @Override
095    public boolean canBeLongAddress(int address) {
096        return isLongAddress(address);
097    }
098
099    /**
100     * Address between 1 and 127 is a short address
101     *
102     */
103    @Override
104    public boolean canBeShortAddress(int address) {
105        return (address >= 1 && !isLongAddress(address));
106    }
107
108    /**
109     * There are no ambiguous addresses on this system.
110     */
111    @Override
112    public boolean addressTypeUnique() {
113        return true;
114    }
115
116    /*
117     * Local method for deciding short/long address
118     * (is it?)
119     */
120    static protected boolean isLongAddress(int num) {
121        return (num >= 128);
122    }
123
124    /**
125     * What speed modes are supported by this system? value should be xor of
126     * possible modes specifed by the DccThrottle interface DCC++ supports
127     * 14,27,28 and 128 speed step modes
128     */
129    @Override
130    public EnumSet<SpeedStepMode> supportedSpeedModes() {
131        return EnumSet.of(SpeedStepMode.NMRA_DCC_128); }
132
133    // Handle incoming messages for throttles.
134    @Override
135    public void message(DCCppReply r) {
136        // handle maxNumSlots and set value in commandstation
137        if (r.getElement(0) == DCCppConstants.MAXNUMSLOTS_REPLY) {
138            log.debug("MaxNumSlots reply received: {}", r);
139            tc.getCommandStation().setCommandStationMaxNumSlots(r);
140        // handle loco state reply by finding the proper throttle and asking it to update itself
141        } else if (r.getElement(0) == DCCppConstants.LOCO_STATE_REPLY){
142            log.debug("LocoState reply received: {}", r);
143            int locoId = r.getLocoIdInt();
144            DccLocoAddress locoAddress = new DccLocoAddress(locoId, !canBeShortAddress(locoId));            
145            if (throttles.containsKey(locoAddress)) {
146                DCCppThrottle throttle = throttles.get(locoAddress);
147                if (log.isDebugEnabled()) log.debug("Passing locoState to throttle {}", throttle.getLocoAddress());
148                throttle.handleLocoState(r);
149            }                
150            
151        } else {
152            log.trace("ignoring reply: {}", r);
153        }
154    }
155
156    // listen for the messages to the command station
157    @Override
158    public void message(DCCppMessage l) {
159    }
160
161    // Handle message timeout notification
162    // If the message still has retries available, reduce retries and send it back to the traffic controller.
163    @Override
164    public void notifyTimeout(DCCppMessage msg) {
165        log.debug("Notified of timeout on message '{}' , {} retries available.", msg, msg.getRetries());
166        if (msg.getRetries() > 0) {
167            msg.setRetries(msg.getRetries() - 1);
168            tc.sendDCCppMessage(msg, this);
169        }        
170    }
171
172    @Override
173    public void releaseThrottle(DccThrottle t, ThrottleListener l) {
174        super.releaseThrottle(t, l);
175    }
176
177    @Override
178    public boolean disposeThrottle(DccThrottle t, ThrottleListener l) {
179        if (super.disposeThrottle(t, l)) {
180            //ask command station to forget this cab
181            DCCppMessage msg = DCCppMessage.makeForgetCabMessage(t.getLocoAddress().getNumber());
182            tc.sendDCCppMessage(msg, this);            
183            //release the "register" for this cab
184            tc.getCommandStation().releaseRegister(t.getLocoAddress().getNumber());            
185            if (t instanceof DCCppThrottle) {
186                DCCppThrottle lnt = (DCCppThrottle) t;
187                throttles.remove(lnt.getLocoAddress()); // remove from throttles map.
188                lnt.throttleDispose();
189                return true;
190            }
191        }
192        return false;
193    }
194
195    /**
196     * {@inheritDoc}
197     */
198    @Override
199    public void dispose() {
200        tc.removeDCCppListener(DCCppInterface.THROTTLE, this);
201        //stopThrottleRequestTimer(); no timer used in this tm
202    }
203
204    private final static Logger log = LoggerFactory.getLogger(DCCppThrottleManager.class);
205
206}