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