001package jmri.jmrix.lenz;
002
003import java.util.Locale;
004import javax.annotation.Nonnull;
005import jmri.Turnout;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009/**
010 * Implement turnout manager for Lenz (XpresssNet) connections.
011 * <p>
012 * System names are "XTnnn", where X is the user configurable system prefix,
013 * nnn is the turnout number without padding.
014 *
015 * @author Bob Jacobsen Copyright (C) 2001
016 * @author Paul Bender Copyright (C) 2003-2010
017 * @navassoc 1 - 1 jmri.jmrix.lenz.XNetProgrammer
018 */
019public class XNetTurnoutManager extends jmri.managers.AbstractTurnoutManager implements XNetListener {
020
021    // ctor has to register for XNet events
022    public XNetTurnoutManager(XNetSystemConnectionMemo memo) {
023        super(memo);
024        tc = memo.getXNetTrafficController();
025        // Force initialization, so it registers first and receives feedbacks before
026        // TurnoutManager autocreates turnout.
027        tc.getFeedbackMessageCache();
028        tc.addXNetListener(XNetInterface.FEEDBACK, this);
029    }
030
031    protected XNetTrafficController tc;
032
033    /**
034     * {@inheritDoc}
035     */
036    @Override
037    @Nonnull
038    public XNetSystemConnectionMemo getMemo() {
039        return (XNetSystemConnectionMemo) memo;
040    }
041
042    // XNet-specific methods
043
044    /**
045     * Create a new Turnout based on the system name.
046     * Assumes calling method has checked that a Turnout with this
047     * system name does not already exist.
048     * {@inheritDoc}
049     */
050    @Nonnull
051    @Override
052    protected Turnout createNewTurnout(@Nonnull String systemName, String userName) throws IllegalArgumentException {
053        // check if the output bit is available
054        int bitNum = XNetAddress.getBitFromSystemName(systemName, getSystemPrefix());
055        if (bitNum == -1) {
056            throw new IllegalArgumentException("Cannot get Bit from System Name " + systemName);
057        }
058        // create the new Turnout object
059        Turnout t = new XNetTurnout(getSystemPrefix(), bitNum, tc);
060        t.setUserName(userName);
061        return t;
062    }
063
064    /**
065     * Listen for turnouts, creating them as needed.
066     */
067    @Override
068    public void message(XNetReply l) {
069        if (log.isDebugEnabled()) {
070            log.debug("received message: {}",l);
071        }
072        if (l.isFeedbackBroadcastMessage()) {
073            int numDataBytes = l.getElement(0) & 0x0f;
074            for (int i = 1; i < numDataBytes; i += 2) {
075                // parse message type
076                int addr = l.getTurnoutMsgAddr(i);
077                if (addr >= 0) {
078                    log.debug("message has address: {}", addr);
079                    // forward to the specified turnout.
080                    String s = getSystemNamePrefix() + addr;
081                    forwardMessageToTurnout(s, l);
082                    if (addr % 2 != 0) {
083                        // if the address is odd, also send the feedback
084                        // message to the even turnout.
085                        s = getSystemNamePrefix() + (addr + 1);
086                        forwardMessageToTurnout(s, l);
087                    }
088                }
089            }
090        }
091    }
092
093    protected void forwardMessageToTurnout(String s, XNetReply l){
094        XNetTurnout t = (XNetTurnout) getBySystemName(s);
095        if ( null == t ) {
096           // need to create a new one, and send the message on 
097           // to the newly created object.
098           ((XNetTurnout) provideTurnout(s)).initmessage(l);
099        } else {
100           // The turnout exists, forward this message to the 
101           // turnout
102           t.message(l);
103        }
104    }
105
106    /**
107     * Get text to be used for the Turnout.CLOSED state in user communication.
108     * Allows text other than "CLOSED" to be use with certain hardware system to
109     * represent the Turnout.CLOSED state.
110     */
111    @Override
112    @Nonnull
113    public String getClosedText() {
114        return Bundle.getMessage("TurnoutStateClosed");
115    }
116
117    /**
118     * Get text to be used for the Turnout.THROWN state in user communication.
119     * Allows text other than "THROWN" to be use with certain hardware system to
120     * represent the Turnout.THROWN state.
121     */
122    @Override
123    @Nonnull
124    public String getThrownText() {
125        return Bundle.getMessage("TurnoutStateThrown");
126    }
127
128    // listen for the messages to the LI100/LI101
129    @Override
130    public void message(XNetMessage l) {
131        //this class does not currently use outgoing messages.
132    }
133
134    // Handle a timeout notification
135    @Override
136    public void notifyTimeout(XNetMessage msg) {
137        log.debug("Notified of timeout on message {}",msg);
138    }
139
140    /**
141     * {@inheritDoc}
142     */
143    @Override
144    @Nonnull
145    public String validateSystemNameFormat(@Nonnull String name, @Nonnull Locale locale) {
146        return validateIntegerSystemNameFormat(name,
147                XNetAddress.MINSENSORADDRESS,
148                XNetAddress.MAXSENSORADDRESS,
149                locale);
150    }
151
152    /**
153     * {@inheritDoc}
154     */
155    @Override
156    public NameValidity validSystemNameFormat(@Nonnull String systemName) {
157        return (XNetAddress.validSystemNameFormat(systemName, 'T', getSystemPrefix()));
158    }
159
160    @Override
161    public boolean allowMultipleAdditions(@Nonnull String systemName) {
162        return true;
163    }
164
165    /**
166     * {@inheritDoc}
167     */
168    @Override
169    public String getEntryToolTip() {
170        return Bundle.getMessage("AddOutputEntryToolTip");
171    }
172
173    private static final Logger log = LoggerFactory.getLogger(XNetTurnoutManager.class);
174
175}