001package jmri.jmrix.sprog;
002
003import java.util.*;
004
005import javax.annotation.Nonnull;
006
007import jmri.*;
008import jmri.jmrix.AbstractProgrammer;
009import jmri.jmrix.sprog.update.*;
010
011/**
012 * Implement the jmri.Programmer interface via commands for the Sprog
013 * programmer. This provides a service mode programmer.
014 *
015 * @author Bob Jacobsen Copyright (C) 2001
016 * @author Andrew Crosland Copyright (C) 2021
017 */
018public class SprogProgrammer extends AbstractProgrammer implements SprogListener, SprogVersionListener {
019
020    private SprogSystemConnectionMemo _memo = null;
021    private SprogVersion _sv = null;
022
023    public SprogProgrammer(SprogSystemConnectionMemo memo) {
024        _memo = memo;
025    }
026
027    /**
028     * {@inheritDoc}
029     *
030     * Implemented Types.
031     */
032    @Override
033    @Nonnull
034    public List<ProgrammingMode> getSupportedModes() {
035        List<ProgrammingMode> ret = new ArrayList<ProgrammingMode>();
036        ret.add(ProgrammingMode.DIRECTBITMODE);
037        ret.add(ProgrammingMode.PAGEMODE);
038        return ret;
039    }
040
041    /**
042     * {@inheritDoc}
043     */
044    @Override
045    public boolean getCanRead() {
046        if (getMode().equals(ProgrammingMode.PAGEMODE)) return true;
047        else if (getMode().equals(ProgrammingMode.DIRECTBITMODE)) return true;
048        else {
049            log.error("Unknown internal mode {} returned true from getCanRead()",getMode());
050            return true;
051        }
052    }
053
054    // members for handling the programmer interface
055    int progState = 0;
056    static final int NOTPROGRAMMING = 0;    // is notProgramming
057    static final int COMMANDSENT = 2;       // read/write command sent, waiting reply
058    int _val; // remember the value being read/written for confirmative reply
059    String _cv;
060    int _startVal;
061    jmri.ProgListener _progListener;
062
063    /**
064     * {@inheritDoc}
065     */
066    @Override
067    synchronized public void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
068        final int CV = Integer.parseInt(CVname);
069        log.debug("writeCV {} mode {} listens {}", CV, getMode(), p);
070        useProgrammer(p);
071        _val = val;
072        startProgramming(_val, CV, 0);
073    }
074
075    /**
076     * {@inheritDoc}
077     */
078    @Override
079    synchronized public void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
080        readCV(CV, p);
081    }
082
083    /**
084     * {@inheritDoc}
085     */
086    @Override
087    synchronized public void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException {
088        readCVWithDefault(CVname, p, 0);
089    }
090
091    /**
092     * {@inheritDoc}
093     */
094    @Override
095    public void readCV(String CVname, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException {
096        _startVal = startVal;
097        if (_sv != null) {
098            if (!_sv.supportsCVHints()) {
099                log.debug("Hardware does not support hints");
100                _startVal = 0;
101            }
102            readCVWithDefault(CVname, p, _startVal);
103        } else {
104            // The SPROG version is not known yet so request the version
105            log.debug("SPROG version is unknown - trying to get it");
106            // save for later
107            _cv = CVname;
108            _progListener = p;
109            _memo.getSprogVersionQuery().requestVersion(this);
110        }
111    }
112
113    /**
114     * Internal method to read a CV with a possible default value
115     *
116     * @param CVname    Index of CV to read
117     * @param p         Programming listener
118     * @param startVal  CV default value, Use 0 if no default available
119     * @throws jmri.ProgrammerException if programming operation fails
120     */
121    synchronized public void readCVWithDefault(String CVname, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException {
122        final int CV = Integer.parseInt(CVname);
123        log.debug("readCV {} mode {} hint {} listens {}", CV, getMode(), startVal, p);
124        useProgrammer(p);
125        _val = -1;
126        startProgramming(_val, CV, startVal);
127    }
128
129    private jmri.ProgListener _usingProgrammer = null;
130
131    /**
132     * Send the command to start programming operation.
133     *
134     * @param val       Value to be written, or -1 for read
135     * @param CV        CV to read/write
136     * @param startVal  Hint of what current CV value may be
137     */
138    private void startProgramming(int val, int CV, int startVal) {
139        // here ready to send the read/write command
140        progState = COMMANDSENT;
141        // see why waiting
142        try {
143            startLongTimer();
144            controller().sendSprogMessage(progTaskStart(getMode(), val, CV, startVal), this);
145        } catch (Exception e) {
146            // program op failed, go straight to end
147            log.error("program operation failed",e);
148            progState = NOTPROGRAMMING;
149        }
150    }
151
152    /**
153     * Internal method to remember who's using the programmer.
154     * @param p Who gets reply
155     * @throws ProgrammerException when programmer in invalid state
156     */
157    protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
158        // test for only one!
159        if (_usingProgrammer != null && _usingProgrammer != p) {
160            if (log.isInfoEnabled()) {
161                log.info("programmer already in use by {}", _usingProgrammer);
162            }
163            throw new jmri.ProgrammerException("programmer in use");
164        } else {
165            _usingProgrammer = p;
166            return;
167        }
168    }
169
170    /**
171     * Internal method to create the SprogMessage for programmer task start.
172     * @param mode Mode to be used
173     * @param val value to be written
174     * @param cvnum CV address to write to
175     * @param startVal Hint of what the CV may contain, or 0
176     * @return formatted message to do programming operation
177     */
178    protected SprogMessage progTaskStart(ProgrammingMode mode, int val, int cvnum, int startVal) {
179        // val = -1 for read command; mode is direct, etc
180        if (val < 0) {
181            if (startVal == 0) {
182                // No hint value, or normal starting value
183                return SprogMessage.getReadCV(cvnum, mode);
184            } else {
185                return SprogMessage.getReadCV(cvnum, mode, startVal);
186            }
187        } else {
188            return SprogMessage.getWriteCV(cvnum, val, mode);
189        }
190    }
191
192    /**
193     * {@inheritDoc}
194     */
195    @Override
196    public void notifyMessage(SprogMessage m) {
197    }
198
199    /**
200     * {@inheritDoc}
201     */
202    @Override
203    synchronized public void notifyReply(SprogReply reply) {
204
205        if (progState == NOTPROGRAMMING) {
206            // we get the complete set of replies now, so ignore these
207            log.debug("reply in NOTPROGRAMMING state [{}]", reply);
208            return;
209        } else if (progState == COMMANDSENT) {
210            log.debug("reply in COMMANDSENT state [{}]", reply);
211            // operation done, capture result, then have to leave programming mode
212            progState = NOTPROGRAMMING;
213            // check for errors
214            if (reply.match("No Ack") >= 0) {
215                log.debug("handle No Ack reply {}", reply);
216                // perhaps no loco present? Fail back to end of programming
217                progState = NOTPROGRAMMING;
218                notifyProgListenerEnd(-1, jmri.ProgListener.NoLocoDetected);
219            } else if (reply.match("!O") >= 0) {
220                log.debug("handle !O reply {}", reply);
221                // Overload. Fail back to end of programming
222                progState = NOTPROGRAMMING;
223                notifyProgListenerEnd(-1, jmri.ProgListener.ProgrammingShort);
224            } else {
225                // see why waiting
226                if (_val == -1) {
227                    // read was in progress - get return value
228                    _val = reply.value();
229                }
230                progState = NOTPROGRAMMING;
231                stopTimer();
232                // if this was a read, we cached the value earlier.  If its a
233                // write, we're to return the original write value
234                notifyProgListenerEnd(_val, jmri.ProgListener.OK);
235            }
236
237            // SPROG always leaves power off after programming so we inform the
238            // power manager of the new state
239            controller().getAdapterMemo().getPowerManager().notePowerState(PowerManager.OFF);
240        } else {
241            log.debug("reply in un-decoded state");
242        }
243    }
244
245    /**
246     * Handle a SprogVersion notification.
247     * <p>
248     * Decode the SPROG version and decode the programming capabilities.
249     *
250     * @param v The SprogVersion being handled
251     */
252    @Override
253    synchronized public void notifyVersion(SprogVersion v) throws jmri.ProgrammerException {
254        // Save it for subsequent operations
255        _sv = v;
256        // Save it for others
257        _memo.setSprogVersion(v);
258        log.debug("Found: {}", v.toString());
259
260        if (!_sv.supportsCVHints()) {
261            log.debug("Hardware does not support hints");
262           _startVal = 0;
263        }
264        readCVWithDefault(_cv, _progListener, _startVal);
265    }
266
267    /**
268     * {@inheritDoc}
269     *
270     * Internal routine to handle a timeout
271     */
272    @Override
273    synchronized protected void timeout() {
274        if (progState != NOTPROGRAMMING) {
275            // we're programming, time to stop
276            log.debug("Timeout in a programming state");
277            // perhaps no loco present? Fail back to end of programming
278            progState = NOTPROGRAMMING;
279            notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout);
280        } else {
281            log.debug("timeout in NOTPROGRAMMING state");
282        }
283    }
284
285    // internal method to notify of the final result
286    protected void notifyProgListenerEnd(int value, int status) {
287        log.debug("notifyProgListenerEnd value {} status {}", value, status);
288        // the programmingOpReply handler might send an immediate reply, so
289        // clear the current listener _first_
290        jmri.ProgListener temp = _usingProgrammer;
291        _usingProgrammer = null;
292        notifyProgListenerEnd(temp, value, status);
293    }
294
295    SprogTrafficController _controller = null;
296
297    protected SprogTrafficController controller() {
298        // connect the first time
299        if (_controller == null) {
300            _controller = _memo.getSprogTrafficController();
301        }
302        return _controller;
303    }
304
305    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SprogProgrammer.class);
306
307}