001package jmri.jmrix.qsi;
002
003import java.util.ArrayList;
004import java.util.List;
005import javax.annotation.Nonnull;
006
007import jmri.ProgrammingMode;
008import jmri.jmrix.AbstractProgrammer;
009
010/**
011 * Implements the jmri.Programmer interface via commands for the QSI programmer.
012 *
013 * @author Bob Jacobsen Copyright (C) 2001, 2008
014 */
015public class QsiProgrammer extends AbstractProgrammer implements QsiListener {
016
017    private QsiSystemConnectionMemo _memo = null;
018
019    protected QsiProgrammer(QsiSystemConnectionMemo memo) {
020        _memo = memo;
021    }
022
023    /** 
024     * {@inheritDoc}
025     */
026    @Override
027    @Nonnull
028    public List<ProgrammingMode> getSupportedModes() {
029        List<ProgrammingMode> ret = new ArrayList<ProgrammingMode>();
030        ret.add(ProgrammingMode.PAGEMODE);
031        ret.add(ProgrammingMode.DIRECTBITMODE);
032        return ret;
033    }
034
035    // members for handling the programmer interface
036    int progState = 0;
037    static final int NOTPROGRAMMING = 0;  // is notProgramming
038    static final int COMMANDSENT = 2;     // read/write command sent, waiting ack
039    static final int WAITRESULT = 4;      // waiting reply with data
040    static final int WAITRESETSTATUS = 6; // waiting reply from reseting status
041    boolean _progRead = false;
042    int _val; // remember the value being read/written for confirmative reply
043    int _cv;  // remember the cv being read/written
044
045    /** 
046     * {@inheritDoc}
047     */
048    @Override
049    public synchronized void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
050        final int CV = Integer.parseInt(CVname);
051        log.debug("writeCV {} listens {}", CV, p);
052        useProgrammer(p);
053        _progRead = false;
054        // set commandPending state
055        progState = COMMANDSENT;
056        _val = val;
057        _cv = CV;
058
059        // start the error timer
060        startShortTimer();
061
062        // format and send message to do write
063        controller().sendQsiMessage(QsiMessage.getWriteCV(CV, val, getMode()), this);
064    }
065
066    /** 
067     * {@inheritDoc}
068     */
069    @Override
070    public synchronized void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
071        readCV(CV, p);
072    }
073
074    /** 
075     * {@inheritDoc}
076     */
077    @Override
078    public synchronized void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException {
079        final int CV = Integer.parseInt(CVname);
080        log.debug("writeCV {} listens {}", CV, p);
081        useProgrammer(p);
082        _progRead = true;
083        // set commandPending state
084        progState = COMMANDSENT;
085        _cv = CV;
086
087        // start the error timer
088        startShortTimer();
089
090        // format and send message to do read
091        // QSI programer is in program mode by default but this doesn't do any harm
092        controller().sendQsiMessage(QsiMessage.getReadCV(CV, getMode()), this);
093    }
094
095    private jmri.ProgListener _usingProgrammer = null;
096
097    // internal method to remember who's using the programmer
098    protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
099        // test for only one!
100        if (_usingProgrammer != null && _usingProgrammer != p) {
101            log.info("programmer already in use by {}", _usingProgrammer);
102            throw new jmri.ProgrammerException("programmer in use");
103        } else {
104            _usingProgrammer = p;
105            return;
106        }
107    }
108
109    /** 
110     * {@inheritDoc}
111     */
112    @Override
113    public void message(QsiMessage m) {
114        log.error("message received unexpectedly: {}", m);
115    }
116
117    /** 
118     * {@inheritDoc}
119     */
120    @Override
121    synchronized public void reply(QsiReply m) {
122        if (progState == NOTPROGRAMMING) {
123            // we get the complete set of replies now, so ignore these
124            log.debug("reply in NOTPROGRAMMING state");
125            return;
126        } else if (progState == COMMANDSENT) {
127            log.debug("reply in COMMANDSENT state");
128            // operation started, move to next mode
129            progState = WAITRESULT;
130            startLongTimer();
131        } else if (progState == WAITRESULT) {
132            if (log.isDebugEnabled()) {
133                log.debug("reply in WAITRESULT state");
134            }
135            stopTimer();
136            // send QSI ack
137            controller().sendQsiMessage(QsiReply.getAck(m), null);
138            // operation done, capture result, then leave programming mode
139            progState = NOTPROGRAMMING;
140            // check for errors
141            if (m.getElement(4) != 0) {
142                // status present
143                log.debug("handle non-zero status in reply {}", m);
144                // perhaps no loco present? 
145                // reset status
146                progState = WAITRESETSTATUS;
147                startShortTimer();
148                controller().sendQsiMessage(QsiMessage.getClearStatus(), this);
149            } else {
150                // ended OK!
151                if (_progRead == true) {
152                    _val = m.value();
153                }
154                notifyProgListenerEnd(_val, jmri.ProgListener.OK);
155            }
156        } else if (progState == WAITRESETSTATUS) {
157            log.debug("reply in WAITRESETSTATUS state");
158            // all done, notify listeners of completion
159            progState = NOTPROGRAMMING;
160            stopTimer();
161            // notify of default error (not timeout)
162            notifyProgListenerEnd(-1, jmri.ProgListener.NoLocoDetected);
163        } else {
164            log.debug("reply in un-decoded state");
165        }
166    }
167
168    /** 
169     * {@inheritDoc}
170     */
171    @Override
172    synchronized protected void timeout() {
173        if (progState != NOTPROGRAMMING) {
174            // we're programming, time to stop
175            log.debug("timeout!");
176            // perhaps no loco present? Fail back to end of programming
177            progState = NOTPROGRAMMING;
178            // send message to clear error
179            controller().sendQsiMessage(QsiMessage.getClearStatus(), null);
180            // report timeout
181            notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout);
182        }
183    }
184
185    // internal method to notify of the final result
186    protected void notifyProgListenerEnd(int value, int status) {
187        log.debug("notifyProgListenerEnd value {} status {}", value, status);
188        // the programmingOpReply handler might send an immediate reply, so
189        // clear the current listener _first_
190        jmri.ProgListener temp = _usingProgrammer;
191        _usingProgrammer = null;
192        notifyProgListenerEnd(temp, value, status);
193    }
194
195    QsiTrafficController _controller = null;
196
197    protected QsiTrafficController controller() {
198        // connect the first time
199        if (_controller == null) {
200            _controller = _memo.getQsiTrafficController();
201        }
202        return _controller;
203    }
204
205    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(QsiProgrammer.class);
206
207}