001package jmri.jmrix.grapevine; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import jmri.JmriException; 005import jmri.Sensor; 006import jmri.jmrix.AbstractMRListener; 007import jmri.jmrix.AbstractMRMessage; 008import jmri.jmrix.AbstractNode; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012/** 013 * Models a serial node. 014 * <p> 015 * Nodes are numbered ala their address, from 0 to 255. Node number 1 carries 016 * sensors 1 to 999, node 2 1001 to 1999 etc. 017 * <p> 018 * The array of sensor states is used to update sensor known state only when 019 * there's a change on the serial bus. This allows for the sensor state to be 020 * updated within the program, keeping this updated state until the next change 021 * on the serial bus. E.g. you can manually change a state via an icon, and not 022 * have it change back the next time that node is polled. 023 * 024 * @author Bob Jacobsen Copyright (C) 2003, 2006, 2007, 2008 025 * @author Bob Jacobsen, Dave Duchamp, multiNode extensions, 2004 026 */ 027public class SerialNode extends AbstractNode { 028 029 /** 030 * Maximum number of sensors a node can carry. 031 * <p> 032 * Note this is less than a current SUSIC motherboard can have, but should 033 * be sufficient for all reasonable layouts. 034 * <p> 035 * Must be less than, and is general one less than, 036 * {@link SerialSensorManager#SENSORSPERNODE} 037 */ 038 static final int MAXSENSORS = 999; 039 040 // class constants 041 // board types 042 public static final int NODE2002V6 = 0; // also default 043 public static final int NODE2002V1 = 1; 044 public static final int NODE2000 = 2; 045 046 static private final String[] boardNames = new String[]{ 047 Bundle.getMessage("BoardName1"), 048 Bundle.getMessage("BoardName2"), 049 Bundle.getMessage("BoardName3")}; 050 051 static public String[] getBoardNames() { 052 return boardNames.clone(); 053 } 054 055 static final int[] outputBits = new int[]{424, 424, 424}; 056 static final int[] inputBits = new int[]{224, 224, 224}; 057 058 // node definition instance variables (must persist between runs) 059 protected int nodeType = NODE2002V6; // See above 060 061 // operational instance variables (should not be preserved between runs) 062 protected byte[] outputArray = new byte[500]; // current values of the output bits for this node 063 protected boolean[] outputByteChanged = new boolean[500]; 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 private SerialTrafficController tc = null; 072 073 /** 074 * Assumes a node address of 1, and a node type of 0 (NODE2002V6). 075 * If this constructor is used, actual node address must be set using 076 * 'setNodeAddress()', and actual node type using 'setNodeType()' 077 * @param tc system connection traffic controller. 078 */ 079 public SerialNode(SerialTrafficController tc) { 080 this(1, tc); 081 } 082 083 public SerialNode(int address, SerialTrafficController tc) { 084 this(address, NODE2002V6, tc); 085 } 086 087 /** 088 * Create a new SerialNode and initialize default instance variables. 089 * 090 * @param address the address of node on serial bus (1-127) 091 * @param type a type constant from the class 092 * @param tc the TrafficController for this connection 093 */ 094 public SerialNode(int address, int type, SerialTrafficController tc) { 095 this.tc = tc; 096 // set address and type and check validity 097 setNodeAddress(address); 098 setNodeType(type); 099 // set default values for other instance variables 100 // clear the Sensor arrays 101 for (int i = 0; i < MAXSENSORS + 1; i++) { 102 sensorArray[i] = null; 103 sensorLastSetting[i] = Sensor.UNKNOWN; 104 sensorTempSetting[i] = Sensor.UNKNOWN; 105 } 106 // clear all output bits 107 for (int i = 0; i < 256; i++) { 108 outputArray[i] = 0; 109 outputByteChanged[i] = false; 110 } 111 // initialize other operational instance variables 112 setMustSend(); 113 hasActiveSensors = false; 114 // register this node 115 tc.registerNode(this); 116 log.debug("new serial node {}", this); 117 } 118 119 /** 120 * Set an output bit on this node. 121 * 122 * @param bitNumber the bit index. Bits are numbered from 1 (not 0) 123 * @param state 'true' for 0, 'false' for 1. 124 */ 125 public void setOutputBit(int bitNumber, boolean state) { 126 // locate in the outputArray 127 int byteNumber = (bitNumber - 1) / 8; 128 // validate that this byte number is defined 129 if (bitNumber > outputBits[nodeType] - 1) { 130 warn("Output bit out-of-range for defined node: " + bitNumber); 131 } 132 if (byteNumber >= 256) { 133 byteNumber = 255; 134 } 135 // update the byte 136 byte bit = (byte) (1 << ((bitNumber - 1) % 8)); 137 byte oldByte = outputArray[byteNumber]; 138 if (state) { 139 outputArray[byteNumber] &= (~bit); 140 } else { 141 outputArray[byteNumber] |= bit; 142 } 143 // check for change, necessitating a send 144 if (oldByte != outputArray[byteNumber]) { 145 setMustSend(); 146 outputByteChanged[byteNumber] = true; 147 } 148 } 149 150 /** 151 * Get state of Sensors. 152 * 153 * @return 'true' if at least one sensor is active for this node 154 */ 155 @Override 156 public boolean getSensorsActive() { 157 return hasActiveSensors; 158 } 159 160 /** 161 * Reset state of needSend flag. Can only reset if there are no 162 * bytes that need to be sent. 163 */ 164 @Override 165 public void resetMustSend() { 166 for (int i = 0; i < (outputBits[nodeType] + 7) / 8; i++) { 167 if (outputByteChanged[i]) { 168 return; 169 } 170 } 171 super.resetMustSend(); 172 } 173 174 /** 175 * Get node type. 176 * @return node type, e.g. NODE2002V1 or NODE2002V6. 177 */ 178 public int getNodeType() { 179 return (nodeType); 180 } 181 182 /** 183 * Set node type. 184 * @param type node type, e.g. NODE2002V1 or NODE2002V6. 185 */ 186 @SuppressWarnings("fallthrough") 187 @SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH") 188 public void setNodeType(int type) { 189 nodeType = type; 190 switch (nodeType) { 191 default: 192 log.error("Unexpected nodeType in setNodeType: {}", nodeType); 193 // use NODE2002V6 as default 194 case NODE2002V6: 195 case NODE2002V1: 196 case NODE2000: 197 break; 198 } 199 } 200 201 /** 202 * Check for valid node address. 203 */ 204 @Override 205 protected boolean checkNodeAddress(int address) { 206 return (address >= 1) && (address <= 127); 207 } 208 209 /** 210 * Create Initialization packets (SerialMessage) for this node. 211 * Initialization consists of multiple parts: 212 * <ul> 213 * <li>Turn on the ASD input 0x71 to bank 0 214 * <li>After a wait, another ASD message 0x73 to bank 0 215 * </ul> 216 * (Eventually, it should also request input values, once we know what 217 * message does that) 218 * <p> 219 * As an Ugly Hack to keep these separate, only the first is put in the 220 * reply from this. The other(s) are sent via the usual output methods. 221 */ 222 @Override 223 public AbstractMRMessage createInitPacket() { 224 225 // first, queue a timer to send 2nd message 226 javax.swing.Timer timer = new javax.swing.Timer(250, null); 227 java.awt.event.ActionListener l = new java.awt.event.ActionListener() { 228 @Override 229 public void actionPerformed(java.awt.event.ActionEvent e) { 230 SerialMessage m2 = new SerialMessage(4); 231 int i = 0; 232 233 // turn on 2nd parallel inputs 234 m2.setElement(i++, getNodeAddress() | 0x80); // address 235 m2.setElement(i++, 0x73); // command 236 m2.setElement(i++, getNodeAddress() | 0x80); // address 237 m2.setElement(i++, 0x00); // bank 0 = init 238 m2.setParity(i - 4); 239 log.debug("Node {} initpacket 2 sent to {} trafficController", getNodeAddress(), 240 tc.getSystemConnectionMemo().getSystemPrefix()); 241 tc.sendSerialMessage(m2, null); 242 } 243 }; 244 timer.addActionListener(l); 245 timer.setRepeats(false); 246 timer.setInitialDelay(250); 247 timer.start(); 248 249 // Now, do the first message, and return it. 250 SerialMessage m1 = new SerialMessage(4); 251 int i = 0; 252 253 // turn on ASD 254 m1.setElement(i++, getNodeAddress() | 0x80); // address 255 m1.setElement(i++, 0x71); // command 256 m1.setElement(i++, getNodeAddress() | 0x80); // address 257 m1.setElement(i++, 0x00); // bank 0 = init 258 m1.setParity(i - 4); 259 log.debug("Node {} initpacket 1 ready to send to {} trafficController", getNodeAddress(), 260 tc.getSystemConnectionMemo().getSystemPrefix()); 261 return m1; 262 } 263 264 /** 265 * Public method to create a Transmit packet (SerialMessage). 266 */ 267 @Override 268 public AbstractMRMessage createOutPacket() { 269 if (log.isDebugEnabled()) { 270 log.debug("createOutPacket for nodeType {} with {} {};{} {};{} {};{} {};", nodeType, outputByteChanged[0], outputArray[0], outputByteChanged[1], outputArray[1], outputByteChanged[2], outputArray[2], outputByteChanged[3], outputArray[3]); 271 } 272 273 // Create a Serial message and add initial bytes 274 SerialMessage m = new SerialMessage(); 275 m.setElement(0, getNodeAddress()); // node address 276 m.setElement(1, 17); 277 // Add output bytes 278 for (int i = 0; i < (outputBits[nodeType] + 7) / 8; i++) { 279 if (outputByteChanged[i]) { 280 outputByteChanged[i] = false; 281 m.setElement(2, i); 282 m.setElement(3, outputArray[i]); 283 return m; 284 } 285 } 286 287 // return result packet for start of card, since need 288 // to do something! 289 m.setElement(2, 0); 290 m.setElement(3, outputArray[0]); 291 return m; 292 } 293 294 boolean warned = false; 295 296 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = "SLF4J_FORMAT_SHOULD_BE_CONST", 297 justification = "only logging 1st warning string passed") 298 void warn(String s) { 299 if (warned) { 300 return; 301 } 302 warned = true; 303 log.warn(s); 304 } 305 306 // Define addressing offsets 307 static final int offsetA = 100; // 'a' advanced occ sensors start at 100+1 308 static final int offsetM = 200; // 'm' advanced movement sensors start at 200+1 309 static final int offsetP = 0; // 'p' parallel sensors start at 200+1 310 static final int offsetS = 20; // 's' serial occupancy sensors start at 200+1 311 312 /** 313 * Use the contents of a reply from the Grapevine to mark changes in the 314 * sensors on the layout. 315 * 316 * @param l Reply to a poll operation 317 */ 318 public void markChanges(SerialReply l) { 319 // first, is it from a sensor? 320 if (!(l.isFromParallelSensor() || l.isFromNewSerialSensor() || l.isFromOldSerialSensor())) { 321 return; // not interesting message 322 } 323 // Yes, continue. 324 // Want to get individual sensor bits, and xor them with the 325 // past state and the inverted bit. 326 327 if (l.isFromNewSerialSensor()) { 328 // Serial sensor has only one bit. Extract value, then address 329 boolean input = ((l.getElement(1) & 0x01) == 0); 330 int card = ((l.getElement(1) & 0x60) >> 5); // number from 0 331 if (card > 2) { 332 log.warn("Did not expect card number {}, message {}", card, l.toString()); 333 } 334 boolean motion = (l.getElement(1) & 0x10) != 0; 335 int number = ((l.getElement(1) & 0x0E) >> 1) + 1; 336 int sensor = card * 8 + (motion ? offsetM : offsetA) + number; 337 // Update 338 markBit(input, sensor); 339 } else if (l.isFromOldSerialSensor()) { 340 // Serial sensor brings in a nibble of four bits 341 int byte1 = l.getElement(1); 342 boolean altPort = ((byte1 & 0x40) != 0); 343 boolean highNibble = ((byte1 & 0x10) != 0); 344 boolean b0 = (byte1 & 0x01) == 0; 345 boolean b1 = (byte1 & 0x02) == 0; 346 boolean b2 = (byte1 & 0x04) == 0; 347 boolean b3 = (byte1 & 0x08) == 0; 348 int number = 1 + (highNibble ? 4 : 0) + (altPort ? 8 : 0) + offsetS; 349 markBit(b0, number); 350 markBit(b1, number + 1); 351 markBit(b2, number + 2); 352 markBit(b3, number + 3); 353 } else { 354 // Parallel sensor brings in a nibble of four bits 355 int byte1 = l.getElement(1); 356 boolean altPort = ((byte1 & 0x40) != 0); 357 boolean highNibble = ((byte1 & 0x10) != 0); 358 boolean b0 = (byte1 & 0x01) == 0; 359 boolean b1 = (byte1 & 0x02) == 0; 360 boolean b2 = (byte1 & 0x04) == 0; 361 boolean b3 = (byte1 & 0x08) == 0; 362 int number = 1 + (highNibble ? 4 : 0) + (altPort ? 8 : 0) + offsetP; 363 markBit(b0, number); 364 markBit(b1, number + 1); 365 markBit(b2, number + 2); 366 markBit(b3, number + 3); 367 } 368 } 369 370 /** 371 * Mark and act on a single input bit. 372 * 373 * @param input true if sensor says active 374 * @param sensorNum from 1 to lastUsedSensor+1 on this node 375 */ 376 void markBit(boolean input, int sensorNum) { 377 log.debug("Mark bit {} {} in node {}", sensorNum, input, getNodeAddress()); 378 if (sensorArray[sensorNum] == null) { 379 log.debug("Try to create sensor {} on node {} since sensor doesn't exist", sensorNum, getNodeAddress()); 380 // try to make the sensor, which will also register it 381 jmri.InstanceManager.sensorManagerInstance() 382 .provideSensor(tc.getSystemConnectionMemo().getSystemPrefix() + "S" + (getNodeAddress() * 1000 + sensorNum)); 383 if (sensorArray[sensorNum] == null) { 384 log.error("Creating sensor {}S{} failed unexpectedly", 385 tc.getSystemConnectionMemo().getSystemPrefix(), 386 (getNodeAddress() * 1000 + sensorNum)); 387 log.debug("node should be {}", this); 388 return; 389 } 390 } 391 392 boolean value = input ^ sensorArray[sensorNum].getInverted(); 393 394 try { 395 if (value) { 396 // bit set, considered ACTIVE 397 if (sensorLastSetting[sensorNum] != Sensor.ACTIVE) { 398 sensorLastSetting[sensorNum] = Sensor.ACTIVE; 399 sensorArray[sensorNum].setKnownState(Sensor.ACTIVE); 400 } 401 } else { 402 // bit reset, considered INACTIVE 403 if (sensorLastSetting[sensorNum] != Sensor.INACTIVE) { 404 sensorLastSetting[sensorNum] = Sensor.INACTIVE; 405 sensorArray[sensorNum].setKnownState(Sensor.INACTIVE); 406 } 407 } 408 } catch (JmriException e) { 409 log.error("exception in markChanges", e); 410 } 411 } 412 413 /** 414 * Register a sensor on a node. 415 * <p> 416 * The numbers here are 0 to MAXSENSORS, not 1 to MAXSENSORS. E.g. the 417 * integer argument is one less than the name of the sensor object. 418 * 419 * @param s Sensor object 420 * @param i bit number corresponding, a 1-based value corresponding to the 421 * low digits of the system name 422 */ 423 public void registerSensor(Sensor s, int i) { 424 log.debug("Register sensor {} index {}", s.getSystemName(), i); 425 // validate the sensor ordinal 426 if ((i < 0) || (i > (inputBits[nodeType])) || (i > MAXSENSORS)) { 427 log.error("Unexpected sensor ordinal in registerSensor: {}", Integer.toString(i)); 428 return; 429 } 430 hasActiveSensors = true; 431 if (sensorArray[i] == null) { 432 sensorArray[i] = s; 433 if (lastUsedSensor < i) { 434 lastUsedSensor = i; 435 } 436 } else { 437 // multiple registration of the same sensor 438 log.warn("multiple registration of same sensor: {}S{}", 439 tc.getSystemConnectionMemo().getSystemPrefix(), 440 (getNodeAddress() * SerialSensorManager.SENSORSPERNODE) + i, 441 new Exception("mult reg " + i + " S:" + s.getSystemName())); 442 } 443 } 444 445 int timeout = 0; 446 447 /** 448 * {@inheritDoc} 449 * 450 * @return true if initialization required 451 */ 452 @Override 453 public boolean handleTimeout(AbstractMRMessage m, AbstractMRListener l) { 454 timeout++; 455 // normal to timeout in response to init, output 456 if (m.getElement(1) != 0x50) { 457 return false; 458 } 459 460 // see how many polls missed 461 if (log.isDebugEnabled()) { 462 log.warn("Timeout to poll for addr = {}: consecutive timeouts: {}", getNodeAddress(), timeout); 463 } 464 465 if (timeout > 5) { // enough, reinit 466 // reset timeout count to zero to give polls another try 467 timeout = 0; 468 // reset poll and send control so will retry initialization 469 setMustSend(); 470 return true; // tells caller to force init 471 } else { 472 return false; 473 } 474 } 475 476 /** 477 * {@inheritDoc} 478 * 479 * @param m GrapevineSerialMessage (ignored) 480 */ 481 @Override 482 public void resetTimeout(AbstractMRMessage m) { 483 if (timeout > 0) { 484 log.debug("Reset {} timeout count", timeout); 485 } 486 timeout = 0; 487 } 488 489 private final static Logger log = LoggerFactory.getLogger(SerialNode.class); 490 491}