001package jmri.jmrix.roco.z21; 002 003import java.util.Locale; 004import javax.annotation.Nonnull; 005import jmri.Manager; 006import jmri.Manager.NameValidity; 007import jmri.NamedBean; 008import jmri.ReporterManager; 009 010/** 011 * Utility Class supporting parsing and testing of addresses for Z21 CanBus 012 * <p> 013 * One address format is supported for Reporters and Sensors: 014 * <ul> 015 * <li> 016 * Ztmm:pp where t is either R or S, mm is the module address and pp is the contact pin number (1-8). 017 * </li> 018 * </ul> 019 * 020 * @author Dave Duchamp, Copyright (C) 2004 - 2006 021 * @author Bob Coleman Copyright (C) 2007, 2008, 2009 022 * @author Egbert Broerse (C) 2017 Based on Acela example, modified for XpressNet. 023 */ 024public class Z21CanBusAddress { 025 026 private Z21CanBusAddress() { 027 // this is a class of static methods. 028 } 029 030 /** 031 * Public static method to parse a Z21CanBus system name. 032 * Note: Bits are numbered from 0. 033 * 034 * @param systemName system name. 035 * @param prefix system prefix. 036 * @return the hardware address number, return -1 if an error is found 037 */ 038 public static int getBitFromSystemName(String systemName, String prefix) { 039 // validate the system Name leader characters 040 if(!systemNameStartsWithPrefix(systemName,prefix)) { 041 return (-1); 042 } 043 // name must be in the Ztmm:pp format (Z is user 044 // configurable) 045 try { 046 String curAddress = systemName.substring(prefix.length() + 1); 047 if( ( systemName.charAt(prefix.length())=='R' || 048 systemName.charAt(prefix.length())=='r' || 049 systemName.charAt(prefix.length())=='S' || 050 systemName.charAt(prefix.length())=='s' ) && 051 curAddress.contains(":")) { 052 //Address format passed is in the form of encoderAddress:input 053 int seperator = curAddress.indexOf(':'); 054 int encoderAddress = parseEncoderAddress(curAddress,0,seperator); 055 log.debug("found module address {}",encoderAddress); 056 // since we aren't supporting bit number, just return the contact 057 // since we know now the module address is valid. 058 return Integer.parseInt(curAddress.substring(seperator + 1)); 059 } else { 060 log.warn("system name {} is in the wrong format. Should be mm:pp.",systemName); 061 } 062 } catch (NumberFormatException e) { 063 log.warn("invalid character in number field of system name: {}", systemName); 064 } 065 return (-1); 066 } 067 068 private static boolean systemNameStartsWithPrefix(String systemName,String prefix){ 069 if (!systemName.startsWith(prefix)) { 070 // here if an invalid Z21 Can Bus system name 071 log.error("invalid character in header field of Z21 Can Bus system name: {}", systemName); 072 return false; 073 } 074 return true; 075 } 076 077 private static int parseEncoderAddress(String addressWithoutPrefix,int start, int end) { 078 int encoderAddress; 079 try { 080 encoderAddress = Integer.parseInt(addressWithoutPrefix.substring(start,end)); 081 } catch (NumberFormatException ex) { 082 // didn't parse as a decimal, check to see if network ID 083 // was used instead. 084 encoderAddress = Integer.parseInt(addressWithoutPrefix.substring(start,end),16); 085 } 086 return encoderAddress; 087 } 088 089 public static String getEncoderAddressString(String systemName, String prefix) { 090 091 // validate the system Name leader characters 092 if (!systemNameStartsWithPrefix(systemName, prefix)) { 093 throw new NamedBean.BadSystemNameException( 094 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameInvalidPrefix", prefix), 095 Bundle.getMessage("InvalidSystemNameInvalidPrefix", prefix)); 096 097 } 098 int seperator = systemName.indexOf(':'); 099 return systemName.substring(prefix.length() + 1,seperator); 100 } 101 102 /** 103 * Validate a system name format. 104 * 105 * @param name the name to validate 106 * @param manager the manager requesting validation 107 * @param locale the locale for user messages 108 * @return name, unchanged 109 * @see jmri.Manager#validateSystemNameFormat(java.lang.String, 110 * java.util.Locale) 111 */ 112 public static String validateSystemNameFormat(String name, Manager<?> manager, Locale locale) { 113 name = manager.validateSystemNamePrefix(name, locale); 114 String[] parts = name.substring(manager.getSystemNamePrefix().length()).split(":"); 115 if (parts.length != 2) { 116 throw newBadSystemNameException(name,"SystemNameInvalidMissingParts",locale); 117 } 118 int num; 119 try { 120 num = parseEncoderAddress(parts[0],0,parts[0].length()); 121 if (num < 0 || num > 65535) { 122 throw newBadSystemNameException(name, "SysteNameInvalidCanAddress", locale); 123 } 124 } catch (NumberFormatException ex) { 125 throw newBadSystemNameException(name,"SysteNameInvalidCanAddress",locale); 126 } 127 try { 128 num = Integer.parseInt(parts[1]); 129 if (num < 0 || num > 7) { 130 throw newBadSystemNameException(name,"SystemNameInvalidPin",locale); 131 } 132 } catch (NumberFormatException ex) { 133 throw newBadSystemNameException(name,"SystemNameInvalidPin",locale); 134 } 135 return name; 136 } 137 138 private static NamedBean.BadSystemNameException newBadSystemNameException(String name, String reasonKey, Locale locale){ 139 return new NamedBean.BadSystemNameException( 140 Bundle.getMessage(Locale.ENGLISH, reasonKey, name), 141 Bundle.getMessage(locale, reasonKey, name)); 142 } 143 144 /** 145 * Public static method to validate system name format. 146 * Logging of handled cases no higher than WARN. 147 * 148 * @param systemName system name. 149 * @param type bean type, S for Sensor, T for Turnout. 150 * @param prefix system prefix. 151 * @return VALID if system name has a valid format, else return INVALID 152 */ 153 public static NameValidity validSystemNameFormat(@Nonnull String systemName, char type, String prefix) { 154 // validate the system Name leader characters 155 if (!(systemName.startsWith(prefix + type))) { 156 // here if an illegal format 157 log.error("invalid character in header field of system name: {}", systemName); 158 return NameValidity.INVALID; 159 } 160 if (getBitFromSystemName(systemName, prefix) >= 0) { 161 return NameValidity.VALID; 162 } else { 163 return NameValidity.INVALID; 164 } 165 } 166 167 /** 168 * Public static method to check the user name for a valid system name. 169 * 170 * @param systemName system name. 171 * @param prefix system prefix. 172 * @return "" (null string) if the system name is not valid or does not exist 173 */ 174 public static String getUserNameFromSystemName(String systemName, String prefix) { 175 // check for a valid system name 176 if ((systemName.length() < (prefix.length() + 2)) || (!systemName.startsWith(prefix))) { 177 // not a valid system name for Z21 Can Bus 178 return (""); 179 } 180 // check for a Reporter 181 if (systemName.charAt(prefix.length() + 1) == 'R') { 182 jmri.Reporter r; 183 r = jmri.InstanceManager.getDefault(ReporterManager.class).getBySystemName(systemName); 184 if (r != null) { 185 return r.getUserName(); 186 } else { 187 return (""); 188 } 189 } 190 // check for a Sensor 191 if (systemName.charAt(prefix.length() + 1) == 'S') { 192 jmri.Sensor s; 193 s = jmri.InstanceManager.sensorManagerInstance().getBySystemName(systemName); 194 if (s != null) { 195 return s.getUserName(); 196 } else { 197 return (""); 198 } 199 } 200 // not any known sensor 201 return (""); 202 } 203 204 public static String buildDecimalSystemNameFromParts(String prefix, char typeLetter, int userAddress,int pin){ 205 return String.format("%s%c%d:%d",prefix,typeLetter, userAddress,pin); 206 } 207 208 public static String buildHexSystemNameFromParts(String prefix, char typeLetter,int globalCANaddress,int pin){ 209 return String.format("%s%c%4X:%d",prefix,typeLetter, globalCANaddress,pin); 210 } 211 212 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Z21CanBusAddress.class); 213 214}