001package jmri.jmrix.lenz.li100;
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 Lenz 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
017 * <li>Send Request for Service Mode Results request
018 * <li>Wait for results reply, interpret
019 * <li>Send Resume Operations request
020 * <li>Wait for Normal Operations Resumed broadcast
021 * </ul>
022 *
023 * @author Bob Jacobsen Copyright (c) 2002, 2007
024 * @author Paul Bender Copyright (c) 2003, 2004, 2005, 2009
025 * @author Giorgio Terdina Copyright (c) 2007
026 */
027public class LI100XNetProgrammer extends XNetProgrammer {
028
029    private static final int RETURNSENT = 3;
030
031    // save the last XpressNet message for retransmission after a
032    // communication error..
033    private XNetMessage lastRequestMessage = null;
034
035    private int _error = 0;
036
037    public LI100XNetProgrammer(XNetTrafficController tc) {
038        super(tc);
039    }
040
041    /** 
042     * {@inheritDoc}
043     */
044    @Override
045    public synchronized void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
046        final int CV = Integer.parseInt(CVname);
047        log.debug("writeCV {} listens {}", CV, p);
048        useProgrammer(p);
049        _progRead = false;
050        // set new state & save values
051        progState = REQUESTSENT;
052        _val = val;
053        _cv = 0xffff & CV;
054
055        try {
056            // start the error timer
057            restartTimer(XNetProgrammerTimeout);
058
059            // format and send message to go to program mode
060            if (getMode().equals(ProgrammingMode.PAGEMODE)) {
061                XNetMessage msg = XNetMessage.getWritePagedCVMsg(CV, val);
062                msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE);
063                lastRequestMessage = new XNetMessage(msg);
064                controller().sendXNetMessage(msg, this);
065            } else if (getMode().equals(ProgrammingMode.DIRECTBITMODE) || getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) {
066                XNetMessage msg = XNetMessage.getWriteDirectCVMsg(CV, val);
067                msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE);
068                lastRequestMessage = new XNetMessage(msg);
069                controller().sendXNetMessage(msg, this);
070            } else { // register mode by elimination
071                XNetMessage msg = XNetMessage.getWriteRegisterMsg(registerFromCV(CV), val);
072                msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE);
073                lastRequestMessage = new XNetMessage(msg);
074                controller().sendXNetMessage(msg, this);
075            }
076        } catch (jmri.ProgrammerException e) {
077            progState = NOTPROGRAMMING;
078            throw e;
079        }
080    }
081
082    /** 
083     * {@inheritDoc}
084     */
085    @Override
086    public synchronized void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
087        readCV(CV, p);
088    }
089
090    /** 
091     * {@inheritDoc}
092     */
093    @Override
094    public synchronized void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException {
095        final int CV = Integer.parseInt(CVname);
096        log.debug("readCV {} listens {}", CV, p);
097
098        if (!getCanRead()) {
099            // should not invoke this if cant read, but if done anyway set NotImplemented error
100            notifyProgListenerEnd(p,CV,jmri.ProgListener.NotImplemented);
101            return;
102        }
103
104        useProgrammer(p);
105        _progRead = true;
106        // set new state
107        progState = REQUESTSENT;
108        _cv = 0xffff & CV;
109        try {
110            // start the error timer
111            restartTimer(XNetProgrammerTimeout);
112
113            // format and send message to go to program mode
114            if (getMode() == ProgrammingMode.PAGEMODE) {
115                XNetMessage msg = XNetMessage.getReadPagedCVMsg(CV);
116                msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE);
117                lastRequestMessage = new XNetMessage(msg);
118                controller().sendXNetMessage(msg, this);
119            } else if (getMode().equals(ProgrammingMode.DIRECTBITMODE) || getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) {
120                XNetMessage msg = XNetMessage.getReadDirectCVMsg(CV);
121                msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE);
122                lastRequestMessage = new XNetMessage(msg);
123                controller().sendXNetMessage(msg, this);
124            } else { // register mode by elimination
125                XNetMessage msg = XNetMessage.getReadRegisterMsg(registerFromCV(CV));
126                msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE);
127                lastRequestMessage = new XNetMessage(msg);
128                controller().sendXNetMessage(msg, this);
129            }
130        } catch (jmri.ProgrammerException e) {
131            progState = NOTPROGRAMMING;
132            throw e;
133        }
134    }
135
136    /** 
137     * {@inheritDoc}
138     */
139    @Override
140    public synchronized void message(XNetReply m) {
141        if (m.getElement(0) == XNetConstants.CS_INFO
142                && m.getElement(1) == XNetConstants.BC_SERVICE_MODE_ENTRY) {
143            if (!_service_mode) {
144                // the command station is in service mode.  An "OK"
145                // message can trigger a request for service mode
146                // results if progrstate is REQUESTSENT.
147                log.debug("change _service_mode to true");
148                _service_mode = true;
149            } else { // _service_mode == true
150                // Since we get this message as both a broadcast and
151                // a directed message, ignore the message if we're
152                //already in the indicated mode
153                log.debug("_service_mode already true");
154                return;
155            }
156        }
157        if (m.getElement(0) == XNetConstants.CS_INFO
158                && m.getElement(1) == XNetConstants.BC_NORMAL_OPERATIONS) {
159            if (_service_mode) {
160                // the command station is not in service mode.  An
161                // "OK" message can not trigger a request for service
162                // mode results if progrstate is REQUESTSENT.
163                log.debug("change _service_mode to false");
164                _service_mode = false;
165            } else { // _service_mode == false
166                // Since we get this message as both a broadcast and
167                // a directed message, ignore the message if we're
168                //already in the indicated mode
169                log.debug("_service_mode already false");
170                return;
171            }
172        }
173
174        if (progState == NOTPROGRAMMING) {
175            // we get the complete set of replies now, so ignore these
176        } else if (progState == REQUESTSENT) {
177            log.debug("reply in REQUESTSENT state");
178            // see if reply is the acknowledge of program mode; if not, wait for next
179            if ((_service_mode && m.isOkMessage())
180                    || (m.getElement(0) == XNetConstants.CS_INFO
181                    && (m.getElement(1) == XNetConstants.BC_SERVICE_MODE_ENTRY
182                    || m.getElement(1) == XNetConstants.PROG_CS_READY))) {
183                stopTimer();
184
185                if (!getCanRead()) {
186                    // on systems like the Roco MultiMaus
187                    // (which does not support reading)
188                    // let a timeout occur so the system
189                    // has time to write data to the
190                    // decoder
191                    restartTimer(SHORT_TIMEOUT);
192                    return;
193                }
194
195                // here ready to request the results
196                progState = INQUIRESENT;
197                //start the error timer
198                restartTimer(XNetProgrammerTimeout);
199
200                controller().sendXNetMessage(XNetMessage.getServiceModeResultsMsg(),
201                        this);
202            } else if (m.getElement(0) == XNetConstants.CS_INFO
203                    && m.getElement(1) == XNetConstants.CS_NOT_SUPPORTED) {
204                // programming operation not supported by this command station
205                progState = RETURNSENT;
206                _error = jmri.ProgListener.NotImplemented;
207                // create a request to exit service mode and
208                // send the message to the command station
209                controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(),
210                        this);
211            } else if (m.getElement(0) == XNetConstants.CS_INFO
212                    && m.getElement(1) == XNetConstants.BC_NORMAL_OPERATIONS) {
213                // We Exited Programming Mode early
214                log.debug("Service mode exited before sequence complete.");
215                progState = NOTPROGRAMMING;
216                stopTimer();
217                notifyProgListenerEnd(_val, jmri.ProgListener.SequenceError);
218            } else if (m.getElement(0) == XNetConstants.CS_INFO
219                    && m.getElement(1) == XNetConstants.PROG_SHORT_CIRCUIT) {
220                // We experienced a short Circuit on the Programming Track
221                log.error("Short Circuit While Programming Decoder");
222                progState = RETURNSENT;
223                _error = jmri.ProgListener.ProgrammingShort;
224                // create a request to exit service mode and
225                // send the message to the command station
226                controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(),
227                        this);
228            } else if (m.isCommErrorMessage()) {
229                // We experienced a communicatiosn error
230                if (m.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_TIMESLOT_ERROR) {
231                    // If this is a Timeslot error, ignore it,
232                    // otherwise report it as an error
233                } else if (!_service_mode) {
234                    log.error("Communications error in REQUESTSENT state before entering service mode.  Error: {}",m);
235                    controller().sendXNetMessage(lastRequestMessage, this);
236                } else {
237                    log.error("Communications error in REQUESTSENT state after entering service mode.  Error: {}",m);
238                    progState = RETURNSENT;
239                    _error = jmri.ProgListener.CommError;
240                    // create a request to exit service mode and
241                    // send the message to the command station
242                    controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(),
243                            this);
244                }
245            }
246        } else if (progState == INQUIRESENT) {
247            log.debug("reply in INQUIRESENT state");
248            // check for right message, else return
249            if (m.isPagedModeResponse()) {
250                // valid operation response, but does it belong to us?
251                try {
252                    // we always save the cv number, but if
253                    // we are using register mode, there is
254                    // at least one case (CV29) where the value
255                    // returned does not match the value we saved.
256                    if (m.getServiceModeCVNumber() != _cv
257                            && m.getServiceModeCVNumber() != registerFromCV(_cv)) {
258                        log.debug(" result for CV {} expecting {}", m.getServiceModeCVNumber(), _cv);
259                        return;
260                    }
261                } catch (jmri.ProgrammerException e) {
262                    progState = NOTPROGRAMMING;
263                    notifyProgListenerEnd(_val, jmri.ProgListener.UnknownError);
264                }
265                // see why waiting
266                if (_progRead) {
267                    // read was in progress - get return value
268                    _val = m.getServiceModeCVValue();
269                }
270                progState = RETURNSENT;
271                _error = jmri.ProgListener.OK;
272                // create a request to exit service mode and
273                // send the message to the command station
274                controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(),
275                        this);
276            } else if (m.isDirectModeResponse()) {
277                // valid operation response, but does it belong to us?
278                if (m.getServiceModeCVNumber() != _cv) {
279                    log.debug(" result for CV {} expecting {}", m.getServiceModeCVNumber(), _cv);
280                    return;
281                }
282
283                // see why waiting
284                if (_progRead) {
285                    // read was in progress - get return value
286                    _val = m.getServiceModeCVValue();
287                }
288                progState = RETURNSENT;
289                _error = jmri.ProgListener.OK;
290                stopTimer();
291                // create a request to exit service mode and
292                // send the message to the command station
293                controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(),
294                        this);
295            } else if (m.getElement(0) == XNetConstants.CS_INFO
296                    && m.getElement(1) == XNetConstants.PROG_BYTE_NOT_FOUND) {
297                // "data byte not found", e.g. no reply
298                progState = RETURNSENT;
299                _error = jmri.ProgListener.NoLocoDetected;
300                // create a request to exit service mode and
301                // send the message to the command station
302                controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(),
303                        this);
304            } else if (m.getElement(0) == XNetConstants.CS_INFO
305                    && m.getElement(1) == XNetConstants.PROG_SHORT_CIRCUIT) {
306                // We experienced a short Circuit on the Programming Track
307                log.error("Short Circuit While Programming Decoder");
308                progState = RETURNSENT;
309                _error = jmri.ProgListener.ProgrammingShort;
310                // create a request to exit service mode and
311                // send the message to the command station
312                controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(),
313                        this);
314            } else if (m.getElement(0) == XNetConstants.CS_INFO
315                    && m.getElement(1) == XNetConstants.BC_NORMAL_OPERATIONS) {
316                // We Exited Programming Mode early
317                log.error("Service mode exited before sequence complete.");
318                progState = NOTPROGRAMMING;
319                stopTimer();
320                notifyProgListenerEnd(_val, jmri.ProgListener.SequenceError);
321            } else if (m.isCommErrorMessage()) {
322                // We experienced a communicatiosn error
323                if (m.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_TIMESLOT_ERROR) {
324                    // If this is a Timeslot error, ignore it
325                } else if (_service_mode) {
326                    // If we're in service mode, retry sending the
327                    // result request.
328                    log.error("Communications error in INQUIRESENT state while in service mode.  Error: {}", m);
329                    controller().sendXNetMessage(XNetMessage.getServiceModeResultsMsg(),
330                            this);
331                } else {
332                    //otherwise report it as an error
333                    log.error("Communications error in INQUIRESENT state after exiting service mode.  Error: {}", m);
334                    progState = RETURNSENT;
335                    _error = jmri.ProgListener.CommError;
336                    // create a request to exit service mode and
337                    // send the message to the command station
338                    controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(),
339                            this);
340                }
341            } else {
342                // nothing related to programming, ignore
343            }
344
345        } else if (progState == RETURNSENT) {
346            log.debug("reply in RETURNSENT state");
347            if (m.getElement(0) == XNetConstants.CS_INFO
348                    && m.getElement(1) == XNetConstants.BC_NORMAL_OPERATIONS) {
349                progState = NOTPROGRAMMING;
350                stopTimer();
351
352                // We've exited service mode.  Notify the programmer of any
353                // the results.
354                notifyProgListenerEnd(_val, _error);
355
356            }
357        } else {
358            log.debug("reply in un-decoded state");
359        }
360    }
361
362    /** 
363     * {@inheritDoc}
364     */
365     @Override
366    public synchronized void message(XNetMessage l) {
367         // this class does not use outbound messages.
368    }
369
370    /** 
371     * {@inheritDoc}
372     */
373    @Override
374    protected synchronized void timeout() {
375        // if a timeout occurs, and we are not
376        // finished programming, we need to exit
377        // service mode.
378        if (progState != NOTPROGRAMMING) {
379            // we're programming, time to stop
380            log.debug("timeout!");
381
382            progState = RETURNSENT;
383            if (!getCanRead()) {
384                _error = jmri.ProgListener.OK;  //MultiMaus etc.
385            } else // perhaps no loco present?
386            {
387                _error = jmri.ProgListener.FailedTimeout;
388            }
389
390            controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(),
391                    this);
392        }
393    }
394
395    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LI100XNetProgrammer.class);
396
397}