001package jmri.jmrix.sprog.update;
002
003import static jmri.jmrix.sprog.SprogConstants.TC_BOOT_REPLY_TIMEOUT;
004
005import java.util.Vector;
006import jmri.jmrix.sprog.SprogListener;
007import jmri.jmrix.sprog.SprogMessage;
008import jmri.jmrix.sprog.SprogReply;
009import jmri.jmrix.sprog.SprogSystemConnectionMemo;
010import jmri.jmrix.sprog.SprogTrafficController;
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014/**
015 * Get the firmware version of the attached SPROG.
016 * <p>
017 * Updated April 2016 by Andrew Crosland: look for the correct replies, which may
018 * not be the very next message after a query is sent, due to slot manager
019 * traffic. Add Pi-SPROG version decoding.
020 *
021 * @author Andrew Crosland Copyright (C) 2012, 2016
022 */
023public class SprogVersionQuery implements SprogListener {
024
025    String replyString;
026    SprogTrafficController tc;
027    SprogVersion ver;
028
029    // enum for version query states
030    enum QueryState {
031
032        IDLE,
033        CRSENT, // awaiting reply to " "
034        QUERYSENT, // awaiting reply to "?"
035        DONE
036    }       // Version has been found
037    QueryState state = QueryState.IDLE;
038
039    final protected int LONG_TIMEOUT = 2000;
040    javax.swing.Timer timer = null;
041
042    private SprogSystemConnectionMemo _memo = null;
043
044    public SprogVersionQuery(SprogSystemConnectionMemo memo) {
045        if (log.isDebugEnabled()) {
046            log.debug("setting instance: {}", this);
047        }
048        _memo = memo;
049        tc = _memo.getSprogTrafficController();
050        state = QueryState.IDLE;
051    }
052
053    protected static final Vector<SprogVersionListener> versionListeners = new Vector<SprogVersionListener>();
054
055    protected synchronized void addSprogVersionListener(SprogVersionListener l) {
056        // add only if not already registered
057        if (l == null) {
058            throw new java.lang.NullPointerException();
059        }
060        if (!versionListeners.contains(l)) {
061            versionListeners.addElement(l);
062        }
063    }
064
065    /**
066     * Remove a SprogVersionListener.
067     * Stops Timer ( if running ), when no further Listeners are present.
068     * @param l the Listener to remove.
069     */
070    public synchronized void removeSprogVersionListener(SprogVersionListener l) {
071        if (versionListeners.contains(l)) {
072            versionListeners.removeElement(l);
073        }
074        if (versionListeners.size() == 0 ) {
075            stopTimer();
076        }
077    }
078
079    @SuppressWarnings("unchecked")
080    private synchronized Vector<SprogVersionListener> getCopyOfListeners() {
081        return (Vector<SprogVersionListener>) versionListeners.clone();
082    }
083
084    synchronized public void requestVersion(SprogVersionListener l) {
085        SprogMessage m;
086        if (log.isDebugEnabled()) {
087            log.debug("SprogVersion requested by {}", l.toString());
088        }
089        if (state == QueryState.DONE) {
090            // Reply immediately
091            try {
092                l.notifyVersion(ver);
093            } catch (jmri.ProgrammerException e) {
094                log.error("Programmer Exception in non-programming context", e);
095            }
096            return;
097        }
098        // Remember this listener
099        this.addSprogVersionListener(l);
100        if (state == QueryState.IDLE) {
101            // Kick things off with a blank message
102            m = new SprogMessage(1);
103            m.setOpCode(' ');
104            // Set a short timeout for the traffic controller
105            tc.setTimeout(TC_BOOT_REPLY_TIMEOUT);
106            tc.sendSprogMessage(m, this);
107            state = QueryState.CRSENT;
108            startLongTimer();
109        }
110    }
111
112    /**
113     * Notify all registered listeners of the SPROG version.
114     *
115     * @param v version to send notify to
116     */
117    protected synchronized void notifyVersion(SprogVersion v) {
118        ver = v;
119        for (SprogVersionListener listener : getCopyOfListeners()) {
120            try {
121                try {
122                    listener.notifyVersion(ver);
123                } catch (jmri.ProgrammerException e) {
124                    log.error("Programmer Exception in non-programming context", e);
125                }
126                versionListeners.remove(listener);
127            } catch (Exception e) {
128                log.warn("notify: During dispatch to {}", listener, e);
129            }
130        }
131    }
132
133    /**
134     * SprogListener notify Message (not used).
135     */
136    @Override
137    public void notifyMessage(SprogMessage m) {
138    }   // Ignore
139
140    /**
141     * SprogListener notifyReply listens to replies and looks for version reply.
142     */
143    @Override
144    synchronized public void notifyReply(SprogReply m) {
145        SprogMessage msg;
146        SprogVersion v;
147        replyString = m.toString();
148        switch (state) {
149            case IDLE: {
150                if (log.isDebugEnabled()) {
151                    log.debug("reply in IDLE state");
152                }
153                break;
154            }
155
156            case CRSENT: {
157                log.debug("reply in CRSENT state {}", replyString);
158                if ((replyString.indexOf("P>")) >= 0) {
159                    stopTimer();
160                    msg = new SprogMessage(1);
161                    msg.setOpCode('?');
162                    tc.sendSprogMessage(msg, this);
163                    state = QueryState.QUERYSENT;
164                    startLongTimer();
165                }
166                break;
167            }
168
169            case QUERYSENT: {
170                log.debug("reply in QUERYSENT state {}", replyString);
171                if (replyString.contains("SPROG")) {
172                    stopTimer();
173                    String[] splits = replyString.split("\n");
174                    splits = splits[1].split(" ");
175                    int index = 1;
176                    log.debug("Elements in version reply: {}", splits.length);
177                    log.debug("First element: <{}>", splits[0]);
178                    if (splits[0].contains("Pi-SPROG")) {
179                        log.debug("Found a Pi-SPROG {}", splits[index]);
180                        switch (splits[1]) {
181                            case "Nano":
182                                v = new SprogVersion(new SprogType(SprogType.PISPROGNANO), splits[2].substring(1));
183                                break;
184                            case "One":
185                                v = new SprogVersion(new SprogType(SprogType.PISPROGONE), splits[2].substring(1));
186                                break;
187                            default:
188                                if (log.isDebugEnabled()) {
189                                    log.debug("Unrecognised Pi-SPROG {}", splits[1]);
190                                }
191                                v = new SprogVersion(new SprogType(SprogType.NOT_RECOGNISED));
192                                break;
193                        }                
194                    } else if (splits[0].contains("SPROG")) {
195                        log.debug("Found a SPROG {}", splits[index]);
196                        switch (splits[index]) {
197                            case "3":
198                                index += 2;
199                                v = new SprogVersion(new SprogType(SprogType.SPROG3), splits[index]);
200                                break;
201                            case "IV":
202                                index += 2;
203                                v = new SprogVersion(new SprogType(SprogType.SPROGIV), splits[index]);
204                                break;
205                            case "5":
206                                index += 2;
207                                v = new SprogVersion(new SprogType(SprogType.SPROG5), splits[index]);
208                                break;
209                            case "Nano":
210                                index += 2;
211                                v = new SprogVersion(new SprogType(SprogType.NANO), splits[index]);
212                                break;
213                            case "Sniffer":
214                                index += 2;
215                                v = new SprogVersion(new SprogType(SprogType.SNIFFER), splits[index]);
216                                break;
217                            case "II":
218                                index++;
219                                if (splits[index].equals("USB")) {
220                                    index += 2;
221                                    v = new SprogVersion(new SprogType(SprogType.SPROGIIUSB), splits[index]);
222                                } else {
223                                    index++;
224                                    v = new SprogVersion(new SprogType(SprogType.SPROGII), splits[index]);
225                                }   break;
226                            case "Ver":
227                                index += 1;
228                                v = new SprogVersion(new SprogType(SprogType.SPROGV4), splits[index]);
229                                break;
230                            default:
231                                log.debug("Unrecognised SPROG {}", splits[index]);
232                                v = new SprogVersion(new SprogType(SprogType.NOT_RECOGNISED));
233                                break;
234                        }
235                    } else {
236                        // Reply contained "SPROG" but couldn't be parsed
237                        log.warn("Found an unknown SPROG {}", splits[index]);
238                        v = new SprogVersion(new SprogType(SprogType.NOT_RECOGNISED));
239                    }
240
241                    // Correct for SPROG IIv3/IIv4 which are different hardware
242                    if ((v.sprogType.sprogType == SprogType.SPROGII) && (v.getMajorVersion() == 3)) {
243                        v = new SprogVersion(new SprogType(SprogType.SPROGIIv3), v.sprogVersion);
244                    } else if ((v.sprogType.sprogType == SprogType.SPROGII) && (v.getMajorVersion() >= 4)) {
245                        v = new SprogVersion(new SprogType(SprogType.SPROGIIv4), v.sprogVersion);
246                    }
247                    log.debug("Found: {}", v.toString());
248                    notifyVersion(v);
249                    state = QueryState.DONE;
250//                    break;
251                }
252                tc.resetTimeout();
253                break;
254            }
255
256            case DONE:
257                break;
258
259            default: {
260                log.error("Unknown case");
261            }
262        }
263    }
264
265    /**
266     * Internal routine to handle a timeout.
267     */
268    synchronized protected void timeout() {
269        SprogVersion v;
270        switch (state) {
271            case CRSENT:
272                log.debug("Timeout no SPROG prompt");
273                state = QueryState.IDLE;
274                v = new SprogVersion(new SprogType(SprogType.TIMEOUT));
275                notifyVersion(v);
276                break;
277            case QUERYSENT:
278                log.debug("Timeout no SPROG found");
279                state = QueryState.IDLE;
280                v = new SprogVersion(new SprogType(SprogType.NOT_A_SPROG));
281                notifyVersion(v);
282                break;
283            case DONE:
284            case IDLE:
285                log.error("Timeout in unexpected state: {}", state);
286                break;
287            default:
288                log.warn("Unhandled timeout state code: {}", state);
289                break;
290        }
291        tc.resetTimeout();
292    }
293
294    /**
295     * Internal routine to restart timer with a long delay.
296     */
297    protected void startLongTimer() {
298        restartTimer(LONG_TIMEOUT);
299    }
300
301    /**
302     * Internal routine to stop timer, as all is well.
303     */
304    protected void stopTimer() {
305        if (timer != null) {
306            timer.stop();
307        }
308    }
309
310    /**
311     * Internal routine to handle timer starts and restarts.
312     * 
313     * @param delay timer delay
314     */
315    protected void restartTimer(int delay) {
316        if (timer == null) {
317            timer = new javax.swing.Timer(delay, new java.awt.event.ActionListener() {
318
319                @Override
320                public void actionPerformed(java.awt.event.ActionEvent e) {
321                    timeout();
322                }
323            });
324        }
325        timer.stop();
326        timer.setInitialDelay(delay);
327        timer.setRepeats(false);
328        timer.start();
329    }
330
331    private final static Logger log = LoggerFactory.getLogger(SprogVersionQuery.class);
332
333}