001package jmri.jmrix.lenz.hornbyelite;
002
003import jmri.ProgrammingMode;
004import jmri.jmrix.lenz.XNetConstants;
005import jmri.jmrix.lenz.XNetMessage;
006import jmri.jmrix.lenz.XNetProgrammer;
007import jmri.jmrix.lenz.XNetReply;
008import jmri.jmrix.lenz.XNetTrafficController;
009
010/**
011 * Programmer support for Hornby Elite implementationn of XpressNet.
012 * <p>
013 * The read operation state sequence is:
014 * <ul>
015 * <li>Send Register Mode / Paged mode /Direct Mode read request
016 * <li>Wait for Broadcast Service Mode Entry message -- not happening on elite
017 * <li>Send Request for Service Mode Results request
018 * <li>Wait for results reply, interpret
019 * <li>Send Resume Operations request -- The Elite does not seem to require this
020 * step.
021 * <li>Wait for Normal Operations Resumed broadcast -- The Elite does not seem
022 * to require this step.
023 * </ul>
024 *
025 * @author Paul Bender Copyright (c) 2008
026 */
027public class EliteXNetProgrammer extends XNetProgrammer {
028
029    // Message timeout lengths.  These have been determined by
030    // experimentation, and may need to be adjusted
031    private static final int ELITEMESSAGETIMEOUT = 10000;
032    private static final int EliteXNetProgrammerTimeout = 20000;
033
034    public EliteXNetProgrammer(XNetTrafficController tc) {
035        super(tc);
036    }
037
038    /** 
039     * {@inheritDoc}
040     */
041    @Override
042    public synchronized void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
043        final int CV = Integer.parseInt(CVname);
044        log.debug("writeCV {} listens {}", CV, p);
045        useProgrammer(p);
046        _progRead = false;
047        // set new state & save values
048        progState = REQUESTSENT;
049        _val = val;
050        _cv = 0xffff & CV;
051
052        try {
053            // start the error timer
054            restartTimer(EliteXNetProgrammerTimeout);
055
056            // format and send message to go to program mode
057            if (getMode().equals(ProgrammingMode.PAGEMODE)) {
058                XNetMessage msg = XNetMessage.getWritePagedCVMsg(CV, val);
059                msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE);
060                msg.setTimeout(ELITEMESSAGETIMEOUT);
061                controller().sendXNetMessage(msg, this);
062            } else if (getMode().equals(ProgrammingMode.DIRECTBITMODE) || getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) {
063                XNetMessage msg = XNetMessage.getWriteDirectCVMsg(CV, val);
064                msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE);
065                msg.setTimeout(ELITEMESSAGETIMEOUT);
066                controller().sendXNetMessage(msg, this);
067            } else { // register mode by elimination
068                XNetMessage msg = XNetMessage.getWriteRegisterMsg(registerFromCV(CV), val);
069                msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE);
070                msg.setTimeout(ELITEMESSAGETIMEOUT);
071                controller().sendXNetMessage(msg, this);
072            }
073        } catch (jmri.ProgrammerException e) {
074            progState = NOTPROGRAMMING;
075            throw e;
076        }
077
078    }
079
080    /** 
081     * {@inheritDoc}
082     */
083    @Override
084    public synchronized void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
085        readCV(CV, p);
086    }
087
088    /** 
089     * {@inheritDoc}
090     */
091    @Override
092    public synchronized void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException {
093        final int CV = Integer.parseInt(CVname);
094        log.debug("readCV {} listens {}", CV, p);
095
096        if (!getCanRead()) {
097            // should not invoke this if cant read, but if done anyway set NotImplemented error
098            notifyProgListenerEnd(p,CV,jmri.ProgListener.NotImplemented);
099            return;
100        }
101
102        useProgrammer(p);
103        _progRead = true;
104        // set new state
105        progState = REQUESTSENT;
106        _cv = 0xffff & CV;
107        try {
108            // start the error timer
109            restartTimer(EliteXNetProgrammerTimeout);
110
111            // format and send message to go to program mode
112            if (getMode().equals(ProgrammingMode.PAGEMODE)) {
113                XNetMessage msg = XNetMessage.getReadPagedCVMsg(CV);
114                msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE);
115                msg.setTimeout(ELITEMESSAGETIMEOUT);
116                controller().sendXNetMessage(msg, this);
117            } else if (getMode().equals(ProgrammingMode.DIRECTBITMODE) || getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) {
118                XNetMessage msg = XNetMessage.getReadDirectCVMsg(CV);
119                msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE);
120                msg.setTimeout(ELITEMESSAGETIMEOUT);
121                controller().sendXNetMessage(msg, this);
122            } else { // register mode by elimination
123                XNetMessage msg = XNetMessage.getReadRegisterMsg(registerFromCV(CV));
124                msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE);
125                msg.setTimeout(ELITEMESSAGETIMEOUT);
126                controller().sendXNetMessage(msg, this);
127            }
128        } catch (jmri.ProgrammerException e) {
129            progState = NOTPROGRAMMING;
130            throw e;
131        }
132
133    }
134
135    /** 
136     * {@inheritDoc}
137     */
138    @Override
139    public synchronized void message(XNetReply m) {
140        if (m.getElement(0) == XNetConstants.CS_INFO
141                && m.getElement(1) == XNetConstants.BC_SERVICE_MODE_ENTRY) {
142            if (!_service_mode ) {
143                // the command station is in service mode.  An "OK"
144                // message can trigger a request for service mode
145                // results if progrstate is REQUESTSENT.
146                _service_mode = true;
147            } else {  // _service_mode == true
148                // Since we get this message as both a broadcast and
149                // a directed message, ignore the message if we're
150                //already in the indicated mode
151                return;
152            }
153        }
154        if (m.getElement(0) == XNetConstants.CS_INFO
155                && m.getElement(1) == XNetConstants.BC_NORMAL_OPERATIONS) {
156            if (_service_mode) {
157                // the command station is not in service mode.  An
158                // "OK" message can not trigger a request for service
159                // mode results if progrstate is REQUESTSENT.
160                _service_mode = false;
161            } else { // _service_mode == false
162                // Since we get this message as both a broadcast and
163                // a directed message, ignore the message if we're
164                //already in the indicated mode
165                return;
166            }
167        }
168
169        if (progState == NOTPROGRAMMING) {
170            // we get the complete set of replies now, so ignore these
171        } else if (progState == REQUESTSENT) {
172            log.debug("reply in REQUESTSENT state");
173            // see if reply is the acknowledge of program mode; if not, wait for next
174            if ((_service_mode && m.isOkMessage())
175                    || (m.getElement(0) == XNetConstants.CS_INFO
176                    && (m.getElement(1) == XNetConstants.BC_SERVICE_MODE_ENTRY
177                    || m.getElement(1) == XNetConstants.PROG_CS_READY))) {
178                stopTimer();
179
180                if (!getCanRead()) {
181                    // should not read here if cant read, because read shouldnt be invoked, but still attempt to handle
182                    log.debug("CV reading not supported, exiting REQUESTSENT state");
183                    stopTimer();
184                    notifyProgListenerEnd(_val, jmri.ProgListener.OK);
185                }
186            } else if (m.getElement(0) == XNetConstants.CS_INFO
187                    && m.getElement(1) == XNetConstants.CS_NOT_SUPPORTED) {
188                // programming operation not supported by this command station
189                progState = NOTPROGRAMMING;
190                notifyProgListenerEnd(_val, jmri.ProgListener.NotImplemented);
191            } else if (m.getElement(0) == XNetConstants.CS_INFO
192                    && m.getElement(1) == XNetConstants.BC_NORMAL_OPERATIONS) {
193                    // On the Elite, the broadcast exit service mode message
194                    // needs to triger the request for results.
195                    progState = INQUIRESENT;
196                    //start the error timer
197                    restartTimer(EliteXNetProgrammerTimeout);
198                    XNetMessage resultMsg = XNetMessage.getServiceModeResultsMsg();
199                    resultMsg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE);
200                    resultMsg.setTimeout(ELITEMESSAGETIMEOUT);
201                    controller().sendXNetMessage(resultMsg, this);
202            } else if (m.getElement(0) == XNetConstants.CS_INFO
203                    && m.getElement(1) == XNetConstants.PROG_SHORT_CIRCUIT) {
204                // We experienced a short Circuit on the Programming Track
205                log.error("Short Circuit While Programming Decoder");
206                progState = NOTPROGRAMMING;
207                stopTimer();
208                notifyProgListenerEnd(_val, jmri.ProgListener.ProgrammingShort);
209            } else if (m.isCommErrorMessage()) {
210                // We experienced a communicatiosn error
211                // If this is a Timeslot error, ignore it,
212                //otherwise report it as an error
213                if (m.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_TIMESLOT_ERROR) {
214                    return;
215                }
216                log.error("Communications error in REQUESTSENT state while programming.  Error: {}", m);
217                progState = NOTPROGRAMMING;
218                stopTimer();
219                notifyProgListenerEnd(_val, jmri.ProgListener.CommError);
220            }
221        } else if (progState == INQUIRESENT) {
222            log.debug("reply in INQUIRESENT state");
223            // check for right message, else return
224            if (m.isPagedModeResponse()) {
225                // valid operation response, but does it belong to us?
226                try {
227                    // we always save the cv number, but if
228                    // we are using register mode, there is
229                    // at least one case (CV29) where the value
230                    // returned does not match the value we saved.
231                    if (m.getServiceModeCVNumber() != _cv
232                            && m.getServiceModeCVNumber() != registerFromCV(_cv)) {
233                        log.debug(" result for CV {} expecting {}", m.getServiceModeCVNumber(), _cv);
234                        return;
235                    }
236                } catch (jmri.ProgrammerException e) {
237                    progState = NOTPROGRAMMING;
238                    notifyProgListenerEnd(_val, jmri.ProgListener.UnknownError);
239                }
240
241                // see why waiting
242                if (_progRead) {
243                    // read was in progress - get return value
244                    _val = m.getServiceModeCVValue();
245                }
246                progState = NOTPROGRAMMING;
247                stopTimer();
248                // if this was a read, we cached the value earlier.
249                // If its a write, we're to return the original write value
250                notifyProgListenerEnd(_val, jmri.ProgListener.OK);
251            } else if (m.isDirectModeResponse()) {
252                // valid operation response, but does it belong to us?
253                if (m.getServiceModeCVNumber() != _cv) {
254                    log.debug(" result for CV {} expecting {}", m.getServiceModeCVNumber(), _cv);
255                    return;
256                }
257                // see why waiting
258                if (_progRead) {
259                    // read was in progress - get return value
260                    _val = m.getServiceModeCVValue();
261                }
262                progState = NOTPROGRAMMING;
263                stopTimer();
264                // if this was a read, we cached the value earlier.  If its a
265                // write, we're to return the original write value
266                notifyProgListenerEnd(_val, jmri.ProgListener.OK);
267            } else if (m.getElement(0) == XNetConstants.CS_INFO
268                    && m.getElement(1) == XNetConstants.PROG_BYTE_NOT_FOUND) {
269                // "data byte not found", e.g. no reply
270                progState = NOTPROGRAMMING;
271                stopTimer();
272                notifyProgListenerEnd(_val, jmri.ProgListener.NoLocoDetected);
273            } else if (m.getElement(0) == XNetConstants.CS_INFO
274                    && m.getElement(1) == XNetConstants.PROG_SHORT_CIRCUIT) {
275                // We experienced a short Circuit on the Programming Track
276                log.error("Short Circuit While Programming Decoder");
277                progState = NOTPROGRAMMING;
278                stopTimer();
279                notifyProgListenerEnd(_val, jmri.ProgListener.ProgrammingShort);
280            } else if (m.isCommErrorMessage()) {
281                // We experienced a communicatiosn error
282                // If this is a Timeslot error, ignore it,
283                //otherwise report it as an error
284                if (m.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_TIMESLOT_ERROR) {
285                    return;
286                }
287                log.error("Communications error in INQUIRESENT state while programming.  Error: {}", m);
288                progState = NOTPROGRAMMING;
289                stopTimer();
290                notifyProgListenerEnd(_val, jmri.ProgListener.CommError);
291            } else {
292                // nothing important, ignore
293            }
294        } else {
295            log.debug("reply in un-decoded state");
296        }
297    }
298
299    /** 
300     * {@inheritDoc}
301     */
302    @Override
303    public synchronized void message(XNetMessage l) {
304        // this class is not interested in messages to the command station.
305    }
306
307    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EliteXNetProgrammer.class);
308
309}