001package jmri.implementation;
002
003import jmri.ProgListener;
004import jmri.Programmer;
005import jmri.ProgrammerException;
006import jmri.jmrix.AbstractProgrammerFacade;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * Programmer facade for access to Accessory Decoder Ops Mode programming
012 * <p>
013 * (Eventually implements four modes, passing all others to underlying
014 * programmer:
015 * <ul>
016 * <li>OPSACCBYTEMODE
017 * <li>OPSACCBITMODE
018 * <li>OPSACCEXTBYTEMODE
019 * <li>OPSACCEXTBITMODE
020 * </ul>
021 * <p>
022 * Used through the String write/read/confirm interface. Accepts integers as
023 * addresses, but then emits NMRA DCC packets through the default CommandStation
024 * interface (which must be present)
025 *
026 * @see jmri.implementation.ProgrammerFacadeSelector
027 *
028 * @author Bob Jacobsen Copyright (C) 2014
029 * @author Andrew Crosland Copyright (C) 2021
030 */
031// @ToDo("transform to annotations requires e.g. http://alchemy.grimoire.ca/m2/sites/ca.grimoire/todo-annotations/")
032// @ToDo("read handling needs to be aligned with other ops mode programmers")
033// @ToDo("make sure jmri/jmrit/progsupport/ProgServiceModePane shows the modes, and that DP/DP3 displays them as it configures a decoder")
034public class OpsModeDelayedProgrammerFacade extends AbstractProgrammerFacade implements ProgListener {
035
036    /**
037     * Programmer facade for access to Accessory Decoder Ops Mode programming.
038     *
039     * @param prog       The Ops Mode Programmer we are piggybacking on.
040     * @param writeDelay A string representing the desired delay after a write
041     *                   operation, in milliseconds.
042     */
043    public OpsModeDelayedProgrammerFacade(Programmer prog, int writeDelay) {
044        super(prog);
045        log.debug("Constructing OpsModeDelayedProgrammerFacade");
046        this._usingProgrammer = null;
047        this.prog = prog;
048        this._readDelay = 0;
049        this._writeDelay = writeDelay;
050    }
051
052    // members for handling the programmer interface
053    int _val;           // remember the value being read/written for confirmative reply
054    String _cv;         // remember the cv number being read/written
055    String _addrType;   // remember the address type: ("decoder" or null) or ("accessory" or "output")
056    int _readDelay;     // remember the programming delay, in milliseconds
057    int _writeDelay;    // remember the programming delay, in milliseconds
058    int _delay;         // remember the programming delay, in milliseconds
059
060    // programming interface
061    @Override
062    public synchronized void writeCV(String cv, int val, ProgListener p) throws ProgrammerException {
063        log.debug("writeCV entry: ProgListener p is {}", p);
064        useProgrammer(p);
065        state = ProgState.WRITECOMMANDSENT;
066        prog.writeCV(cv, val, this);
067    }
068
069    @Override
070    public synchronized void readCV(String cv, jmri.ProgListener p) throws jmri.ProgrammerException {
071        readCV(cv, p, 0);
072    }
073
074    @Override
075    public synchronized void readCV(String cv, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException {
076        useProgrammer(p);
077        state = ProgState.READCOMMANDSENT;
078        prog.readCV(cv, this, startVal);
079    }
080
081    @Override
082    public synchronized void confirmCV(String cv, int val, ProgListener p) throws ProgrammerException {
083        useProgrammer(p);
084        state = ProgState.READCOMMANDSENT;
085        prog.confirmCV(cv, val, this);
086    }
087
088    private transient volatile jmri.ProgListener _usingProgrammer;
089
090    /**
091     * Internal method to remember who's using the programmer.
092     *
093     *
094     * @param p the programmer
095     * @throws ProgrammerException if p is already in use
096     */
097    protected synchronized void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
098        // test for only one!
099        log.debug("useProgrammer entry: _usingProgrammer is {}", _usingProgrammer);
100        if (_usingProgrammer != null && _usingProgrammer != p) {
101            if (log.isInfoEnabled()) {
102                log.info("programmer already in use by {}", _usingProgrammer);
103            }
104            throw new jmri.ProgrammerException("programmer in use");
105        } else {
106            _usingProgrammer = p;
107        }
108        log.debug("useProgrammer exit: _usingProgrammer is {}", _usingProgrammer);
109    }
110
111    enum ProgState {
112
113        READCOMMANDSENT, WRITECOMMANDSENT, NOTPROGRAMMING
114    }
115    ProgState state = ProgState.NOTPROGRAMMING;
116
117    // get notified of the final result
118    // Note this assumes that there's only one phase to the operation
119    @Override
120    public synchronized void programmingOpReply(int value, int status) {
121        log.debug("notifyProgListenerEnd value={}, status={}", value, status);
122
123        if (status != OK) {
124            // pass abort up
125            log.debug("Reset and pass abort up");
126            jmri.ProgListener temp = _usingProgrammer;
127            _usingProgrammer = null; // done
128            state = ProgState.NOTPROGRAMMING;
129            temp.programmingOpReply(value, status);
130            return;
131        }
132
133        if (_usingProgrammer == null) {
134            log.error("No listener to notify, reset and ignore");
135            state = ProgState.NOTPROGRAMMING;
136            return;
137        }
138
139        switch (state) {
140            case READCOMMANDSENT:
141                _delay = _readDelay;
142                break;
143            case WRITECOMMANDSENT:
144                _delay = _writeDelay;
145                break;
146            default:
147                log.error("Unexpected state on reply: {}", state);
148                // clean up as much as possible
149                _usingProgrammer = null;
150                state = ProgState.NOTPROGRAMMING;
151        }
152
153        log.debug("delaying {} milliseconds", _delay);
154        jmri.util.ThreadingUtil.runOnLayoutDelayed(() -> {
155            // the programmingOpReply handler might send an immediate reply, so
156            // clear the current listener _first_
157            log.debug("going NOTPROGRAMMING after value {}, status={}", value, status);
158            jmri.ProgListener temp = _usingProgrammer;
159            _usingProgrammer = null; // done
160            state = ProgState.NOTPROGRAMMING;
161            log.debug("notifying value {} status {}", value, status);
162            temp.programmingOpReply(value, status);
163        }, _delay);
164
165    }
166
167    private final static Logger log = LoggerFactory.getLogger(OpsModeDelayedProgrammerFacade.class);
168
169}