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. Above the top CV, the upper part of the
016 * address is written to a specific CV, followed by an operation to just the
017 * lower part of the address. The upper and lower parts are calculated using a
018 * supplied modulus, e.g. 100, and an indicator value that's added in. Finally,
019 * the specific CV is reset to zero to end the offset operation.
020 * <p>
021 * For example, to write the value N to CV xyy, this will do (modulo = 100,
022 * indicator = 200):
023 * <ul>
024 * <li>Write 200+x*10 to CV7 where 10 is cvFactor, 200 is indicator and 7 is
025 * addrCV
026 * <li>Write N to CVyy
027 * <li>Write 0 to CV7
028 * </ul>
029 * <p>
030 * This method is used by some Zimo decoders
031 *
032 * @see jmri.implementation.ProgrammerFacadeSelector
033 *
034 * @author Bob Jacobsen Copyright (C) 2013
035 * @author Andrew Crosland Copyright (C) 2021
036 */
037public class ResettingOffsetHighCvProgrammerFacade extends AbstractProgrammerFacade implements ProgListener {
038
039    /**
040     * @param prog      the programmer this facade is attached to
041     * @param top       CVs above this use the indirect method
042     * @param addrCV    CV to which the high part of address is to be written
043     * @param cvFactor  CV to which the low part of address is to be written
044     * @param modulo    modulus for determining high/low address parts
045     * @param indicator value added to calculation to split high and low parts
046     */
047    public ResettingOffsetHighCvProgrammerFacade(Programmer prog, String top, String addrCV, String cvFactor, String modulo, String indicator) {
048        super(prog);
049        this.top = Integer.parseInt(top);
050        this.addrCV = addrCV;
051        this.cvFactor = Integer.parseInt(cvFactor);
052        this.modulo = Integer.parseInt(modulo);
053        this.indicator = Integer.parseInt(indicator);
054    }
055
056    int top;
057    String addrCV;
058    int cvFactor;
059    int modulo;
060    int indicator;
061
062    // members for handling the programmer interface
063    int _val; // remember the value being read/written for confirmative reply
064    int _cv; // remember the cv being read/written
065    int _startVal; // remember the starting value (hint)
066
067    @Override
068    public void writeCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
069        log.debug("start writeCV");
070        _cv = Integer.parseInt(CV);
071        _val = val;
072        useProgrammer(p);
073        if (prog.getCanWrite(CV) || _cv <= top) {
074            state = ProgState.PROGRAMMING;
075            prog.writeCV(CV, val, this);
076        } else {
077            // write index first
078            state = ProgState.FINISHWRITE;
079            prog.writeCV(addrCV, (_cv / modulo) * cvFactor + indicator, this);
080        }
081    }
082
083    @Override
084    public void readCV(String CV, jmri.ProgListener p) throws jmri.ProgrammerException {
085        readCV(CV, p, 0);
086    }
087
088    @Override
089    public void readCV(String CV, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException {
090        log.debug("start readCV");
091        _cv = Integer.parseInt(CV);
092        _startVal = startVal;
093        useProgrammer(p);
094        if (prog.getCanRead(CV) || _cv <= top) {
095            state = ProgState.PROGRAMMING;
096            prog.readCV(CV, this, startVal);
097        } else {
098            // write index first
099            state = ProgState.FINISHREAD;
100            prog.writeCV(addrCV, (_cv / modulo) * cvFactor + indicator, this);
101        }
102    }
103
104    private jmri.ProgListener _usingProgrammer = null;
105
106    // internal method to remember who's using the programmer
107    protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
108        // test for only one!
109        if (_usingProgrammer != null && _usingProgrammer != p) {
110            if (log.isInfoEnabled()) {
111                log.info("programmer already in use by {}", _usingProgrammer);
112            }
113            throw new jmri.ProgrammerException("programmer in use");
114        } else {
115            _usingProgrammer = p;
116        }
117    }
118
119    enum ProgState {
120
121        PROGRAMMING, FINISHREAD, FINISHWRITE, RESET, NOTPROGRAMMING
122    }
123    ProgState state = ProgState.NOTPROGRAMMING;
124
125    // get notified of the final result
126    // Note this assumes that there's only one phase to the operation
127    @Override
128    public void programmingOpReply(int value, int status) {
129        if (log.isDebugEnabled()) {
130            log.debug("notifyProgListenerEnd value {} status {}", value, status);
131        }
132
133        if (status != OK ) {
134            // pass abort up
135            log.debug("Reset and pass abort up");
136            jmri.ProgListener temp = _usingProgrammer;
137            _usingProgrammer = null; // done
138            state = ProgState.NOTPROGRAMMING;
139            temp.programmingOpReply(value, status);
140            return;
141        }
142        
143        if (_usingProgrammer == null) {
144            log.error("No listener to notify, reset and ignore");
145            state = ProgState.NOTPROGRAMMING;
146            return;
147        }
148
149        switch (state) {
150            case FINISHREAD:
151                try {
152                    state = ProgState.RESET;
153                    prog.readCV(""+(_cv % modulo), this, _startVal);
154                } catch (jmri.ProgrammerException e) {
155                    log.error("Exception doing final read", e);
156                }
157                break;
158            case FINISHWRITE:
159                try {
160                    state = ProgState.RESET;
161                    prog.writeCV(""+(_cv % modulo), _val, this);
162                } catch (jmri.ProgrammerException e) {
163                    log.error("Exception doing final write", e);
164                }
165                break;
166            case RESET:
167                try {
168                    state = ProgState.PROGRAMMING;
169                    prog.writeCV(addrCV, 0, this);
170                } catch (jmri.ProgrammerException e) {
171                    log.error("Exception doing reset write", e);
172                }
173                break;
174            case PROGRAMMING:
175                // the programmingOpReply handler might send an immediate reply, so
176                // clear the current listener _first_
177                jmri.ProgListener temp = _usingProgrammer;
178                _usingProgrammer = null; // done
179                state = ProgState.NOTPROGRAMMING;
180                temp.programmingOpReply(value, status);
181                break;
182            default:
183                log.error("Unexpected state on reply: {}", state);
184                // clean up as much as possible
185                _usingProgrammer = null;
186                state = ProgState.NOTPROGRAMMING;
187
188        }
189
190    }
191
192    // Access to full address space provided by this.
193    @Override
194    public boolean getCanRead() {
195        return true;
196    }
197
198    @Override
199    public boolean getCanRead(String addr) {
200        return Integer.parseInt(addr) <= 1024;
201    }
202
203    @Override
204    public boolean getCanWrite() {
205        return true;
206    }
207
208    @Override
209    public boolean getCanWrite(String addr) {
210        return Integer.parseInt(addr) <= 1024;
211    }
212
213    private final static Logger log = LoggerFactory.getLogger(ResettingOffsetHighCvProgrammerFacade.class);
214
215}