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 value;
027    protected jmri.ProgListener progListener = null;
028  
029    // possible states.
030    static protected final int NOTPROGRAMMING = 0; // is notProgramming
031    static protected final int REQUESTSENT = 1; // read/write command sent, waiting reply
032
033    protected XNetTrafficController tc;
034
035    public XNetOpsModeProgrammer(int pAddress, XNetTrafficController controller) {
036        tc = controller;
037        if (log.isDebugEnabled()) {
038            log.debug("Creating Ops Mode Programmer for Address {}", pAddress);
039        }
040        mAddressLow = LenzCommandStation.getDCCAddressLow(pAddress);
041        mAddressHigh = LenzCommandStation.getDCCAddressHigh(pAddress);
042        mAddress = pAddress;
043        if (log.isDebugEnabled()) {
044            log.debug("High Address: {} Low Address: {}", mAddressHigh, mAddressLow);
045        }
046        // register as a listener
047        tc.addXNetListener(XNetInterface.COMMINFO | XNetInterface.CS_INFO, this);
048    }
049
050    /** 
051     * {@inheritDoc}
052     *
053     * Send an ops-mode write request to the Xpressnet.
054     */
055    @Override
056    synchronized public void writeCV(String CVname, int val, ProgListener p) throws ProgrammerException {
057        final int CV = Integer.parseInt(CVname);
058        XNetMessage msg = XNetMessage.getWriteOpsModeCVMsg(mAddressHigh, mAddressLow, CV, val);
059        tc.sendXNetMessage(msg, this);
060        /* we need to save the programer and value so we can send messages 
061         back to the screen when the programming screen when we receive
062         something from the command station */
063        progListener = p;
064        value = val;
065        progState = REQUESTSENT;
066        restartTimer(msg.getTimeout());
067    }
068
069    /** 
070     * {@inheritDoc}
071     */
072    @Override
073    synchronized public void readCV(String CVname, ProgListener p) throws ProgrammerException {
074        final int CV = Integer.parseInt(CVname);
075        XNetMessage msg = XNetMessage.getVerifyOpsModeCVMsg(mAddressHigh, mAddressLow, CV, value);
076        tc.sendXNetMessage(msg, this);
077        /* We can trigger a read to an LRC120, but the information is not
078         currently sent back to us via the XpressNet */
079        notifyProgListenerEnd(p,CV,jmri.ProgListener.NotImplemented);
080    }
081
082    /** 
083     * {@inheritDoc}
084     */
085    @Override
086    public void confirmCV(String CVname, int val, ProgListener p) throws ProgrammerException {
087        int CV = Integer.parseInt(CVname);
088        XNetMessage msg = XNetMessage.getVerifyOpsModeCVMsg(mAddressHigh, mAddressLow, CV, val);
089        tc.sendXNetMessage(msg, this);
090        /* We can trigger a read to an LRC120, but the information is not
091         currently sent back to us via the XpressNet */
092        notifyProgListenerEnd(p,val,jmri.ProgListener.NotImplemented);
093    }
094
095    /** 
096     * {@inheritDoc}
097     *
098     * Types implemented here.
099     */
100    @Override
101    @Nonnull
102    public List<ProgrammingMode> getSupportedModes() {
103        List<ProgrammingMode> ret = new ArrayList<>();
104        ret.add(ProgrammingMode.OPSBYTEMODE);
105        return ret;
106    }
107
108    /** 
109     * {@inheritDoc}
110     *
111     * Can this ops-mode programmer read back values?
112     * Indirectly we can, though this requires an external display 
113     * (a Lenz LRC120) and enabling railcom.
114     *
115     * @return true to allow us to trigger an ops mode read
116     */
117    @Override
118    public boolean getCanRead() {
119        // An operations mode read can be triggered on command 
120        // stations which support Operations Mode Writes (LZ100,
121        // LZV100,MultiMouse).  Whether or not the operation produces
122        // a result depends on additional external hardware (a booster 
123        // with an enabled  RailCom cutout (LV102 or similar) and a 
124        // RailCom receiver circuit (LRC120 or similar)).
125        // We have no way of determining if the required external 
126        // hardware is present, so we return true for all command 
127        // stations on which the Operations Mode Programmer is enabled.
128
129        // yes, we just call the superclass method.  Leave this in place
130        // so the comments and javadoc above make sense.
131        return super.getCanRead();
132    }
133
134
135    /** 
136     * {@inheritDoc}
137     */
138    @Override
139    synchronized public void message(XNetReply l) {
140        if (progState == NOTPROGRAMMING) {
141            // We really don't care about any messages unless we send a 
142            // request, so just ignore anything that comes in
143        } else if (progState == REQUESTSENT) {
144            if (l.isOkMessage()) {
145                // Before we set the programmer state to not programming, 
146                // delay for a short time to give the decoder a chance to 
147                // process the request.
148                new jmri.util.WaitHandler(this,250);
149                progState = NOTPROGRAMMING;
150                stopTimer();
151                notifyProgListenerEnd(progListener,value,jmri.ProgListener.OK);
152            } else {
153                /* this is an error */
154                if (l.isRetransmittableErrorMsg()) {
155                    // just ignore this, since we are retransmitting
156                    // the message.
157                } else if (l.getElement(0) == XNetConstants.CS_INFO
158                        && l.getElement(1) == XNetConstants.CS_NOT_SUPPORTED) {
159                    progState = NOTPROGRAMMING;
160                    stopTimer();
161                    notifyProgListenerEnd(progListener,value,jmri.ProgListener.NotImplemented);
162                } else {
163                    /* this is an unknown error */
164                    progState = NOTPROGRAMMING;
165                    stopTimer();
166                    notifyProgListenerEnd(progListener,value,jmri.ProgListener.UnknownError);
167                }
168            }
169        }
170    }
171
172    /** 
173     * {@inheritDoc}
174     */
175    @Override
176    public boolean getLongAddress() {
177        return true;
178    }
179
180    /** 
181     * {@inheritDoc}
182     */
183    @Override
184    public int getAddressNumber() {
185        return mAddress;
186    }
187
188    /** 
189     * {@inheritDoc}
190     */
191    @Override
192    public String getAddress() {
193        return "" + getAddressNumber() + " " + getLongAddress();
194    }
195
196    /** 
197     * {@inheritDoc}
198     */
199    @Override
200    public synchronized void message(XNetMessage l) {
201    }
202
203    /** 
204     * {@inheritDoc}
205     *
206     * Handle a timeout notification
207     */
208    @Override
209    public void notifyTimeout(XNetMessage msg) {
210        if (log.isDebugEnabled()) {
211            log.debug("Notified of timeout on message{}", msg.toString());
212        }
213    }
214
215    /** 
216     * {@inheritDoc}
217     */
218    @Override
219    synchronized protected void timeout() {
220        if (progState != NOTPROGRAMMING) {
221            // we're programming, time to stop
222            if (log.isDebugEnabled()) {
223                log.debug("timeout!");
224            }
225            // perhaps no loco present? Fail back to end of programming
226            progState = NOTPROGRAMMING;
227            if (getCanRead()) {
228               notifyProgListenerEnd(progListener,value,jmri.ProgListener.FailedTimeout);
229            } else {
230               notifyProgListenerEnd(progListener,value,jmri.ProgListener.OK);
231            }
232        }
233    }
234
235    // initialize logging
236    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(XNetOpsModeProgrammer.class);
237
238}