001package jmri.jmrix.lenz;
002
003import java.util.ArrayList;
004import java.util.List;
005import javax.annotation.Nonnull;
006
007import jmri.AddressedProgrammer;
008import jmri.ProgListener;
009import jmri.ProgrammerException;
010import jmri.ProgrammingMode;
011
012/**
013 * Provides an Ops mode programming interface for XpressNet Currently only Byte
014 * mode is implemented, though XpressNet also supports bit mode writes for POM
015 *
016 * @see jmri.Programmer
017 * @author Paul Bender Copyright (C) 2003-2010
018 * @author Girgio Terdina Copyright (C) 2007
019 */
020public class XNetOpsModeProgrammer extends jmri.jmrix.AbstractProgrammer implements XNetListener, AddressedProgrammer {
021
022    protected final int mAddressHigh;
023    protected final int mAddressLow;
024    protected final int mAddress;
025    protected int progState = NOTPROGRAMMING;
026    protected int progMethod = WRITE;
027    protected int value;
028    protected jmri.ProgListener progListener = null;
029
030    // possible states.
031    static protected final int NOTPROGRAMMING = 0; // is notProgramming
032    static protected final int REQUESTSENT = 1; // read/write command sent, waiting reply
033    static protected final int RESULTREQUESTED = 2; // result request sent, waiting reply
034    static protected final int WRITE = 0; // write mode
035    static protected final int READ = 1; // read mode
036
037    protected XNetTrafficController tc;
038
039    public XNetOpsModeProgrammer(int pAddress, XNetTrafficController controller) {
040        tc = controller;
041        if (log.isDebugEnabled()) {
042            log.debug("Creating Ops Mode Programmer for Address {}", pAddress);
043        }
044        mAddressLow = LenzCommandStation.getDCCAddressLow(pAddress);
045        mAddressHigh = LenzCommandStation.getDCCAddressHigh(pAddress);
046        mAddress = pAddress;
047        if (log.isDebugEnabled()) {
048            log.debug("High Address: {} Low Address: {}", mAddressHigh, mAddressLow);
049        }
050        // register as a listener
051        tc.addXNetListener(XNetInterface.COMMINFO | XNetInterface.CS_INFO, this);
052    }
053
054    /**
055     * {@inheritDoc}
056     *
057     * Send an ops-mode write request to the Xpressnet.
058     */
059    @Override
060    synchronized public void writeCV(String CVname, int val, ProgListener p) throws ProgrammerException {
061        final int CV = Integer.parseInt(CVname);
062        XNetMessage msg = XNetMessage.getWriteOpsModeCVMsg(mAddressHigh, mAddressLow, CV, val);
063        tc.sendXNetMessage(msg, this);
064        /* we need to save the programer and value so we can send messages
065         back to the screen when the programming screen when we receive
066         something from the command station */
067        progListener = p;
068        value = val;
069        progState = REQUESTSENT;
070        progMethod = WRITE;
071        restartTimer(msg.getTimeout());
072    }
073
074    /**
075     * {@inheritDoc}
076     */
077    @Override
078    synchronized public void readCV(String CVname, ProgListener p) throws ProgrammerException {
079        final int CV = Integer.parseInt(CVname);
080        XNetMessage msg = XNetMessage.getVerifyOpsModeCVMsg(mAddressHigh, mAddressLow, CV, value);
081        tc.sendXNetMessage(msg, this);
082        /* we need to save the programer and value so we can send messages 
083         back to the screen when the programming screen when we receive
084         something from the command station */
085        progListener = p;
086        progState = REQUESTSENT;
087        progMethod = READ;
088        restartTimer(msg.getTimeout());
089    }
090
091    /**
092     * {@inheritDoc}
093     */
094    @Override
095    public void confirmCV(String CVname, int val, ProgListener p) throws ProgrammerException {
096        int CV = Integer.parseInt(CVname);
097        XNetMessage msg = XNetMessage.getVerifyOpsModeCVMsg(mAddressHigh, mAddressLow, CV, val);
098        tc.sendXNetMessage(msg, this);
099        /* we need to save the programer and value so we can send messages 
100         back to the screen when the programming screen when we receive
101         something from the command station */
102        progListener = p;
103        value = val;
104        progState = REQUESTSENT;
105        progMethod = READ;
106        restartTimer(msg.getTimeout());
107    }
108
109    /**
110     * {@inheritDoc}
111     *
112     * Types implemented here.
113     */
114    @Override
115    @Nonnull
116    public List<ProgrammingMode> getSupportedModes() {
117        List<ProgrammingMode> ret = new ArrayList<>();
118        ret.add(ProgrammingMode.OPSBYTEMODE);
119        return ret;
120    }
121
122    /**
123     * {@inheritDoc}
124     *
125     * Can this ops-mode programmer read back values?
126     * Indirectly we can, though this requires an external display
127     * (a Lenz LRC120) and enabling railcom.
128     *
129     * @return true to allow us to trigger an ops mode read
130     */
131    @Override
132    public boolean getCanRead() {
133        // An operations mode read can be triggered on command
134        // stations which support Operations Mode Writes (LZ100,
135        // LZV100, MultiMouse).  Whether or not the operation produces
136        // a result depends on additional external hardware (a booster 
137        // with an enabled  RailCom cutout (LV102 or similar) and a 
138        // RailCom receiver circuit (LRC120 or similar)).
139        // We have no way of determining if the required external
140        // hardware is present, so we return true for all command
141        // stations on which the Operations Mode Programmer is enabled.
142
143        // yes, we just call the superclass method.  Leave this in place
144        // so the comments and javadoc above make sense.
145        return super.getCanRead();
146    }
147
148
149    /**
150     * {@inheritDoc}
151     */
152    @Override
153    synchronized public void message(XNetReply l) {
154        if (progState == NOTPROGRAMMING) {
155            // We really don't care about any messages unless we send a
156            // request, so just ignore anything that comes in
157        } else if (progState == REQUESTSENT) {
158            if (l.isOkMessage()) {
159                // Before we set the programmer state to not programming,
160                // delay for a short time to give the decoder a chance to
161                // process the request.
162                if (progMethod == WRITE) {
163                    new jmri.util.WaitHandler(this, 250);
164                    progState = NOTPROGRAMMING;
165                    stopTimer();
166                    notifyProgListenerEnd(progListener, value, jmri.ProgListener.OK);
167                } else if (progMethod == READ) {
168                    stopTimer();
169                    new jmri.util.WaitHandler(this, 1000); // XPressNet documentations say to wait up to 0.5 to 1 second before requesting the result
170                    progState = RESULTREQUESTED;
171                    XNetMessage msg = XNetMessage.getOpsModeResultsMsg();
172                    tc.sendXNetMessage(msg, this);
173                    restartTimer(msg.getTimeout());
174                }
175            } else {
176                handleXNetError(l);
177            }
178        } else if(progState == RESULTREQUESTED) {
179            if (l.isOpsModeResultMessage()) {
180                // We got a result message, so we can stop the timer
181                int address = l.getOpsModeResultAddress();
182                if (address != mAddress && address != 0) {
183                    // This is not the address we are looking for, so ignore it
184                    if (log.isDebugEnabled()) {
185                        log.debug("Received result message for address {}, expected {}", address, mAddress);
186                    }
187                    return;
188                }
189                if (log.isDebugEnabled()) {
190                    log.debug("Received result message: {}", l);
191                }
192                if (address == 0) {
193                    progState = NOTPROGRAMMING;
194                    stopTimer();
195                    // this indicates that there was no result from the read, so notify the programmer listener of that
196                    notifyProgListenerEnd(progListener, value, ProgListener.NoAck);
197                } else {
198                    progState = NOTPROGRAMMING;
199                    stopTimer();
200                    // Notify the listener of the result
201                    notifyProgListenerEnd(progListener, l.getOpsModeResultValue(), jmri.ProgListener.OK);
202                }
203            } else {
204                handleXNetError(l);
205            }
206        }
207    }
208
209    private void handleXNetError(XNetReply l) {
210        /* this is an error */
211        if (l.isRetransmittableErrorMsg()) {
212            // just ignore this, since we are retransmitting
213            // the message.
214        } else if (l.getElement(0) == XNetConstants.CS_INFO
215                    && l.getElement(1) == XNetConstants.CS_NOT_SUPPORTED) {
216            progState = NOTPROGRAMMING;
217            stopTimer();
218            notifyProgListenerEnd(progListener,value, ProgListener.NotImplemented);
219        } else {
220            /* this is an unknown error */
221            progState = NOTPROGRAMMING;
222            stopTimer();
223            notifyProgListenerEnd(progListener,value, ProgListener.UnknownError);
224        }
225    }
226
227    /**
228     * {@inheritDoc}
229     */
230    @Override
231    public boolean getLongAddress() {
232        return true;
233    }
234
235    /**
236     * {@inheritDoc}
237     */
238    @Override
239    public int getAddressNumber() {
240        return mAddress;
241    }
242
243    /**
244     * {@inheritDoc}
245     */
246    @Override
247    public String getAddress() {
248        return "" + getAddressNumber() + " " + getLongAddress();
249    }
250
251    /**
252     * {@inheritDoc}
253     */
254    @Override
255    public synchronized void message(XNetMessage l) {
256    }
257
258    /**
259     * {@inheritDoc}
260     *
261     * Handle a timeout notification
262     */
263    @Override
264    public void notifyTimeout(XNetMessage msg) {
265        if (log.isDebugEnabled()) {
266            log.debug("Notified of timeout on message{}", msg.toString());
267        }
268    }
269
270    /**
271     * {@inheritDoc}
272     */
273    @Override
274    synchronized protected void timeout() {
275        if (progState != NOTPROGRAMMING) {
276            // we're programming, time to stop
277            if (log.isDebugEnabled()) {
278                log.debug("timeout!");
279            }
280            // perhaps no loco present? Fail back to end of programming
281            progState = NOTPROGRAMMING;
282            if (getCanRead()) {
283               notifyProgListenerEnd(progListener,value,jmri.ProgListener.FailedTimeout);
284            } else {
285               notifyProgListenerEnd(progListener,value,jmri.ProgListener.OK);
286            }
287        }
288    }
289
290    @Override
291    public Configurator getConfigurator() {
292        return new XNetOpsConfigurator();
293    }
294
295    /**
296     * This class is used by tests.
297     */
298    public static class XNetOpsConfigurator implements Configurator {
299    }
300
301    // initialize logging
302    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(XNetOpsModeProgrammer.class);
303
304}