001package jmri.jmrix.can.cbus;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import javax.annotation.Nonnull;
007
008import jmri.ProgrammingMode;
009import jmri.jmrix.AbstractProgrammer;
010import jmri.jmrix.can.CanListener;
011import jmri.jmrix.can.CanMessage;
012import jmri.jmrix.can.CanReply;
013import jmri.jmrix.can.cbus.node.CbusNode;
014
015/**
016 * Implements the jmri.Programmer interface via commands for the CBUS
017 * programmer.
018 *
019 * @author Andrew Crosland Copyright (C) 2009
020 */
021public class CbusDccProgrammer extends AbstractProgrammer implements CanListener {
022
023    public CbusDccProgrammer(jmri.jmrix.can.TrafficController tc) {
024        this.tc = tc;
025        addTc(tc);
026    }
027
028    private jmri.jmrix.can.CanSystemConnectionMemo _memo;
029
030    public CbusDccProgrammer(jmri.jmrix.can.CanSystemConnectionMemo m) {
031        this.tc = m.getTrafficController();
032        _memo = m;
033        addTc(tc);
034    }
035
036    jmri.jmrix.can.TrafficController tc;
037
038    /**
039     * {@inheritDoc}
040     * Types implemented here.
041     */
042    @Override
043    @Nonnull
044    public List<ProgrammingMode> getSupportedModes() {
045        List<ProgrammingMode> ret = new ArrayList<>(4);
046        ret.add(ProgrammingMode.DIRECTBITMODE);
047        ret.add(ProgrammingMode.DIRECTBYTEMODE);
048        ret.add(ProgrammingMode.PAGEMODE);
049        ret.add(ProgrammingMode.REGISTERMODE);
050        return ret;
051    }
052
053    // members for handling the programmer interface
054    int progState = 0;
055    static final int NOTPROGRAMMING = 0;// is notProgramming
056    static final int MODESENT = 1;      // waiting reply to command to go into programming mode
057    static final int COMMANDSENT = 2;   // read/write command sent, waiting reply
058    static final int RETURNSENT = 4;    // waiting reply to go back to ops mode
059    static final int NVCOMMANDSENT = 8; // read/write command sent, waiting reply
060    boolean _progRead = false;
061    int _val;                           // remember the value being read/written for confirmative reply
062    int _cv;                            // remember the cv being read/written
063    static final int _nvOffset = 10000; // Offset to acces CBUS node NVs rather than DCC decoder CVs
064    private CbusNode _nodeOfInterest;   // Sets the node to be used for CBUS module programming
065
066    /**
067     * Set the CBUS Node to be used for NV programming
068     * 
069     * @param n a CBUS node
070     */
071    public synchronized void setNodeOfInterest(CbusNode n) {
072        _nodeOfInterest = n;
073    }
074
075    /**
076     * {@inheritDoc}
077     */
078    @Override
079    synchronized public void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
080        final int CV = Integer.parseInt(CVname);
081        log.debug("writeCV {} listener {}",CV, p);
082        useProgrammer(p);
083        _progRead = false;
084        progState = COMMANDSENT;
085        _val = val;
086        _cv = CV;
087        // see why waiting
088        try {
089            startLongTimer();
090            // write was in progress - send write command
091            if (_cv < _nvOffset) {
092                progState = COMMANDSENT;
093                tc.sendCanMessage(CbusMessage.getWriteCV(_cv, _val, getMode(), tc.getCanid()), this);
094            } else {
095                progState = NVCOMMANDSENT;
096                _nodeOfInterest.send.nVSET(_nodeOfInterest.getNodeNumber(), CV - _nvOffset, _val);
097            }
098        } catch (Exception e) {
099            // program op failed, go straight to end
100            log.error("Write operation failed",e);
101            progState = RETURNSENT;
102            //controller().sendCanMessage(CbusMessage.getExitProgMode(), this);
103        }
104    }
105
106    /**
107     * {@inheritDoc}
108     */
109    @Override
110    synchronized public void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
111        readCV(CV, p);
112    }
113
114    /**
115     * {@inheritDoc}
116     */
117    @Override
118    synchronized public void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException {
119        readCV(CVname, p, 0);
120    }
121
122    /**
123     * {@inheritDoc}
124     */
125    @Override
126    synchronized public void readCV(String CVname, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException {
127        final int CV = Integer.parseInt(CVname);
128        log.debug("readCV {} listens {}",CV, p);
129        useProgrammer(p);
130        _progRead = true;
131        progState = COMMANDSENT;
132        _cv = CV;
133        // see why waiting
134        try {
135            startLongTimer();
136            // read was in progress - send read command
137            if (_cv < _nvOffset) {
138                progState = COMMANDSENT;
139                if (_memo.supportsCVHints()) {
140                    tc.sendCanMessage(CbusMessage.getVerifyCV(_cv, getMode(), startVal, tc.getCanid()), this);
141                } else {
142                    tc.sendCanMessage(CbusMessage.getReadCV(_cv, getMode(), tc.getCanid()), this);
143                }
144            } else {
145                progState = NVCOMMANDSENT;
146                _nodeOfInterest.send.nVRD(_nodeOfInterest.getNodeNumber(), CV - _nvOffset);
147            }
148        } catch (Exception e) {
149            // program op failed, go straight to end
150            log.error("Read operation failed", e);
151            progState = RETURNSENT;
152            //controller().sendCanMessage(CbusMessage.getExitProgMode(), this);
153        }
154    }
155
156    private jmri.ProgListener _usingProgrammer = null;
157
158    // internal method to remember who's using the programmer
159    protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
160        // test for only one!
161        if (_usingProgrammer != null && _usingProgrammer != p) {
162            log.info("programmer already in use by {}", _usingProgrammer);
163            throw new jmri.ProgrammerException("programmer in use");
164        } else {
165            _usingProgrammer = p;
166        }
167    }
168
169    /**
170     * {@inheritDoc}
171     * Only listening for frames coming in to JMRI, see CanReply
172     */
173    @Override
174    public void message(CanMessage m) {
175    }
176
177    /**
178     * {@inheritDoc}
179     */
180    @Override
181    synchronized public void reply(CanReply m) {
182        if ( m.extendedOrRtr() ) {
183            return;
184        }
185        if (progState == COMMANDSENT) {
186            log.debug("reply in COMMANDSENT state");
187            // operation done, capture result, then have to leave programming mode
188            // check for errors
189            if ((m.getElement(0) == CbusConstants.CBUS_SSTAT)
190                && (m.getElement(2) == CbusConstants.SSTAT_NO_ACK)) {
191                log.warn("handle error reply {}", m);
192                // perhaps no loco present? Fail back to end of programming
193                //controller().sendCanMessage(CbusMessage.getExitProgMode(), this);
194                stopTimer();
195                notifyProgListenerEnd(-1, jmri.ProgListener.NoLocoDetected);
196            } else if ((m.getElement(0) == CbusConstants.CBUS_SSTAT)
197                && (m.getElement(2) == CbusConstants.SSTAT_OVLD)) {
198                log.warn("Programming overload {}", m);
199                // Overload. Fail back to end of programming
200                stopTimer();
201                notifyProgListenerEnd(-1, jmri.ProgListener.ProgrammingShort);
202            } else {
203                // see why waiting
204                if (_progRead && (m.getElement(0) == CbusConstants.CBUS_PCVS)) {
205                    // read was in progress - received report CV message
206                    _val = m.getElement(4);
207                    progState = NOTPROGRAMMING;
208                    stopTimer();
209                    // if this was a read, we cached the value earlier.  If its a
210                    // write, we're to return the original write value
211                    notifyProgListenerEnd(_val, jmri.ProgListener.OK);
212                } else if ((!_progRead) && (m.getElement(0) == CbusConstants.CBUS_SSTAT)
213                        && (m.getElement(2) == CbusConstants.SSTAT_WR_ACK)) {
214                    // write was in progress - acknowledge received
215                    progState = NOTPROGRAMMING;
216                    stopTimer();
217                    // if this was a read, we cached the value earlier.  If its a
218                    // write, we're to return the original write value
219                    notifyProgListenerEnd(_val, jmri.ProgListener.OK);
220                } else {
221                    // Carry on waiting
222                    log.debug("Reply ignored: {}", m);
223                }
224            }
225        } else if (progState == NVCOMMANDSENT) {
226            log.debug("reply in NVCOMMANDSENT state");
227            // operation done, capture result, then have to leave programming mode
228            // see why waiting
229            if (_progRead && (m.getElement(0) == CbusConstants.CBUS_NVANS)) {
230                // read was in progress - received report CV message
231                _val = m.getElement(4);
232                progState = NOTPROGRAMMING;
233                stopTimer();
234                // if this was a read, we cached the value earlier.  If its a
235                // write, we're to return the original write value
236                notifyProgListenerEnd(_val, jmri.ProgListener.OK);
237            } else if ((!_progRead) && (m.getElement(0) == CbusConstants.CBUS_WRACK)) {
238                // write was in progress - acknowledge received
239                progState = NOTPROGRAMMING;
240                stopTimer();
241                // if this was a read, we cached the value earlier.  If its a
242                // write, we're to return the original write value
243                notifyProgListenerEnd(_val, jmri.ProgListener.OK);
244            } else {
245                // Carry on waiting
246                log.debug("Reply ignored: {}", m);
247            }
248        }
249    }
250
251    /**
252     * {@inheritDoc}
253     *
254     * Internal routine to handle a timeout
255     */
256    @Override
257    synchronized protected void timeout() {
258        if (progState != NOTPROGRAMMING) {
259            // we're programming, time to stop
260            log.debug("timeout!");
261            // perhaps no loco present? Fail back to end of programming
262            progState = NOTPROGRAMMING;
263            //controller().sendCbusMessage(CbusMessage.getExitProgMode(), this);
264            notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout);
265        }
266    }
267
268    // internal method to notify of the final result
269    protected void notifyProgListenerEnd(int value, int status) {
270        log.debug("notifyProgListenerEnd value {}, status {}", value, status);
271        // the programmingOpReply handler might send an immediate reply, so
272        // clear the current listener _first_
273        jmri.ProgListener temp = _usingProgrammer;
274        _usingProgrammer = null;
275        notifyProgListenerEnd(temp,value,status);
276    }
277
278    @Override
279    public void dispose() {
280        removeTc(tc);
281    }
282
283    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusDccProgrammer.class);
284
285}