001package jmri.jmrix.ecos;
002
003import java.util.ArrayList;
004import java.util.List;
005import javax.annotation.Nonnull;
006
007import jmri.ProgrammingMode;
008import jmri.jmrix.AbstractProgrammer;
009import jmri.jmrix.ecos.utilities.GetEcosObjectNumber;
010
011/**
012 * Implements the jmri.Programmer interface via commands for the ECoS
013 * programmer. This provides a service mode programmer.
014 *
015 * @author Karl Johan Lisby Copyright (C) 2015 and 2018
016 */
017public class EcosProgrammer extends AbstractProgrammer implements EcosListener {
018
019    public EcosProgrammer(EcosTrafficController etc) {
020        tc = etc;
021    }
022
023    EcosTrafficController tc;
024    int ecosObject = 5;
025    String readCommand  = "mode[readdccdirect]";
026    String writeCommand = "mode[writedccdirect]";
027
028    /**
029     * {@inheritDoc}
030     *
031     * @return list of programming modes implemented for ECoS
032     */
033    @Override
034    @Nonnull
035    public List<ProgrammingMode> getSupportedModes() {
036        List<ProgrammingMode> ret = new ArrayList<ProgrammingMode>();
037        ret.add(ProgrammingMode.DIRECTBYTEMODE);
038        return ret;
039    }
040
041    // members for handling the programmer interface
042    int progState = 0;
043    static final int NOTPROGRAMMING = 0; // is notProgramming
044    static final int MODESENT = 1;       // waiting reply to command to go into programming mode
045    static final int COMMANDSENT = 2;    // read/write command sent, waiting reply
046    boolean _progRead = false;
047    int _val; // remember the value being read/written for confirmative reply
048    int _cv; // remember the cv being read/written
049
050    // programming interface
051
052    /**
053     * {@inheritDoc}
054     */
055    @Override
056    synchronized public void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
057        final int CV = Integer.parseInt(CVname);
058        if (log.isDebugEnabled()) {
059            log.debug("writeCV {} listens {}", CV, p);
060        }
061        useProgrammer(p);
062        _progRead = false;
063        // set commandPending state
064        progState = MODESENT;
065        _val = val;
066        _cv = CV;
067
068        // start the error timer
069        startShortTimer();
070
071        // format and send message to go to program mode
072        // ECOS is in program mode by default but we need to subscribe to events
073        EcosMessage m;
074        m = new EcosMessage("request("+ecosObject+",view)");
075        tc.sendEcosMessage(m, this);
076    }
077
078    /**
079     * {@inheritDoc}
080     */
081    @Override
082    synchronized public void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
083        readCV(CV, p);
084    }
085
086    /**
087     * {@inheritDoc}
088     */
089    @Override
090    synchronized public void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException {
091        final int CV = Integer.parseInt(CVname);
092        if (log.isDebugEnabled()) {
093            log.debug("readCV {} listens {}", CV, p);
094        }
095        useProgrammer(p);
096        _progRead = true;
097        // set commandPending state
098        // set commandPending state
099        progState = MODESENT;
100        _cv = CV;
101
102        // start the error timer
103        startShortTimer();
104
105        // format and send message to go to program mode
106        // ECOS is in program mode by default but we need to subscribe to events
107        EcosMessage m;
108        m = new EcosMessage("request("+ecosObject+",view)");
109        tc.sendEcosMessage(m, this);
110    }
111
112    private jmri.ProgListener _usingProgrammer = null;
113
114    // internal method to remember who's using the programmer
115    protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
116        // test for only one!
117        if (_usingProgrammer != null && _usingProgrammer != p) {
118            if (log.isInfoEnabled()) {
119                log.info("programmer already in use by {}", _usingProgrammer);
120            }
121            throw new jmri.ProgrammerException("programmer in use");
122        } else {
123            _usingProgrammer = p;
124            return;
125        }
126    }
127
128    /**
129     * {@inheritDoc}
130     */
131    @Override
132    public void message(EcosMessage m) {
133        log.info("message: {}", m);
134    }
135
136    /**
137     * {@inheritDoc}
138     */
139    @Override
140    synchronized public void reply(EcosReply reply) {
141        log.info("reply: {}", reply);
142        if (progState == NOTPROGRAMMING) {
143            // we get the complete set of replies now, so ignore these
144            if (log.isDebugEnabled()) {
145                log.debug("reply in NOTPROGRAMMING state");
146            }
147            return;
148        } else if (progState == MODESENT) {
149            log.debug("reply in MODESENT state");
150            // see if reply is the acknowledge of requesting view of events; if not, wait
151            if (reply.match("<REPLY request("+ecosObject+",view)>") == -1) {
152                return;
153            }
154            if (reply.match("<END 0 (OK)>") == -1) {
155                return;
156            }
157            // here ready to send the read/write command
158            progState = COMMANDSENT;
159            // send the command for reading or writing CV
160            try {
161                startLongTimer();
162                EcosMessage m;
163                if (_progRead) {
164                    // read was in progress - send read command
165                    m = new EcosMessage("set(" + ecosObject + "," + readCommand + ",cv[" + _cv + "])");
166                } else {
167                    // write was in progress - send write command
168                    m = new EcosMessage("set(" + ecosObject + "," + writeCommand + ",cv[" + _cv + "," + _val + "])");
169                }
170                tc.sendEcosMessage(m, this);
171            } catch (Exception e) {
172                // program op failed, go straight to end
173                log.error("program operation failed, exception", e);
174                progState = NOTPROGRAMMING;
175                EcosMessage m;
176                m = new EcosMessage("release("+ecosObject+",view)");
177                tc.sendEcosMessage(m, this);
178                notifyProgListenerEnd(-1, jmri.ProgListener.NoLocoDetected);
179                return;
180            }
181        } else if (progState == COMMANDSENT) {
182            if (log.isDebugEnabled()) {
183                log.debug("reply in COMMANDSENT state");
184            }
185            // The real reply comes in an event; if this is not that event, wait
186            if (reply.match("<EVENT "+ecosObject+">") == -1) {
187                return;
188            }
189            // operation done, capture result, then leave programming mode
190            progState = NOTPROGRAMMING;
191            stopTimer();
192            EcosMessage m;
193            m = new EcosMessage("release("+ecosObject+",view)");
194            tc.sendEcosMessage(m, this);
195            // check for errors
196            if (reply.match("error") >= 0 || reply.match(",ok]") == -1) {
197                log.debug("ERROR during programming {}", reply);
198                // ECOS is not very informative about the precise nature of errors.
199                // We might guess that there is no loco present
200                notifyProgListenerEnd(-1, jmri.ProgListener.NoLocoDetected);
201                return;
202            }
203            // Get the CV value from the reply if reading
204            if (_progRead) {
205                // read was in progress - get return value
206                _val = GetEcosObjectNumber.getEcosObjectNumber(reply.toString(), ",", ",ok]");
207                log.debug("read CV {} value: {}", _cv, _val);
208            }
209
210            // if this was a read, we cached the value earlier.  If its a
211            // write, we're to return the original write value
212            notifyProgListenerEnd(_val, jmri.ProgListener.OK);
213
214        } else {
215            log.debug("reply in un-decoded state");
216        }
217    }
218
219    /**
220     * {@inheritDoc}
221     *
222     * Internal routine to handle a timeout.
223     */
224    @Override
225    synchronized protected void timeout() {
226        if (progState != NOTPROGRAMMING) {
227            // we're programming, time to stop
228            if (log.isDebugEnabled()) {
229                log.debug("timeout!");
230            }
231            // perhaps no loco present? Fail back to end of programming
232            progState = NOTPROGRAMMING;
233            EcosMessage m;
234            m = new EcosMessage("release("+ecosObject+",view)");
235            tc.sendEcosMessage(m, this);
236            notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout);
237        }
238    }
239
240    /**
241     * Internal method to notify of the final result.
242     * @param value Value transferred, particularly if a read operation
243     * @param status Status of completed operation
244     */
245    protected void notifyProgListenerEnd(int value, int status) {
246        if (log.isDebugEnabled()) {
247            log.debug("notifyProgListenerEnd value {} status {}", value, status);
248        }
249        // the programmingOpReply handler might send an immediate reply, so
250        // clear the current listener _first_
251        jmri.ProgListener temp = _usingProgrammer;
252        _usingProgrammer = null;
253        notifyProgListenerEnd(temp,value,status);
254    }
255
256    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EcosProgrammer.class);
257
258}