001package jmri.jmrix.easydcc;
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 EasyDCC
012 * powerstation.
013 *
014 * @author Bob Jacobsen Copyright (C) 2001
015 */
016public class EasyDccProgrammer extends AbstractProgrammer implements EasyDccListener {
017
018    public EasyDccProgrammer(EasyDccSystemConnectionMemo memo) {
019        tc = memo.getTrafficController();
020        // need a longer LONG_TIMEOUT
021        LONG_TIMEOUT = 180000;
022    }
023
024    protected EasyDccTrafficController tc = null;
025
026    /** 
027     * {@inheritDoc}
028     */
029    @Override
030    @Nonnull
031    public List<ProgrammingMode> getSupportedModes() {
032        List<ProgrammingMode> ret = new ArrayList<ProgrammingMode>();
033        ret.add(ProgrammingMode.PAGEMODE);
034        ret.add(ProgrammingMode.REGISTERMODE);
035        return ret;
036    }
037
038    // members for handling the programmer interface
039    int progState = 0;
040    static final int NOTPROGRAMMING = 0;// is notProgramming
041    static final int COMMANDSENT = 2;  // read/write command sent, waiting reply
042    boolean _progRead = false;
043    int _val; // remember the value being read/written for confirmative reply
044    int _cv; // remember the cv being read/written
045
046    /** 
047     * {@inheritDoc}
048     */
049    @Override
050    public synchronized void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
051        final int CV = Integer.parseInt(CVname);
052        if (log.isDebugEnabled()) {
053            log.debug("writeCV {} listens {}", CV, p);
054        }
055        useProgrammer(p);
056        _progRead = false;
057        // set commandPending state
058        progState = COMMANDSENT;
059        _val = val;
060        _cv = CV;
061
062        try {
063            // start the error timer
064            startLongTimer();
065
066            // format and send the write message
067            tc.sendEasyDccMessage(progTaskStart(getMode(), _val, _cv), this);
068        } catch (jmri.ProgrammerException e) {
069            progState = NOTPROGRAMMING;
070            throw e;
071        }
072    }
073
074    /** 
075     * {@inheritDoc}
076     */
077    @Override
078    public synchronized void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
079        readCV(CV, p);
080    }
081
082    /** 
083     * {@inheritDoc}
084     */
085    @Override
086    public synchronized void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException {
087        final int CV = Integer.parseInt(CVname);
088        if (log.isDebugEnabled()) {
089            log.debug("readCV {} listens {}", CV, p);
090        }
091        useProgrammer(p);
092        _progRead = true;
093
094        progState = COMMANDSENT;
095        _cv = CV;
096
097        try {
098            // start the error timer
099            startLongTimer();
100
101            // format and send the write message
102            tc.sendEasyDccMessage(progTaskStart(getMode(), -1, _cv), this);
103        } catch (jmri.ProgrammerException e) {
104            progState = NOTPROGRAMMING;
105            throw e;
106        }
107
108    }
109
110    private jmri.ProgListener _usingProgrammer = null;
111
112    // internal method to remember who's using the programmer
113    protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
114        // test for only one!
115        if (_usingProgrammer != null && _usingProgrammer != p) {
116            if (log.isDebugEnabled()) {
117                log.debug("programmer already in use by {}", _usingProgrammer);
118            }
119            throw new jmri.ProgrammerException("programmer in use");
120        } else {
121            _usingProgrammer = p;
122            return;
123        }
124    }
125
126    /**
127     * Internal method to create the EasyDccMessage for programmer task start.
128     * @param mode Programming mode to iniate
129     * @param val Value to program
130     * @param cvnum CV number to address
131     * @return formatted message for layout
132     * @throws jmri.ProgrammerException if programmer or mode not available
133     */
134    protected EasyDccMessage progTaskStart(ProgrammingMode mode, int val, int cvnum) throws jmri.ProgrammerException {
135        // val = -1 for read command; mode is direct, etc
136        if (val < 0) {
137            // read
138            if (getMode().equals(ProgrammingMode.PAGEMODE)) {
139                return EasyDccMessage.getReadPagedCV(cvnum);
140            } else {
141                return EasyDccMessage.getReadRegister(registerFromCV(cvnum));
142            }
143        } else {
144            // write
145            if (getMode().equals(ProgrammingMode.PAGEMODE)) {
146                return EasyDccMessage.getWritePagedCV(cvnum, val);
147            } else {
148                return EasyDccMessage.getWriteRegister(registerFromCV(cvnum), val);
149            }
150        }
151    }
152
153    /** 
154     * {@inheritDoc}
155     */
156    @Override
157    public void message(EasyDccMessage m) {
158        log.error("message received unexpectedly: {}", m.toString());
159    }
160
161    /** 
162     * {@inheritDoc}
163     */
164    @Override
165    synchronized public void reply(EasyDccReply m) {
166        if (progState == NOTPROGRAMMING) {
167            // we get the complete set of replies now, so ignore these
168            if (log.isDebugEnabled()) {
169                log.debug("reply in NOTPROGRAMMING state");
170            }
171            return;
172        } else if (progState == COMMANDSENT) {
173            if (log.isDebugEnabled()) {
174                log.debug("reply in COMMANDSENT state");
175            }
176            // operation done, capture result, then have to leave programming mode
177            progState = NOTPROGRAMMING;
178            // check for errors
179            if (m.match("--") >= 0) {
180                if (log.isDebugEnabled()) {
181                    log.debug("handle error reply {}", m);
182                }
183                // perhaps no loco present? Fail back to end of programming
184                notifyProgListenerEnd(-1, jmri.ProgListener.NoLocoDetected);
185            } else {
186                // see why waiting
187                if (_progRead) {
188                    // read was in progress - get return value
189                    _val = m.value();
190                }
191                // if this was a read, we retreived the value above.  If its a
192                // write, we're to return the original write value
193                notifyProgListenerEnd(_val, jmri.ProgListener.OK);
194            }
195        }
196    }
197
198    /** 
199     * {@inheritDoc}
200     */
201    @Override
202    synchronized protected void timeout() {
203        if (progState != NOTPROGRAMMING) {
204            // we're programming, time to stop
205            if (log.isDebugEnabled()) {
206                log.debug("timeout!");
207            }
208            // perhaps no loco present? Fail back to end of programming
209            progState = NOTPROGRAMMING;
210            cleanup();
211            notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout);
212        }
213    }
214
215    /**
216     * Internal method to send a cleanup message (if needed) on timeout.
217     * <p>
218     * Here, it sends a request to exit from programming mode. But subclasses,
219     * e.g. ops mode, may redefine that.
220     */
221    void cleanup() {
222        tc.sendEasyDccMessage(EasyDccMessage.getExitProgMode(), this);
223    }
224
225    // internal method to notify of the final result
226    protected void notifyProgListenerEnd(int value, int status) {
227        if (log.isDebugEnabled()) {
228            log.debug("notifyProgListenerEnd value {} status {}", value, status);
229        }
230        // the programmingOpReply handler might send an immediate reply, so
231        // clear the current listener _first_
232        jmri.ProgListener temp = _usingProgrammer;
233        _usingProgrammer = null;
234        notifyProgListenerEnd(temp,value,status);
235    }
236
237    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EasyDccProgrammer.class);
238
239}