001package jmri.implementation;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.util.ArrayList;
005import java.util.List;
006import javax.annotation.Nonnull;
007import jmri.AddressedProgrammer;
008import jmri.CommandStation;
009import jmri.InstanceManager;
010import jmri.NmraPacket;
011import jmri.ProgListener;
012import jmri.Programmer;
013import jmri.ProgrammerException;
014import jmri.ProgrammingMode;
015import jmri.jmrix.AbstractProgrammerFacade;
016import org.slf4j.Logger;
017import org.slf4j.LoggerFactory;
018
019/**
020 * Programmer facade for access to Accessory Decoder Ops Mode programming
021 * <p>
022 * (Eventually implements four modes, passing all others to underlying
023 * programmer:
024 * <ul>
025 * <li>OPSACCBYTEMODE
026 * <li>OPSACCBITMODE
027 * <li>OPSACCEXTBYTEMODE
028 * <li>OPSACCEXTBITMODE
029 * </ul>
030 * <p>
031 * Used through the String write/read/confirm interface. Accepts integers as
032 * addresses, but then emits NMRA DCC packets through the default CommandStation
033 * interface (which must be present)
034 *
035 * @see jmri.implementation.ProgrammerFacadeSelector
036 *
037 * @author Bob Jacobsen Copyright (C) 2014
038 * @author Andrew Crosland Copyright (C) 2021
039 */
040// @ToDo("transform to annotations requires e.g. http://alchemy.grimoire.ca/m2/sites/ca.grimoire/todo-annotations/")
041// @ToDo("get address from underlyng programmer (which might require adding a new subclass structure to Programmer)")
042// @ToDo("finish mode handling; what gets passed through?")
043// @ToDo("read handling needs to be aligned with other ops mode programmers")
044// @ToDo("make sure jmri/jmrit/progsupport/ProgServiceModePane shows the modes, and that DP/DP3 displays them as it configures a decoder")
045public class AccessoryOpsModeProgrammerFacade extends AbstractProgrammerFacade implements ProgListener {
046
047    /**
048     * Programmer facade for access to Accessory Decoder Ops Mode programming.
049     *
050     * @param prog     The (possibly already decorated) programmer we are
051     *                 piggybacking on.
052     * @param addrType A string. "accessory" or "output" causes the address to
053     *                 be interpreted as an 11 bit accessory output address.
054     *                 "decoder" causes the address to be interpreted as a 9 bit
055     *                 accessory decoder address "signal" causes the address to
056     *                 be interpreted as an 11 bit signal decoder address.
057     * @param delay    A string representing the desired delay between
058     *                 programming operations, in milliseconds.
059     * @param baseProg The underlying undecorated Ops Mode Programmer we are
060     *                 piggybacking on.
061     */
062    @SuppressFBWarnings(value = "DM_CONVERT_CASE",
063            justification = "parameter value is never localised")  // NOI18N
064    public AccessoryOpsModeProgrammerFacade(Programmer prog, @Nonnull String addrType, int delay, AddressedProgrammer baseProg) {
065        super(prog);
066        log.debug("Constructing AccessoryOpsModeProgrammerFacade");
067        this._usingProgrammer = null;
068        this.mode = prog.getMode();
069        this.aprog = prog;
070        this._addrType = (addrType == null) ? "" : addrType.toLowerCase(); // NOI18N
071        this._delay = delay;
072        this._baseProg = baseProg;
073    }
074
075    // ops accessory mode can't read locally
076    ProgrammingMode mode;
077
078    @Override
079    @Nonnull
080    public List<ProgrammingMode> getSupportedModes() {
081        List<ProgrammingMode> ret = new ArrayList<>();
082        ret.add(ProgrammingMode.OPSACCBYTEMODE);
083        ret.add(ProgrammingMode.OPSACCBITMODE);
084        ret.add(ProgrammingMode.OPSACCEXTBYTEMODE);
085        ret.add(ProgrammingMode.OPSACCEXTBITMODE);
086        return ret;
087    }
088
089    /**
090     * Don't pass this mode through, as the underlying doesn't have it (although
091     * we should check).
092     *
093     * @param p The desired programming mode
094     */
095    @Override
096    public void setMode(ProgrammingMode p) {
097    }
098
099    Programmer aprog;
100
101    @Override
102    public boolean getCanRead() {
103        return prog.getCanRead();
104    }
105
106    @Override
107    public boolean getCanRead(String addr) {
108        return prog.getCanRead(addr);
109    }
110
111    @Override
112    public boolean getCanWrite() {
113        return prog.getCanWrite();
114    }
115
116    @Override
117    public boolean getCanWrite(String addr) {
118        return prog.getCanWrite(addr);
119    }
120
121    // members for handling the programmer interface
122    int _val;                       // remember the value being read/written for confirmative reply
123    String _cv;                     // remember the cv number being read/written
124    String _addrType;               // remember the address type: ("decoder" or null) or ("accessory" or "output")
125    int _delay;                     // remember the programming delay, in milliseconds
126    AddressedProgrammer _baseProg;   // remember the underlying programmer
127
128    // programming interface
129    @Override
130    public synchronized void writeCV(String cv, int val, ProgListener p) throws ProgrammerException {
131        log.debug("writeCV entry: ProgListener p is {}", p);
132        _val = val;
133        useProgrammer(p);
134        state = ProgState.PROGRAMMING;
135        byte[] b;
136
137        // Send DCC commands to implement prog.writeCV(cv, val, this);
138        switch (_addrType) {
139            case "accessory":
140            case "output":
141                // interpret address as accessory address
142                log.debug("Send an accDecoderPktOpsMode: address={}, cv={}, value={}",
143                        _baseProg.getAddressNumber(), Integer.parseInt(cv), val);
144                b = NmraPacket.accDecoderPktOpsMode(_baseProg.getAddressNumber(), Integer.parseInt(cv), val);
145                break;
146            case "signal":
147                // interpret address as signal address
148                log.debug("Send an accSignalDecoderPktOpsMode: address={}, cv={}, value={}",
149                        _baseProg.getAddressNumber(), Integer.parseInt(cv), val);
150                b = NmraPacket.accSignalDecoderPktOpsMode(_baseProg.getAddressNumber(), Integer.parseInt(cv), val);
151                break;
152            case "altsignal":
153                // interpret address as signal address using the alternative interpretation of S-9.2.1
154                log.debug("Send an altAccSignalDecoderPktOpsMode: address={}, cv={}, value={}",
155                        _baseProg.getAddressNumber(), Integer.parseInt(cv), val);
156                b = NmraPacket.altAccSignalDecoderPktOpsMode(_baseProg.getAddressNumber(), Integer.parseInt(cv), val);
157                break;
158            case "decoder":
159                // interpet address as decoder address
160                log.debug("Send an accDecPktOpsMode: address={}, cv={}, value={}",
161                        _baseProg.getAddressNumber(), Integer.parseInt(cv), val);
162                b = NmraPacket.accDecPktOpsMode(_baseProg.getAddressNumber(), Integer.parseInt(cv), val);
163                break;
164            case "legacy":
165                // interpet address as decoder address and send legacy packet
166                log.debug("Send an accDecPktOpsModeLegacy: address={}, cv={}, value={}",
167                        _baseProg.getAddressNumber(), Integer.parseInt(cv), val);
168                b = NmraPacket.accDecPktOpsModeLegacy(_baseProg.getAddressNumber(), Integer.parseInt(cv), val);
169                break;
170            default:
171                log.error("Unknown Address Type \"{}\"", _addrType);
172                programmingOpReply(val, ProgListener.UnknownError);
173                return;
174        }
175        boolean ret = InstanceManager.getDefault(CommandStation.class).sendPacket(b, 2); // send two packets
176        if (!ret) {
177                log.error("Unable to program cv={}, value={}: Operation not implemented in command station", Integer.parseInt(cv), val);
178                programmingOpReply(val, ProgListener.NotImplemented);
179                return;
180        }
181
182        // set up a delayed completion reply
183        log.debug("delaying {} milliseconds for cv={}, value={}", _delay, Integer.parseInt(cv), val);
184        jmri.util.ThreadingUtil.runOnLayoutDelayed(() -> {
185            log.debug("            delay elapsed for cv={}, value={}", Integer.parseInt(cv), val);
186            programmingOpReply(val, ProgListener.OK);
187        }, _delay);
188    }
189
190    @Override
191    public synchronized void readCV(String cv, jmri.ProgListener p) throws jmri.ProgrammerException {
192        readCV(cv, p, 0);
193    }
194
195    @Override
196    public synchronized void readCV(String cv, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException {
197        useProgrammer(p);
198        state = ProgState.PROGRAMMING;
199        prog.readCV(cv, this, startVal);
200    }
201
202    @Override
203    public synchronized void confirmCV(String cv, int val, ProgListener p) throws ProgrammerException {
204        useProgrammer(p);
205        state = ProgState.PROGRAMMING;
206        prog.confirmCV(cv, val, this);
207    }
208
209    private transient volatile jmri.ProgListener _usingProgrammer;
210
211    /**
212     * Internal method to remember who's using the programmer.
213     *
214     *
215     * @param p the programmer
216     * @throws ProgrammerException if p is already in use
217     */
218    protected synchronized void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
219        // test for only one!
220        log.debug("useProgrammer entry: _usingProgrammer is {}", _usingProgrammer);
221        if (_usingProgrammer != null && _usingProgrammer != p) {
222            if (log.isInfoEnabled()) {
223                log.info("programmer already in use by {}", _usingProgrammer);
224            }
225            throw new jmri.ProgrammerException("programmer in use");
226        } else {
227            _usingProgrammer = p;
228        }
229        log.debug("useProgrammer exit: _usingProgrammer is {}", _usingProgrammer);
230    }
231
232    enum ProgState {
233
234        PROGRAMMING, NOTPROGRAMMING
235    }
236    ProgState state = ProgState.NOTPROGRAMMING;
237
238    // get notified of the final result
239    // Note this assumes that there's only one phase to the operation
240    @Override
241    public synchronized void programmingOpReply(int value, int status) {
242        log.debug("notifyProgListenerEnd value={}, status={}", value, status);
243
244        if (status != OK) {
245            // pass abort up
246            log.debug("Reset and pass abort up");
247            jmri.ProgListener temp = _usingProgrammer;
248            _usingProgrammer = null; // done
249            state = ProgState.NOTPROGRAMMING;
250            temp.programmingOpReply(value, status);
251            return;
252        }
253
254        if (_usingProgrammer == null) {
255            log.error("No listener to notify, reset and ignore");
256            state = ProgState.NOTPROGRAMMING;
257            return;
258        }
259
260        switch (state) {
261            case PROGRAMMING:
262                // the programmingOpReply handler might send an immediate reply, so
263                // clear the current listener _first_
264                log.debug("going NOTPROGRAMMING after value {}, status={}", value, status);
265                jmri.ProgListener temp = _usingProgrammer;
266                _usingProgrammer = null; // done
267                state = ProgState.NOTPROGRAMMING;
268                temp.programmingOpReply(value, status);
269                break;
270            default:
271                log.error("Unexpected state on reply: {}", state);
272                // clean up as much as possible
273                _usingProgrammer = null;
274                state = ProgState.NOTPROGRAMMING;
275
276        }
277    }
278
279    private final static Logger log = LoggerFactory.getLogger(AccessoryOpsModeProgrammerFacade.class);
280
281}