001package jmri.implementation; 002import java.awt.event.ActionEvent; 003import java.awt.event.ActionListener; 004import javax.swing.Timer; 005import jmri.ProgListener; 006import jmri.Programmer; 007import jmri.jmrix.AbstractProgrammerFacade; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011/** 012 * Programmer facade for single index multi-CV access. 013 * <p> 014 * Used through the String write/read/confirm interface. Accepts address 015 * formats: 016 * <ul> 017 * <li> T2CV.11.12 <br> 018 * The write operation writes 11 to the first index CV (201), 12 to the 2nd 019 * index CV (202), then writes the data to CV 203 (MSB) and 204 (LSB).<br> 020 * The read operation is slightly different, writing 111 (100+11) to CV201, 021 * then 12 to the 2nd index CV (202), then writes 100 to CV204, then reads the 022 * two values from CV203 and CV204. 023 * <li> T3CV.11.12.13 <br> 024 * The write operation writes 11 to the first index CV (201), the data to the 025 * 2nd index CV (202), then writes 12 to CV203 and 13 to CV204.<br> 026 * The read operation writes 11 to CV201, then 12 to CV203, then 13 to CV204, 027 * then reads from CV202. 028 * </ul> 029 * All others pass through to the next facade or programmer. E.g. 123 will do a 030 * write/read/confirm to 123, or some other facade can provide "normal" indexed 031 * addressing. 032 * 033 * @see jmri.implementation.ProgrammerFacadeSelector 034 * 035 * @author Bob Jacobsen Copyright (C) 2013, 2016 036 * @author Andrew Crosland Copyright (C) 2021 037 */ 038public class TwoIndexTcsProgrammerFacade extends AbstractProgrammerFacade implements ProgListener { 039 040 /** 041 * @param prog the programmer this facade is attached to 042 */ 043 public TwoIndexTcsProgrammerFacade(Programmer prog) { 044 super(prog); 045 } 046 047 // these could be constructor arguments, but until there's another decoder 048 // this weird, for simplicity we leave them as constants 049 static final String indexPI = "201"; 050 static final String indexSI = "202"; 051 static final String valMSB = "203"; 052 static final String valLSB = "204"; 053 static final String readStrobe = "204"; // CV that has to be written before read 054 static final String format2Flag = "T2CV"; // flag to indicate this type of CV 055 static final String format3Flag = "T3CV"; // flag to indicate this type of CV 056 static final int readOffset = 100; 057 058 // members for handling the programmer interface 059 int _val; // remember the value being read/written for confirmative reply 060 String _cv; // remember the cv number being read/written 061 int valuePI; // value to write to PI or -1 062 int valueSI; // value to write to SI or -1 063 int valueMSB; // value to write to MSB or -1 064 int valueLSB; // value to write to LSB or -1 065 int _startVal; // Current CV value hint 066 int _startMSB; 067 int _startLSB; 068 069 private void parseCV(String cv) throws IllegalArgumentException { 070 valuePI = -1; 071 valueSI = -1; 072 if (cv.contains(".")) { 073 String[] splits = cv.split("\\."); 074 if (splits.length == 3 && splits[0].equals(format2Flag)) { 075 valuePI = Integer.parseInt(splits[1]); 076 valueSI = Integer.parseInt(splits[2]); 077 } else if (splits.length == 4 && splits[0].equals(format3Flag)) { 078 valuePI = Integer.parseInt(splits[1]); 079 valueMSB = Integer.parseInt(splits[2]); 080 valueLSB = Integer.parseInt(splits[3]); 081 } else { 082 _cv = cv; // this is a pass through operation 083 } 084 } else { 085 _cv = cv; 086 } 087 } 088 089 // programming interface 090 @Override 091 synchronized public void writeCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 092 _val = val; 093 useProgrammer(p); 094 parseCV(CV); 095 upperByte = 0; 096 if (valuePI == -1) { // this is pass through 097 state = ProgState.PROGRAMMING; 098 prog.writeCV(_cv, val, this); 099 } else { 100 // write index first 101 state = ProgState.DOSIFORWRITE; 102 prog.writeCV(indexPI, valuePI, this); 103 } 104 } 105 106 @Override 107 synchronized public void readCV(String CV, jmri.ProgListener p) throws jmri.ProgrammerException { 108 readCV(CV, p, 0); 109 } 110 111 @Override 112 synchronized public void readCV(String CV, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException { 113 useProgrammer(p); 114 parseCV(CV); 115 _startVal = startVal; 116 _startMSB = startVal / 256; 117 _startLSB = startVal % 256; 118 upperByte = 0; 119 if (valuePI == -1) { 120 state = ProgState.PROGRAMMING; 121 prog.readCV(_cv, this, startVal); 122 } else { 123 // write index first; 2nd operation depends on type 124 if (valueSI == -1) { 125 state = ProgState.DOMSBFORREAD; 126 } else { 127 state = ProgState.DOSIFORREAD; 128 } 129 prog.writeCV(indexPI, valuePI + readOffset, this); 130 } 131 } 132 133 @Override 134 synchronized public void confirmCV(String CV, int startVal, jmri.ProgListener p) throws jmri.ProgrammerException { 135 useProgrammer(p); 136 parseCV(CV); 137 _startVal = startVal; 138 _startMSB = startVal/256; 139 _startLSB = startVal%256; 140 upperByte = 0; 141 if (valuePI == -1) { 142 state = ProgState.PROGRAMMING; 143 prog.confirmCV(_cv, startVal, this); 144 } else { 145 // write index first; 2nd operation depends on type 146 if (valueSI == -1) { 147 state = ProgState.DOMSBFORREAD; 148 } else { 149 state = ProgState.DOSIFORREAD; 150 } 151 prog.writeCV(indexPI, valuePI + readOffset, this); 152 } 153 } 154 155 private jmri.ProgListener _usingProgrammer = null; 156 157 // internal method to remember who's using the programmer 158 protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException { 159 // test for only one! 160 if (_usingProgrammer != null && _usingProgrammer != p) { 161 if (log.isInfoEnabled()) { 162 log.info("programmer already in use by {}", _usingProgrammer); 163 } 164 throw new jmri.ProgrammerException("programmer in use"); 165 } else { 166 _usingProgrammer = p; 167 } 168 } 169 170 enum ProgState { 171 172 PROGRAMMING, // doing last read/write, next reply is end 173 DOSIFORREAD, // reading, write to SI next 174 DOSTROBEFORREAD,// reading, write to strobe CV next 175 DOMSBFORREAD, // reading, write to MSB next 176 DOLSBFORREAD, // reading, write to LSB next 177 DOREADFIRST, // reading, get MSB next 178 FINISHREAD, // reading, read CV (LSB) next 179 DOSIFORWRITE, // writing, write to SI next 180 DOWRITEFIRST, // writing, write CV (MSB) next 181 FINISHWRITE, // writing, write CV (LSB) next 182 NOTPROGRAMMING // idle, doing nothing, no reply expected 183 } 184 185 ProgState state = ProgState.NOTPROGRAMMING; 186 187 int upperByte; 188 189 public static int delayInterval = 10; // public static so can be changed in a script 190 191 /** {@inheritDoc} 192 * Note this assumes that there's only one phase to the operation 193 */ 194 @Override 195 synchronized public void programmingOpReply(int value, int status) { 196 if (log.isDebugEnabled()) { 197 log.debug("notifyProgListenerEnd value {} status {}", value, status); 198 } 199 200 if (_usingProgrammer == null) { 201 log.error("No listener to notify, reset and ignore"); 202 state = ProgState.NOTPROGRAMMING; 203 return; 204 } 205 206 // Complete processing later 207 // originally installed so that WOWDecoder will go through a complete power on reset and not brown out between CV read/writes 208 ActionListener taskPerformer = new ActionListener() { 209 final int myValue = value; 210 final int myStatus = status; 211 @Override 212 public void actionPerformed(ActionEvent evt) { 213 processProgrammingOpReply(myValue, myStatus); 214 } 215 }; 216 Timer t = new Timer(delayInterval, taskPerformer ); 217 t.setRepeats(false); 218 t.start(); 219 } 220 221 // After a Swing delay, this processes the reply 222 protected void processProgrammingOpReply(int value, int status) { 223 if (status != OK && state != ProgState.DOREADFIRST) { // we accept errors on the CV204 write due to CS-105 behavior with TCS decoders. 224 // pass abort up 225 log.debug("Reset and pass abort up"); 226 jmri.ProgListener temp = _usingProgrammer; 227 _usingProgrammer = null; // done 228 state = ProgState.NOTPROGRAMMING; 229 temp.programmingOpReply(value, status); 230 return; 231 } 232 233 switch (state) { 234 case DOSIFORREAD: 235 try { 236 state = ProgState.DOSTROBEFORREAD; 237 prog.writeCV(indexSI, valueSI, this); 238 } catch (jmri.ProgrammerException e) { 239 log.error("Exception doing write SI for read", e); 240 } 241 break; 242 case DOSTROBEFORREAD: 243 try { 244 state = ProgState.DOREADFIRST; 245 prog.writeCV(readStrobe, 0, this); 246 } catch (jmri.ProgrammerException e) { 247 log.error("Exception doing write strobe for read", e); 248 } 249 break; 250 case DOREADFIRST: 251 try { 252 state = ProgState.FINISHREAD; 253 prog.readCV(valMSB, this, _startMSB); 254 } catch (jmri.ProgrammerException e) { 255 log.error("Exception doing read first", e); 256 } 257 break; 258 case FINISHREAD: 259 try { 260 state = ProgState.PROGRAMMING; 261 if (valuePI != -1 && valueSI == -1) { 262 upperByte = 0; 263 prog.readCV(indexSI, this, _startVal); 264 } else { 265 upperByte = value; 266 prog.readCV(valLSB, this, _startLSB); 267 } 268 } catch (jmri.ProgrammerException e) { 269 log.error("Exception doing final read", e); 270 } 271 break; 272 273 case DOMSBFORREAD: 274 try { 275 state = ProgState.DOLSBFORREAD; 276 prog.writeCV(valMSB, valueMSB, this); 277 } catch (jmri.ProgrammerException e) { 278 log.error("Exception doing write strobe for read", e); 279 } 280 break; 281 case DOLSBFORREAD: 282 try { 283 state = ProgState.FINISHREAD; 284 prog.writeCV(valLSB, valueLSB, this); 285 } catch (jmri.ProgrammerException e) { 286 log.error("Exception doing write strobe for read", e); 287 } 288 break; 289 290 case DOSIFORWRITE: 291 if (valueSI != -1) { 292 // writing SI index after PI 293 try { 294 state = ProgState.DOWRITEFIRST; 295 prog.writeCV(indexSI, valueSI, this); 296 } catch (jmri.ProgrammerException e) { 297 log.error("Exception doing write SI for write", e); 298 } 299 } else { 300 // writing data after PI 301 try { 302 state = ProgState.DOWRITEFIRST; 303 prog.writeCV(indexSI, _val, this); 304 } catch (jmri.ProgrammerException e) { 305 log.error("Exception doing write SI for write", e); 306 } 307 } 308 break; 309 case DOWRITEFIRST: 310 if (valueSI != -1) { 311 // write upper data 312 try { 313 state = ProgState.FINISHWRITE; 314 prog.writeCV(valMSB, _val / 256, this); 315 } catch (jmri.ProgrammerException e) { 316 log.error("Exception doing write MSB for write", e); 317 } 318 } else { 319 // write 2nd index 320 try { 321 state = ProgState.FINISHWRITE; 322 prog.writeCV(valMSB, valueMSB, this); 323 } catch (jmri.ProgrammerException e) { 324 log.error("Exception doing write MSB for write", e); 325 } 326 } 327 break; 328 case FINISHWRITE: 329 if (valueSI != -1) { 330 try { 331 state = ProgState.PROGRAMMING; 332 prog.writeCV(valLSB, _val & 255, this); 333 } catch (jmri.ProgrammerException e) { 334 log.error("Exception doing final write", e); 335 } 336 } else { 337 try { 338 state = ProgState.PROGRAMMING; 339 prog.writeCV(valLSB, valueLSB, this); 340 } catch (jmri.ProgrammerException e) { 341 log.error("Exception doing final write", e); 342 } 343 } 344 break; 345 346 case PROGRAMMING: 347 // the programmingOpReply handler might send an immediate reply, so 348 // clear the current listener _first_ 349 jmri.ProgListener temp = _usingProgrammer; 350 _usingProgrammer = null; // done 351 state = ProgState.NOTPROGRAMMING; 352 temp.programmingOpReply(upperByte * 256 + value, status); 353 break; 354 355 default: 356 log.error("Unexpected state on reply: {}", state); 357 // clean up as much as possible 358 _usingProgrammer = null; 359 state = ProgState.NOTPROGRAMMING; 360 break; 361 } 362 } 363 364 private final static Logger log = LoggerFactory.getLogger(TwoIndexTcsProgrammerFacade.class); 365 366}