001package jmri.jmrix.oaktree; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import jmri.InstanceManager; 005import jmri.JmriException; 006import jmri.Sensor; 007import jmri.jmrix.AbstractMRListener; 008import jmri.jmrix.AbstractMRMessage; 009import jmri.jmrix.AbstractNode; 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013/** 014 * Models a serial node. 015 * <p> 016 * Nodes are numbered ala their address, from 0 to 255. Node number 1 carries 017 * sensors 1 to 999, node 2 carries 1001 to 1999 etc. 018 * <p> 019 * The array of sensor states is used to update sensor known state only when 020 * there's a change on the serial bus. This allows for the sensor state to be 021 * updated within the program, keeping this updated state until the next change 022 * on the serial bus. E.g. you can manually change a state via an icon, and not 023 * have it change back the next time that node is polled. 024 * 025 * @author Bob Jacobsen Copyright (C) 2003, 2006, 2008 026 * @author Bob Jacobsen, Dave Duchamp, multiNode extensions, 2004 027 */ 028public class SerialNode extends AbstractNode { 029 030 /** 031 * Maximum number of sensors a node can carry. 032 * <p> 033 * Note this is less than a current SUSIC motherboard can have, but should 034 * be sufficient for all reasonable layouts. 035 * <p> 036 * Must be less than, and is general one less than, 037 * {@link SerialSensorManager#SENSORSPERNODE} 038 */ 039 static final int MAXSENSORS = 999; 040 041 // class constants 042 // board types 043 public static final int IO24 = 0; // also default 044 public static final int IO48 = 1; 045 public static final int O48 = 2; 046 047 private static final String[] boardNames = new String[]{"IO24", "IO48", "O48"}; // NOI18N 048 049 public static String[] getBoardNames() { 050 return boardNames.clone(); 051 } 052 053 static final int[] outputBytes = new int[]{2, 4, 6}; 054 static final int[] inputBytes = new int[]{1, 2, 0}; 055 056 // node definition instance variables (must persist between runs) 057 protected int nodeType = IO24; // See above 058 059 // operational instance variables (should not be preserved between runs) 060 protected byte[] outputArray = new byte[256]; // current values of the output bits for this node 061 protected boolean[] outputByteChanged = new boolean[256]; 062 063 protected boolean hasActiveSensors = false; // 'true' if there are active Sensors for this node 064 protected int lastUsedSensor = 0; // grows as sensors defined 065 protected Sensor[] sensorArray = new Sensor[MAXSENSORS + 1]; 066 protected int[] sensorLastSetting = new int[MAXSENSORS + 1]; 067 protected int[] sensorTempSetting = new int[MAXSENSORS + 1]; 068 069 OakTreeSystemConnectionMemo _memo = null; 070 071 /** 072 * Create a new SerialNode without a name supplied. 073 * <p> 074 * Assumes a node address of 0, and a node type of 0 (IO24). 075 * If this constructor is used, actual node address must be set using 076 * setNodeAddress, and actual node type using 'setNodeType' 077 * @param memo system connection. 078 */ 079 public SerialNode(OakTreeSystemConnectionMemo memo) { 080 this(0, IO24, memo); 081 } 082 083 /** 084 * Create a new SerialNode and initialize default instance variables 085 * 086 * @param address Address of node on serial bus (0-255). 087 * @param type type constant from the class. 088 * @param memo system connection. 089 */ 090 public SerialNode(int address, int type, OakTreeSystemConnectionMemo memo) { 091 _memo = memo; 092 // set address and type and check validity 093 setNodeAddress(address); 094 setNodeType(type); 095 // set default values for other instance variables 096 // clear the Sensor arrays 097 for (int i = 0; i < MAXSENSORS + 1; i++) { 098 sensorArray[i] = null; 099 sensorLastSetting[i] = Sensor.UNKNOWN; 100 sensorTempSetting[i] = Sensor.UNKNOWN; 101 } 102 // clear all output bits 103 for (int i = 0; i < 256; i++) { 104 outputArray[i] = 0; 105 outputByteChanged[i] = false; 106 } 107 // initialize other operational instance variables 108 setMustSend(); 109 hasActiveSensors = false; 110 // register this node 111 _memo.getTrafficController().registerNode(this); 112 } 113 114 /** 115 * Set an output bit. 116 * 117 * @param bitNumber bit id, numbered from 1 (not 0) 118 * @param state 'true' for 0, 'false' for 1 119 */ 120 public void setOutputBit(int bitNumber, boolean state) { 121 // locate in the outputArray 122 int byteNumber = (bitNumber - 1) / 8; 123 // validate that this byte number is defined 124 if (byteNumber > outputBytes[nodeType]) { // logged only once 125 warn("Output bit out-of-range for defined node: " + bitNumber); 126 } 127 if (byteNumber >= 256) { 128 byteNumber = 255; 129 } 130 // update the byte 131 byte bit = (byte) (1 << ((bitNumber - 1) % 8)); 132 byte oldByte = outputArray[byteNumber]; 133 if (state) { 134 outputArray[byteNumber] &= (~bit); 135 } else { 136 outputArray[byteNumber] |= bit; 137 } 138 // check for change, necessitating a send 139 if (oldByte != outputArray[byteNumber]) { 140 setMustSend(); 141 outputByteChanged[byteNumber] = true; 142 } 143 } 144 145 /** 146 * Get state of Sensors. 147 * 148 * @return 'true' if at least one sensor is active for this node 149 */ 150 @Override 151 public boolean getSensorsActive() { 152 return hasActiveSensors; 153 } 154 155 /** 156 * Reset state of needSend flag. Can only reset if there are no 157 * bytes that need to be sent 158 */ 159 @Override 160 public void resetMustSend() { 161 for (int i = 0; i < outputBytes[nodeType]; i++) { 162 if (outputByteChanged[i]) { 163 return; 164 } 165 } 166 super.resetMustSend(); 167 } 168 169 /** 170 * Get Node type. 171 * <p> 172 * Current types are: IO24, I048, O48. 173 * @return node type. 174 */ 175 public int getNodeType() { 176 return (nodeType); 177 } 178 179 /** 180 * Set Node type. 181 * @param type node type e.g. IO48 , IO24 182 */ 183 @SuppressWarnings("fallthrough") 184 @SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH") 185 public void setNodeType(int type) { 186 nodeType = type; 187 switch (nodeType) { 188 default: 189 log.error("Unexpected nodeType in setNodeType: {}", nodeType); 190 // use IO-48 as default 191 case IO48: 192 case IO24: 193 case O48: 194 break; 195 } 196 } 197 198 /** 199 * Check for valid node address. 200 */ 201 @Override 202 protected boolean checkNodeAddress(int address) { 203 return (address >= 0) && (address < 256); 204 } 205 206 /** 207 * Create an Initialization packet (SerialMessage) for this 208 * node. There are currently no Oak Tree boards that need an init message, 209 * so this returns null. 210 */ 211 @Override 212 public AbstractMRMessage createInitPacket() { 213 return null; 214 } 215 216 /** 217 * Create an Transmit packet (SerialMessage). 218 */ 219 @Override 220 public AbstractMRMessage createOutPacket() { 221 if (log.isDebugEnabled()) { 222 log.debug("createOutPacket for nodeType {} with {} {};{} {};{} {};{} {}.", 223 nodeType, 224 outputByteChanged[0], outputArray[0], 225 outputByteChanged[1], outputArray[1], 226 outputByteChanged[2], outputArray[2], 227 outputByteChanged[3], outputArray[3]); 228 } 229 230 // create a Serial message and add initial bytes 231 SerialMessage m = new SerialMessage(1); 232 m.setElement(0, getNodeAddress()); // node address 233 m.setElement(1, 17); 234 // Add output bytes 235 for (int i = 0; i < outputBytes[nodeType]; i++) { 236 if (outputByteChanged[i]) { 237 outputByteChanged[i] = false; 238 m.setElement(2, i); 239 m.setElement(3, outputArray[i]); 240 return m; 241 } 242 } 243 244 // return result packet for start of card, since need 245 // to do something! 246 m.setElement(2, 0); 247 m.setElement(3, outputArray[0]); 248 return m; 249 } 250 251 boolean warned = false; 252 253 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = "SLF4J_FORMAT_SHOULD_BE_CONST", 254 justification = "only logging 1st warning string passed") 255 void warn(String s) { 256 if (warned) { 257 return; 258 } 259 warned = true; 260 log.warn(s); 261 } 262 263 /** 264 * Use the contents of the poll reply to mark changes. 265 * 266 * @param l Reply to a poll operation 267 */ 268 public void markChanges(SerialReply l) { 269 try { 270 for (int i = 0; i <= lastUsedSensor; i++) { 271 if (sensorArray[i] == null) { 272 continue; // skip ones that don't exist 273 } 274 int loc = i / 8; 275 int bit = i % 8; 276 boolean value = (((l.getElement(loc + 2) >> bit) & 0x01) == 1) ^ sensorArray[i].getInverted(); // byte 2 is first of data 277 log.debug("markChanges loc={} bit={} is {}", loc, bit, value); 278 if (value) { 279 // bit set, considered ACTIVE 280 if (((sensorTempSetting[i] == Sensor.ACTIVE) 281 || (sensorTempSetting[i] == Sensor.UNKNOWN)) 282 && (sensorLastSetting[i] != Sensor.ACTIVE)) { 283 sensorLastSetting[i] = Sensor.ACTIVE; 284 sensorArray[i].setKnownState(Sensor.ACTIVE); 285 } 286 // save for next time 287 sensorTempSetting[i] = Sensor.ACTIVE; 288 } else { 289 // bit reset, considered INACTIVE 290 if (((sensorTempSetting[i] == Sensor.INACTIVE) 291 || (sensorTempSetting[i] == Sensor.UNKNOWN)) 292 && (sensorLastSetting[i] != Sensor.INACTIVE)) { 293 sensorLastSetting[i] = Sensor.INACTIVE; 294 sensorArray[i].setKnownState(Sensor.INACTIVE); 295 } 296 // save for next time 297 sensorTempSetting[i] = Sensor.INACTIVE; 298 } 299 } 300 } catch (JmriException e) { 301 log.error("exception in markChanges", e); 302 } 303 } 304 305 /** 306 * The numbers here are 0 to MAXSENSORS, not 1 to MAXSENSORS. 307 * 308 * @param s sensor object 309 * @param i number of sensor's input bit on this node (0 to MAXSENSORS) 310 */ 311 public void registerSensor(Sensor s, int i) { 312 // validate the sensor ordinal 313 if ((i < 0) || (i > (inputBytes[nodeType] * 8 - 1)) || (i > MAXSENSORS)) { 314 log.error("Unexpected sensor ordinal in registerSensor: {}", Integer.toString(i + 1)); 315 return; 316 } 317 hasActiveSensors = true; 318 if (sensorArray[i] == null) { 319 sensorArray[i] = s; 320 if (lastUsedSensor < i) { 321 lastUsedSensor = i; 322 } 323 } else { 324 // multiple registration of the same sensor 325 log.warn("multiple registration of same sensor: {}S{}", 326 InstanceManager.getDefault(OakTreeSystemConnectionMemo.class).getSystemPrefix(), // multichar prefix 327 Integer.toString((getNodeAddress() * SerialSensorManager.SENSORSPERNODE) + i + 1)); 328 } 329 } 330 331 int timeout = 0; 332 333 /** 334 * {@inheritDoc} 335 */ 336 @Override 337 public boolean handleTimeout(AbstractMRMessage m, AbstractMRListener l) { 338 timeout++; 339 // normal to timeout in response to init, output 340 if (m.getElement(1) != 0x50) { 341 return false; 342 } 343 344 // see how many polls missed 345 log.warn("Timeout to poll for addr={}: consecutive timeouts: {}", getNodeAddress(), timeout); 346 347 if (timeout > 5) { // enough, reinit 348 // reset timeout count to zero to give polls another try 349 timeout = 0; 350 // reset poll and send control so will retry initialization 351 setMustSend(); 352 return true; // tells caller to force init 353 } else { 354 return false; 355 } 356 } 357 358 @Override 359 public void resetTimeout(AbstractMRMessage m) { 360 if (timeout > 0) { 361 log.debug("Reset {} timeout count", timeout); 362 } 363 timeout = 0; 364 } 365 366 private final static Logger log = LoggerFactory.getLogger(SerialNode.class); 367 368}