001package jmri.jmrix.tams;
002
003import java.util.ArrayList;
004import java.util.List;
005import javax.annotation.Nonnull;
006
007import jmri.ProgrammingMode;
008import jmri.jmrix.AbstractProgrammer;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012/**
013 * Convert the jmri.Programmer interface into commands for the NCE power house.
014 * <p>
015 * This has two states: NOTPROGRAMMING, and COMMANDSENT. The transitions to and
016 * from programming mode are now handled in the TrafficController code. Based on
017 * work by Bob Jacobsen
018 *
019 * @author Kevin Dickerson Copyright (C) 2012
020 */
021public class TamsProgrammer extends AbstractProgrammer implements TamsListener {
022
023    protected TamsTrafficController tc;
024
025    public TamsProgrammer(TamsTrafficController tc) {
026        this.tc = tc;
027        super.SHORT_TIMEOUT = 6000;
028    }
029
030    /** 
031     * {@inheritDoc}
032     */
033    @Override
034    @Nonnull
035    public List<ProgrammingMode> getSupportedModes() {
036        List<ProgrammingMode> ret = new ArrayList<ProgrammingMode>();
037        ret.add(ProgrammingMode.PAGEMODE);
038        ret.add(ProgrammingMode.DIRECTBITMODE);
039        ret.add(ProgrammingMode.DIRECTBYTEMODE);
040        ret.add(ProgrammingMode.REGISTERMODE);
041        return ret;
042    }
043
044    // members for handling the programmer interface
045    int progState = 0;
046    static final int NOTPROGRAMMING = 0; // is notProgramming
047    static final int COMMANDSENT = 2;    // read/write command sent, waiting reply
048    static final int COMMANDSENT_2 = 4;  // ops programming mode, send msg twice
049    boolean _progRead = false;
050    int _val; // remember the value being read/written for confirmative reply
051    int _cv;  // remember the cv being read/written
052
053    /** 
054     * {@inheritDoc}
055     */
056    @Override
057    public synchronized void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
058        final int CV = Integer.parseInt(CVname);
059        if (log.isDebugEnabled()) {
060            log.debug("writeCV {} listens {}", CV, p);
061        }
062        useProgrammer(p);
063        _progRead = false;
064        // set state
065        progState = COMMANDSENT;
066        _val = val;
067        _cv = CV;
068
069        try {
070            // start the error timer
071            startLongTimer();
072
073            // format and send the write message
074            tc.sendTamsMessage(progTaskStart(_val, _cv), this);
075        } catch (jmri.ProgrammerException e) {
076            useProgrammer(null);
077            progState = NOTPROGRAMMING;
078            throw e;
079        }
080    }
081
082    /** 
083     * {@inheritDoc}
084     */
085    @Override
086    public void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
087        readCV(CV, p);
088    }
089
090    /** 
091     * {@inheritDoc}
092     */
093    @Override
094    public synchronized void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException {
095        final int CV = Integer.parseInt(CVname);
096        if (log.isDebugEnabled()) {
097            log.debug("readCV {} listens {}", CV, p);
098        }
099        useProgrammer(p);
100        _progRead = true;
101
102        // set commandPending state
103        progState = COMMANDSENT;
104        _cv = CV;
105
106        try {
107            // start the error timer
108            startLongTimer();
109
110            // format and send the write message
111            tc.sendTamsMessage(progTaskStart(-1, _cv), this);
112        } catch (jmri.ProgrammerException e) {
113            useProgrammer(null);
114            progState = NOTPROGRAMMING;
115            throw e;
116        }
117    }
118
119    private jmri.ProgListener _usingProgrammer = null;
120
121    // internal method to remember who's using the programmer
122    protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
123        // test for only one!
124        if (_usingProgrammer != null && _usingProgrammer != p) {
125            if (log.isInfoEnabled()) {
126                log.info("programmer already in use by {}", _usingProgrammer);
127            }
128            throw new jmri.ProgrammerException("programmer in use");
129        } else {
130            _usingProgrammer = p;
131            return;
132        }
133    }
134
135    // internal method to create the TamsMessage for programmer task start
136    protected TamsMessage progTaskStart(int val, int cvnum) throws jmri.ProgrammerException {
137        // val = -1 for read command; mode is direct, etc
138        if (val < 0) {
139            // read
140            if (getMode() == ProgrammingMode.PAGEMODE) {
141                return TamsMessage.getReadPagedCV(cvnum);
142            } else if (getMode() == ProgrammingMode.DIRECTBYTEMODE) {
143                return TamsMessage.getReadDirectByteCV(cvnum);
144            } else {
145                return TamsMessage.getReadRegister(registerFromCV(cvnum));
146            }
147        } else {
148            // write
149            if (getMode() == ProgrammingMode.PAGEMODE) {
150                return TamsMessage.getWritePagedCV(cvnum, val);
151            } else if (getMode() == ProgrammingMode.DIRECTBYTEMODE) {
152                return TamsMessage.getWriteDirectByteCV(cvnum, val);
153            } else {
154                return TamsMessage.getWriteRegister(registerFromCV(cvnum), val);
155            }
156        }
157    }
158
159    /** 
160     * {@inheritDoc}
161     */
162    @Override
163    public void message(TamsMessage m) {
164        log.error("message received unexpectedly: {}", m.toString());
165    }
166
167    /** 
168     * {@inheritDoc}
169     */
170    @Override
171    public synchronized void reply(TamsReply m) {
172        if (progState == NOTPROGRAMMING) {
173            // we get the complete set of replies now, so ignore these
174            if (log.isDebugEnabled()) {
175                log.debug("reply in NOTPROGRAMMING state");
176            }
177            return;
178        } else if (progState == COMMANDSENT) {
179            if (log.isDebugEnabled()) {
180                log.debug("reply in COMMANDSENT state");
181            }
182            // operation done, capture result, then post response
183            progState = NOTPROGRAMMING;
184            // check for errors
185            if (m.match("Ok") >= 0) {
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 retrieved the value above.  If its a
192                // write, we're to return the original write value
193                notifyProgListenerEnd(_val, jmri.ProgListener.OK);
194
195            } else if ((m.match("No ack") >= 0)) {
196                if (log.isDebugEnabled()) {
197                    log.debug("handle NO Ack");
198                }
199                // perhaps no loco present? Fail back to end of programming
200                notifyProgListenerEnd(_val, jmri.ProgListener.NoAck);
201            } else if (m.match("Busy") >= 0) {
202                if (log.isDebugEnabled()) {
203                    log.debug("handle Busy");
204                }
205                // perhaps no loco present? Fail back to end of programming
206                notifyProgListenerEnd(_val, jmri.ProgListener.ProgrammerBusy);
207            } else if (m.match("Timeout") >= 0) {
208                if (log.isDebugEnabled()) {
209                    log.debug("handle Timeout");
210                }
211                // perhaps no loco present? Fail back to end of programming
212                notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout);
213            } else if (m.match("Error") >= 0) {
214                if (log.isDebugEnabled()) {
215                    log.debug("handle Other Error");
216                }
217                // perhaps no loco present? Fail back to end of programming
218                notifyProgListenerEnd(_val, jmri.ProgListener.UnknownError);
219            } else {
220                // see why waiting
221                if (_progRead) {
222                    // read was in progress - get return value
223                    _val = m.value();
224                }
225                // if this was a read, we retrieved the value above.  If its a
226                // write, we're to return the original write value
227                notifyProgListenerEnd(_val, jmri.ProgListener.OK);
228
229            }
230
231        } else if (progState == COMMANDSENT_2) {
232            if (log.isDebugEnabled()) {
233                log.debug("first reply in COMMANDSENT_2 state");
234            }
235            // first message sent, now wait for second reply to arrive
236            progState = COMMANDSENT;
237        } else {
238            if (log.isDebugEnabled()) {
239                log.debug("reply in un-decoded state");
240            }
241        }
242    }
243
244    /** 
245     * {@inheritDoc}
246     */
247    @Override
248    protected synchronized void timeout() {
249        if (progState != NOTPROGRAMMING) {
250            // we're programming, time to stop
251            if (log.isDebugEnabled()) {
252                log.debug("timeout!");
253            }
254            // perhaps no loco present? Fail back to end of programming
255            progState = NOTPROGRAMMING;
256            cleanup();
257            notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout);
258        }
259    }
260
261    // Internal method to cleanup in case of a timeout. Separate routine
262    // so it can be changed in subclasses.
263    void cleanup() {
264    }
265
266    // internal method to notify of the final result
267    protected void notifyProgListenerEnd(int value, int status) {
268        if (log.isDebugEnabled()) {
269            log.debug("notifyProgListenerEnd value {} status {}", value, status);
270        }
271        // the programmingOpReply handler might send an immediate reply, so
272        // clear the current listener _first_
273        jmri.ProgListener temp = _usingProgrammer;
274        _usingProgrammer = null;
275        notifyProgListenerEnd(temp, value, status);
276    }
277
278    private final static Logger log = LoggerFactory.getLogger(TamsProgrammer.class);
279
280}