001package jmri.jmrit;
002
003import jmri.Programmer;
004import jmri.ProgrammingMode;
005import org.slf4j.Logger;
006import org.slf4j.LoggerFactory;
007
008/**
009 * Abstract base for common code of {@link jmri.jmrit.roster.IdentifyLoco} and
010 * {@link jmri.jmrit.decoderdefn.IdentifyDecoder}, the two classes that use a
011 * programmer to match Roster entries to what's on the programming track.
012 * <p>
013 * This is a class (instead of a {@link jmri.jmrit.roster.Roster} member
014 * function) to simplify use of {@link jmri.Programmer} callbacks.
015 *
016 * @author Bob Jacobsen Copyright (C) 2001, 2015
017 * @see jmri.jmrit.symbolicprog.CombinedLocoSelPane
018 * @see jmri.jmrit.symbolicprog.NewLocoSelPane
019 */
020public abstract class AbstractIdentify implements jmri.ProgListener {
021
022    static final int RETRY_COUNT = 2;
023
024    public abstract boolean test1();  // no argument to start
025
026    public abstract boolean test2(int value);
027
028    public abstract boolean test3(int value);
029
030    public abstract boolean test4(int value);
031
032    public abstract boolean test5(int value);
033
034    public abstract boolean test6(int value);
035
036    public abstract boolean test7(int value);
037
038    public abstract boolean test8(int value);
039
040    public abstract boolean test9(int value);
041
042    protected AbstractIdentify(Programmer p) {
043        this.programmer = p;
044    }
045    Programmer programmer;
046    ProgrammingMode savedMode;
047
048    /**
049     * Update the status field (if any). Invoked with "Done" when the results
050     * are in.
051     *
052     * @param status the new status
053     */
054    protected abstract void statusUpdate(String status);
055
056    /**
057     * Start the identification state machine.
058     */
059    public void start() {
060        if (log.isDebugEnabled()) {
061            log.debug("identify begins");
062        }
063        // must be idle, or something quite bad has happened
064        if (state != 0) {
065            log.error("start with state {}, should have been zero", state);
066        }
067
068        if (programmer != null) {
069            savedMode = programmer.getMode(); // In case we need to change modes
070        }
071
072        // The first test is invoked here; the rest are handled in the programmingOpReply callback
073        state = 1;
074        retry = 0;
075        lastValue = -1;
076        setOptionalCv(false);
077        test1();
078    }
079
080    /**
081     * Stop the identification state machine. This also stops the identification
082     * process. Its invoked when a testN returns true; that routine should _not_
083     * have invoked a read or write that will result in a callback.
084     */
085    protected void identifyDone() {
086        if (log.isDebugEnabled()) {
087            log.debug("identifyDone ends in state {}", state);
088        }
089        statusUpdate("Done");
090        state = 0;
091    }
092
093    /**
094     * Internal method to handle the programmer callbacks, e.g. when a CV read
095     * request terminates. Each will reduce (if possible) the list of consistent
096     * decoders, and starts the next step.
097     *
098     * @param value  the value returned
099     * @param status the status reported
100     */
101    @Override
102    public void programmingOpReply(int value, int status) {
103        // we abort if there's no programmer
104        //  (doing this now to simplify later)
105        if (programmer == null) {
106            log.warn("No programmer connected");
107            statusUpdate("No programmer connected");
108
109            state = 0;
110            retry = 0;
111            error();
112            return;
113        }
114        log.debug("Entering programmingOpReply, state {}, isOptionalCv {},retry {}, value {}, status {}", state, isOptionalCv(), retry, value, programmer.decodeErrorCode(status));
115
116        // we check if the status isn't normal
117        if (status != jmri.ProgListener.OK) {
118            if (retry < RETRY_COUNT) {
119                statusUpdate("Programmer error: "
120                        + programmer.decodeErrorCode(status));
121                state--;
122                retry++;
123                value = lastValue;  // Restore the last good value. Needed for retries.
124            } else if (state == 1 && programmer.getMode() != ProgrammingMode.PAGEMODE
125                    && programmer.getSupportedModes().contains(ProgrammingMode.PAGEMODE)) {
126                programmer.setMode(ProgrammingMode.PAGEMODE); // Try paged mode only if test1 (CV8)
127                retry = 0;
128                state--;
129                value = lastValue;  // Restore the last good value. Needed for retries.
130                log.warn("{} readng CV {}, trying {} mode", programmer.decodeErrorCode(status),
131                        cvToRead, programmer.getMode().toString());
132            } else {
133                retry = 0;
134                if (programmer.getMode() != savedMode) {  // restore original mode
135                    log.warn("Restoring {} mode", savedMode.toString());
136                    programmer.setMode(savedMode);
137                }
138                if (isOptionalCv()) {
139                    log.warn("CV {} is optional. Will assume not present...", cvToRead);
140                    statusUpdate("CV " + cvToRead + " is optional. Will assume not present...");
141                } else {
142                    log.warn("Stopping due to error: {}", programmer.decodeErrorCode(status));
143                    statusUpdate("Stopping due to error: "
144                            + programmer.decodeErrorCode(status));
145                    state = 0;
146                    error();
147                    return;
148                }
149            }
150        } else {
151            retry = 0;
152            lastValue = value;  // Save the last good value. Needed for retries.
153            setOptionalCv(false); // successful read clears flag
154        }
155        // continuing for normal operation
156        // this should eventually be something smarter, maybe using reflection,
157        // but for now...
158        log.debug("Was state {}, switching to state {}, test{}, isOptionalCv {},retry {}, value {}, status {}",
159                state, state + 1, state + 1, isOptionalCv(), retry, value, programmer.decodeErrorCode(status));
160        switch (state) {
161            case 0:
162                state = 1;
163                if (test1()) {
164                    identifyDone();
165                }
166                return;
167            case 1:
168                state = 2;
169                if (test2(value)) {
170                    identifyDone();
171                }
172                return;
173            case 2:
174                state = 3;
175                if (test3(value)) {
176                    identifyDone();
177                }
178                return;
179            case 3:
180                state = 4;
181                if (test4(value)) {
182                    identifyDone();
183                }
184                return;
185            case 4:
186                state = 5;
187                if (test5(value)) {
188                    identifyDone();
189                }
190                return;
191            case 5:
192                state = 6;
193                if (test6(value)) {
194                    identifyDone();
195                }
196                return;
197            case 6:
198                state = 7;
199                if (test7(value)) {
200                    identifyDone();
201                }
202                return;
203            case 7:
204                state = 8;
205                if (test8(value)) {
206                    identifyDone();
207                }
208                return;
209            case 8:
210                state = 9;
211                if (test9(value)) {
212                    identifyDone();
213                } else {
214                    log.error("test9 with value = {} returned false, but there is no next step", value);
215                }
216                return;
217            default:
218                // this is an error
219                log.error("unexpected state in normal operation: {} value: {}, ending identification", state, value);
220                identifyDone();
221        }
222    }
223
224    /**
225     * Abstract routine to notify of errors.
226     */
227    protected abstract void error();
228
229    /**
230     * To check if running now.
231     *
232     * @return true if running; false otherwise
233     */
234    public boolean isRunning() {
235        return state != 0;
236    }
237
238    /**
239     * State of the internal sequence.
240     */
241    int state = 0;
242    int retry = 0;
243    int lastValue = 0;
244    boolean optionalCv = false;
245    String cvToRead;
246    String cvToWrite;
247
248    /**
249     * Read a single CV for the next step.
250     *
251     * @param cv the CV to read
252     */
253    protected void readCV(String cv) {
254        if (programmer == null) {
255            statusUpdate("No programmer connected");
256        } else {
257            cvToRead = cv;
258            log.debug("Invoking readCV {}", cvToRead);
259            try {
260                programmer.readCV(cv, this);
261            } catch (jmri.ProgrammerException ex) {
262                statusUpdate("" + ex);
263            }
264        }
265    }
266
267    /**
268     * Write a single CV for the next step.
269     *
270     * @param cv    the CV to write
271     * @param value to write to the CV
272     *
273     */
274    protected void writeCV(String cv, int value) {
275        if (programmer == null) {
276            statusUpdate("No programmer connected");
277        } else {
278            cvToWrite = cv;
279            log.debug("Invoking writeCV {}", cvToWrite);
280            try {
281                programmer.writeCV(cv, value, this);
282            } catch (jmri.ProgrammerException ex) {
283                statusUpdate("" + ex);
284            }
285        }
286    }
287
288    /**
289     * Check the current status of the {@code optionalCv} flag.
290     * <ul>
291     * <li>If {@code true}, prevents the next CV read from aborting the
292     * identification process.</li>
293     * <li>Always {@code false} after a successful read.</li>
294     * </ul>
295     *
296     * @return the current status of the {@code optionalCv} flag
297     */
298    public boolean isOptionalCv() {
299        return optionalCv;
300    }
301
302    /**
303     *
304     * Specify whether the next CV read may legitimately fail in some cases.
305     *
306     * @param flag Set {@code true} to indicate that the next read may fail. A
307     *             successful read will automatically set to {@code false}.
308     */
309    public void setOptionalCv(boolean flag) {
310        this.optionalCv = flag;
311    }
312
313    // initialize logging
314    private final static Logger log = LoggerFactory.getLogger(AbstractIdentify.class);
315
316}