001package jmri.implementation;
002
003import jmri.ProgListener;
004import jmri.Programmer;
005import jmri.jmrix.AbstractProgrammerFacade;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009/**
010 * Programmer facade, at this point just an example.
011 * <p>
012 * This is for decoders that have an alternate high-CV access method for command
013 * stations that can't address all 1024. It falls back to that mode if the CS
014 * can't directly address an requested CV address. In the fall back, CVs from 0
015 * to "top" are addressed directly. (Top being a supplied parameter) Above the
016 * top CV, the upper part of the CV address written to a specific CV, followed
017 * by an write with just the lower part to a second CV, then access to a 3rd CV
018 * for the value read/write. The upper and lower parts are calculated using a
019 * supplied modulus, e.g. 100.
020 * <p>
021 * This method is used by some ESU decoders.
022 *
023 * @see jmri.implementation.ProgrammerFacadeSelector
024 *
025 * @author Bob Jacobsen Copyright (C) 2013
026 * @author Andrew Crosland Copyright (C) 2021
027 */
028public class AddressedHighCvProgrammerFacade extends AbstractProgrammerFacade implements ProgListener {
029
030    /**
031     * @param prog       the programmer associated with this facade
032     * @param top        CVs above this use the indirect method
033     * @param addrCVhigh CV to which the high part of address is to be written
034     * @param addrCVlow  CV to which the low part of address is to be written
035     * @param valueCV    Value read/written here once address has been written
036     * @param modulo     Modulus for determining high/low address parts
037     */
038    public AddressedHighCvProgrammerFacade(Programmer prog, String top, String addrCVhigh, String addrCVlow, String valueCV, String modulo) {
039        super(prog);
040        this.top = Integer.parseInt(top);
041        this.addrCVhigh = addrCVhigh;
042        this.addrCVlow = addrCVlow;
043        this.valueCV = valueCV;
044        this.modulo = Integer.parseInt(modulo);
045        _prog = prog;
046        log.debug("Created with {}, {}, {}, {}, {}, {}", prog, this.top, this.addrCVhigh, this.addrCVlow, this.valueCV, this.modulo);
047    }
048
049    int top;
050    String addrCVhigh;
051    String addrCVlow;
052    String valueCV;
053    int modulo;
054    Programmer _prog;
055
056    // members for handling the programmer interface
057    int _val; // remember the value being read/written for confirmative reply
058    int _cv; // remember the cv being read/written
059    int _startVal; // remember the starting value (hint)
060
061    // programming interface
062    @Override
063    public void writeCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
064        log.debug("start writeCV");
065        _cv = Integer.parseInt(CV);
066        _val = val;
067        useProgrammer(p);
068        if (prog.getCanWrite(CV) || _cv <= top) {
069            state = ProgState.PROGRAMMING;
070            prog.writeCV(CV, val, this);
071        } else {
072            // write index first
073            state = ProgState.WRITELOWWRITE;
074            prog.writeCV(addrCVhigh, _cv / modulo, this);
075        }
076    }
077
078    @Override
079    public void readCV(String CV, jmri.ProgListener p) throws jmri.ProgrammerException {
080        readCV(CV, p, 0);
081    }
082
083    @Override
084    public void readCV(String CV, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException {
085        log.debug("start readCV");
086        _cv = Integer.parseInt(CV);
087        _startVal = startVal;
088        useProgrammer(p);
089        if (prog.getCanRead(CV) || _cv <= top) {
090            state = ProgState.PROGRAMMING;
091            prog.readCV(CV, this, startVal);
092        } else {
093            // write index first
094            state = ProgState.WRITELOWREAD;
095            prog.writeCV(addrCVhigh, _cv / modulo, this);
096        }
097    }
098
099    private jmri.ProgListener _usingProgrammer = null;
100
101    // internal method to remember who's using the programmer
102    protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
103        // test for only one!
104        if (_usingProgrammer != null && _usingProgrammer != p) {
105            log.info("programmer already in use by {}", _usingProgrammer);
106            throw new jmri.ProgrammerException("programmer in use");
107        } else {
108            _usingProgrammer = p;
109            return;
110        }
111    }
112
113    enum ProgState {
114        /**
115         * A pass-through operation, waiting reply, when done the entire
116         * operation is done
117         */
118        PROGRAMMING,
119        /**
120         * Wrote 1st index on a read operation, waiting for reply
121         */
122        WRITELOWREAD,
123        /**
124         * Wrote 1st index on a write operation, waiting for reply
125         */
126        WRITELOWWRITE,
127        /**
128         * Wrote 2nd index on a read operation, waiting for reply
129         */
130        FINISHREAD,
131        /**
132         * Wrote 2nd index on a write operation, waiting for reply
133         */
134        FINISHWRITE,
135        /**
136         * nothing happening, no reply expected
137         */
138        NOTPROGRAMMING
139    }
140    ProgState state = ProgState.NOTPROGRAMMING;
141
142    // get notified of the final result
143    // Note this assumes that there's only one phase to the operation
144    @Override
145    public void programmingOpReply(int value, int status) {
146        if (log.isDebugEnabled()) {
147            log.debug("notifyProgListenerEnd value {} status {}", value, status);
148        }
149
150        if (status != OK) {
151            // pass abort up
152            log.debug("Reset and pass abort up");
153            jmri.ProgListener temp = _usingProgrammer;
154            _usingProgrammer = null; // done
155            state = ProgState.NOTPROGRAMMING;
156            temp.programmingOpReply(value, status);
157            return;
158        }
159
160        if (_usingProgrammer == null) {
161            log.error("No listener to notify, reset and ignore");
162            state = ProgState.NOTPROGRAMMING;
163            return;
164        }
165
166        switch (state) {
167            case PROGRAMMING:
168                // the programmingOpReply handler might send an immediate reply, so
169                // clear the current listener _first_
170                jmri.ProgListener temp = _usingProgrammer;
171                _usingProgrammer = null; // done
172                state = ProgState.NOTPROGRAMMING;
173                temp.programmingOpReply(value, status);
174                break;
175            case WRITELOWREAD:
176                try {
177                    state = ProgState.FINISHREAD;
178                    prog.writeCV(addrCVlow, _cv % modulo, this);
179                } catch (jmri.ProgrammerException e) {
180                    log.error("Exception doing final read", e);
181                }
182                break;
183            case WRITELOWWRITE:
184                try {
185                    state = ProgState.FINISHWRITE;
186                    prog.writeCV(addrCVlow, _cv % modulo, this);
187                } catch (jmri.ProgrammerException e) {
188                    log.error("Exception doing final write", e);
189                }
190                break;
191            case FINISHREAD:
192                try {
193                    state = ProgState.PROGRAMMING;
194                    prog.readCV(valueCV, this, _startVal);
195                } catch (jmri.ProgrammerException e) {
196                    log.error("Exception doing final read", e);
197                }
198                break;
199            case FINISHWRITE:
200                try {
201                    state = ProgState.PROGRAMMING;
202                    prog.writeCV(valueCV, _val, this);
203                } catch (jmri.ProgrammerException e) {
204                    log.error("Exception doing final write", e);
205                }
206                break;
207            default:
208                log.error("Unexpected state on reply: {}", state);
209                // clean up as much as possible
210                _usingProgrammer = null;
211                state = ProgState.NOTPROGRAMMING;
212
213        }
214
215    }
216
217    // Access to full address space provided by this.
218    @Override
219    public boolean getCanRead() {
220        return _prog.getCanRead();
221    }
222
223    @Override
224    public boolean getCanRead(String addr) {
225        return _prog.getCanRead() && (Integer.parseInt(addr) <= 1024);
226    }
227
228    @Override
229    public boolean getCanWrite() {
230        return _prog.getCanWrite();
231    }
232
233    @Override
234    public boolean getCanWrite(String addr) {
235        return _prog.getCanWrite() && (Integer.parseInt(addr) <= 1024);
236    }
237
238    private final static Logger log = LoggerFactory.getLogger(AddressedHighCvProgrammerFacade.class);
239
240}