001package jmri.jmrix.lenz;
002
003import jmri.Manager.NameValidity;
004import org.slf4j.Logger;
005import org.slf4j.LoggerFactory;
006
007import javax.annotation.Nonnull;
008
009/**
010 * Utility Class supporting parsing and testing of addresses for Lenz XpressNet
011 * <p>
012 * Two address format are supported: 
013 * <ul>
014 * <li> 
015 * Xtxxxx where: t is the type code, 'T' for turnouts, 'S' for sensors, 
016 * and 'L' for lights xxxx is a int for the hardware address (1-1024) 
017 * examples: XT2 (address 2), XS1003 (address 1003), XL134 (address 134)
018 * </li>
019 * <li>
020 * XSmm:pp where mm is the module address (1-128) and pp is the contact pin number (1-8).
021 * </li>
022 * </ul>
023 *
024 * @author Dave Duchamp, Copyright (C) 2004 - 2006
025 * @author Bob Coleman Copyright (C) 2007, 2008, 2009
026 * @author Egbert Broerse (C) 2017 Based on Acela example, modified for XpressNet.
027 */
028public class XNetAddress {
029
030    public XNetAddress() {
031    }
032
033    static final int MINSENSORADDRESS = 1;
034    static final int MAXSENSORADDRESS = 1024; // same for outputs
035
036    /**
037     * Public static method to parse a Lenz XpressNet system name.
038     * Note: Bits are numbered from 1.
039     *
040     * @param systemName system name to parse.
041     * @param prefix system prefix.
042     * @return the hardware address number, return -1 if an error is found
043     */
044    public static int getBitFromSystemName(String systemName, String prefix) {
045        // validate the system Name leader characters
046        if (!systemName.startsWith(prefix)) {
047            // here if an invalid XpressNet system name
048            log.error("invalid character in header field of XpressNet system name: {} wants prefix {}", 
049                systemName, prefix);
050            return (-1);
051        }
052        // name must be in the Xtnnnnn or XSmm:pp format (X is user 
053        // configurable)
054        int num;
055        try {
056            String curAddress = systemName.substring(prefix.length() + 1);
057            if( ( systemName.charAt(prefix.length())=='S' ||
058                  systemName.charAt(prefix.length())=='s' ) && 
059                  curAddress.contains(":")) {
060               // Address format passed is in the form of encoderAddress:input or T:turnout address
061               int seperator = curAddress.indexOf(":");
062               int encoderAddress = Integer.parseInt(curAddress.substring(0, seperator));
063               int input = Integer.parseInt(curAddress.substring(seperator + 1));
064               num = ((encoderAddress - 1) * 8) + input;
065            } else {
066               num = Integer.parseInt(curAddress);
067            }
068        } catch (NumberFormatException e) {
069            log.warn("invalid character in number field of system name: {}", systemName);
070            return (-1);
071        }
072        if ((num >= MINSENSORADDRESS) && (num <= MAXSENSORADDRESS)) {
073            return (num);
074        }
075        log.warn("XpressNet hardware address out of range in system name {}", systemName);
076        return (-1);
077    }
078
079    /**
080     * Public static method to validate system name format.
081     * Logging of handled cases no higher than WARN.
082     *
083     * @param systemName system name.
084     * @param type bean type, S for Sensor, T for Turnout, L for Light.
085     * @param prefix system prefix.
086     * @return VALID if system name has a valid format, else return INVALID
087     */
088    public static NameValidity validSystemNameFormat(@Nonnull String systemName, char type, String prefix) {
089        // validate the system Name leader characters
090        if (!(systemName.startsWith(prefix + type))) {
091            // here if an illegal format 
092            log.error("invalid character in header field of system name: {} wants prefix {} type {}", 
093                systemName, prefix, type);
094            return NameValidity.INVALID;
095        }
096        if (getBitFromSystemName(systemName, prefix) > 0) {
097            return NameValidity.VALID;
098        } else {
099            return NameValidity.INVALID;
100        }
101    }
102
103    /**
104     * Public static method to check the user name for a valid system name.
105     *
106     * @param systemName system name to check.
107     * @param prefix system prefix.
108     * @return "" (null string) if the system name is not valid or does not exist
109     */
110    public static String getUserNameFromSystemName(String systemName, String prefix) {
111        // check for a valid system name
112        if ((systemName.length() < (prefix.length() + 2)) || (!systemName.startsWith(prefix))) {
113            // not a valid system name for XNet
114            return ("");
115        }
116        // check for a sensor
117        if (systemName.charAt(prefix.length() + 1) == 'S') {
118            jmri.Sensor s;
119            s = jmri.InstanceManager.sensorManagerInstance().getBySystemName(systemName);
120            if (s != null) {
121                return s.getUserName();
122            } else {
123                return ("");
124            }
125        } // check for a turnout
126        else if (systemName.charAt(prefix.length() + 1) == 'T') {
127            jmri.Turnout t;
128            t = jmri.InstanceManager.turnoutManagerInstance().getBySystemName(systemName);
129            if (t != null) {
130                return t.getUserName();
131            } else {
132                return ("");
133            }
134        } // check for a light
135        else if (systemName.charAt(prefix.length() + 1) == 'L') {
136            jmri.Light lgt;
137            lgt = jmri.InstanceManager.lightManagerInstance().getBySystemName(systemName);
138            if (lgt != null) {
139                return lgt.getUserName();
140            } else {
141                return ("");
142            }
143        }
144
145        // not any known sensor, light, or turnout
146        return ("");
147    }
148
149    private static final Logger log = LoggerFactory.getLogger(XNetAddress.class);
150
151}