001package jmri.jmrix.tams; 002 003import java.util.ArrayList; 004import java.util.List; 005import javax.annotation.Nonnull; 006 007import jmri.ProgrammingMode; 008import jmri.jmrix.AbstractProgrammer; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012/** 013 * Convert the jmri.Programmer interface into commands for the NCE power house. 014 * <p> 015 * This has two states: NOTPROGRAMMING, and COMMANDSENT. The transitions to and 016 * from programming mode are now handled in the TrafficController code. Based on 017 * work by Bob Jacobsen 018 * 019 * @author Kevin Dickerson Copyright (C) 2012 020 */ 021public class TamsProgrammer extends AbstractProgrammer implements TamsListener { 022 023 protected TamsTrafficController tc; 024 025 public TamsProgrammer(TamsTrafficController tc) { 026 this.tc = tc; 027 super.SHORT_TIMEOUT = 6000; 028 } 029 030 /** 031 * {@inheritDoc} 032 */ 033 @Override 034 @Nonnull 035 public List<ProgrammingMode> getSupportedModes() { 036 List<ProgrammingMode> ret = new ArrayList<ProgrammingMode>(); 037 ret.add(ProgrammingMode.PAGEMODE); 038 ret.add(ProgrammingMode.DIRECTBITMODE); 039 ret.add(ProgrammingMode.DIRECTBYTEMODE); 040 ret.add(ProgrammingMode.REGISTERMODE); 041 return ret; 042 } 043 044 // members for handling the programmer interface 045 int progState = 0; 046 static final int NOTPROGRAMMING = 0; // is notProgramming 047 static final int COMMANDSENT = 2; // read/write command sent, waiting reply 048 static final int COMMANDSENT_2 = 4; // ops programming mode, send msg twice 049 boolean _progRead = false; 050 int _val; // remember the value being read/written for confirmative reply 051 int _cv; // remember the cv being read/written 052 053 /** 054 * {@inheritDoc} 055 */ 056 @Override 057 public synchronized void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 058 final int CV = Integer.parseInt(CVname); 059 if (log.isDebugEnabled()) { 060 log.debug("writeCV {} listens {}", CV, p); 061 } 062 useProgrammer(p); 063 _progRead = false; 064 // set state 065 progState = COMMANDSENT; 066 _val = val; 067 _cv = CV; 068 069 try { 070 // start the error timer 071 startLongTimer(); 072 073 // format and send the write message 074 tc.sendTamsMessage(progTaskStart(_val, _cv), this); 075 } catch (jmri.ProgrammerException e) { 076 useProgrammer(null); 077 progState = NOTPROGRAMMING; 078 throw e; 079 } 080 } 081 082 /** 083 * {@inheritDoc} 084 */ 085 @Override 086 public 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 if (log.isDebugEnabled()) { 097 log.debug("readCV {} listens {}", CV, p); 098 } 099 useProgrammer(p); 100 _progRead = true; 101 102 // set commandPending state 103 progState = COMMANDSENT; 104 _cv = CV; 105 106 try { 107 // start the error timer 108 startLongTimer(); 109 110 // format and send the write message 111 tc.sendTamsMessage(progTaskStart(-1, _cv), this); 112 } catch (jmri.ProgrammerException e) { 113 useProgrammer(null); 114 progState = NOTPROGRAMMING; 115 throw e; 116 } 117 } 118 119 private jmri.ProgListener _usingProgrammer = null; 120 121 // internal method to remember who's using the programmer 122 protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException { 123 // test for only one! 124 if (_usingProgrammer != null && _usingProgrammer != p) { 125 if (log.isInfoEnabled()) { 126 log.info("programmer already in use by {}", _usingProgrammer); 127 } 128 throw new jmri.ProgrammerException("programmer in use"); 129 } else { 130 _usingProgrammer = p; 131 return; 132 } 133 } 134 135 // internal method to create the TamsMessage for programmer task start 136 protected TamsMessage progTaskStart(int val, int cvnum) throws jmri.ProgrammerException { 137 // val = -1 for read command; mode is direct, etc 138 if (val < 0) { 139 // read 140 if (getMode() == ProgrammingMode.PAGEMODE) { 141 return TamsMessage.getReadPagedCV(cvnum); 142 } else if (getMode() == ProgrammingMode.DIRECTBYTEMODE) { 143 return TamsMessage.getReadDirectByteCV(cvnum); 144 } else { 145 return TamsMessage.getReadRegister(registerFromCV(cvnum)); 146 } 147 } else { 148 // write 149 if (getMode() == ProgrammingMode.PAGEMODE) { 150 return TamsMessage.getWritePagedCV(cvnum, val); 151 } else if (getMode() == ProgrammingMode.DIRECTBYTEMODE) { 152 return TamsMessage.getWriteDirectByteCV(cvnum, val); 153 } else { 154 return TamsMessage.getWriteRegister(registerFromCV(cvnum), val); 155 } 156 } 157 } 158 159 /** 160 * {@inheritDoc} 161 */ 162 @Override 163 public void message(TamsMessage m) { 164 log.error("message received unexpectedly: {}", m.toString()); 165 } 166 167 /** 168 * {@inheritDoc} 169 */ 170 @Override 171 public synchronized void reply(TamsReply m) { 172 if (progState == NOTPROGRAMMING) { 173 // we get the complete set of replies now, so ignore these 174 if (log.isDebugEnabled()) { 175 log.debug("reply in NOTPROGRAMMING state"); 176 } 177 return; 178 } else if (progState == COMMANDSENT) { 179 if (log.isDebugEnabled()) { 180 log.debug("reply in COMMANDSENT state"); 181 } 182 // operation done, capture result, then post response 183 progState = NOTPROGRAMMING; 184 // check for errors 185 if (m.match("Ok") >= 0) { 186 // see why waiting 187 if (_progRead) { 188 // read was in progress - get return value 189 _val = m.value(); 190 } 191 // if this was a read, we retrieved the value above. If its a 192 // write, we're to return the original write value 193 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 194 195 } else if ((m.match("No ack") >= 0)) { 196 if (log.isDebugEnabled()) { 197 log.debug("handle NO Ack"); 198 } 199 // perhaps no loco present? Fail back to end of programming 200 notifyProgListenerEnd(_val, jmri.ProgListener.NoAck); 201 } else if (m.match("Busy") >= 0) { 202 if (log.isDebugEnabled()) { 203 log.debug("handle Busy"); 204 } 205 // perhaps no loco present? Fail back to end of programming 206 notifyProgListenerEnd(_val, jmri.ProgListener.ProgrammerBusy); 207 } else if (m.match("Timeout") >= 0) { 208 if (log.isDebugEnabled()) { 209 log.debug("handle Timeout"); 210 } 211 // perhaps no loco present? Fail back to end of programming 212 notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout); 213 } else if (m.match("Error") >= 0) { 214 if (log.isDebugEnabled()) { 215 log.debug("handle Other Error"); 216 } 217 // perhaps no loco present? Fail back to end of programming 218 notifyProgListenerEnd(_val, jmri.ProgListener.UnknownError); 219 } else { 220 // see why waiting 221 if (_progRead) { 222 // read was in progress - get return value 223 _val = m.value(); 224 } 225 // if this was a read, we retrieved the value above. If its a 226 // write, we're to return the original write value 227 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 228 229 } 230 231 } else if (progState == COMMANDSENT_2) { 232 if (log.isDebugEnabled()) { 233 log.debug("first reply in COMMANDSENT_2 state"); 234 } 235 // first message sent, now wait for second reply to arrive 236 progState = COMMANDSENT; 237 } else { 238 if (log.isDebugEnabled()) { 239 log.debug("reply in un-decoded state"); 240 } 241 } 242 } 243 244 /** 245 * {@inheritDoc} 246 */ 247 @Override 248 protected synchronized void timeout() { 249 if (progState != NOTPROGRAMMING) { 250 // we're programming, time to stop 251 if (log.isDebugEnabled()) { 252 log.debug("timeout!"); 253 } 254 // perhaps no loco present? Fail back to end of programming 255 progState = NOTPROGRAMMING; 256 cleanup(); 257 notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout); 258 } 259 } 260 261 // Internal method to cleanup in case of a timeout. Separate routine 262 // so it can be changed in subclasses. 263 void cleanup() { 264 } 265 266 // internal method to notify of the final result 267 protected void notifyProgListenerEnd(int value, int status) { 268 if (log.isDebugEnabled()) { 269 log.debug("notifyProgListenerEnd value {} status {}", value, status); 270 } 271 // the programmingOpReply handler might send an immediate reply, so 272 // clear the current listener _first_ 273 jmri.ProgListener temp = _usingProgrammer; 274 _usingProgrammer = null; 275 notifyProgListenerEnd(temp, value, status); 276 } 277 278 private final static Logger log = LoggerFactory.getLogger(TamsProgrammer.class); 279 280}