001package jmri.jmrix.dccpp;
002
003import static jmri.jmrix.dccpp.DCCppConstants.MAX_TURNOUT_ADDRESS;
004
005import java.util.Locale;
006import javax.annotation.Nonnull;
007import jmri.Turnout;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * Implement TurnoutManager for DCC++ systems.
013 * <p>
014 * System names are "DxppTnnn", where Dx is the system prefix and nnn is the turnout number without padding.
015 *
016 * @author Bob Jacobsen Copyright (C) 2001
017 * @author Paul Bender Copyright (C) 2003-2010
018 * @author Mark Underwood Copyright (C) 2015
019 */
020public class DCCppTurnoutManager extends jmri.managers.AbstractTurnoutManager implements DCCppListener {
021
022    protected DCCppTrafficController tc = null;
023
024    /**
025     * Create a new DCC++ TurnoutManager.
026     * Has to register for DCC++ events.
027     *
028     * @param memo the supporting system connection memo
029     */
030    public DCCppTurnoutManager(DCCppSystemConnectionMemo memo) {
031        super(memo);
032        tc = memo.getDCCppTrafficController();
033        // set up listener
034        tc.addDCCppListener(DCCppInterface.FEEDBACK, this);
035        // request list of turnouts
036        tc.sendDCCppMessage(DCCppMessage.makeTurnoutListMsg(), this);
037        // request list of outputs
038        tc.sendDCCppMessage(DCCppMessage.makeOutputListMsg(), this);
039    }
040
041    /**
042     * {@inheritDoc}
043     */
044    @Override
045    @Nonnull
046    public DCCppSystemConnectionMemo getMemo() {
047        return (DCCppSystemConnectionMemo) memo;
048    }
049
050    // DCCpp-specific methods
051
052    /**
053     * {@inheritDoc}
054     */
055    @Nonnull
056    @Override
057    protected Turnout createNewTurnout(@Nonnull String systemName, String userName) throws IllegalArgumentException {
058        // check if the output bit is available
059        int bitNum = getBitFromSystemName(systemName);
060        if (bitNum < 0) {
061            throw new IllegalArgumentException("Cannot get Bit from System Name " + systemName);
062        }
063        // make the new Turnout object
064        Turnout t = new DCCppTurnout(getSystemPrefix(), bitNum, tc);
065        t.setUserName(userName);
066        return t;
067    }
068
069    /**
070     * {@inheritDoc}
071     * Listen for turnouts, creating them as needed.
072     */
073    @Override
074    public void message(DCCppReply l) {
075        if (l.isTurnoutReply()) {
076            log.debug("received Turnout Reply message: '{}'", l);
077            // parse message type
078            int addr = l.getTOIDInt();
079            if (addr >= 0) {
080                // check to see if the address has been operated before
081                // continuing.
082                log.debug("message has address: {}", addr);
083                // reach here for switch command; make sure we know 
084                // about this one
085                String s = getSystemNamePrefix() + addr;
086                DCCppTurnout found = (DCCppTurnout) getBySystemName(s);
087                if ( found == null) {
088                    // need to create a new one, and send the message on 
089                    // to the newly created object.
090                    ((DCCppTurnout) provideTurnout(s)).setFeedbackMode(Turnout.MONITORING);
091                    ((DCCppTurnout) provideTurnout(s)).initmessage(l);
092                } else {
093                    // The turnout exists, forward this message to the 
094                    // turnout
095                    found.message(l);
096                }
097            }
098        } else if (l.isOutputCmdReply()) {
099            log.debug("received Output Cmd Reply message: '{}'", l);
100            // parse message type
101            int addr = l.getOutputNumInt();
102            if (addr >= 0) {
103                // check to see if the address has been operated before
104                // continuing.
105                log.debug("message has address: {}", addr);
106                // reach here for switch command; make sure we know 
107                // about this one
108                String s = getSystemNamePrefix() + addr;
109                DCCppTurnout found = (DCCppTurnout) getBySystemName(s);
110                if (found == null) {
111                    // need to create a new one, and send the message on 
112                    // to the newly created object.
113                    ((DCCppTurnout) provideTurnout(s)).setFeedbackMode(Turnout.EXACT);
114                    ((DCCppTurnout) provideTurnout(s)).initmessage(l);
115                } else {
116                    // The turnout exists, forward this message to the 
117                    // turnout
118                    found.message(l);
119                }
120            }
121        }
122    }
123    
124    /**
125     * Get text to be used for the Turnout.CLOSED state in user communication.
126     * Allows text other than "CLOSED" to be use with certain hardware system to
127     * represent the Turnout.CLOSED state.
128     */
129    @Override
130    @Nonnull
131    public String getClosedText() {
132        return Bundle.getMessage("TurnoutStateClosed");
133    }
134
135    /**
136     * Get text to be used for the Turnout.THROWN state in user communication.
137     * Allows text other than "THROWN" to be use with certain hardware system to
138     * represent the Turnout.THROWN state.
139     */
140    @Override
141    @Nonnull
142    public String getThrownText() {
143        return Bundle.getMessage("TurnoutStateThrown");
144    }
145
146    /**
147     * Listen for the outgoing messages (to the command station)
148     */
149    @Override
150    public void message(DCCppMessage l) {
151    }
152
153    // Handle message timeout notification
154    // If the message still has retries available, reduce retries and send it back to the traffic controller.
155    @Override
156    public void notifyTimeout(DCCppMessage msg) {
157        log.debug("Notified of timeout on message '{}' , {} retries available.", msg, msg.getRetries());
158        if (msg.getRetries() > 0) {
159            msg.setRetries(msg.getRetries() - 1);
160            tc.sendDCCppMessage(msg, this);
161        }        
162    }
163
164    /** {@inheritDoc} */
165    @Override
166    public boolean allowMultipleAdditions(@Nonnull String systemName) {
167        return true;
168    }
169
170    /**
171     * {@inheritDoc}
172     */
173    @Override
174    public NameValidity validSystemNameFormat(@Nonnull String systemName) {
175        return (getBitFromSystemName(systemName) != -1) ? NameValidity.VALID : NameValidity.INVALID;
176    }
177
178    /**
179     * {@inheritDoc}
180     */
181    @Override
182    @Nonnull
183    public String validateSystemNameFormat(@Nonnull String systemName, @Nonnull Locale locale) {
184        return validateIntegerSystemNameFormat(systemName, 0, MAX_TURNOUT_ADDRESS, locale);
185    }
186
187    /**
188     * Get the bit address from the system name.
189     *
190     * @param systemName a valid Turnout System Name
191     * @return the turnout number extracted from the system name
192     */
193    public int getBitFromSystemName(String systemName) {
194        try {
195            validateSystemNameFormat(systemName, Locale.getDefault());
196        } catch (IllegalArgumentException ex) {
197            return -1;
198        }
199        return Integer.parseInt(systemName.substring(getSystemNamePrefix().length()));
200    }
201
202    /** {@inheritDoc} */
203    @Override
204    public String getEntryToolTip() {
205        return Bundle.getMessage("AddOutputEntryToolTip");
206    }
207
208    private final static Logger log = LoggerFactory.getLogger(DCCppTurnoutManager.class);
209
210}