001package jmri.jmrix.dccpp;
002
003import static jmri.jmrix.dccpp.DCCppConstants.MAX_SENSOR_ID;
004
005import java.util.Locale;
006import javax.annotation.Nonnull;
007import jmri.JmriException;
008import jmri.Sensor;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012/**
013 * Implement SensorManager for DCC++ systems.
014 * <p>
015 * System names are "DSnnn", where D is the user configurable system prefix,
016 * nnn is the sensor number without padding.
017 *
018 * @author Paul Bender Copyright (C) 2003-2010
019 * @author Mark Underwood Copyright (C) 2015
020 */
021public class DCCppSensorManager extends jmri.managers.AbstractSensorManager implements DCCppListener {
022
023    protected DCCppTrafficController tc = null;
024
025    /**
026     * Create an new DCC++ SensorManager.
027     * Has to register for DCC++ events.
028     *
029     * @param memo the supporting system connection memo
030     */
031    public DCCppSensorManager(DCCppSystemConnectionMemo memo) {
032        super(memo);
033        tc = memo.getDCCppTrafficController();
034        // set up listener
035        tc.addDCCppListener(DCCppInterface.FEEDBACK, this);
036        // request list of sensors
037        DCCppMessage msg = DCCppMessage.makeSensorListMsg();
038        tc.sendDCCppMessage(msg, this);
039    }
040
041    /**
042     * {@inheritDoc}
043     */
044    @Override
045    @Nonnull
046    public DCCppSystemConnectionMemo getMemo() {
047        return (DCCppSystemConnectionMemo) memo;
048    }
049
050    // to free resources when no longer used
051    @Override
052    public void dispose() {
053        tc.removeDCCppListener(DCCppInterface.FEEDBACK, this);
054        super.dispose();
055    }
056
057    // DCCpp specific methods
058
059    /**
060     * {@inheritDoc}
061     *
062     * @throws IllegalArgumentException when SystemName can't be converted
063     */
064    @Override
065    @Nonnull
066    protected Sensor createNewSensor(@Nonnull String systemName, String userName) throws IllegalArgumentException {
067        int addr;
068        try {
069            addr = Integer.parseInt(systemName.substring(getSystemPrefix().length() + 1));
070        } catch (NumberFormatException e) {
071            throw new IllegalArgumentException("Can't convert " +  // NOI18N
072                    systemName.substring(getSystemNamePrefix().length()) +
073                    " to DCC++ sensor address"); // NOI18N
074        }
075        return new DCCppSensor(getSystemNamePrefix() + addr, userName, tc);
076    }
077
078    /**
079     * Listen for sensors, creating them as needed.
080     *
081     * @param l the message to parse
082     */
083    @Override
084    public void message(DCCppReply l) {
085        int addr = -1;  // -1 flags that no sensor address was found in reply
086        if (l.isSensorDefReply()) {
087            addr = l.getSensorDefNumInt();
088            if (log.isDebugEnabled()) {
089                log.debug("SensorDef Reply for Encoder {}", Integer.toString(addr));
090            }
091
092        } else if (l.isSensorReply()) {
093            addr = l.getSensorNumInt();
094            if (log.isDebugEnabled()) {
095                log.debug("Sensor Status Reply for Encoder {}", Integer.toString(addr));
096            }
097        }
098        if (addr >= 0) {
099            String s = getSystemNamePrefix() + (addr);
100            if (null == getBySystemName(s)) {
101                // The sensor doesn't exist.  We need to create a
102                // new sensor, and forward this message to it.
103                DCCppSensor sn = (DCCppSensor) provideSensor(s);
104                sn.initmessage(l);
105            } else {
106                // The sensor exists.  We need to forward this
107                // message to it.
108                Sensor sen = getBySystemName(s);
109                if (sen == null) {
110                    log.error("Failed to get sensor for {}", s);
111                } else {
112                    ((DCCppSensor) sen).message(l);
113                }
114            }
115        }
116    }
117
118    /**
119     * Listen for the outgoing messages (to the command station)
120     *
121     * @param l the message to parse
122     */
123    @Override
124    public void message(DCCppMessage l) {
125    }
126
127    // Handle message timeout notification
128    // If the message still has retries available, reduce retries and send it back to the traffic controller.
129    @Override
130    public void notifyTimeout(DCCppMessage msg) {
131        log.debug("Notified of timeout on message '{}' , {} retries available.", msg, msg.getRetries());
132        if (msg.getRetries() > 0) {
133            msg.setRetries(msg.getRetries() - 1);
134            tc.sendDCCppMessage(msg, this);
135        }
136    }
137
138    @Override
139    public boolean allowMultipleAdditions(@Nonnull String systemName) {
140        return true;
141    }
142
143    @Override
144    @Nonnull
145    synchronized public String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws JmriException {
146        int encoderAddress = 0;
147        int input = 0;
148
149        if (curAddress.contains(":")) {
150            // Address format passed is in the form of encoderAddress:input or T:turnout address
151            int seperator = curAddress.indexOf(":");
152            try {
153                encoderAddress = Integer.parseInt(curAddress.substring(0, seperator));
154                input = Integer.parseInt(curAddress.substring(seperator + 1));
155            } catch (NumberFormatException ex) {
156                throw new JmriException("Unable to convert " + curAddress + " into the cab and input format of nn:xx");
157            }
158            iName = ((encoderAddress - 1) * 8) + input;
159        } else {
160            // Entered in using the old format
161            try {
162                iName = Integer.parseInt(curAddress);
163            } catch (NumberFormatException ex) {
164                throw new JmriException("Hardware Address "+curAddress+" should be a number or cab and input format of nn:xx");
165            }
166        }
167
168        return prefix + typeLetter() + iName;
169    }
170
171    int iName; // must synchronize to avoid race conditions.
172
173    /**
174     * {@inheritDoc}
175     */
176    @Override
177    public NameValidity validSystemNameFormat(@Nonnull String systemName) {
178        return (getBitFromSystemName(systemName) != 0) ? NameValidity.VALID : NameValidity.INVALID;
179    }
180
181    /**
182     * {@inheritDoc}
183     */
184    @Override
185    public String validateSystemNameFormat(String systemName, Locale locale) {
186        return validateIntegerSystemNameFormat(systemName, 1, MAX_SENSOR_ID, locale);
187    }
188
189    /**
190     * Get the bit address from the system name.
191     * @param systemName a valid Sensor System Name
192     * @return the sensor number extracted from the system name
193     */
194    public int getBitFromSystemName(String systemName) {
195        try {
196            validateSystemNameFormat(systemName, Locale.getDefault());
197        } catch (IllegalArgumentException ex) {
198            return 0;
199        }
200        return Integer.parseInt(systemName.substring(getSystemNamePrefix().length()));
201    }
202
203    /**
204     * {@inheritDoc}
205     */
206    @Override
207    public String getEntryToolTip() {
208        return Bundle.getMessage("AddOutputEntryToolTip");
209    }
210
211    private final static Logger log = LoggerFactory.getLogger(DCCppSensorManager.class);
212
213}