001package jmri.jmrix.nce;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import javax.annotation.Nonnull;
007
008import jmri.ProgrammingMode;
009import jmri.jmrix.AbstractProgrammer;
010
011/**
012 * Convert the jmri.Programmer interface into commands for the NCE power house.
013 * <p>
014 * This has two states: NOTPROGRAMMING, and COMMANDSENT. The transitions to and
015 * from programming mode are now handled in the TrafficController code.
016 *
017 * @author Bob Jacobsen Copyright (C) 2001, 2016
018 * @author kcameron Copyright (C) 2014
019 */
020public class NceProgrammer extends AbstractProgrammer implements NceListener {
021
022    protected NceTrafficController tc;
023
024    public NceProgrammer(NceTrafficController tc) {
025        this.tc = tc;
026        super.SHORT_TIMEOUT = 4000;
027
028        if (getSupportedModes().size() > 0) {
029            setMode(getSupportedModes().get(0));
030        }
031    }
032
033    /**
034     * {@inheritDoc}
035     * <p>
036     * NCE programming modes available depend on settings
037     */
038    @Override
039    @Nonnull
040    public List<ProgrammingMode> getSupportedModes() {
041        List<ProgrammingMode> ret = new ArrayList<ProgrammingMode>();
042        if (tc == null) {
043            log.warn("getSupportedModes called with null tc", new Exception("traceback"));
044        }
045        java.util.Objects.requireNonNull(tc, "TrafficController reference needed");
046
047        if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) {
048            // USB connection
049            switch (tc.getUsbSystem()) {
050                case NceTrafficController.USB_SYSTEM_POWERCAB:
051                case NceTrafficController.USB_SYSTEM_TWIN:
052                    ret.add(ProgrammingMode.DIRECTMODE);
053                    ret.add(ProgrammingMode.PAGEMODE);
054                    ret.add(ProgrammingMode.REGISTERMODE);
055                    return ret;
056
057                case NceTrafficController.USB_SYSTEM_SB3:
058                case NceTrafficController.USB_SYSTEM_SB5:
059                case NceTrafficController.USB_SYSTEM_POWERPRO:
060                    log.trace("no programming modes available for USB {}", tc.getUsbSystem());
061                    return ret;
062
063                default:
064                    log.warn("should not have hit default");
065                    return ret;
066            }
067        }
068
069        // here not USB
070        if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006) {
071            ret.add(ProgrammingMode.DIRECTMODE);
072        }
073
074        ret.add(ProgrammingMode.PAGEMODE);
075        ret.add(ProgrammingMode.REGISTERMODE);
076
077        return ret;
078    }
079
080    /**
081     * {@inheritDoc}
082     */
083    @Override
084    public boolean getCanRead() {
085        return !(tc != null && tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_POWERCAB
086                && tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_TWIN
087                && tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE);
088    }
089
090    /**
091     * {@inheritDoc}
092     */
093    @Override
094    public boolean getCanWrite(String cv) {
095        return getCanWrite(Integer.parseInt(cv));
096    }
097
098    boolean getCanWrite(int cv) {
099        // prevent writing Prog Track mode CV > 256 on PowerPro 2007C and earlier
100        return !((cv > 256)
101                && ((getMode() == ProgrammingMode.PAGEMODE)
102                || (getMode() == ProgrammingMode.DIRECTMODE)
103                || (getMode() == ProgrammingMode.REGISTERMODE))
104                && ((tc != null)
105                && ((tc.getCommandOptions() == NceTrafficController.OPTION_1999)
106                || (tc.getCommandOptions() == NceTrafficController.OPTION_2004)
107                || (tc.getCommandOptions() == NceTrafficController.OPTION_2006)))
108                && (!tc.isPwrProVer060203orLater()));
109    }
110
111    // members for handling the programmer interface
112    int progState = 0;
113    static final int NOTPROGRAMMING = 0;// is notProgramming
114    static final int COMMANDSENT = 2;  // read/write command sent, waiting reply
115    static final int COMMANDSENT_2 = 4; // ops programming mode, send msg twice
116    boolean _progRead = false;
117    int _val; // remember the value being read/written for confirmative reply
118    int _cv; // remember the cv being read/written
119
120    /**
121     * {@inheritDoc}
122     */
123    @Override
124    public synchronized void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
125        final int CV = Integer.parseInt(CVname);
126        if (log.isDebugEnabled()) {
127            log.debug("writeCV {} listens {}", CV, p);
128        }
129        useProgrammer(p);
130        // prevent writing Prog Track mode CV > 256 on PowerPro 2007C and earlier
131        if (!getCanWrite(CV)) {
132            log.error("Write {} CV {} unsupported by NCE EPROM revision {}", getMode(), CV, tc.getPwrProVersHexText());
133            progState = NOTPROGRAMMING;
134            cleanup();
135            notifyProgListenerEnd(_val, jmri.ProgListener.NotImplemented);
136            return;
137        }
138        _progRead = false;
139        // set state
140        progState = COMMANDSENT;
141        _val = val;
142        _cv = CV;
143
144        try {
145            // start the error timer
146            startLongTimer();
147
148            // format and send the write message
149            tc.sendNceMessage(progTaskStart(getMode(), _val, _cv), this);
150        } catch (jmri.ProgrammerException e) {
151            progState = NOTPROGRAMMING;
152            throw e;
153        }
154    }
155
156    /**
157     * {@inheritDoc}
158     */
159    @Override
160    public void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
161        readCV(CV, p);
162    }
163
164    /**
165     * {@inheritDoc}
166     */
167    @Override
168    public synchronized void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException {
169        final int CV = Integer.parseInt(CVname);
170        if (log.isDebugEnabled()) {
171            log.debug("readCV {} listens {}", CV, p);
172        }
173        useProgrammer(p);
174        _progRead = true;
175
176        // set commandPending state
177        progState = COMMANDSENT;
178        _cv = CV;
179
180        try {
181            // start the error timer
182            startLongTimer();
183
184            // format and send the write message
185            tc.sendNceMessage(progTaskStart(getMode(), -1, _cv), this);
186        } catch (jmri.ProgrammerException e) {
187            progState = NOTPROGRAMMING;
188            throw e;
189        }
190    }
191
192    private jmri.ProgListener _usingProgrammer = null;
193
194    // internal method to remember who's using the programmer
195    protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
196        // test for only one!
197        if (_usingProgrammer != null && _usingProgrammer != p) {
198            if (log.isInfoEnabled()) {
199                log.info("programmer already in use by {}", _usingProgrammer);
200            }
201            throw new jmri.ProgrammerException("programmer in use");
202        } else {
203            _usingProgrammer = p;
204            return;
205        }
206    }
207
208    // internal method to create the NceMessage for programmer task start
209    protected NceMessage progTaskStart(ProgrammingMode mode, int val, int cvnum) throws jmri.ProgrammerException {
210        // val = -1 for read command; mode is direct, etc
211        if (val < 0) {
212            // read
213            if (mode == ProgrammingMode.PAGEMODE) {
214                return NceMessage.getReadPagedCV(tc, cvnum);
215            } else if (mode == ProgrammingMode.DIRECTMODE) {
216                return NceMessage.getReadDirectCV(tc, cvnum);
217            } else {
218                return NceMessage.getReadRegister(tc, registerFromCV(cvnum));
219            }
220        } else {
221            // write
222            if (mode == ProgrammingMode.PAGEMODE) {
223                return NceMessage.getWritePagedCV(tc, cvnum, val);
224            } else if (mode == ProgrammingMode.DIRECTMODE) {
225                return NceMessage.getWriteDirectCV(tc, cvnum, val);
226            } else {
227                return NceMessage.getWriteRegister(tc, registerFromCV(cvnum), val);
228            }
229        }
230    }
231
232    /**
233     * {@inheritDoc}
234     */
235    @Override
236    public void message(NceMessage m) {
237        log.error("message received unexpectedly: {}", m.toString());
238    }
239
240    /**
241     * {@inheritDoc}
242     */
243    @Override
244    public synchronized void reply(NceReply m) {
245        if (progState == NOTPROGRAMMING) {
246            // we get the complete set of replies now, so ignore these
247            if (log.isDebugEnabled()) {
248                log.debug("reply in NOTPROGRAMMING state");
249            }
250            return;
251        } else if (progState == COMMANDSENT) {
252            if (log.isDebugEnabled()) {
253                log.debug("reply in COMMANDSENT state");
254            }
255            // operation done, capture result, then post response
256            progState = NOTPROGRAMMING;
257            // check for errors
258            if ((m.match("NO FEEDBACK DETECTED") >= 0)
259                    || (m.isBinary() && !_progRead && (m.getElement(0) != NceMessage.NCE_OKAY))
260                    || (m.isBinary() && _progRead && (m.getElement(1) != NceMessage.NCE_OKAY))) {
261                if (log.isDebugEnabled()) {
262                    log.debug("handle NO FEEDBACK DETECTED");
263                }
264                // perhaps no loco present? Fail back to end of programming
265                notifyProgListenerEnd(_val, jmri.ProgListener.NoLocoDetected);
266            } else {
267                // see why waiting
268                if (_progRead) {
269                    // read was in progress - get return value
270                    _val = m.value();
271                }
272                // if this was a read, we retrieved the value above.  If its a
273                // write, we're to return the original write value
274                notifyProgListenerEnd(_val, jmri.ProgListener.OK);
275            }
276
277        } else if (progState == COMMANDSENT_2) {
278            if (log.isDebugEnabled()) {
279                log.debug("first reply in COMMANDSENT_2 state");
280            }
281            // first message sent, now wait for second reply to arrive
282            progState = COMMANDSENT;
283        } else {
284            if (log.isDebugEnabled()) {
285                log.debug("reply in un-decoded state");
286            }
287        }
288    }
289
290    /**
291     * {@inheritDoc}
292     * <p>
293     * Internal routine to handle a timeout
294     */
295    @Override
296    protected synchronized void timeout() {
297        if (progState != NOTPROGRAMMING) {
298            // we're programming, time to stop
299            if (log.isDebugEnabled()) {
300                log.debug("timeout!");
301            }
302            // perhaps no loco present? Fail back to end of programming
303            progState = NOTPROGRAMMING;
304            cleanup();
305            notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout);
306        }
307    }
308
309    // Internal method to cleanup in case of a timeout. Separate routine
310    // so it can be changed in subclasses.
311    void cleanup() {
312    }
313
314    // internal method to notify of the final result
315    protected void notifyProgListenerEnd(int value, int status) {
316        if (log.isDebugEnabled()) {
317            log.debug("notifyProgListenerEnd value {} status {}", value, status);
318        }
319        // the programmingOpReply handler might send an immediate reply, so
320        // clear the current listener _first_
321        jmri.ProgListener temp = _usingProgrammer;
322        _usingProgrammer = null;
323        notifyProgListenerEnd(temp, value, status);
324    }
325
326    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NceProgrammer.class);
327
328}