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}