001package jmri.jmrix.sprog; 002 003import java.util.*; 004 005import javax.annotation.Nonnull; 006 007import jmri.*; 008import jmri.jmrix.AbstractProgrammer; 009import jmri.jmrix.sprog.update.*; 010 011/** 012 * Implement the jmri.Programmer interface via commands for the Sprog 013 * programmer. This provides a service mode programmer. 014 * 015 * @author Bob Jacobsen Copyright (C) 2001 016 * @author Andrew Crosland Copyright (C) 2021 017 */ 018public class SprogProgrammer extends AbstractProgrammer implements SprogListener, SprogVersionListener { 019 020 private SprogSystemConnectionMemo _memo = null; 021 private SprogVersion _sv = null; 022 023 public SprogProgrammer(SprogSystemConnectionMemo memo) { 024 _memo = memo; 025 } 026 027 /** 028 * {@inheritDoc} 029 * 030 * Implemented Types. 031 */ 032 @Override 033 @Nonnull 034 public List<ProgrammingMode> getSupportedModes() { 035 List<ProgrammingMode> ret = new ArrayList<ProgrammingMode>(); 036 ret.add(ProgrammingMode.DIRECTBITMODE); 037 ret.add(ProgrammingMode.PAGEMODE); 038 return ret; 039 } 040 041 /** 042 * {@inheritDoc} 043 */ 044 @Override 045 public boolean getCanRead() { 046 if (getMode().equals(ProgrammingMode.PAGEMODE)) return true; 047 else if (getMode().equals(ProgrammingMode.DIRECTBITMODE)) return true; 048 else { 049 log.error("Unknown internal mode {} returned true from getCanRead()",getMode()); 050 return true; 051 } 052 } 053 054 // members for handling the programmer interface 055 int progState = 0; 056 static final int NOTPROGRAMMING = 0; // is notProgramming 057 static final int COMMANDSENT = 2; // read/write command sent, waiting reply 058 int _val; // remember the value being read/written for confirmative reply 059 String _cv; 060 int _startVal; 061 jmri.ProgListener _progListener; 062 063 /** 064 * {@inheritDoc} 065 */ 066 @Override 067 synchronized public void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 068 final int CV = Integer.parseInt(CVname); 069 log.debug("writeCV {} mode {} listens {}", CV, getMode(), p); 070 useProgrammer(p); 071 _val = val; 072 startProgramming(_val, CV, 0); 073 } 074 075 /** 076 * {@inheritDoc} 077 */ 078 @Override 079 synchronized public void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 080 readCV(CV, p); 081 } 082 083 /** 084 * {@inheritDoc} 085 */ 086 @Override 087 synchronized public void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException { 088 readCVWithDefault(CVname, p, 0); 089 } 090 091 /** 092 * {@inheritDoc} 093 */ 094 @Override 095 public void readCV(String CVname, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException { 096 _startVal = startVal; 097 if (_sv != null) { 098 if (!_sv.supportsCVHints()) { 099 log.debug("Hardware does not support hints"); 100 _startVal = 0; 101 } 102 readCVWithDefault(CVname, p, _startVal); 103 } else { 104 // The SPROG version is not known yet so request the version 105 log.debug("SPROG version is unknown - trying to get it"); 106 // save for later 107 _cv = CVname; 108 _progListener = p; 109 _memo.getSprogVersionQuery().requestVersion(this); 110 } 111 } 112 113 /** 114 * Internal method to read a CV with a possible default value 115 * 116 * @param CVname Index of CV to read 117 * @param p Programming listener 118 * @param startVal CV default value, Use 0 if no default available 119 * @throws jmri.ProgrammerException if programming operation fails 120 */ 121 synchronized public void readCVWithDefault(String CVname, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException { 122 final int CV = Integer.parseInt(CVname); 123 log.debug("readCV {} mode {} hint {} listens {}", CV, getMode(), startVal, p); 124 useProgrammer(p); 125 _val = -1; 126 startProgramming(_val, CV, startVal); 127 } 128 129 private jmri.ProgListener _usingProgrammer = null; 130 131 /** 132 * Send the command to start programming operation. 133 * 134 * @param val Value to be written, or -1 for read 135 * @param CV CV to read/write 136 * @param startVal Hint of what current CV value may be 137 */ 138 private void startProgramming(int val, int CV, int startVal) { 139 // here ready to send the read/write command 140 progState = COMMANDSENT; 141 // see why waiting 142 try { 143 startLongTimer(); 144 controller().sendSprogMessage(progTaskStart(getMode(), val, CV, startVal), this); 145 } catch (Exception e) { 146 // program op failed, go straight to end 147 log.error("program operation failed",e); 148 progState = NOTPROGRAMMING; 149 } 150 } 151 152 /** 153 * Internal method to remember who's using the programmer. 154 * @param p Who gets reply 155 * @throws ProgrammerException when programmer in invalid state 156 */ 157 protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException { 158 // test for only one! 159 if (_usingProgrammer != null && _usingProgrammer != p) { 160 if (log.isInfoEnabled()) { 161 log.info("programmer already in use by {}", _usingProgrammer); 162 } 163 throw new jmri.ProgrammerException("programmer in use"); 164 } else { 165 _usingProgrammer = p; 166 return; 167 } 168 } 169 170 /** 171 * Internal method to create the SprogMessage for programmer task start. 172 * @param mode Mode to be used 173 * @param val value to be written 174 * @param cvnum CV address to write to 175 * @param startVal Hint of what the CV may contain, or 0 176 * @return formatted message to do programming operation 177 */ 178 protected SprogMessage progTaskStart(ProgrammingMode mode, int val, int cvnum, int startVal) { 179 // val = -1 for read command; mode is direct, etc 180 if (val < 0) { 181 if (startVal == 0) { 182 // No hint value, or normal starting value 183 return SprogMessage.getReadCV(cvnum, mode); 184 } else { 185 return SprogMessage.getReadCV(cvnum, mode, startVal); 186 } 187 } else { 188 return SprogMessage.getWriteCV(cvnum, val, mode); 189 } 190 } 191 192 /** 193 * {@inheritDoc} 194 */ 195 @Override 196 public void notifyMessage(SprogMessage m) { 197 } 198 199 /** 200 * {@inheritDoc} 201 */ 202 @Override 203 synchronized public void notifyReply(SprogReply reply) { 204 205 if (progState == NOTPROGRAMMING) { 206 // we get the complete set of replies now, so ignore these 207 log.debug("reply in NOTPROGRAMMING state [{}]", reply); 208 return; 209 } else if (progState == COMMANDSENT) { 210 log.debug("reply in COMMANDSENT state [{}]", reply); 211 // operation done, capture result, then have to leave programming mode 212 progState = NOTPROGRAMMING; 213 // check for errors 214 if (reply.match("No Ack") >= 0) { 215 log.debug("handle No Ack reply {}", reply); 216 // perhaps no loco present? Fail back to end of programming 217 progState = NOTPROGRAMMING; 218 notifyProgListenerEnd(-1, jmri.ProgListener.NoLocoDetected); 219 } else if (reply.match("!O") >= 0) { 220 log.debug("handle !O reply {}", reply); 221 // Overload. Fail back to end of programming 222 progState = NOTPROGRAMMING; 223 notifyProgListenerEnd(-1, jmri.ProgListener.ProgrammingShort); 224 } else { 225 // see why waiting 226 if (_val == -1) { 227 // read was in progress - get return value 228 _val = reply.value(); 229 } 230 progState = NOTPROGRAMMING; 231 stopTimer(); 232 // if this was a read, we cached the value earlier. If its a 233 // write, we're to return the original write value 234 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 235 } 236 237 // SPROG always leaves power off after programming so we inform the 238 // power manager of the new state 239 controller().getAdapterMemo().getPowerManager().notePowerState(PowerManager.OFF); 240 } else { 241 log.debug("reply in un-decoded state"); 242 } 243 } 244 245 /** 246 * Handle a SprogVersion notification. 247 * <p> 248 * Decode the SPROG version and decode the programming capabilities. 249 * 250 * @param v The SprogVersion being handled 251 */ 252 @Override 253 synchronized public void notifyVersion(SprogVersion v) throws jmri.ProgrammerException { 254 // Save it for subsequent operations 255 _sv = v; 256 // Save it for others 257 _memo.setSprogVersion(v); 258 log.debug("Found: {}", v.toString()); 259 260 if (!_sv.supportsCVHints()) { 261 log.debug("Hardware does not support hints"); 262 _startVal = 0; 263 } 264 readCVWithDefault(_cv, _progListener, _startVal); 265 } 266 267 /** 268 * {@inheritDoc} 269 * 270 * Internal routine to handle a timeout 271 */ 272 @Override 273 synchronized protected void timeout() { 274 if (progState != NOTPROGRAMMING) { 275 // we're programming, time to stop 276 log.debug("Timeout in a programming state"); 277 // perhaps no loco present? Fail back to end of programming 278 progState = NOTPROGRAMMING; 279 notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout); 280 } else { 281 log.debug("timeout in NOTPROGRAMMING state"); 282 } 283 } 284 285 // internal method to notify of the final result 286 protected void notifyProgListenerEnd(int value, int status) { 287 log.debug("notifyProgListenerEnd value {} status {}", value, status); 288 // the programmingOpReply handler might send an immediate reply, so 289 // clear the current listener _first_ 290 jmri.ProgListener temp = _usingProgrammer; 291 _usingProgrammer = null; 292 notifyProgListenerEnd(temp, value, status); 293 } 294 295 SprogTrafficController _controller = null; 296 297 protected SprogTrafficController controller() { 298 // connect the first time 299 if (_controller == null) { 300 _controller = _memo.getSprogTrafficController(); 301 } 302 return _controller; 303 } 304 305 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SprogProgrammer.class); 306 307}