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}