001package jmri.jmrix.nce; 002 003import java.util.ArrayList; 004import java.util.List; 005 006import javax.annotation.Nonnull; 007 008import jmri.ProgrammingMode; 009import jmri.jmrix.AbstractProgrammer; 010 011/** 012 * Convert the jmri.Programmer interface into commands for the NCE power house. 013 * <p> 014 * This has two states: NOTPROGRAMMING, and COMMANDSENT. The transitions to and 015 * from programming mode are now handled in the TrafficController code. 016 * 017 * @author Bob Jacobsen Copyright (C) 2001, 2016 018 * @author kcameron Copyright (C) 2014 019 */ 020public class NceProgrammer extends AbstractProgrammer implements NceListener { 021 022 protected NceTrafficController tc; 023 024 public NceProgrammer(NceTrafficController tc) { 025 this.tc = tc; 026 super.SHORT_TIMEOUT = 4000; 027 028 if (getSupportedModes().size() > 0) { 029 setMode(getSupportedModes().get(0)); 030 } 031 } 032 033 /** 034 * {@inheritDoc} 035 * <p> 036 * NCE programming modes available depend on settings 037 */ 038 @Override 039 @Nonnull 040 public List<ProgrammingMode> getSupportedModes() { 041 List<ProgrammingMode> ret = new ArrayList<ProgrammingMode>(); 042 if (tc == null) { 043 log.warn("getSupportedModes called with null tc", new Exception("traceback")); 044 } 045 java.util.Objects.requireNonNull(tc, "TrafficController reference needed"); 046 047 if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) { 048 // USB connection 049 switch (tc.getUsbSystem()) { 050 case NceTrafficController.USB_SYSTEM_POWERCAB: 051 case NceTrafficController.USB_SYSTEM_TWIN: 052 ret.add(ProgrammingMode.DIRECTMODE); 053 ret.add(ProgrammingMode.PAGEMODE); 054 ret.add(ProgrammingMode.REGISTERMODE); 055 return ret; 056 057 case NceTrafficController.USB_SYSTEM_SB3: 058 case NceTrafficController.USB_SYSTEM_SB5: 059 case NceTrafficController.USB_SYSTEM_POWERPRO: 060 log.trace("no programming modes available for USB {}", tc.getUsbSystem()); 061 return ret; 062 063 default: 064 log.warn("should not have hit default"); 065 return ret; 066 } 067 } 068 069 // here not USB 070 if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006) { 071 ret.add(ProgrammingMode.DIRECTMODE); 072 } 073 074 ret.add(ProgrammingMode.PAGEMODE); 075 ret.add(ProgrammingMode.REGISTERMODE); 076 077 return ret; 078 } 079 080 /** 081 * {@inheritDoc} 082 */ 083 @Override 084 public boolean getCanRead() { 085 return !(tc != null && tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_POWERCAB 086 && tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_TWIN 087 && tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE); 088 } 089 090 /** 091 * {@inheritDoc} 092 */ 093 @Override 094 public boolean getCanWrite(String cv) { 095 return getCanWrite(Integer.parseInt(cv)); 096 } 097 098 boolean getCanWrite(int cv) { 099 // prevent writing Prog Track mode CV > 256 on PowerPro 2007C and earlier 100 return !((cv > 256) 101 && ((getMode() == ProgrammingMode.PAGEMODE) 102 || (getMode() == ProgrammingMode.DIRECTMODE) 103 || (getMode() == ProgrammingMode.REGISTERMODE)) 104 && ((tc != null) 105 && ((tc.getCommandOptions() == NceTrafficController.OPTION_1999) 106 || (tc.getCommandOptions() == NceTrafficController.OPTION_2004) 107 || (tc.getCommandOptions() == NceTrafficController.OPTION_2006))) 108 && (!tc.isPwrProVer060203orLater())); 109 } 110 111 // members for handling the programmer interface 112 int progState = 0; 113 static final int NOTPROGRAMMING = 0;// is notProgramming 114 static final int COMMANDSENT = 2; // read/write command sent, waiting reply 115 static final int COMMANDSENT_2 = 4; // ops programming mode, send msg twice 116 boolean _progRead = false; 117 int _val; // remember the value being read/written for confirmative reply 118 int _cv; // remember the cv being read/written 119 120 /** 121 * {@inheritDoc} 122 */ 123 @Override 124 public synchronized void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 125 final int CV = Integer.parseInt(CVname); 126 if (log.isDebugEnabled()) { 127 log.debug("writeCV {} listens {}", CV, p); 128 } 129 useProgrammer(p); 130 // prevent writing Prog Track mode CV > 256 on PowerPro 2007C and earlier 131 if (!getCanWrite(CV)) { 132 log.error("Write {} CV {} unsupported by NCE EPROM revision {}", getMode(), CV, tc.getPwrProVersHexText()); 133 progState = NOTPROGRAMMING; 134 cleanup(); 135 notifyProgListenerEnd(_val, jmri.ProgListener.NotImplemented); 136 return; 137 } 138 _progRead = false; 139 // set state 140 progState = COMMANDSENT; 141 _val = val; 142 _cv = CV; 143 144 try { 145 // start the error timer 146 startLongTimer(); 147 148 // format and send the write message 149 tc.sendNceMessage(progTaskStart(getMode(), _val, _cv), this); 150 } catch (jmri.ProgrammerException e) { 151 progState = NOTPROGRAMMING; 152 throw e; 153 } 154 } 155 156 /** 157 * {@inheritDoc} 158 */ 159 @Override 160 public void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 161 readCV(CV, p); 162 } 163 164 /** 165 * {@inheritDoc} 166 */ 167 @Override 168 public synchronized void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException { 169 final int CV = Integer.parseInt(CVname); 170 if (log.isDebugEnabled()) { 171 log.debug("readCV {} listens {}", CV, p); 172 } 173 useProgrammer(p); 174 _progRead = true; 175 176 // set commandPending state 177 progState = COMMANDSENT; 178 _cv = CV; 179 180 try { 181 // start the error timer 182 startLongTimer(); 183 184 // format and send the write message 185 tc.sendNceMessage(progTaskStart(getMode(), -1, _cv), this); 186 } catch (jmri.ProgrammerException e) { 187 progState = NOTPROGRAMMING; 188 throw e; 189 } 190 } 191 192 private jmri.ProgListener _usingProgrammer = null; 193 194 // internal method to remember who's using the programmer 195 protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException { 196 // test for only one! 197 if (_usingProgrammer != null && _usingProgrammer != p) { 198 if (log.isInfoEnabled()) { 199 log.info("programmer already in use by {}", _usingProgrammer); 200 } 201 throw new jmri.ProgrammerException("programmer in use"); 202 } else { 203 _usingProgrammer = p; 204 return; 205 } 206 } 207 208 // internal method to create the NceMessage for programmer task start 209 protected NceMessage progTaskStart(ProgrammingMode mode, int val, int cvnum) throws jmri.ProgrammerException { 210 // val = -1 for read command; mode is direct, etc 211 if (val < 0) { 212 // read 213 if (mode == ProgrammingMode.PAGEMODE) { 214 return NceMessage.getReadPagedCV(tc, cvnum); 215 } else if (mode == ProgrammingMode.DIRECTMODE) { 216 return NceMessage.getReadDirectCV(tc, cvnum); 217 } else { 218 return NceMessage.getReadRegister(tc, registerFromCV(cvnum)); 219 } 220 } else { 221 // write 222 if (mode == ProgrammingMode.PAGEMODE) { 223 return NceMessage.getWritePagedCV(tc, cvnum, val); 224 } else if (mode == ProgrammingMode.DIRECTMODE) { 225 return NceMessage.getWriteDirectCV(tc, cvnum, val); 226 } else { 227 return NceMessage.getWriteRegister(tc, registerFromCV(cvnum), val); 228 } 229 } 230 } 231 232 /** 233 * {@inheritDoc} 234 */ 235 @Override 236 public void message(NceMessage m) { 237 log.error("message received unexpectedly: {}", m.toString()); 238 } 239 240 /** 241 * {@inheritDoc} 242 */ 243 @Override 244 public synchronized void reply(NceReply m) { 245 if (progState == NOTPROGRAMMING) { 246 // we get the complete set of replies now, so ignore these 247 if (log.isDebugEnabled()) { 248 log.debug("reply in NOTPROGRAMMING state"); 249 } 250 return; 251 } else if (progState == COMMANDSENT) { 252 if (log.isDebugEnabled()) { 253 log.debug("reply in COMMANDSENT state"); 254 } 255 // operation done, capture result, then post response 256 progState = NOTPROGRAMMING; 257 // check for errors 258 if ((m.match("NO FEEDBACK DETECTED") >= 0) 259 || (m.isBinary() && !_progRead && (m.getElement(0) != NceMessage.NCE_OKAY)) 260 || (m.isBinary() && _progRead && (m.getElement(1) != NceMessage.NCE_OKAY))) { 261 if (log.isDebugEnabled()) { 262 log.debug("handle NO FEEDBACK DETECTED"); 263 } 264 // perhaps no loco present? Fail back to end of programming 265 notifyProgListenerEnd(_val, jmri.ProgListener.NoLocoDetected); 266 } else { 267 // see why waiting 268 if (_progRead) { 269 // read was in progress - get return value 270 _val = m.value(); 271 } 272 // if this was a read, we retrieved the value above. If its a 273 // write, we're to return the original write value 274 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 275 } 276 277 } else if (progState == COMMANDSENT_2) { 278 if (log.isDebugEnabled()) { 279 log.debug("first reply in COMMANDSENT_2 state"); 280 } 281 // first message sent, now wait for second reply to arrive 282 progState = COMMANDSENT; 283 } else { 284 if (log.isDebugEnabled()) { 285 log.debug("reply in un-decoded state"); 286 } 287 } 288 } 289 290 /** 291 * {@inheritDoc} 292 * <p> 293 * Internal routine to handle a timeout 294 */ 295 @Override 296 protected synchronized void timeout() { 297 if (progState != NOTPROGRAMMING) { 298 // we're programming, time to stop 299 if (log.isDebugEnabled()) { 300 log.debug("timeout!"); 301 } 302 // perhaps no loco present? Fail back to end of programming 303 progState = NOTPROGRAMMING; 304 cleanup(); 305 notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout); 306 } 307 } 308 309 // Internal method to cleanup in case of a timeout. Separate routine 310 // so it can be changed in subclasses. 311 void cleanup() { 312 } 313 314 // internal method to notify of the final result 315 protected void notifyProgListenerEnd(int value, int status) { 316 if (log.isDebugEnabled()) { 317 log.debug("notifyProgListenerEnd value {} status {}", value, status); 318 } 319 // the programmingOpReply handler might send an immediate reply, so 320 // clear the current listener _first_ 321 jmri.ProgListener temp = _usingProgrammer; 322 _usingProgrammer = null; 323 notifyProgListenerEnd(temp, value, status); 324 } 325 326 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NceProgrammer.class); 327 328}