001package jmri.jmrix.ieee802154.xbee; 002 003import com.digi.xbee.api.RemoteXBeeDevice; 004import com.digi.xbee.api.exceptions.InterfaceNotOpenException; 005import com.digi.xbee.api.exceptions.TimeoutException; 006import com.digi.xbee.api.exceptions.XBeeException; 007import com.digi.xbee.api.io.IOLine; 008import com.digi.xbee.api.io.IOMode; 009import com.digi.xbee.api.io.IOSample; 010import com.digi.xbee.api.listeners.IIOSampleReceiveListener; 011 012import java.util.Locale; 013import javax.annotation.Nonnull; 014import jmri.JmriException; 015import jmri.NamedBean; 016import jmri.Sensor; 017import org.slf4j.Logger; 018import org.slf4j.LoggerFactory; 019 020/** 021 * Manage the XBee specific Sensor implementation. 022 * <p> 023 * System names are formatted as one of: 024 * <ul> 025 * <li>"ZSnnn", where nnn is the sensor number without padding</li> 026 * <li>"ZSstring:pin", where string is a node address and pin is the io pin used.</li> 027 * </ul> 028 * Z is the user-configurable system prefix. 029 * 030 * @author Paul Bender Copyright (C) 2003-2016 031 */ 032public class XBeeSensorManager extends jmri.managers.AbstractSensorManager implements IIOSampleReceiveListener{ 033 034 // ctor has to register for XBee events 035 public XBeeSensorManager(XBeeConnectionMemo memo) { 036 super(memo); 037 tc = (XBeeTrafficController) memo.getTrafficController(); 038 tc.getXBee().addIOSampleListener(this); 039 } 040 041 /** 042 * {@inheritDoc} 043 */ 044 @Override 045 @Nonnull 046 public XBeeConnectionMemo getMemo() { 047 return (XBeeConnectionMemo) memo; 048 } 049 050 protected XBeeTrafficController tc = null; 051 052 // to free resources when no longer used 053 @Override 054 public void dispose() { 055 tc.getXBee().removeIOSampleListener(this); 056 super.dispose(); 057 } 058 059 // XBee specific methods 060 061 /** 062 * {@inheritDoc} 063 * <p> 064 * System name is normalized to ensure uniqueness. 065 * @throws IllegalArgumentException when SystemName can't be converted 066 */ 067 @Override 068 @Nonnull 069 protected Sensor createNewSensor(@Nonnull String systemName, String userName) throws IllegalArgumentException { 070 XBeeNode curNode; 071 String name = addressFromSystemName(systemName); 072 if ((curNode = (XBeeNode) tc.getNodeFromName(name)) == null) { 073 if ((curNode = (XBeeNode) tc.getNodeFromAddress(name)) == null) { 074 try { 075 curNode = (XBeeNode) tc.getNodeFromAddress(Integer.parseInt(name)); 076 } catch (java.lang.NumberFormatException nfe) { 077 // we couldn't find the node 078 throw new IllegalArgumentException("Unable to convert " + // NOI18N 079 systemName + " to XBee sensor address"); // NOI18N 080 } 081 } 082 } 083 int pin = pinFromSystemName(systemName); 084 if (curNode != null && !curNode.getPinAssigned(pin)) { 085 log.debug("Adding sensor to pin {}", pin); 086 curNode.setPinBean(pin, new XBeeSensor(systemName, userName, tc)); 087 return (XBeeSensor) curNode.getPinBean(pin); 088 } else { 089 log.debug("Failed to create sensor {}", systemName); 090 throw new IllegalArgumentException("Can't assign pin for " + // NOI18N 091 systemName + 092 " XBee sensor"); // NOI18N 093 } 094 } 095 096 /** 097 * {@inheritDoc} 098 */ 099 @Override 100 @Nonnull 101 public String validateSystemNameFormat(@Nonnull String name, @Nonnull Locale locale) { 102 super.validateSystemNameFormat(name, locale); 103 int pin = pinFromSystemName(name); 104 if (pin < 0 || pin > 7) { 105 throw new NamedBean.BadSystemNameException( 106 Bundle.getMessage(Locale.ENGLISH, "SystemNameInvalidPin", name), 107 Bundle.getMessage(locale, "SystemNameInvalidPin", name)); 108 } 109 return name; 110 } 111 112 /** 113 * {@inheritDoc} 114 */ 115 @Override 116 public NameValidity validSystemNameFormat(@Nonnull String systemName) { 117 if (tc.getNodeFromName(addressFromSystemName(systemName)) == null 118 && tc.getNodeFromAddress(addressFromSystemName(systemName)) == null) { 119 try { 120 if (tc.getNodeFromAddress(Integer.parseInt(addressFromSystemName(systemName))) == null) { 121 return NameValidity.INVALID; 122 } else { 123 return (pinFromSystemName(systemName) >= 0 124 && pinFromSystemName(systemName) <= 7) ? NameValidity.VALID : NameValidity.INVALID; 125 } 126 } catch (java.lang.NumberFormatException nfe) { 127 // if there was a number format exception, we couldn't find the node. 128 log.debug("Unable to convert {} into the XBee node and pin format of nn:xx", systemName); 129 return NameValidity.INVALID; 130 } 131 132 } else { 133 return (pinFromSystemName(systemName) >= 0 134 && pinFromSystemName(systemName) <= 7) ? NameValidity.VALID : NameValidity.INVALID; 135 } 136 } 137 138 // IIOSampleReceiveListener methods 139 140 @Override 141 public synchronized void ioSampleReceived(RemoteXBeeDevice remoteDevice,IOSample ioSample) { 142 if (log.isDebugEnabled()) { 143 log.debug("received io sample {} from {}",ioSample,remoteDevice); 144 } 145 146 XBeeNode node = (XBeeNode) tc.getNodeFromXBeeDevice(remoteDevice); 147 for (int i = 0; i <= 8; i++) { 148 if (!node.getPinAssigned(i) 149 && ioSample.hasDigitalValue(IOLine.getDIO(i))) { 150 // get pin direction 151 IOMode mode = IOMode.DISABLED; // assume disabled as default. 152 try { 153 mode = remoteDevice.getIOConfiguration(IOLine.getDIO(i)); 154 } catch (TimeoutException toe) { 155 log.debug("Timeout retrieving IO line mode for {} on {}",IOLine.getDIO(i),remoteDevice); 156 // is this a hidden terminal? This was triggered by an 157 // IO Sample, so we know we can hear the other node, but 158 // it may not hear us. In this case, assume we are 159 // working with an input pin. 160 mode = IOMode.DIGITAL_IN; 161 } catch (InterfaceNotOpenException ino) { 162 log.error("Interface Not Open retrieving IO line mode for {} on {}",IOLine.getDIO(i),remoteDevice); 163 } catch (XBeeException xbe) { 164 log.error("Error retrieving IO line mode for {} on {}",IOLine.getDIO(i),remoteDevice); 165 } 166 167 if(mode == IOMode.DIGITAL_IN ) { 168 // thisis an input, check to see if it exists as a sensor. 169 node = (XBeeNode) tc.getNodeFromXBeeDevice(remoteDevice); 170 171 // Sensor name is prefix followed by NI/address 172 // followed by the bit number. 173 String sName = getSystemNamePrefix() 174 + node.getPreferedName() + ":" + i; 175 XBeeSensor s = (XBeeSensor) getSensor(sName); 176 if (s == null) { 177 // the sensor doesn't exist, so provide a new one. 178 try { 179 provideSensor(sName); 180 if (log.isDebugEnabled()) { 181 log.debug("DIO {} enabled as sensor",sName); 182 } 183 } catch(java.lang.IllegalArgumentException iae){ 184 // if provideSensor fails, it will throw an IllegalArgumentException, so catch that,log it if debugging is enabled, and then re-throw it. 185 if (log.isDebugEnabled()) { 186 log.debug("Attempt to enable DIO {} as sensor failed",sName); 187 } 188 throw iae; 189 } 190 } 191 } 192 } 193 } 194 } 195 196 // for now, set this to false. multiple additions currently works 197 // partially, but not for all possible cases. 198 @Override 199 public boolean allowMultipleAdditions(@Nonnull String systemName) { 200 return false; 201 } 202 203 @Override 204 @Nonnull 205 public String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws JmriException { 206 String encoderAddress = addressFromSystemName(prefix + typeLetter() + curAddress); 207 int input = pinFromSystemName(prefix + typeLetter() + curAddress); 208 209 if (encoderAddress.equals("")) { 210 throw new JmriException("I unable to determine hardware address"); 211 } 212 return prefix + typeLetter() + encoderAddress + ":" + input; 213 } 214 215 private String addressFromSystemName(String systemName) { 216 String encoderAddress; 217 218 if (systemName.contains(":")) { 219 //Address format passed is in the form of encoderAddress:input or S:light address 220 int seperator = systemName.indexOf(":"); 221 encoderAddress = systemName.substring(getSystemPrefix().length() + 1, seperator); 222 } else { 223 encoderAddress = systemName.substring(getSystemPrefix().length() + 1, systemName.length() - 1); 224 } 225 if (log.isDebugEnabled()) { 226 log.debug("Converted {} to hardware address {}", systemName, encoderAddress); 227 } 228 return encoderAddress; 229 } 230 231 private int pinFromSystemName(String systemName) { 232 int input = 0; 233 int iName = 0; 234 235 if (systemName.contains(":")) { 236 //Address format passed is in the form of encoderAddress:input or L:light address 237 int seperator = systemName.indexOf(":"); 238 try { 239 input = Integer.parseInt(systemName.substring(seperator + 1)); 240 } catch (NumberFormatException ex) { 241 log.debug("Unable to convert {} into the cab and input format of nn:xx", systemName); 242 return -1; 243 } 244 } else { 245 try { 246 iName = Integer.parseInt(systemName.substring(getSystemPrefix().length() + 1)); 247 input = iName % 10; 248 } catch (NumberFormatException ex) { 249 log.debug("Unable to convert {} Hardware Address to a number", systemName); 250 return -1; 251 } 252 } 253 if (log.isDebugEnabled()) { 254 log.debug("Converted {} to pin number{}", systemName, input); 255 } 256 return input; 257 } 258 259 @Override 260 public void deregister(@Nonnull jmri.Sensor s) { 261 super.deregister(s); 262 // remove the specified sensor from the associated XBee pin. 263 String systemName = s.getSystemName(); 264 String name = addressFromSystemName(systemName); 265 int pin = pinFromSystemName(systemName); 266 XBeeNode curNode; 267 if ((curNode = (XBeeNode) tc.getNodeFromName(name)) == null) { 268 if ((curNode = (XBeeNode) tc.getNodeFromAddress(name)) == null) { 269 try { 270 curNode = (XBeeNode) tc.getNodeFromAddress(Integer.parseInt(name)); 271 } catch (java.lang.NumberFormatException nfe) { 272 // if there was a number format exception, we couldn't 273 // find the node. 274 curNode = null; 275 } 276 } 277 } 278 if (curNode != null) { 279 if (curNode.removePinBean(pin, s)) { 280 log.debug("Removing sensor from pin {}", pin); 281 } else { 282 log.debug("Failed to removing sensor from pin {}", pin); 283 } 284 } 285 } 286 287 /** 288 * Do the sensor objects provided by this manager support configuring 289 * an internal pullup or pull down resistor? 290 * <p> 291 * For Raspberry Pi systems, it is possible to set the pullup or 292 * pulldown resistor, so return true. 293 * 294 * @return true if pull up/pull down configuration is supported. 295 */ 296 @Override 297 public boolean isPullResistanceConfigurable(){ 298 return true; 299 } 300 301 /** 302 * {@inheritDoc} 303 */ 304 @Override 305 public String getEntryToolTip() { 306 return Bundle.getMessage("AddEntryToolTip"); 307 } 308 309 private final static Logger log = LoggerFactory.getLogger(XBeeSensorManager.class); 310 311}