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