001package jmri.jmrix.srcp;
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 * Implements the jmri.Programmer interface via commands for the SRCP
014 * powerstation
015 *
016 * @author Bob Jacobsen Copyright (C) 2001, 2008
017 */
018public class SRCPProgrammer extends AbstractProgrammer implements SRCPListener {
019
020    protected SRCPBusConnectionMemo _memo = null;
021    private int _bus;
022
023    public SRCPProgrammer(SRCPBusConnectionMemo memo) {
024        _bus = memo.getBus();
025        _memo = memo;
026        // need a longer LONG_TIMEOUT
027        LONG_TIMEOUT = 180000;
028    }
029
030    /** 
031     * {@inheritDoc}
032     *
033     * Types implemented here.
034     */
035    @Override
036    @Nonnull
037    public List<ProgrammingMode> getSupportedModes() {
038        List<ProgrammingMode> ret = new ArrayList<>();
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    boolean _progRead = false;
049    boolean _progConfirm = false;
050    int _confirmVal;  // remember the value to be confirmed for reply
051    int _val; // remember the value being read/written for confirmative reply
052    int _cv;  // remember the cv being read/written
053
054    /** 
055     * {@inheritDoc}
056     */
057    @Override
058    synchronized public void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
059        final int CV = Integer.parseInt(CVname);
060        if (log.isDebugEnabled()) {
061            log.debug("writeCV {} listens {}", CV, p);
062        }
063        useProgrammer(p);
064        _progRead = false;
065        _progConfirm = false;
066        // set commandPending state
067        progState = COMMANDSENT;
068        _val = val;
069        _cv = CV;
070
071        try {
072            SRCPMessage m;
073            // start the error timer
074            startLongTimer();
075
076            // write
077            if (getMode() == ProgrammingMode.DIRECTBYTEMODE) {
078                m = SRCPMessage.getWriteDirectCV(_bus, _cv, _val);
079            } else {
080                m = SRCPMessage.getWriteRegister(_bus, registerFromCV(_cv), _val);
081            }
082            // format and send the write message
083            controller().sendSRCPMessage(m, this);
084        } catch (jmri.ProgrammerException e) {
085            progState = NOTPROGRAMMING;
086            throw e;
087        }
088    }
089
090    /** 
091     * {@inheritDoc}
092     */
093    @Override
094    synchronized public void confirmCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
095        int CV = Integer.parseInt(CVname);
096        if (log.isDebugEnabled()) {
097            log.debug("confirmCV {} val {} listens {}", CV, val, p);
098        }
099        useProgrammer(p);
100        _progRead = false;
101        _progConfirm = true;
102
103        progState = COMMANDSENT;
104        _cv = CV;
105        _confirmVal = val;
106
107        try {
108            SRCPMessage m;
109            // start the error timer
110            startLongTimer();
111
112            if (getMode() == ProgrammingMode.DIRECTBYTEMODE) {
113                m = SRCPMessage.getConfirmDirectCV(_bus, _cv, _confirmVal);
114            } else {
115                m = SRCPMessage.getConfirmRegister(_bus, registerFromCV(_cv), _confirmVal);
116            }
117
118            // format and send the confirm message
119            controller().sendSRCPMessage(m, this);
120        } catch (jmri.ProgrammerException e) {
121            progState = NOTPROGRAMMING;
122            throw e;
123        }
124    }
125
126    /** 
127     * {@inheritDoc}
128     */
129    @Override
130    synchronized public void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException {
131        final int CV = Integer.parseInt(CVname);
132        if (log.isDebugEnabled()) {
133            log.debug("readCV {} listens {}", CV, p);
134        }
135        useProgrammer(p);
136        _progRead = true;
137        _progConfirm = false;
138
139        progState = COMMANDSENT;
140        _cv = CV;
141
142        try {
143            SRCPMessage m;
144            // start the error timer
145            startLongTimer();
146
147            // format and send the write message
148            if (getMode() == ProgrammingMode.DIRECTBYTEMODE) {
149                m = SRCPMessage.getReadDirectCV(_bus, _cv);
150            } else {
151                m = SRCPMessage.getReadRegister(_bus, registerFromCV(_cv));
152            }
153
154            controller().sendSRCPMessage(m, this);
155        } catch (jmri.ProgrammerException e) {
156            progState = NOTPROGRAMMING;
157            throw e;
158        }
159
160    }
161
162    private jmri.ProgListener _usingProgrammer = null;
163
164    // internal method to remember who's using the programmer
165    protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
166        // test for only one!
167        if (_usingProgrammer != null && _usingProgrammer != p) {
168            if (log.isDebugEnabled()) {
169                log.debug("programmer already in use by {}", _usingProgrammer);
170            }
171            throw new jmri.ProgrammerException("programmer in use");
172        } else {
173            _usingProgrammer = p;
174        }
175    }
176
177    /** 
178     * {@inheritDoc}
179     */
180    @Override
181    public void message(SRCPMessage m) {
182        log.error("message received unexpectedly: {}", m.toString());
183    }
184
185    /** 
186     * {@inheritDoc}
187     */
188    @Override
189    synchronized public void reply(SRCPReply m) {
190        if (progState == NOTPROGRAMMING) {
191            // we get the complete set of replies now, so ignore these
192            if (log.isDebugEnabled()) {
193                log.debug("reply in NOTPROGRAMMING state");
194            }
195            if (!m.isResponseOK()) {
196                log.warn("Reply \"{}\"", m.toString());
197            }
198        } else if (progState == COMMANDSENT) {
199            if (log.isDebugEnabled()) {
200                log.debug("reply in COMMANDSENT state");
201            }
202            // operation done, capture result, then have to leave programming mode
203            progState = NOTPROGRAMMING;
204            // check for errors
205            if (!m.isResponseOK()) {
206                if (log.isDebugEnabled()) {
207                    log.debug("handle error reply {}", m);
208                }
209                log.warn("Reply \"{}\"", m.toString());
210                if (_progConfirm && m.getResponseCode().equals("412")) {
211                    // handle the Verify return message "412 ERROR wrong value"
212                    notifyProgListenerEnd(_val, jmri.ProgListener.ConfirmFailed);
213                    return;
214                }
215                // perhaps no loco present? Fail back to end of programming
216                notifyProgListenerEnd(-1, jmri.ProgListener.NoLocoDetected);
217            } else {
218                // see why waiting
219                if (_progRead) {
220                    // read was in progress - get return value
221                    _val = m.value();
222                }
223                if (_progConfirm) {
224                    _val = _confirmVal;
225                }
226                // If this was a read or verify, we retreived the value above. 
227                // If its a write, we're to return the original write value.
228                notifyProgListenerEnd(_val, jmri.ProgListener.OK);
229            }
230        }
231    }
232
233    /** 
234     * {@inheritDoc}
235     */
236    @Override
237    synchronized public void reply(jmri.jmrix.srcp.parser.SimpleNode n) {
238        if (log.isDebugEnabled()) {
239            log.debug("reply called with simpleNode {}", n.jjtGetValue());
240        }
241        if (n.jjtGetChild(3) instanceof jmri.jmrix.srcp.parser.ASTsm) {
242            reply(new SRCPReply(n));
243        }
244    }
245
246    /** 
247     * {@inheritDoc}
248     *
249     * Internal routine to handle a timeout
250     */
251    @Override
252    synchronized protected void timeout() {
253        if (progState != NOTPROGRAMMING) {
254            // we're programming, time to stop
255            if (log.isDebugEnabled()) {
256                log.debug("timeout!");
257            }
258            // perhaps no loco present? Fail back to end of programming
259            progState = NOTPROGRAMMING;
260            cleanup();
261            notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout);
262        }
263    }
264
265    /**
266     * Internal method to send a cleanup message (if needed) on timeout.
267     * <p>
268     * Here, it sends a request to exit from programming mode. But subclasses
269     * may redefine that.
270     */
271    void cleanup() {
272        controller().sendSRCPMessage(SRCPMessage.getExitProgMode(_bus), this);
273    }
274
275    // internal method to notify of the final result
276    protected void notifyProgListenerEnd(int value, int status) {
277        if (log.isDebugEnabled()) {
278            log.debug("notifyProgListenerEnd value {} status {}", value, status);
279        }
280        // the programmingOpReply handler might send an immediate reply, so
281        // clear the current listener _first_
282        jmri.ProgListener temp = _usingProgrammer;
283        _usingProgrammer = null;
284        notifyProgListenerEnd(temp,value,status);
285    }
286
287    SRCPTrafficController _controller = null;
288
289    protected SRCPTrafficController controller() {
290        // connect the first time
291        if (_controller == null) {
292            _controller = _memo.getTrafficController();
293        }
294        return _controller;
295    }
296
297    private final static Logger log = LoggerFactory.getLogger(SRCPProgrammer.class);
298
299}