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}