001package jmri.jmrix.dccpp;
002
003import java.util.ArrayList;
004import java.util.List;
005import javax.annotation.Nonnull;
006import jmri.ProgListener;
007import jmri.Programmer;
008import jmri.ProgrammingMode;
009import jmri.jmrix.AbstractProgrammer;
010
011/**
012 * Programmer support for DCC++.
013 * <p>
014 * The read operation state sequence is:
015 * <ul>
016 * <li>Send Register Mode / Paged mode /Direct Mode read request
017 * <li>Wait for results reply, interpret
018 * </ul>
019 *
020 * @author Bob Jacobsen Copyright (c) 2002, 2007
021 * @author Paul Bender Copyright (c) 2003-2010
022 * @author Giorgio Terdina Copyright (c) 2007
023 * @author Mark Underwood Copyright (c) 2015
024 */
025public class DCCppProgrammer extends AbstractProgrammer implements DCCppListener {
026
027    // NOTE: We will embed the command opcode in the CALLBACKSUB field
028    // so that we can tell what type of message the response keys to.
029
030    static protected final int DCCppProgrammerTimeout = 90000;
031
032    // keep track of whether or not the command station is in service 
033    // mode.  Used for determining if "OK" message is an aproriate 
034    // response to a request to a programming request. 
035    protected boolean _service_mode = false;  // TODO: Is this even meaningful for DCC++?
036
037    static protected final int LISTENER_MASK = DCCppInterface.CS_INFO | DCCppInterface.COMMINFO | DCCppInterface.INTERFACE;
038
039    public DCCppProgrammer(@Nonnull DCCppTrafficController tc) {
040        // error if more than one constructed?
041        _controller = tc;
042        init();
043    }
044
045    private void init() {
046        // connect to listen
047        controller().addDCCppListener(LISTENER_MASK, this);
048        setMode(ProgrammingMode.DIRECTBYTEMODE);
049    }
050
051    /** 
052     * {@inheritDoc}
053     */
054    @Override
055    @Nonnull
056    public List<ProgrammingMode> getSupportedModes() {
057        List<ProgrammingMode> ret = new ArrayList<>();
058        //ret.add(ProgrammingMode.PAGEMODE);
059//        ret.add(ProgrammingMode.DIRECTBITMODE);
060        ret.add(ProgrammingMode.DIRECTBYTEMODE);
061        //ret.add(ProgrammingMode.REGISTERMODE);
062        return ret;
063    }
064
065    /** 
066     * {@inheritDoc}
067     *
068     * Can we read from a specific CV in the specified mode? Answer may not be
069     * correct if the command station type and version sent by the command
070     * station mimics one of the known command stations.
071     */
072    @Override
073    public boolean getCanRead(String addr) {
074        if (log.isDebugEnabled()) {
075            log.debug("check mode {} CV {}", getMode(), addr);
076        }
077        if (!getCanRead()) {
078            return false; // check basic implementation first
079        }
080        if (getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) {
081            return Integer.parseInt(addr) <= DCCppConstants.MAX_DIRECT_CV;
082        } else {
083            return Integer.parseInt(addr) <= 256;
084        }
085    }
086
087    /** 
088     * {@inheritDoc}
089     *
090     * Can we write to a specific CV in the specified mode? Answer may not be
091     * correct if the command station type and version sent by the command
092     * station mimics one of the known command stations.
093     */
094    @Override
095    public boolean getCanWrite(String addr) {
096        log.debug("check CV {}", addr);
097        log.debug("cs Type: {} CS Build: {}", controller().getCommandStation().getStationType(), controller().getCommandStation().getBuild());
098        if (!getCanWrite()) {
099            return false; // check basic implementation first
100        }
101        if (getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) {
102            return Integer.parseInt(addr) <= DCCppConstants.MAX_DIRECT_CV;
103        } else {
104            return Integer.parseInt(addr) <= 256;
105        }
106    }
107
108    /** 
109     * {@inheritDoc}
110     */
111    @Nonnull
112    @Override
113    public Programmer.WriteConfirmMode getWriteConfirmMode(String addr) { return WriteConfirmMode.DecoderReply; }
114
115    // members for handling the programmer interface
116    protected int progState = 0;
117    static protected final int NOTPROGRAMMING = 0; // is notProgramming
118    static protected final int REQUESTSENT = 1; // waiting reply to command to go into programming mode
119    static protected final int INQUIRESENT = 2; // read/write command sent, waiting reply
120    protected boolean _progRead = false;
121    protected int _val; // remember the value being read/written for confirmative reply
122    protected int _cv; // remember the cv being read/written
123
124    // programming interface
125
126    /** 
127     * {@inheritDoc}
128     */
129    @Override
130    public synchronized void writeCV(String CVname, int val, ProgListener p) throws jmri.ProgrammerException {
131        final int CV = Integer.parseInt(CVname);
132        if (log.isDebugEnabled()) {
133            log.debug("writeCV {} listens {}", CV, p);
134        }
135        useProgrammer(p);
136        _progRead = false;
137        // set new state & save values
138        progState = REQUESTSENT;
139        _val = val;
140        _cv = 0xffff & CV;
141
142        // start the error timer
143        restartTimer(DCCppProgrammerTimeout);
144
145        // format and send message to go to program mode
146        if (getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) {
147            DCCppMessage msg;
148            if (controller().getCommandStation().isProgramV4Supported()) { //drops the callbacks
149                msg = DCCppMessage.makeWriteDirectCVMsgV4(CV, val);
150            } else {
151                msg = DCCppMessage.makeWriteDirectCVMsg(CV, val); //older syntax with dummy callbacks
152            }
153            controller().sendDCCppMessage(msg, this);
154        }
155    }
156
157    /** 
158     * {@inheritDoc}
159     */
160    @Override
161    public synchronized void confirmCV(String CV, int val, ProgListener p) throws jmri.ProgrammerException {
162        readCV(CV, p, val);
163    }
164
165    /** 
166     * {@inheritDoc}
167     */
168    @Override
169    public synchronized void readCV(String CVname, ProgListener p) throws jmri.ProgrammerException {
170        readCV(CVname, p, 0); //default starting value to zero
171    }
172
173    /** 
174     * {@inheritDoc}
175     */
176    @Override
177    public synchronized void readCV(String CVname, ProgListener p, int startVal) throws jmri.ProgrammerException {
178        final int CV = Integer.parseInt(CVname);
179        log.debug("readCV {}, startVal {}", CV, startVal);
180        // If can't read (e.g. multiMaus CS), this shouldnt be invoked, but
181        // still we need to do something rational by returning a NotImplemented error
182        if (!getCanRead()) {
183            notifyProgListenerEnd(p,CV,ProgListener.NotImplemented);
184            return;
185        }
186        useProgrammer(p);
187        _cv = 0xffff & CV;
188        _progRead = true;
189        // set new state
190        progState = REQUESTSENT;
191        // start the error timer
192        restartTimer(DCCppProgrammerTimeout);
193
194        if (getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) {
195            if (controller().getCommandStation().isReadStartValSupported()) { //use the 'V' command with a startVal
196                DCCppMessage msg = DCCppMessage.makeVerifyCVMsg(CV, startVal);
197                controller().sendDCCppMessage(msg, this);                
198            } else { //use the older 'R' command
199                DCCppMessage msg = DCCppMessage.makeReadDirectCVMsg(CV);
200                controller().sendDCCppMessage(msg, this);
201            }
202        }
203    }
204
205    private ProgListener _usingProgrammer = null;
206
207    // internal method to remember who's using the programmer
208    protected void useProgrammer(ProgListener p) throws jmri.ProgrammerException {
209        // test for only one!
210        if (_usingProgrammer != null && _usingProgrammer != p) {
211            if (log.isInfoEnabled()) {
212                log.info("programmer already in use by {}", _usingProgrammer);
213            }
214            throw new jmri.ProgrammerException("programmer in use");
215        } else {
216            _usingProgrammer = p;
217        }
218    }
219
220    /** 
221     * {@inheritDoc}
222     */
223    @Override
224    public synchronized void message(DCCppReply m) {
225        if (progState == NOTPROGRAMMING) {
226            return;
227        }
228        if (m.getElement(0) == DCCppConstants.PROGRAM_REPLY || 
229                m.getElement(0) == DCCppConstants.VERIFY_REPLY) {
230            if (log.isDebugEnabled()) {
231                log.debug("reply in REQUESTSENT state");
232                log.debug("DCC++ Program or Verify Reply value = {}", m.getCVString());
233            }
234            _val = m.getReadValueInt();
235            progState = NOTPROGRAMMING;
236            if (_val == -1) {
237                log.debug("Reporting NoAck");
238                notifyProgListenerEnd(_val, ProgListener.NoAck);
239            } else {
240                log.debug("Reporting OK");
241                notifyProgListenerEnd(_val, ProgListener.OK);
242            }
243        }
244    }
245
246    /** 
247     * {@inheritDoc}
248     */
249    @Override
250    public synchronized void message(DCCppMessage l) {
251    }
252
253    // Handle a timeout notification
254    @Override
255    public void notifyTimeout(DCCppMessage msg) {
256        log.debug("Notified of timeout on message '{}'", msg);
257    }
258
259
260    /*
261     * Indicate when the Programmer is in the middle of an operation.
262     */
263    public synchronized boolean programmerBusy() {
264        return (progState != NOTPROGRAMMING);
265    }
266
267    /** 
268     * {@inheritDoc}
269     */
270    @Override
271    protected synchronized void timeout() {
272        if (progState != NOTPROGRAMMING) {
273            // we're programming, time to stop
274            if (log.isDebugEnabled()) {
275                log.debug("timeout!");
276            }
277            // perhaps no loco present? Fail back to end of programming
278            progState = NOTPROGRAMMING;
279            if (getCanRead()) {
280                notifyProgListenerEnd(_val, ProgListener.FailedTimeout);
281            } else {
282                notifyProgListenerEnd(_val, ProgListener.OK);
283            }
284        }
285    }
286
287    // internal method to notify of the final result
288    protected void notifyProgListenerEnd(int value, int status) {
289        if (log.isDebugEnabled()) {
290            log.debug("notifyProgListenerEnd value {} status {}", value, status);
291        }
292        // the programmingOpReply handler might send an immediate reply, so
293        // clear the current listener _first_
294        jmri.ProgListener temp = _usingProgrammer;
295        _usingProgrammer = null;
296        notifyProgListenerEnd(temp,value,status);
297    }
298
299    private final DCCppTrafficController _controller;
300
301    protected DCCppTrafficController controller() {
302        return _controller;
303    }
304
305    @Override
306    public void dispose() {
307        if ( _controller != null ) {
308            _controller.removeDCCppListener(LISTENER_MASK, this);
309        }
310    }
311
312    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DCCppProgrammer.class);
313
314}