001package jmri.jmrix.maple;
002
003import jmri.JmriException;
004import jmri.Sensor;
005import org.slf4j.Logger;
006import org.slf4j.LoggerFactory;
007
008/**
009 * Utility Class supporting input from Maple HMI's
010 * <p>
011 * All of the Maple HMI panels are configured with the same input bits. As each
012 * HMI is polled, the results are ORed together in an input array that is
013 * initialized to all 0 when a polling cycle is initiated. That way, if a bit is
014 * 1 in any of the HMI's, it will be 1 in the input array at the end of the
015 * polling cycle. At the end of each polling cycle, each input bit is compared
016 * to the input bit from the last polling cycle. If a bit has changed, or if
017 * this is the first polling cycle, the correspnding Sensor state is updated. No
018 * updating occurs if all panels timed out. Serial systems with unique input
019 * bits for each node keep their input array in each node. That code has been
020 * moved to this utility class for Maple Systems because all nodes share the
021 * same set of input bits. Coil bits within Maple Systems HMI's are divided into
022 * input (1-1000) and output (1001-9000), so input bits are read starting from
023 * HMI address 1, and output bits are written starting at HMI address 1001.
024 *
025 * @author Dave Duchamp, Copyright (C) 2009
026 */
027public class InputBits {
028
029    private SerialTrafficController tc = null;
030
031    public InputBits(SerialTrafficController _tc) {
032        // clear the Sensor arrays
033        for (int i = 0; i < MAXSENSORS + 1; i++) {
034            sensorArray[i] = null;
035            sensorLastSetting[i] = Sensor.UNKNOWN;
036            sensorTempSetting[i] = Sensor.UNKNOWN;
037            sensorORedSetting[i] = false;
038        }
039        tc = _tc;
040    }
041
042    // class constants
043    static final int MAXSENSORS = 1000;
044
045    // operational variables
046    private static int mNumInputBits = 48;     // number of Sensors that are configured
047    private static int mTimeoutTime = 2000;    // timeout time when polling nodes (milliseconds)
048    private int lastUsedSensor = -1; // number of Sensors that are in use - 1 (less than above)
049    protected Sensor[] sensorArray = new Sensor[MAXSENSORS + 1];
050    protected int[] sensorLastSetting = new int[MAXSENSORS + 1];
051    protected int[] sensorTempSetting = new int[MAXSENSORS + 1];
052    protected boolean[] sensorORedSetting = new boolean[MAXSENSORS + 1];
053
054    // access routines
055    public static void setNumInputBits(int n) {
056        mNumInputBits = n;
057    }
058
059    public static int getNumInputBits() {
060        return mNumInputBits;
061    }
062
063    public static void setTimeoutTime(int n) {
064        mTimeoutTime = n;
065    }
066
067    public static int getTimeoutTime() {
068        return mTimeoutTime;
069    }
070
071    public int getLastSensor() {
072        return lastUsedSensor;
073    }
074
075    public int getMaxSensors() {
076        return MAXSENSORS;
077    }
078
079    public void forceSensorsUnknown() {
080        // force sensors to UNKNOWN, including callbacks; might take some time
081        for (int i = 0; i <= lastUsedSensor; i++) {
082            if (sensorArray[i] != null) {
083                sensorLastSetting[i] = Sensor.UNKNOWN;
084                sensorTempSetting[i] = Sensor.UNKNOWN;
085                sensorORedSetting[i] = false;
086                try {
087                    sensorArray[i].setKnownState(Sensor.UNKNOWN);
088                } catch (jmri.JmriException ex) {
089                    log.error("unexpected exception setting sensor i={}", i, ex);
090                }
091            }
092        }
093    }
094
095    /**
096     * Use the contents of the poll reply to mark changes Bits from all the
097     * polled panels are ORed together. So if any panel has the bit on (after
098     * any inversion is provided for), the resulting bit will be on when the
099     * polling cycle is complete.
100     *
101     * @param l Reply to a poll operation
102     */
103    public void markChanges(SerialReply l) {
104        int begAddress = tc.getSavedPollAddress();
105        int count = l.getNumDataElements() - 8;
106        for (int i = 0; i < count; i++) {
107            if (sensorArray[i + begAddress - 1] == null) {
108                continue; // skip ones that don't exist
109            }
110            boolean value = ((l.getElement(5 + i) & 0x01) != 0) ^ sensorArray[i + begAddress - 1].getInverted();
111
112            if (value) {
113                // considered ACTIVE
114                sensorORedSetting[i + begAddress - 1] = true;
115            }
116        }
117    }
118
119    /**
120     * This routine is invoked at the end of each polling cycle to change those
121     * Sensors that have changed during the polling cycle. After making whatever
122     * changes are warranted, this routine clears the array for accumulating a
123     * new polling cycle. Only sensors that are actually defined are updated
124     */
125    public void makeChanges() {
126        // update Sensors according to poll results
127        try {
128            for (int i = 0; i <= lastUsedSensor; i++) {
129                if (sensorArray[i] == null) {
130                    continue; // skip ones that don't exist
131                }
132                boolean value = sensorORedSetting[i];
133
134                if (value) {
135                    // considered ACTIVE
136                    if (((sensorTempSetting[i] == Sensor.ACTIVE)
137                            || (sensorTempSetting[i] == Sensor.UNKNOWN))
138                            && (sensorLastSetting[i] != Sensor.ACTIVE)) { // see comment at top; allows persistent local changes
139                        sensorLastSetting[i] = Sensor.ACTIVE;
140                        sensorArray[i].setKnownState(Sensor.ACTIVE);
141                        // log.debug("set active");
142                    }
143                    // save for next time
144                    sensorTempSetting[i] = Sensor.ACTIVE;
145                } else {
146                    // considered INACTIVE
147                    if (((sensorTempSetting[i] == Sensor.INACTIVE)
148                            || (sensorTempSetting[i] == Sensor.UNKNOWN))
149                            && (sensorLastSetting[i] != Sensor.INACTIVE)) {  // see comment at top; allows persistent local changes
150                        sensorLastSetting[i] = Sensor.INACTIVE;
151                        sensorArray[i].setKnownState(Sensor.INACTIVE);
152                        // log.debug("set inactive");
153                    }
154                    // save for next time
155                    sensorTempSetting[i] = Sensor.INACTIVE;
156                }
157            }
158        } catch (JmriException e) {
159            log.error("exception in makeChanges", e);
160        }
161
162        // clear the accumulation array;
163        for (int i = 0; i < lastUsedSensor; i++) {
164            sensorORedSetting[i] = false;
165        }
166    }
167
168    /**
169     * The numbers here are 0 to MAXSENSORS, not 1 to MAXSENSORS.
170     *
171     * @param s  Sensor object
172     * @param i  0 to MAXSENSORS number of sensor's input bit on this node
173     */
174    public void registerSensor(Sensor s, int i) {
175        // validate the sensor ordinal
176        if ((i < 0) || (i >= mNumInputBits)) {
177            log.error("Unexpected sensor ordinal in registerSensor: {}", Integer.toString(i + 1));
178            return;
179        }
180        if (sensorArray[i] == null) {
181            sensorArray[i] = s;
182            if (lastUsedSensor < i) {
183                lastUsedSensor = i;
184            }
185            sensorLastSetting[i] = Sensor.UNKNOWN;
186            sensorTempSetting[i] = Sensor.UNKNOWN;
187            sensorORedSetting[i] = false;
188        }
189    }
190
191    private final static Logger log = LoggerFactory.getLogger(InputBits.class);
192
193}