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 /** {@inheritDoc} 190 * Note this assumes that there's only one phase to the operation 191 */ 192 @Override 193 synchronized public void programmingOpReply(int value, int status) { 194 if (log.isDebugEnabled()) { 195 log.debug("notifyProgListenerEnd value {} status {}", value, status); 196 } 197 198 if (_usingProgrammer == null) { 199 log.error("No listener to notify, reset and ignore"); 200 state = ProgState.NOTPROGRAMMING; 201 return; 202 } 203 204 // Complete processing later so that WOWDecoder will go through a complete power on reset and not brown out between CV read/writes 205 int interval = 150; 206 ActionListener taskPerformer = new ActionListener() { 207 final int myValue = value; 208 final int myStatus = status; 209 @Override 210 public void actionPerformed(ActionEvent evt) { 211 processProgrammingOpReply(myValue, myStatus); 212 } 213 }; 214 Timer t = new Timer(interval, taskPerformer ); 215 t.setRepeats(false); 216 t.start(); 217 } 218 219 // After a Swing delay, this processes the reply 220 protected void processProgrammingOpReply(int value, int status) { 221 if (status != OK ) { 222 // pass abort up 223 log.debug("Reset and pass abort up"); 224 jmri.ProgListener temp = _usingProgrammer; 225 _usingProgrammer = null; // done 226 state = ProgState.NOTPROGRAMMING; 227 temp.programmingOpReply(value, status); 228 return; 229 } 230 231 switch (state) { 232 case DOSIFORREAD: 233 try { 234 state = ProgState.DOSTROBEFORREAD; 235 prog.writeCV(indexSI, valueSI, this); 236 } catch (jmri.ProgrammerException e) { 237 log.error("Exception doing write SI for read", e); 238 } 239 break; 240 case DOSTROBEFORREAD: 241 try { 242 state = ProgState.DOREADFIRST; 243 prog.writeCV(readStrobe, readOffset, this); 244 } catch (jmri.ProgrammerException e) { 245 log.error("Exception doing write strobe for read", e); 246 } 247 break; 248 case DOREADFIRST: 249 try { 250 state = ProgState.FINISHREAD; 251 prog.readCV(valMSB, this, _startMSB); 252 } catch (jmri.ProgrammerException e) { 253 log.error("Exception doing read first", e); 254 } 255 break; 256 case FINISHREAD: 257 try { 258 state = ProgState.PROGRAMMING; 259 if (valuePI != -1 && valueSI == -1) { 260 upperByte = 0; 261 prog.readCV(indexSI, this, _startVal); 262 } else { 263 upperByte = value; 264 prog.readCV(valLSB, this, _startLSB); 265 } 266 } catch (jmri.ProgrammerException e) { 267 log.error("Exception doing final read", e); 268 } 269 break; 270 271 case DOMSBFORREAD: 272 try { 273 state = ProgState.DOLSBFORREAD; 274 prog.writeCV(valMSB, valueMSB, this); 275 } catch (jmri.ProgrammerException e) { 276 log.error("Exception doing write strobe for read", e); 277 } 278 break; 279 case DOLSBFORREAD: 280 try { 281 state = ProgState.FINISHREAD; 282 prog.writeCV(valLSB, valueLSB, this); 283 } catch (jmri.ProgrammerException e) { 284 log.error("Exception doing write strobe for read", e); 285 } 286 break; 287 288 case DOSIFORWRITE: 289 if (valueSI != -1) { 290 // writing SI index after PI 291 try { 292 state = ProgState.DOWRITEFIRST; 293 prog.writeCV(indexSI, valueSI, this); 294 } catch (jmri.ProgrammerException e) { 295 log.error("Exception doing write SI for write", e); 296 } 297 } else { 298 // writing data after PI 299 try { 300 state = ProgState.DOWRITEFIRST; 301 prog.writeCV(indexSI, _val, this); 302 } catch (jmri.ProgrammerException e) { 303 log.error("Exception doing write SI for write", e); 304 } 305 } 306 break; 307 case DOWRITEFIRST: 308 if (valueSI != -1) { 309 // write upper data 310 try { 311 state = ProgState.FINISHWRITE; 312 prog.writeCV(valMSB, _val / 256, this); 313 } catch (jmri.ProgrammerException e) { 314 log.error("Exception doing write MSB for write", e); 315 } 316 } else { 317 // write 2nd index 318 try { 319 state = ProgState.FINISHWRITE; 320 prog.writeCV(valMSB, valueMSB, this); 321 } catch (jmri.ProgrammerException e) { 322 log.error("Exception doing write MSB for write", e); 323 } 324 } 325 break; 326 case FINISHWRITE: 327 if (valueSI != -1) { 328 try { 329 state = ProgState.PROGRAMMING; 330 prog.writeCV(valLSB, _val & 255, this); 331 } catch (jmri.ProgrammerException e) { 332 log.error("Exception doing final write", e); 333 } 334 } else { 335 try { 336 state = ProgState.PROGRAMMING; 337 prog.writeCV(valLSB, valueLSB, this); 338 } catch (jmri.ProgrammerException e) { 339 log.error("Exception doing final write", e); 340 } 341 } 342 break; 343 344 case PROGRAMMING: 345 // the programmingOpReply handler might send an immediate reply, so 346 // clear the current listener _first_ 347 jmri.ProgListener temp = _usingProgrammer; 348 _usingProgrammer = null; // done 349 state = ProgState.NOTPROGRAMMING; 350 temp.programmingOpReply(upperByte * 256 + value, status); 351 break; 352 353 default: 354 log.error("Unexpected state on reply: {}", state); 355 // clean up as much as possible 356 _usingProgrammer = null; 357 state = ProgState.NOTPROGRAMMING; 358 break; 359 } 360 } 361 362 private final static Logger log = LoggerFactory.getLogger(TwoIndexTcsProgrammerFacade.class); 363 364}