001package jmri.progdebugger; 002 003import java.util.Arrays; 004import java.util.Hashtable; 005import java.util.List; 006import javax.annotation.Nonnull; 007import jmri.AddressedProgrammer; 008import jmri.ProgListener; 009import jmri.Programmer; 010import jmri.ProgrammerException; 011import jmri.ProgrammingMode; 012import jmri.beans.PropertyChangeSupport; 013import jmri.jmrix.loconet.hexfile.HexFileFrame; 014import org.slf4j.Logger; 015import org.slf4j.LoggerFactory; 016 017/** 018 * Debugging implementation of Programmer interface. 019 * <p> 020 * Note that running a simulated LocoNet connection, {@link HexFileFrame#configure()} will substitute the 021 * {@link jmri.progdebugger.ProgDebugger} instead of the {@link jmri.jmrix.loconet.LnOpsModeProgrammer}, 022 * overriding {@link #readCV(String, ProgListener)} and {@link #writeCV(String, int, ProgListener)}. 023 * <p> 024 * Remembers writes, and returns the last written value when a read to the same 025 * CV is made. 026 * <p> 027 * Only supports the DCC single-number address space, should be updated to handle 028 * any string address. As a temporary fix we simply discard the first part of any CV name 029 * containing "." and use the rest. 030 * TODO Fully support numberformat "113.12" in ProgDebugger (used in LOCONETLNCVMODE and LOCONETBDOPSWMODE) 031 * 032 * @author Bob Jacobsen Copyright (C) 2001, 2007, 2013 033 */ 034public class ProgDebugger extends PropertyChangeSupport implements AddressedProgrammer { 035 036 public ProgDebugger() { 037 mode = ProgrammingMode.PAGEMODE; 038 } 039 040 public ProgDebugger(boolean pLongAddress, int pAddress) { 041 longAddr = pLongAddress; 042 address = pAddress; 043 mode = ProgrammingMode.OPSBITMODE; 044 } 045 046 // write CV is recorded for later use 047 private int _lastWriteVal = -1; 048 private int _lastWriteCv = -1; 049 //private String cvNumPrefix; // TODO use part 0 of composite CVnames for simulated replies? 050 051 public int lastWrite() { 052 return _lastWriteVal; 053 } 054 055 public int lastWriteCv() { 056 return _lastWriteCv; 057 } 058 059 public int nOperations = 0; 060 061 /** 062 * Reset the CV to a value so one can detect if it's been written. 063 * <p> 064 * Does not change the "lastWrite" and "lastWriteCv" results. 065 * 066 * @param cv the CV to reset 067 * @param val the value 068 */ 069 public void resetCv(int cv, int val) { 070 mValues.put(cv, val); 071 } 072 073 /** 074 * Get the CV value directly, without going through the usual indirect 075 * protocol. Used, for example, while testing. 076 * <p> 077 * Does not change the "lastRead" and "lastReadCv" results. 078 * 079 * @param cv the CV to get 080 * @return the value or -1 081 */ 082 public int getCvVal(int cv) { 083 // try to get something from hash table 084 Integer saw = (mValues.get(cv)); 085 if (saw != null) { 086 return saw; 087 } 088 log.warn("CV {} has no defined value", cv); 089 return -1; 090 } 091 092 /** 093 * See if a CV has been written. 094 * 095 * @param cv the CV to check 096 * @return true if written, false otherwise 097 */ 098 public boolean hasBeenWritten(int cv) { 099 Integer saw = (mValues.get(cv)); 100 return (saw != null); 101 } 102 103 /** 104 * Clear written status. 105 * 106 * @param cv the CV to clear 107 */ 108 public void clearHasBeenWritten(int cv) { 109 mValues.remove(cv); 110 } 111 112 // write CV values are remembered for later reads 113 Hashtable<Integer, Integer> mValues = new Hashtable<>(); 114 115 /** 116 * {@inheritDoc} 117 */ 118 @Override 119 @Nonnull 120 public String decodeErrorCode(int i) { 121 log.debug("decoderErrorCode {}", i); 122 return "error " + i; 123 } 124 125 /** 126 * {@inheritDoc} 127 */ 128 @Override 129 public void writeCV(String CVname, int val, ProgListener p) throws ProgrammerException { 130 final int CV; 131 // Check CVname contents for int parsing 132 if (CVname.contains(".")) { 133 String[] parts = CVname.split("\\."); 134 //cvNumPrefix = parts[0]; 135 CVname = parts[1]; 136 // in LocoNet LOCONETLNCVMODE and LOCONETBDOPSWMODE the CV value (string) eg. "25.2" 137 // contains a first part for Article ID/Board typeword. cvNumPrefix is discarded during debug/simulation. 138 // See jmri.jmrix.loconet.LnOpsModeProgrammer.writeCV() 139 } 140 CV = Integer.parseInt(CVname); 141 nOperations++; 142 final ProgListener m = p; 143 // log out the request 144 log.info("write CV: {} to: {} mode: {}", CV, val, getMode()); 145 _lastWriteVal = val; 146 _lastWriteCv = CV; 147 // save for later retrieval 148 mValues.put(CV, val); 149 150 // return a notification via the queue to ensure end 151 Runnable r = new Runnable() { 152 ProgListener l = m; 153 154 @Override 155 public void run() { 156 log.debug("write CV reply"); 157 if (l != null) { 158 notifyProgListenerEnd(l, val, 0); 159 } 160 } // 0 is OK status 161 }; 162 sendReturn(r); 163 } 164 165 // read CV values 166 // note that the hashTable will be used if the CV has been written 167 private int _nextRead = 123; 168 169 public void nextRead(int r) { 170 _nextRead = r; 171 } 172 173 private int _lastReadCv = -1; 174 175 public int lastReadCv() { 176 return _lastReadCv; 177 } 178 179 boolean confirmOK; // cached result of last compare 180 181 /** 182 * {@inheritDoc} 183 */ 184 @Override 185 public void confirmCV(String CVname, int val, ProgListener p) throws ProgrammerException { 186 final int CV = Integer.parseInt(CVname); 187 final ProgListener m = p; 188 189 nOperations++; 190 // guess by comparing current value in val to has table 191 Integer saw = mValues.get(CV); 192 int result; // what was read 193 if (saw != null) { 194 result = saw; 195 confirmOK = (result == val); 196 log.info("confirm CV: {} mode: {} will return {} pass: {}", CV, getMode(), result, confirmOK); 197 } else { 198 result = -1; 199 confirmOK = false; 200 log.info("confirm CV: {} mode: {} will return -1 pass: false due to no previous value", CV, getMode()); 201 } 202 _lastReadCv = CV; 203 // return a notification via the queue to ensure end 204 final int returnResult = result; // final to allow passing to inner class 205 Runnable r = new Runnable() { 206 ProgListener l = m; 207 int res = returnResult; 208 209 @Override 210 public void run() { 211 log.debug("read CV reply"); 212 if (confirmOK) { 213 notifyProgListenerEnd(l, val, ProgListener.OK); 214 } else { 215 notifyProgListenerEnd(l, res, ProgListener.ConfirmFailed); 216 } 217 } 218 }; 219 sendReturn(r); 220 221 } 222 223 /** 224 * {@inheritDoc} 225 */ 226 @Override 227 public void readCV(String CVname, ProgListener p) throws ProgrammerException { 228 final int CV; 229 // Check CVname contents for int parsing 230 if (CVname.contains(".")) { 231 String[] parts = CVname.split("\\."); 232 //cvNumPrefix = parts[0]; 233 CVname = parts[1]; 234 // in LocoNet LOCONETLNCVMODE and LOCONETBDOPSWMODE the CV value (string) e.g. "113.12" 235 // contains a first part for Article ID/Board typeword. cvNumPrefix is discarded during debug/simulation. 236 // See jmri.jmrix.loconet.LnOpsModeProgrammer.writeCV() 237 } 238 CV = Integer.parseInt(CVname); 239 final ProgListener m = p; 240 _lastReadCv = CV; 241 nOperations++; 242 243 int readValue = _nextRead; 244 // try to get something from hash table 245 Integer saw = mValues.get(CV); 246 if (saw != null) { 247 readValue = saw; 248 } 249 250 log.info("read CV: {} mode: {} will read {}", CV, getMode(), readValue); 251 252 final int returnValue = readValue; 253 // return a notification via the queue to ensure end 254 Runnable r = new Runnable() { 255 int retval = returnValue; 256 ProgListener l = m; 257 258 @Override 259 public void run() { 260 log.debug("read CV reply"); 261 notifyProgListenerEnd(l, retval, 0); 262 } // 0 is OK status 263 }; 264 sendReturn(r); 265 266 } 267 268 // handle mode 269 protected ProgrammingMode mode; 270 271 /** 272 * {@inheritDoc} 273 */ 274 @Override 275 public final void setMode(ProgrammingMode m) { 276 log.debug("Setting mode from {} to {}", mode, m); 277 if (getSupportedModes().contains(m)) { 278 ProgrammingMode oldMode = mode; 279 mode = m; 280 firePropertyChange("Mode", oldMode, m); 281 } else { 282 throw new IllegalArgumentException("Invalid requested mode: " + m); 283 } 284 } 285 286 /** 287 * {@inheritDoc} 288 */ 289 @Override 290 public final ProgrammingMode getMode() { 291 return mode; 292 } 293 294 /** 295 * {@inheritDoc} 296 */ 297 @Override 298 @Nonnull 299 public List<ProgrammingMode> getSupportedModes() { 300 if (address >= 0) { 301 // addressed programmer 302 return Arrays.asList(ProgrammingMode.OPSBITMODE, 303 ProgrammingMode.OPSBYTEMODE); 304 } else { 305 // global programmer 306 return Arrays.asList(ProgrammingMode.PAGEMODE, 307 ProgrammingMode.DIRECTBITMODE, 308 ProgrammingMode.DIRECTBYTEMODE, 309 ProgrammingMode.DIRECTMODE); 310 } 311 } 312 /** 313 * By default, the highest test CV is 256 so that we can test composite 314 * operations 315 */ 316 int writeLimit = 256; 317 int readLimit = 256; 318 319 public void setTestReadLimit(int lim) { 320 readLimit = lim; 321 } 322 323 public void setTestWriteLimit(int lim) { 324 writeLimit = lim; 325 } 326 327 @Override 328 public boolean getCanRead() { 329 log.debug("getCanRead() returns true"); 330 return true; 331 } 332 333 @Override 334 public boolean getCanRead(String addr) { 335 log.debug("getCanRead({}) returns {}", addr, Integer.parseInt(addr) <= readLimit); 336 return Integer.parseInt(addr) <= readLimit; 337 } 338 339 @Override 340 public boolean getCanWrite() { 341 log.debug("getCanWrite() returns true"); 342 return true; 343 } 344 345 @Override 346 public boolean getCanWrite(String addr) { 347 log.debug("getCanWrite({}) returns {}", addr, Integer.parseInt(addr) <= writeLimit); 348 return Integer.parseInt(addr) <= writeLimit; 349 } 350 351 /** 352 * {@inheritDoc} 353 * <p> 354 * By default, say that no verification is done. 355 * @return Always WriteConfirmMode.NotVerified 356 */ 357 @Nonnull 358 @Override 359 public Programmer.WriteConfirmMode getWriteConfirmMode(String addr) { return WriteConfirmMode.NotVerified; } 360 361 boolean longAddr = true; 362 363 @Override 364 public boolean getLongAddress() { 365 return true; 366 } 367 368 int address = -1; 369 370 @Override 371 public int getAddressNumber() { 372 return address; 373 } 374 375 @Override 376 public String getAddress() { 377 return "" + getAddressNumber() + " " + getLongAddress(); 378 } 379 380 static final boolean IMMEDIATERETURN = false; 381 static final int DELAY = 10; 382 383 /** 384 * Arrange for the return to be invoked on the Swing thread. 385 * 386 * @param run the Runnable 387 */ 388 void sendReturn(Runnable run) { 389 if (IMMEDIATERETURN) { 390 javax.swing.SwingUtilities.invokeLater(run); 391 } else { 392 javax.swing.Timer timer = new javax.swing.Timer(DELAY, null); 393 java.awt.event.ActionListener l = new java.awt.event.ActionListener() { 394 javax.swing.Timer myTimer; 395 Runnable runnable; 396 397 java.awt.event.ActionListener init(javax.swing.Timer t, Runnable r) { 398 this.myTimer = t; 399 this.runnable = r; 400 return this; 401 } 402 403 @Override 404 public void actionPerformed(java.awt.event.ActionEvent e) { 405 this.myTimer.stop(); 406 javax.swing.SwingUtilities.invokeLater(runnable); 407 } 408 }.init(timer, run); 409 timer.addActionListener(l); 410 timer.start(); 411 } 412 } 413 414 private final static Logger log = LoggerFactory.getLogger(ProgDebugger.class); 415 416}