001package jmri.jmrix.dccpp; 002 003import java.util.ArrayList; 004import java.util.List; 005import javax.annotation.Nonnull; 006import jmri.ProgListener; 007import jmri.Programmer; 008import jmri.ProgrammingMode; 009import jmri.jmrix.AbstractProgrammer; 010 011/** 012 * Programmer support for DCC++. 013 * <p> 014 * The read operation state sequence is: 015 * <ul> 016 * <li>Send Register Mode / Paged mode /Direct Mode read request 017 * <li>Wait for results reply, interpret 018 * </ul> 019 * 020 * @author Bob Jacobsen Copyright (c) 2002, 2007 021 * @author Paul Bender Copyright (c) 2003-2010 022 * @author Giorgio Terdina Copyright (c) 2007 023 * @author Mark Underwood Copyright (c) 2015 024 */ 025public class DCCppProgrammer extends AbstractProgrammer implements DCCppListener { 026 027 // NOTE: We will embed the command opcode in the CALLBACKSUB field 028 // so that we can tell what type of message the response keys to. 029 030 static protected final int DCCppProgrammerTimeout = 90000; 031 032 // keep track of whether or not the command station is in service 033 // mode. Used for determining if "OK" message is an aproriate 034 // response to a request to a programming request. 035 protected boolean _service_mode = false; // TODO: Is this even meaningful for DCC++? 036 037 static protected final int LISTENER_MASK = DCCppInterface.CS_INFO | DCCppInterface.COMMINFO | DCCppInterface.INTERFACE; 038 039 public DCCppProgrammer(@Nonnull DCCppTrafficController tc) { 040 // error if more than one constructed? 041 _controller = tc; 042 init(); 043 } 044 045 private void init() { 046 // connect to listen 047 controller().addDCCppListener(LISTENER_MASK, this); 048 setMode(ProgrammingMode.DIRECTBYTEMODE); 049 } 050 051 /** 052 * {@inheritDoc} 053 */ 054 @Override 055 @Nonnull 056 public List<ProgrammingMode> getSupportedModes() { 057 List<ProgrammingMode> ret = new ArrayList<>(); 058 //ret.add(ProgrammingMode.PAGEMODE); 059// ret.add(ProgrammingMode.DIRECTBITMODE); 060 ret.add(ProgrammingMode.DIRECTBYTEMODE); 061 //ret.add(ProgrammingMode.REGISTERMODE); 062 return ret; 063 } 064 065 /** 066 * {@inheritDoc} 067 * 068 * Can we read from a specific CV in the specified mode? Answer may not be 069 * correct if the command station type and version sent by the command 070 * station mimics one of the known command stations. 071 */ 072 @Override 073 public boolean getCanRead(String addr) { 074 if (log.isDebugEnabled()) { 075 log.debug("check mode {} CV {}", getMode(), addr); 076 } 077 if (!getCanRead()) { 078 return false; // check basic implementation first 079 } 080 if (getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) { 081 return Integer.parseInt(addr) <= DCCppConstants.MAX_DIRECT_CV; 082 } else { 083 return Integer.parseInt(addr) <= 256; 084 } 085 } 086 087 /** 088 * {@inheritDoc} 089 * 090 * Can we write to a specific CV in the specified mode? Answer may not be 091 * correct if the command station type and version sent by the command 092 * station mimics one of the known command stations. 093 */ 094 @Override 095 public boolean getCanWrite(String addr) { 096 log.debug("check CV {}", addr); 097 log.debug("cs Type: {} CS Build: {}", controller().getCommandStation().getStationType(), controller().getCommandStation().getBuild()); 098 if (!getCanWrite()) { 099 return false; // check basic implementation first 100 } 101 if (getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) { 102 return Integer.parseInt(addr) <= DCCppConstants.MAX_DIRECT_CV; 103 } else { 104 return Integer.parseInt(addr) <= 256; 105 } 106 } 107 108 /** 109 * {@inheritDoc} 110 */ 111 @Nonnull 112 @Override 113 public Programmer.WriteConfirmMode getWriteConfirmMode(String addr) { return WriteConfirmMode.DecoderReply; } 114 115 // members for handling the programmer interface 116 protected int progState = 0; 117 static protected final int NOTPROGRAMMING = 0; // is notProgramming 118 static protected final int REQUESTSENT = 1; // waiting reply to command to go into programming mode 119 static protected final int INQUIRESENT = 2; // read/write command sent, waiting reply 120 protected boolean _progRead = false; 121 protected int _val; // remember the value being read/written for confirmative reply 122 protected int _cv; // remember the cv being read/written 123 124 // programming interface 125 126 /** 127 * {@inheritDoc} 128 */ 129 @Override 130 public synchronized void writeCV(String CVname, int val, ProgListener p) throws jmri.ProgrammerException { 131 final int CV = Integer.parseInt(CVname); 132 if (log.isDebugEnabled()) { 133 log.debug("writeCV {} listens {}", CV, p); 134 } 135 useProgrammer(p); 136 _progRead = false; 137 // set new state & save values 138 progState = REQUESTSENT; 139 _val = val; 140 _cv = 0xffff & CV; 141 142 // start the error timer 143 restartTimer(DCCppProgrammerTimeout); 144 145 // format and send message to go to program mode 146 if (getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) { 147 DCCppMessage msg; 148 if (controller().getCommandStation().isProgramV4Supported()) { //drops the callbacks 149 msg = DCCppMessage.makeWriteDirectCVMsgV4(CV, val); 150 } else { 151 msg = DCCppMessage.makeWriteDirectCVMsg(CV, val); //older syntax with dummy callbacks 152 } 153 controller().sendDCCppMessage(msg, this); 154 } 155 } 156 157 /** 158 * {@inheritDoc} 159 */ 160 @Override 161 public synchronized void confirmCV(String CV, int val, ProgListener p) throws jmri.ProgrammerException { 162 readCV(CV, p, val); 163 } 164 165 /** 166 * {@inheritDoc} 167 */ 168 @Override 169 public synchronized void readCV(String CVname, ProgListener p) throws jmri.ProgrammerException { 170 readCV(CVname, p, 0); //default starting value to zero 171 } 172 173 /** 174 * {@inheritDoc} 175 */ 176 @Override 177 public synchronized void readCV(String CVname, ProgListener p, int startVal) throws jmri.ProgrammerException { 178 final int CV = Integer.parseInt(CVname); 179 log.debug("readCV {}, startVal {}", CV, startVal); 180 // If can't read (e.g. multiMaus CS), this shouldnt be invoked, but 181 // still we need to do something rational by returning a NotImplemented error 182 if (!getCanRead()) { 183 notifyProgListenerEnd(p,CV,ProgListener.NotImplemented); 184 return; 185 } 186 useProgrammer(p); 187 _cv = 0xffff & CV; 188 _progRead = true; 189 // set new state 190 progState = REQUESTSENT; 191 // start the error timer 192 restartTimer(DCCppProgrammerTimeout); 193 194 if (getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) { 195 if (controller().getCommandStation().isReadStartValSupported()) { //use the 'V' command with a startVal 196 DCCppMessage msg = DCCppMessage.makeVerifyCVMsg(CV, startVal); 197 controller().sendDCCppMessage(msg, this); 198 } else { //use the older 'R' command 199 DCCppMessage msg = DCCppMessage.makeReadDirectCVMsg(CV); 200 controller().sendDCCppMessage(msg, this); 201 } 202 } 203 } 204 205 private ProgListener _usingProgrammer = null; 206 207 // internal method to remember who's using the programmer 208 protected void useProgrammer(ProgListener p) throws jmri.ProgrammerException { 209 // test for only one! 210 if (_usingProgrammer != null && _usingProgrammer != p) { 211 if (log.isInfoEnabled()) { 212 log.info("programmer already in use by {}", _usingProgrammer); 213 } 214 throw new jmri.ProgrammerException("programmer in use"); 215 } else { 216 _usingProgrammer = p; 217 } 218 } 219 220 /** 221 * {@inheritDoc} 222 */ 223 @Override 224 public synchronized void message(DCCppReply m) { 225 if (progState == NOTPROGRAMMING) { 226 return; 227 } 228 if (m.getElement(0) == DCCppConstants.PROGRAM_REPLY || 229 m.getElement(0) == DCCppConstants.VERIFY_REPLY) { 230 if (log.isDebugEnabled()) { 231 log.debug("reply in REQUESTSENT state"); 232 log.debug("DCC++ Program or Verify Reply value = {}", m.getCVString()); 233 } 234 _val = m.getReadValueInt(); 235 progState = NOTPROGRAMMING; 236 if (_val == -1) { 237 log.debug("Reporting NoAck"); 238 notifyProgListenerEnd(_val, ProgListener.NoAck); 239 } else { 240 log.debug("Reporting OK"); 241 notifyProgListenerEnd(_val, ProgListener.OK); 242 } 243 } 244 } 245 246 /** 247 * {@inheritDoc} 248 */ 249 @Override 250 public synchronized void message(DCCppMessage l) { 251 } 252 253 // Handle a timeout notification 254 @Override 255 public void notifyTimeout(DCCppMessage msg) { 256 log.debug("Notified of timeout on message '{}'", msg); 257 } 258 259 260 /* 261 * Indicate when the Programmer is in the middle of an operation. 262 */ 263 public synchronized boolean programmerBusy() { 264 return (progState != NOTPROGRAMMING); 265 } 266 267 /** 268 * {@inheritDoc} 269 */ 270 @Override 271 protected synchronized void timeout() { 272 if (progState != NOTPROGRAMMING) { 273 // we're programming, time to stop 274 if (log.isDebugEnabled()) { 275 log.debug("timeout!"); 276 } 277 // perhaps no loco present? Fail back to end of programming 278 progState = NOTPROGRAMMING; 279 if (getCanRead()) { 280 notifyProgListenerEnd(_val, ProgListener.FailedTimeout); 281 } else { 282 notifyProgListenerEnd(_val, ProgListener.OK); 283 } 284 } 285 } 286 287 // internal method to notify of the final result 288 protected void notifyProgListenerEnd(int value, int status) { 289 if (log.isDebugEnabled()) { 290 log.debug("notifyProgListenerEnd value {} status {}", value, status); 291 } 292 // the programmingOpReply handler might send an immediate reply, so 293 // clear the current listener _first_ 294 jmri.ProgListener temp = _usingProgrammer; 295 _usingProgrammer = null; 296 notifyProgListenerEnd(temp,value,status); 297 } 298 299 private final DCCppTrafficController _controller; 300 301 protected DCCppTrafficController controller() { 302 return _controller; 303 } 304 305 @Override 306 public void dispose() { 307 if ( _controller != null ) { 308 _controller.removeDCCppListener(LISTENER_MASK, this); 309 } 310 } 311 312 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DCCppProgrammer.class); 313 314}