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