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