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}