001package jmri.jmrit; 002 003import jmri.Programmer; 004import jmri.ProgrammingMode; 005import org.slf4j.Logger; 006import org.slf4j.LoggerFactory; 007 008/** 009 * Abstract base for common code of {@link jmri.jmrit.roster.IdentifyLoco} and 010 * {@link jmri.jmrit.decoderdefn.IdentifyDecoder}, the two classes that use a 011 * programmer to match Roster entries to what's on the programming track. 012 * <p> 013 * This is a class (instead of a {@link jmri.jmrit.roster.Roster} member 014 * function) to simplify use of {@link jmri.Programmer} callbacks. 015 * 016 * @author Bob Jacobsen Copyright (C) 2001, 2015 017 * @see jmri.jmrit.symbolicprog.CombinedLocoSelPane 018 * @see jmri.jmrit.symbolicprog.NewLocoSelPane 019 */ 020public abstract class AbstractIdentify implements jmri.ProgListener { 021 022 static final int RETRY_COUNT = 2; 023 024 public abstract boolean test1(); // no argument to start 025 026 public abstract boolean test2(int value); 027 028 public abstract boolean test3(int value); 029 030 public abstract boolean test4(int value); 031 032 public abstract boolean test5(int value); 033 034 public abstract boolean test6(int value); 035 036 public abstract boolean test7(int value); 037 038 public abstract boolean test8(int value); 039 040 public abstract boolean test9(int value); 041 042 protected AbstractIdentify(Programmer p) { 043 this.programmer = p; 044 } 045 Programmer programmer; 046 ProgrammingMode savedMode; 047 048 /** 049 * Update the status field (if any). Invoked with "Done" when the results 050 * are in. 051 * 052 * @param status the new status 053 */ 054 protected abstract void statusUpdate(String status); 055 056 /** 057 * Start the identification state machine. 058 */ 059 public void start() { 060 if (log.isDebugEnabled()) { 061 log.debug("identify begins"); 062 } 063 // must be idle, or something quite bad has happened 064 if (state != 0) { 065 log.error("start with state {}, should have been zero", state); 066 } 067 068 if (programmer != null) { 069 savedMode = programmer.getMode(); // In case we need to change modes 070 } 071 072 // The first test is invoked here; the rest are handled in the programmingOpReply callback 073 state = 1; 074 retry = 0; 075 lastValue = -1; 076 setOptionalCv(false); 077 test1(); 078 } 079 080 /** 081 * Stop the identification state machine. This also stops the identification 082 * process. Its invoked when a testN returns true; that routine should _not_ 083 * have invoked a read or write that will result in a callback. 084 */ 085 protected void identifyDone() { 086 if (log.isDebugEnabled()) { 087 log.debug("identifyDone ends in state {}", state); 088 } 089 statusUpdate("Done"); 090 state = 0; 091 } 092 093 /** 094 * Internal method to handle the programmer callbacks, e.g. when a CV read 095 * request terminates. Each will reduce (if possible) the list of consistent 096 * decoders, and starts the next step. 097 * 098 * @param value the value returned 099 * @param status the status reported 100 */ 101 @Override 102 public void programmingOpReply(int value, int status) { 103 // we abort if there's no programmer 104 // (doing this now to simplify later) 105 if (programmer == null) { 106 log.warn("No programmer connected"); 107 statusUpdate("No programmer connected"); 108 109 state = 0; 110 retry = 0; 111 error(); 112 return; 113 } 114 log.debug("Entering programmingOpReply, state {}, isOptionalCv {},retry {}, value {}, status {}", state, isOptionalCv(), retry, value, programmer.decodeErrorCode(status)); 115 116 // we check if the status isn't normal 117 if (status != jmri.ProgListener.OK) { 118 if (retry < RETRY_COUNT) { 119 statusUpdate("Programmer error: " 120 + programmer.decodeErrorCode(status)); 121 state--; 122 retry++; 123 value = lastValue; // Restore the last good value. Needed for retries. 124 } else if (state == 1 && programmer.getMode() != ProgrammingMode.PAGEMODE 125 && programmer.getSupportedModes().contains(ProgrammingMode.PAGEMODE)) { 126 programmer.setMode(ProgrammingMode.PAGEMODE); // Try paged mode only if test1 (CV8) 127 retry = 0; 128 state--; 129 value = lastValue; // Restore the last good value. Needed for retries. 130 log.warn("{} readng CV {}, trying {} mode", programmer.decodeErrorCode(status), 131 cvToRead, programmer.getMode().toString()); 132 } else { 133 retry = 0; 134 if (programmer.getMode() != savedMode) { // restore original mode 135 log.warn("Restoring {} mode", savedMode.toString()); 136 programmer.setMode(savedMode); 137 } 138 if (isOptionalCv()) { 139 log.warn("CV {} is optional. Will assume not present...", cvToRead); 140 statusUpdate("CV " + cvToRead + " is optional. Will assume not present..."); 141 } else { 142 log.warn("Stopping due to error: {}", programmer.decodeErrorCode(status)); 143 statusUpdate("Stopping due to error: " 144 + programmer.decodeErrorCode(status)); 145 state = 0; 146 error(); 147 return; 148 } 149 } 150 } else { 151 retry = 0; 152 lastValue = value; // Save the last good value. Needed for retries. 153 setOptionalCv(false); // successful read clears flag 154 } 155 // continuing for normal operation 156 // this should eventually be something smarter, maybe using reflection, 157 // but for now... 158 log.debug("Was state {}, switching to state {}, test{}, isOptionalCv {},retry {}, value {}, status {}", 159 state, state + 1, state + 1, isOptionalCv(), retry, value, programmer.decodeErrorCode(status)); 160 switch (state) { 161 case 0: 162 state = 1; 163 if (test1()) { 164 identifyDone(); 165 } 166 return; 167 case 1: 168 state = 2; 169 if (test2(value)) { 170 identifyDone(); 171 } 172 return; 173 case 2: 174 state = 3; 175 if (test3(value)) { 176 identifyDone(); 177 } 178 return; 179 case 3: 180 state = 4; 181 if (test4(value)) { 182 identifyDone(); 183 } 184 return; 185 case 4: 186 state = 5; 187 if (test5(value)) { 188 identifyDone(); 189 } 190 return; 191 case 5: 192 state = 6; 193 if (test6(value)) { 194 identifyDone(); 195 } 196 return; 197 case 6: 198 state = 7; 199 if (test7(value)) { 200 identifyDone(); 201 } 202 return; 203 case 7: 204 state = 8; 205 if (test8(value)) { 206 identifyDone(); 207 } 208 return; 209 case 8: 210 state = 9; 211 if (test9(value)) { 212 identifyDone(); 213 } else { 214 log.error("test9 with value = {} returned false, but there is no next step", value); 215 } 216 return; 217 default: 218 // this is an error 219 log.error("unexpected state in normal operation: {} value: {}, ending identification", state, value); 220 identifyDone(); 221 } 222 } 223 224 /** 225 * Abstract routine to notify of errors. 226 */ 227 protected abstract void error(); 228 229 /** 230 * To check if running now. 231 * 232 * @return true if running; false otherwise 233 */ 234 public boolean isRunning() { 235 return state != 0; 236 } 237 238 /** 239 * State of the internal sequence. 240 */ 241 int state = 0; 242 int retry = 0; 243 int lastValue = 0; 244 boolean optionalCv = false; 245 String cvToRead; 246 String cvToWrite; 247 248 /** 249 * Read a single CV for the next step. 250 * 251 * @param cv the CV to read 252 */ 253 protected void readCV(String cv) { 254 if (programmer == null) { 255 statusUpdate("No programmer connected"); 256 } else { 257 cvToRead = cv; 258 log.debug("Invoking readCV {}", cvToRead); 259 try { 260 programmer.readCV(cv, this); 261 } catch (jmri.ProgrammerException ex) { 262 statusUpdate("" + ex); 263 } 264 } 265 } 266 267 /** 268 * Write a single CV for the next step. 269 * 270 * @param cv the CV to write 271 * @param value to write to the CV 272 * 273 */ 274 protected void writeCV(String cv, int value) { 275 if (programmer == null) { 276 statusUpdate("No programmer connected"); 277 } else { 278 cvToWrite = cv; 279 log.debug("Invoking writeCV {}", cvToWrite); 280 try { 281 programmer.writeCV(cv, value, this); 282 } catch (jmri.ProgrammerException ex) { 283 statusUpdate("" + ex); 284 } 285 } 286 } 287 288 /** 289 * Check the current status of the {@code optionalCv} flag. 290 * <ul> 291 * <li>If {@code true}, prevents the next CV read from aborting the 292 * identification process.</li> 293 * <li>Always {@code false} after a successful read.</li> 294 * </ul> 295 * 296 * @return the current status of the {@code optionalCv} flag 297 */ 298 public boolean isOptionalCv() { 299 return optionalCv; 300 } 301 302 /** 303 * 304 * Specify whether the next CV read may legitimately fail in some cases. 305 * 306 * @param flag Set {@code true} to indicate that the next read may fail. A 307 * successful read will automatically set to {@code false}. 308 */ 309 public void setOptionalCv(boolean flag) { 310 this.optionalCv = flag; 311 } 312 313 // initialize logging 314 private final static Logger log = LoggerFactory.getLogger(AbstractIdentify.class); 315 316}