001package jmri.jmrix;
002
003import java.util.List;
004import javax.annotation.Nonnull;
005import jmri.ProgListener;
006import jmri.Programmer;
007import jmri.ProgrammerException;
008import jmri.ProgrammingMode;
009import jmri.beans.PropertyChangeSupport;
010
011/**
012 * Common implementations for the Programmer interface.
013 * <p>
014 * Contains two time-out handlers:
015 * <ul>
016 * <li> SHORT_TIMEOUT, the "short timer", is on operations other than reads
017 * <li> LONG_TIMEOUT, the "long timer", is for the "read from decoder" step, which can take a long time.
018 * </ul>
019 * The duration of these can be adjusted by changing the values of those constants in subclasses.
020 *
021 * @author Bob Jacobsen Copyright (C) 2001, 2012, 2013
022 */
023public abstract class AbstractProgrammer extends PropertyChangeSupport implements Programmer {
024
025    /** {@inheritDoc} */
026    @Override
027    @Nonnull
028    public String decodeErrorCode(int code) {
029        if (code == ProgListener.OK) {
030            return Bundle.getMessage("StatusOK");
031        }
032        StringBuilder sbuf = new StringBuilder();
033        // add each code; terminate each string with ";" please.
034        if ((code & ProgListener.NoLocoDetected) != 0) {
035            sbuf.append(Bundle.getMessage("NoLocoDetected")).append(" ");
036        }
037        if ((code & ProgListener.ProgrammerBusy) != 0) {
038            sbuf.append(Bundle.getMessage("ProgrammerBusy")).append(" ");
039        }
040        if ((code & ProgListener.NotImplemented) != 0) {
041            sbuf.append(Bundle.getMessage("NotImplemented")).append(" ");
042        }
043        if ((code & ProgListener.UserAborted) != 0) {
044            sbuf.append(Bundle.getMessage("UserAborted")).append(" ");
045        }
046        if ((code & ProgListener.ConfirmFailed) != 0) {
047            sbuf.append(Bundle.getMessage("ConfirmFailed")).append(" ");
048        }
049        if ((code & ProgListener.FailedTimeout) != 0) {
050            sbuf.append(Bundle.getMessage("FailedTimeout")).append(" ");
051        }
052        if ((code & ProgListener.UnknownError) != 0) {
053            sbuf.append(Bundle.getMessage("UnknownError")).append(" ");
054        }
055        if ((code & ProgListener.NoAck) != 0) {
056            sbuf.append(Bundle.getMessage("NoAck")).append(" ");
057        }
058        if ((code & ProgListener.ProgrammingShort) != 0) {
059            sbuf.append(Bundle.getMessage("ProgrammingShort")).append(" ");
060        }
061        if ((code & ProgListener.SequenceError) != 0) {
062            sbuf.append(Bundle.getMessage("SequenceError")).append(" ");
063        }
064        if ((code & ProgListener.CommError) != 0) {
065            sbuf.append(Bundle.getMessage("CommError")).append(" ");
066        }
067
068        // remove trailing separators
069        if (sbuf.length() > 2) {
070            sbuf.setLength(sbuf.length() - 2);
071        }
072
073        String retval = sbuf.toString();
074        if (retval.isEmpty()) {
075            return "unknown status code: " + code;
076        } else {
077            return retval;
078        }
079    }
080
081    /** {@inheritDoc} */
082    @Override
083    abstract public void writeCV(String CV, int val, ProgListener p) throws ProgrammerException;
084
085    /** {@inheritDoc} */
086    @Override
087    abstract public void readCV(String CV, ProgListener p) throws ProgrammerException;
088
089    /** {@inheritDoc} */
090    @Override
091    abstract public void confirmCV(String CV, int val, ProgListener p) throws ProgrammerException;
092
093
094    /** {@inheritDoc}
095     * Basic implementation. Override this to turn reading on and off globally.
096     */
097    @Override
098    public boolean getCanRead() {
099        return true;
100    }
101
102    /** {@inheritDoc}
103     * Checks using the current default programming mode
104     */
105    @Override
106    public boolean getCanRead(String addr) {
107        if (!getCanRead()) {
108            return false; // check basic implementation first
109        }
110        return Integer.parseInt(addr) <= 1024;
111    }
112
113    // handle mode
114    private ProgrammingMode mode = null;
115
116    /** {@inheritDoc} */
117    @Override
118    public final void setMode(ProgrammingMode m) {
119        List<ProgrammingMode> validModes = getSupportedModes();
120
121        if (m == null) {
122            if (!validModes.isEmpty()) {
123                // null can only be set if there are no valid modes
124                throw new IllegalArgumentException("Cannot set null mode when modes are present");
125            } else {
126                mode = null;
127            }
128        }
129
130        if (validModes.contains(m)) {
131            ProgrammingMode oldMode = mode;
132            mode = m;
133            firePropertyChange("Mode", oldMode, m);
134        } else {
135            throw new IllegalArgumentException("Invalid requested mode: " + m);
136        }
137    }
138
139    /**
140     * Define the "best" programming mode, which provides the initial setting.
141     * <p>
142     * The definition of "best" is up to the specific-system developer.
143     * By default, this is the first of the available methods from getSupportedModes;
144     * override this method to change that.
145     *
146     * @return The recommended ProgrammingMode or null if none exists or is defined.
147     */
148    public ProgrammingMode getBestMode() {
149        if (!getSupportedModes().isEmpty()) {
150            return getSupportedModes().get(0);
151        }
152        return null;
153    }
154
155    /** {@inheritDoc} */
156    @Override
157    public final ProgrammingMode getMode() {
158        if (mode == null) {
159            mode = getBestMode();
160        }
161        return mode;
162    }
163
164    @Override
165    @Nonnull
166    abstract public List<ProgrammingMode> getSupportedModes();
167
168    /** {@inheritDoc}
169     * Basic implementation. Override this to turn writing on and off globally.
170     */
171    @Override
172    public boolean getCanWrite() {
173        return true;
174    }
175
176    /** {@inheritDoc}
177     * Checks using the current default programming mode.
178     */
179    @Override
180    public boolean getCanWrite(String addr) {
181        return getCanWrite();
182    }
183
184    /** {@inheritDoc}
185     * By default, say that no verification is done.
186     *
187     * @param addr A CV address to check (in case this varies with CV range) or null for any
188     * @return Always WriteConfirmMode.NotVerified
189     */
190    @Nonnull
191    @Override
192    public Programmer.WriteConfirmMode getWriteConfirmMode(String addr) { return WriteConfirmMode.NotVerified; }
193
194
195    /**
196     * Internal routine to start timer to protect the mode-change.
197     */
198    protected void startShortTimer() {
199        log.debug("startShortTimer");
200        restartTimer(SHORT_TIMEOUT);
201    }
202
203    /**
204     * Internal routine to restart timer with a long delay
205     */
206    protected void startLongTimer() {
207        log.debug("startLongTimer");
208        restartTimer(LONG_TIMEOUT);
209    }
210
211    /**
212     * Internal routine to stop timer, as all is well
213     */
214    protected synchronized void stopTimer() {
215        log.debug("stop timer");
216        if (timer != null) {
217            timer.stop();
218        }
219    }
220
221    /**
222     * Internal routine to handle timer starts and restarts.
223     *
224     * @param delay the initial delay, in milliseconds
225     */
226    protected synchronized void restartTimer(int delay) {
227        log.debug("(re)start timer with delay {}", delay);
228
229        if (timer == null) {
230            timer = new javax.swing.Timer(delay, e -> timeout());
231        }
232        timer.stop();
233        timer.setInitialDelay(delay);
234        timer.setRepeats(false);
235        timer.start();
236    }
237
238    /**
239     * Find the register number that corresponds to a specific CV number.
240     *
241     * @param cv CV number (1 through 512) for which equivalent register is
242     *           desired
243     * @throws ProgrammerException if the requested CV does not correspond to a
244     *                             register
245     * @return register number corresponding to cv
246     */
247    public int registerFromCV(int cv) throws ProgrammerException {
248        if (cv <= 4) {
249            return cv;
250        }
251        switch (cv) {
252            case 29:
253                return 5;
254            case 7:
255                return 7;
256            case 8:
257                return 8;
258            default:
259                log.warn("Unhandled register from cv: {}", cv);
260                break;
261        }
262        throw new ProgrammerException();
263    }
264
265    /**
266     * Internal routine to handle a timeout, should be synchronized!
267     */
268    abstract protected void timeout();
269
270    protected int SHORT_TIMEOUT = 2000;
271    protected int LONG_TIMEOUT = 60000;
272
273    javax.swing.Timer timer = null;
274
275    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractProgrammer.class);
276
277}