001package jmri.jmrix; 002 003import java.util.List; 004import javax.annotation.Nonnull; 005import jmri.ProgListener; 006import jmri.Programmer; 007import jmri.ProgrammerException; 008import jmri.ProgrammingMode; 009import jmri.beans.PropertyChangeSupport; 010 011/** 012 * Common implementations for the Programmer interface. 013 * <p> 014 * Contains two time-out handlers: 015 * <ul> 016 * <li> SHORT_TIMEOUT, the "short timer", is on operations other than reads 017 * <li> LONG_TIMEOUT, the "long timer", is for the "read from decoder" step, which can take a long time. 018 * </ul> 019 * The duration of these can be adjusted by changing the values of those constants in subclasses. 020 * 021 * @author Bob Jacobsen Copyright (C) 2001, 2012, 2013 022 */ 023public abstract class AbstractProgrammer extends PropertyChangeSupport implements Programmer { 024 025 /** {@inheritDoc} */ 026 @Override 027 @Nonnull 028 public String decodeErrorCode(int code) { 029 if (code == ProgListener.OK) { 030 return Bundle.getMessage("StatusOK"); 031 } 032 StringBuilder sbuf = new StringBuilder(); 033 // add each code; terminate each string with ";" please. 034 if ((code & ProgListener.NoLocoDetected) != 0) { 035 sbuf.append(Bundle.getMessage("NoLocoDetected")).append(" "); 036 } 037 if ((code & ProgListener.ProgrammerBusy) != 0) { 038 sbuf.append(Bundle.getMessage("ProgrammerBusy")).append(" "); 039 } 040 if ((code & ProgListener.NotImplemented) != 0) { 041 sbuf.append(Bundle.getMessage("NotImplemented")).append(" "); 042 } 043 if ((code & ProgListener.UserAborted) != 0) { 044 sbuf.append(Bundle.getMessage("UserAborted")).append(" "); 045 } 046 if ((code & ProgListener.ConfirmFailed) != 0) { 047 sbuf.append(Bundle.getMessage("ConfirmFailed")).append(" "); 048 } 049 if ((code & ProgListener.FailedTimeout) != 0) { 050 sbuf.append(Bundle.getMessage("FailedTimeout")).append(" "); 051 } 052 if ((code & ProgListener.UnknownError) != 0) { 053 sbuf.append(Bundle.getMessage("UnknownError")).append(" "); 054 } 055 if ((code & ProgListener.NoAck) != 0) { 056 sbuf.append(Bundle.getMessage("NoAck")).append(" "); 057 } 058 if ((code & ProgListener.ProgrammingShort) != 0) { 059 sbuf.append(Bundle.getMessage("ProgrammingShort")).append(" "); 060 } 061 if ((code & ProgListener.SequenceError) != 0) { 062 sbuf.append(Bundle.getMessage("SequenceError")).append(" "); 063 } 064 if ((code & ProgListener.CommError) != 0) { 065 sbuf.append(Bundle.getMessage("CommError")).append(" "); 066 } 067 068 // remove trailing separators 069 if (sbuf.length() > 2) { 070 sbuf.setLength(sbuf.length() - 2); 071 } 072 073 String retval = sbuf.toString(); 074 if (retval.isEmpty()) { 075 return "unknown status code: " + code; 076 } else { 077 return retval; 078 } 079 } 080 081 /** {@inheritDoc} */ 082 @Override 083 abstract public void writeCV(String CV, int val, ProgListener p) throws ProgrammerException; 084 085 /** {@inheritDoc} */ 086 @Override 087 abstract public void readCV(String CV, ProgListener p) throws ProgrammerException; 088 089 /** {@inheritDoc} */ 090 @Override 091 abstract public void confirmCV(String CV, int val, ProgListener p) throws ProgrammerException; 092 093 094 /** {@inheritDoc} 095 * Basic implementation. Override this to turn reading on and off globally. 096 */ 097 @Override 098 public boolean getCanRead() { 099 return true; 100 } 101 102 /** {@inheritDoc} 103 * Checks using the current default programming mode 104 */ 105 @Override 106 public boolean getCanRead(String addr) { 107 if (!getCanRead()) { 108 return false; // check basic implementation first 109 } 110 return Integer.parseInt(addr) <= 1024; 111 } 112 113 // handle mode 114 private ProgrammingMode mode = null; 115 116 /** {@inheritDoc} */ 117 @Override 118 public final void setMode(ProgrammingMode m) { 119 List<ProgrammingMode> validModes = getSupportedModes(); 120 121 if (m == null) { 122 if (!validModes.isEmpty()) { 123 // null can only be set if there are no valid modes 124 throw new IllegalArgumentException("Cannot set null mode when modes are present"); 125 } else { 126 mode = null; 127 } 128 } 129 130 if (validModes.contains(m)) { 131 ProgrammingMode oldMode = mode; 132 mode = m; 133 firePropertyChange("Mode", oldMode, m); 134 } else { 135 throw new IllegalArgumentException("Invalid requested mode: " + m); 136 } 137 } 138 139 /** 140 * Define the "best" programming mode, which provides the initial setting. 141 * <p> 142 * The definition of "best" is up to the specific-system developer. 143 * By default, this is the first of the available methods from getSupportedModes; 144 * override this method to change that. 145 * 146 * @return The recommended ProgrammingMode or null if none exists or is defined. 147 */ 148 public ProgrammingMode getBestMode() { 149 if (!getSupportedModes().isEmpty()) { 150 return getSupportedModes().get(0); 151 } 152 return null; 153 } 154 155 /** {@inheritDoc} */ 156 @Override 157 public final ProgrammingMode getMode() { 158 if (mode == null) { 159 mode = getBestMode(); 160 } 161 return mode; 162 } 163 164 @Override 165 @Nonnull 166 abstract public List<ProgrammingMode> getSupportedModes(); 167 168 /** {@inheritDoc} 169 * Basic implementation. Override this to turn writing on and off globally. 170 */ 171 @Override 172 public boolean getCanWrite() { 173 return true; 174 } 175 176 /** {@inheritDoc} 177 * Checks using the current default programming mode. 178 */ 179 @Override 180 public boolean getCanWrite(String addr) { 181 return getCanWrite(); 182 } 183 184 /** {@inheritDoc} 185 * By default, say that no verification is done. 186 * 187 * @param addr A CV address to check (in case this varies with CV range) or null for any 188 * @return Always WriteConfirmMode.NotVerified 189 */ 190 @Nonnull 191 @Override 192 public Programmer.WriteConfirmMode getWriteConfirmMode(String addr) { return WriteConfirmMode.NotVerified; } 193 194 195 /** 196 * Internal routine to start timer to protect the mode-change. 197 */ 198 protected void startShortTimer() { 199 log.debug("startShortTimer"); 200 restartTimer(SHORT_TIMEOUT); 201 } 202 203 /** 204 * Internal routine to restart timer with a long delay 205 */ 206 protected void startLongTimer() { 207 log.debug("startLongTimer"); 208 restartTimer(LONG_TIMEOUT); 209 } 210 211 /** 212 * Internal routine to stop timer, as all is well 213 */ 214 protected synchronized void stopTimer() { 215 log.debug("stop timer"); 216 if (timer != null) { 217 timer.stop(); 218 } 219 } 220 221 /** 222 * Internal routine to handle timer starts and restarts. 223 * 224 * @param delay the initial delay, in milliseconds 225 */ 226 protected synchronized void restartTimer(int delay) { 227 log.debug("(re)start timer with delay {}", delay); 228 229 if (timer == null) { 230 timer = new javax.swing.Timer(delay, e -> timeout()); 231 } 232 timer.stop(); 233 timer.setInitialDelay(delay); 234 timer.setRepeats(false); 235 timer.start(); 236 } 237 238 /** 239 * Find the register number that corresponds to a specific CV number. 240 * 241 * @param cv CV number (1 through 512) for which equivalent register is 242 * desired 243 * @throws ProgrammerException if the requested CV does not correspond to a 244 * register 245 * @return register number corresponding to cv 246 */ 247 public int registerFromCV(int cv) throws ProgrammerException { 248 if (cv <= 4) { 249 return cv; 250 } 251 switch (cv) { 252 case 29: 253 return 5; 254 case 7: 255 return 7; 256 case 8: 257 return 8; 258 default: 259 log.warn("Unhandled register from cv: {}", cv); 260 break; 261 } 262 throw new ProgrammerException(); 263 } 264 265 /** 266 * Internal routine to handle a timeout, should be synchronized! 267 */ 268 abstract protected void timeout(); 269 270 protected int SHORT_TIMEOUT = 2000; 271 protected int LONG_TIMEOUT = 60000; 272 273 javax.swing.Timer timer = null; 274 275 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractProgrammer.class); 276 277}