001package jmri.jmrix.mrc;
002
003import java.util.ArrayList;
004import java.util.Date;
005import java.util.List;
006import javax.annotation.Nonnull;
007
008import jmri.ProgrammingMode;
009import jmri.jmrix.AbstractProgrammer;
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013/**
014 * Convert the jmri.Programmer interface into commands for the MRC power house.
015 * <p>
016 * This has two states: NOTPROGRAMMING, and COMMANDSENT. The transitions to and
017 * from programming mode are now handled in the TrafficController code.
018 *
019 * @author Bob Jacobsen Copyright (C) 2002
020 * @author Ken Cameron Copyright (C) 2014
021 * @author Kevin Dickerson Copyright (C) 2014
022 */
023public class MrcProgrammer extends AbstractProgrammer implements MrcTrafficListener {
024
025    protected MrcSystemConnectionMemo memo;
026
027    // the traffic controller this programmer is listening to for a reply,
028    // kept so that the listener is removed from the controller it was added
029    // to; null when not listening
030    private MrcTrafficController listeningTo = null;
031
032    public MrcProgrammer(MrcSystemConnectionMemo memo) {
033        this.memo = memo;
034        super.SHORT_TIMEOUT = 15000;
035        super.LONG_TIMEOUT = 700000;
036    }
037
038    int PACKET_TIMEOUT = 5000;
039    int PACKET_READTIMEOUT = 650000;
040
041    /** 
042     * {@inheritDoc}
043     *
044     * Types implemented here.
045     */
046    @Override
047    @Nonnull
048    public List<ProgrammingMode> getSupportedModes() {
049        List<ProgrammingMode> retval = new ArrayList<ProgrammingMode>();
050        retval.add(AUTOMATICMODE);
051        return retval;
052    }
053
054    static final ProgrammingMode AUTOMATICMODE = new ProgrammingMode("Automatic", Bundle.getMessage("MrcAutomaticMode"));
055
056    /** 
057     * {@inheritDoc}
058     */
059    @Override
060    public boolean getCanRead() {
061        return true;
062    }
063
064    /** 
065     * {@inheritDoc}
066     */
067    @Override
068    public boolean getCanWrite() {
069        return true;
070    }
071
072    /** 
073     * {@inheritDoc}
074     * 
075     * CV1 to 1024 valid
076     */
077    @Override
078    public boolean getCanWrite(String cv) {
079        if (Integer.parseInt(cv) > 1024) {
080            return false;
081        }
082        return true;
083    }
084
085    // members for handling the programmer interface
086    int progState = 0;
087    static final int NOTPROGRAMMING = 0;// is notProgramming
088    static final int READCOMMANDSENT = 2;  // read command sent, waiting reply
089    static final int WRITECOMMANDSENT = 4; // POM write command sent 
090    static final int POMCOMMANDSENT = 6; // ops programming mode, send msg twice
091    boolean _progRead = false;
092    int _val; // remember the value being read/written for confirmative reply
093    int _cv; // remember the cv being read/written
094
095    /** 
096     * {@inheritDoc}
097     */
098    @Override
099    public synchronized void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
100        final int CV = Integer.parseInt(CVname);
101        log.debug("writeCV {} listens {}", CV, p); // NOI18N
102        useProgrammer(p);
103        _progRead = false;
104        // set state
105        progState = WRITECOMMANDSENT;
106        _val = val;
107        _cv = CV;
108
109        try {
110            // start the error timer
111            startShortTimer();//we get no confirmation back that the packet has been read.
112            // format and send the write message
113            listenForReply().sendMrcMessage(progTaskStart(getMode(), _val, _cv));
114        } catch (jmri.ProgrammerException e) {
115            progState = NOTPROGRAMMING;
116            throw e;
117        }
118    }
119
120    /** 
121     * {@inheritDoc}
122     */
123    @Override
124    public void confirmCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
125        readCV(CVname, p);
126    }
127
128    /** 
129     * {@inheritDoc}
130     */
131    @Override
132    public synchronized void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException {
133        final int CV = Integer.parseInt(CVname);
134        log.debug("readCV {} listens {}", CV, p); // NOI18N
135        useProgrammer(p);
136        _progRead = true;
137
138        // set commandPending state
139        progState = READCOMMANDSENT;
140        _cv = CV;
141
142        try {
143            // start the error timer
144            startLongTimer();
145
146            // format and send the read message
147            listenForReply().sendMrcMessage(progTaskStart(getMode(), -1, _cv));
148        } catch (jmri.ProgrammerException e) {
149            progState = NOTPROGRAMMING;
150            throw e;
151        }
152    }
153
154    private jmri.ProgListener _usingProgrammer = null;
155
156    // internal method to remember who's using the programmer
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); // NOI18N
162            }
163            throw new jmri.ProgrammerException("programmer in use"); // NOI18N
164        } else {
165            _usingProgrammer = p;
166            return;
167        }
168    }
169
170    // internal method to create the MrcMessage for programmer task start
171    /* todo MRC doesn't set the prog mode the command station sorts it out.*/
172    protected MrcMessage progTaskStart(ProgrammingMode mode, int val, int cvnum) throws jmri.ProgrammerException {
173        // val = -1 for read command; mode is direct, etc
174        MrcMessage m;
175        if (val < 0) {
176            // read
177
178            m = MrcMessage.getReadCV(cvnum);
179        } else {
180            m = MrcMessage.getWriteCV(cvnum, val);
181        }
182        m.setTimeout(PACKET_TIMEOUT);
183        m.setSource(this);
184        return m;
185    }
186
187    /** 
188     * {@inheritDoc}
189     */
190    @Override
191    public synchronized void notifyXmit(Date timestamp, MrcMessage m) {
192    }
193
194    /** 
195     * {@inheritDoc}
196     */
197    @Override
198    public synchronized void notifyFailedXmit(Date timestamp, MrcMessage m) {
199        if (progState == NOTPROGRAMMING && m.getMessageClass() != MrcInterface.PROGRAMMING) {
200            return;
201        }
202        timeout();
203    }
204
205    /** 
206     * {@inheritDoc}
207     */
208    @Override
209    public synchronized void notifyRcv(Date timestamp, MrcMessage m) {
210        //public synchronized void message(MrcMessage m) {
211        if (progState == NOTPROGRAMMING) {
212            // we get the complete set of replies now, so ignore these
213            log.debug("reply in NOTPROGRAMMING state"); // NOI18N
214            return;
215        }
216        if (m.getMessageClass() != MrcInterface.PROGRAMMING) {
217            return;
218        }
219        if (MrcPackets.startsWith(m, MrcPackets.PROGCMDSENT)) {
220            progState = NOTPROGRAMMING;
221            notifyProgListenerEnd(_val, jmri.ProgListener.OK);
222        } else if (MrcPackets.startsWith(m, MrcPackets.READCVHEADERREPLY) && progState == READCOMMANDSENT) {
223            progState = NOTPROGRAMMING;
224            //Currently we have no way to know if the write was sucessful or not.
225            if (_progRead) {
226                log.debug("prog Read {}", _cv);
227                // read was in progress - get return value
228                _val = m.value();
229            }
230            // if this was a read, we retrieved the value above.  If its a
231            // write, we're to return the original write value
232            log.debug("Has value {}", _val); // NOI18N
233            notifyProgListenerEnd(_val, jmri.ProgListener.OK);
234
235        } else {
236            log.debug("reply in un-decoded state cv:{} {}", _cv, m.toString()); // NOI18N
237        }
238    }
239
240    /** 
241     * {@inheritDoc}
242     *
243     * Internal routine to handle a timeout
244     */
245    @Override
246    protected synchronized void timeout() {
247        if (progState != NOTPROGRAMMING) {
248            // we're programming, time to stop
249            log.debug("timeout!{}", _cv);
250            // perhaps no loco present? Fail back to end of programming
251            progState = NOTPROGRAMMING;
252            cleanup();
253            notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout);
254        }
255    }
256
257    // Internal method to cleanup in case of a timeout. Separate routine
258    // so it can be changed in subclasses.
259    void cleanup() {
260    }
261
262    // start listening for the reply to a programming message
263    private MrcTrafficController listenForReply() {
264        listeningTo = memo.getMrcTrafficController();
265        listeningTo.addTrafficListener(MrcInterface.PROGRAMMING, this);
266        return listeningTo;
267    }
268
269    // internal method to notify of the final result
270    protected void notifyProgListenerEnd(int value, int status) {
271        log.debug("notifyProgListenerEnd value {} status {}", value, status); // NOI18N
272        // the programmingOpReply handler might send an immediate reply, so
273        // clear the current listener _first_
274        if (listeningTo != null) {
275            listeningTo.removeTrafficListener(MrcInterface.PROGRAMMING, this);
276            listeningTo = null;
277        }
278        jmri.ProgListener temp = _usingProgrammer;
279        _usingProgrammer = null;
280        notifyProgListenerEnd(temp,value,status);
281    }
282
283    private static final Logger log = LoggerFactory.getLogger(MrcProgrammer.class);
284}