001package jmri.jmrix.lenz;
002
003import java.util.Locale;
004import javax.annotation.Nonnull;
005import jmri.JmriException;
006import jmri.NamedBean;
007import jmri.Sensor;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * Manage the XpressNet specific Sensor implementation.
013 * <p>
014 * System names are "XSnnn", where X is the user configurable system prefix,
015 * nnn is the sensor number without padding.
016 *
017 * @author Paul Bender Copyright (C) 2003-2010
018 * @navassoc 1 - * jmri.jmrix.lenz.XNetSensor
019 */
020public class XNetSensorManager extends jmri.managers.AbstractSensorManager implements XNetListener {
021
022    // ctor has to register for XNet events
023    public XNetSensorManager(XNetSystemConnectionMemo memo) {
024        super(memo);
025        tc = memo.getXNetTrafficController();
026        tc.addXNetListener(XNetInterface.FEEDBACK, this);
027    }
028
029    protected XNetTrafficController tc;
030
031    /**
032     * {@inheritDoc}
033     */
034    @Override
035    @Nonnull
036    public XNetSystemConnectionMemo getMemo() {
037        return (XNetSystemConnectionMemo) memo;
038    }
039
040    // to free resources when no longer used
041    @Override
042    public void dispose() {
043        tc.removeXNetListener(XNetInterface.FEEDBACK, this);
044        super.dispose();
045    }
046
047    // XpressNet specific methods
048
049    /**
050     * {@inheritDoc}
051     * <p>
052     * Assumes calling method has checked that a Sensor with this
053     * system name does not already exist.
054     *
055     * @throws IllegalArgumentException when SystemName can't be converted
056     */
057    @Override
058    @Nonnull
059    protected Sensor createNewSensor(@Nonnull String systemName, String userName) throws IllegalArgumentException {
060        // check if the output bit is available
061        int bitNum = XNetAddress.getBitFromSystemName(systemName, getSystemPrefix());
062        if (bitNum == -1) {
063            throw new IllegalArgumentException("Unable to convert " +  // NOI18N
064                    systemName +
065                    " to XNet sensor address"); // NOI18N
066        }
067        // normalize system name
068        String sName = getSystemNamePrefix() + bitNum;
069        // create the new Sensor object
070        return new XNetSensor(sName, userName, tc, getSystemPrefix());
071    }
072
073    // listen for sensors, creating them as needed
074    @Override
075    public void message(XNetReply l) {
076        log.debug("received message: {}", l);
077        if (l.isFeedbackBroadcastMessage()) {
078            int numDataBytes = l.getElement(0) & 0x0f;
079            for (int i = 1; i < numDataBytes; i += 2) {
080                if (l.getFeedbackMessageType(i) == 2) {
081                    // This is a feedback encoder message. The address of the
082                    // Feedback sensor is byte two of the message.
083                    int address = l.getFeedbackEncoderMsgAddr(i);
084                    log.debug("Message for feedback encoder {}", address);
085
086                    int firstaddress = ((address) * 8) + 1;
087                    // Each Feedback encoder includes 8 addresses, so register
088                    // a sensor for each address.
089                    for (int j = 0; j < 8; j++) {
090                        String s = getSystemNamePrefix() + (firstaddress + j);
091                        if (null == getBySystemName(s)) {
092                            // The sensor doesn't exist.  We need to create a
093                            // new sensor, and forward this message to it.
094                            ((XNetSensor) provideSensor(s)).initmessage(l);
095                        } else {
096                            // The sensor exists.  We need to forward this
097                            // message to it.
098                            Sensor xns = getBySystemName(s);
099                            if (xns == null) {
100                                log.error("Failed to get sensor for {}", s);
101                            } else {
102                                ((XNetSensor) xns).message(l);
103                            }
104                        }
105                    }
106                }
107            }
108        }
109    }
110
111    /**
112     * Listen for the messages to the LI100/LI101.
113     */
114    @Override
115    public void message(XNetMessage l) {
116    }
117
118    /**
119     * Handle a timeout notification.
120     */
121    @Override
122    public void notifyTimeout(XNetMessage msg) {
123        if (log.isDebugEnabled()) {
124            log.debug("Notified of timeout on message{}", msg.toString());
125        }
126    }
127
128    /**
129     * {@inheritDoc}
130     */
131    @Override
132    @Nonnull
133    public String validateSystemNameFormat(@Nonnull String name, @Nonnull Locale locale) {
134        if (name.contains(":")) {
135            validateSystemNamePrefix(name, locale);
136            String[] parts = name.substring(getSystemNamePrefix().length()).split(":");
137            if (parts.length != 2) {
138                throw new NamedBean.BadSystemNameException(
139                        Bundle.getMessage(Locale.ENGLISH, "SystemNameInvalidAddress", name),
140                        Bundle.getMessage(locale, "SystemNameInvalidAddress", name));
141            }
142            try {
143                int address = Integer.parseInt(parts[0]);
144                if (address < 1 || address > 127) {
145                    throw new NamedBean.BadSystemNameException(
146                            Bundle.getMessage(Locale.ENGLISH, "SystemNameInvalidModule", name, parts[0]),
147                            Bundle.getMessage(locale, "SystemNameInvalidModule", name, parts[0]));
148                }
149            } catch (NumberFormatException ex) {
150                throw new NamedBean.BadSystemNameException(
151                        Bundle.getMessage(Locale.ENGLISH, "SystemNameInvalidModule", name, parts[0]),
152                        Bundle.getMessage(locale, "SystemNameInvalidModule", name, parts[0]));
153            }
154            try {
155                int bit = Integer.parseInt(parts[1]);
156                if (bit < 1 || bit > 8) {
157                    throw new NamedBean.BadSystemNameException(
158                            Bundle.getMessage(Locale.ENGLISH, "SystemNameInvalidBit", name, parts[1]),
159                            Bundle.getMessage(locale, "SystemNameInvalidBit", name, parts[1]));
160                }
161            } catch (NumberFormatException ex) {
162                throw new NamedBean.BadSystemNameException(
163                        Bundle.getMessage(Locale.ENGLISH, "SystemNameInvalidBit", name, parts[1]),
164                        Bundle.getMessage(locale, "SystemNameInvalidBit", name, parts[1]));
165            }
166            return name;
167        } else {
168            return validateIntegerSystemNameFormat(name,
169                    XNetAddress.MINSENSORADDRESS,
170                    XNetAddress.MAXSENSORADDRESS,
171                    locale);
172        }
173    }
174
175    /**
176     * {@inheritDoc}
177     */
178    @Override
179    public NameValidity validSystemNameFormat(@Nonnull String systemName) {
180        return (XNetAddress.validSystemNameFormat(systemName, 'S', getSystemPrefix()));
181    }
182
183    @Override
184    public boolean allowMultipleAdditions(@Nonnull String systemName) {
185        return true;
186    }
187
188    @Override
189    @Nonnull
190    synchronized public String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws JmriException {
191        int encoderAddress;
192        int input;
193
194        if (curAddress.contains(":")) {
195            // Address format passed is in the form of encoderAddress:input or T:turnout address
196            int seperator = curAddress.indexOf(":");
197            try {
198                encoderAddress = Integer.parseInt(curAddress.substring(0, seperator));
199                input = Integer.parseInt(curAddress.substring(seperator + 1));
200            } catch (NumberFormatException ex) {
201                throw new JmriException("Unable to convert "+curAddress+" into the cab and input format of nn:xx");
202            }
203            iName = ((encoderAddress - 1) * 8) + input;
204        } else {
205            // Entered in using the old format
206            try {
207                iName = Integer.parseInt(curAddress);
208            } catch (NumberFormatException ex) {
209                throw new JmriException("Hardware Address "+curAddress+" should be a number or the cab and input format of nn:xx");
210            }
211        }
212        return prefix + typeLetter() + iName;
213    }
214
215    private int iName; // must synchronize to avoid race conditions.
216
217    /**
218     * {@inheritDoc}
219     */
220    @Override
221    public String getEntryToolTip() {
222        return Bundle.getMessage("AddInputEntryToolTip");
223    }
224
225    private static final Logger log = LoggerFactory.getLogger(XNetSensorManager.class);
226
227}