001package jmri.implementation; 002 003import java.util.ArrayDeque; 004import java.util.ArrayList; 005import java.util.Deque; 006import java.util.List; 007 008import jmri.ProgListener; 009import jmri.Programmer; 010import jmri.jmrix.AbstractProgrammerFacade; 011import jmri.util.ThreadingUtil; 012 013/** 014 * Programmer facade for single index multi-CV access. 015 * <p> 016 * Used through the String write/read/confirm interface. Accepts one address 017 * format in LOCONETBDOPSWMODE mode only: 018 * <ul> 019 * <li> 115.25.16<br> 020 * The 115 is the device type (115 is a DS64); 25 is the first OpSw for 021 * setting the offset address; 16 is the address to be written. 022 * This will write/read 32 bits from the associated Op Switch word. 023 * </ul> 024 * All others pass through to the next facade or programmer. E.g. 123 will do a 025 * write/read/confirm to 123, or some other facade can provide "normal" indexed 026 * addressing. 027 * 028 * @see jmri.implementation.ProgrammerFacadeSelector 029 * 030 * @author Bob Jacobsen Copyright (C) 2013, 2016, 2026 031 */ 032public class DigitraxOpSwWordFacade extends AbstractProgrammerFacade implements ProgListener { 033 034 /** 035 * @param prog the programmer this facade is attached to 036 */ 037 public DigitraxOpSwWordFacade(Programmer prog) { 038 super(prog); 039 } 040 041 String _cv; // the CV being processed 042 int _val; // value to be written or being read 043 ProgState state = ProgState.NOTPROGRAMMING; 044 045 int accumulator; // when accumulating a value during read 046 String typeValue; // device type value 047 int pageAddress; // if > 0, this is the OpSw to start writing the page number, 048 // else this is an unpaged access 049 int pageNumber; // the page number to be written to the address 050 051 // members for handling the programmer interface 052 static class OpSwCommand { 053 OpSwCommand(String cv, int value, boolean read) { 054 this.cv = cv; 055 this.value = value; 056 this.read = read; 057 } 058 String cv; 059 int value; 060 boolean read; // write if false 061 int bit; // 0-31 number to shift the returned bit 062 } 063 064 Deque<OpSwCommand> commands = new ArrayDeque<OpSwCommand>(); 065 066 private void parseCV(String cv) throws IllegalArgumentException { 067 pageAddress = -1; 068 069 if (cv.contains(".")) { 070 String[] splits = cv.split("\\."); 071 if (splits.length == 3) { 072 typeValue = splits[0]; 073 pageAddress = Integer.parseInt(splits[1]); 074 pageNumber = Integer.parseInt(splits[2]); 075 } else { 076 _cv = cv; // this is a pass through operation 077 } 078 } else { 079 _cv = cv; 080 } 081 } 082 083 // typical typeValue value is "155" 084 List<OpSwCommand> addressToCommands(String typeValue, int address) { 085 var result = new ArrayList<OpSwCommand>(); 086 for (int i = 0; i <= 7; i++) { 087 int bit = (address>>i)&0x01; 088 String cv = typeValue+"."+Integer.toString(i+pageAddress); 089 result.add(new OpSwCommand(cv, bit, false)); 090 } 091 return result; 092 } 093 094 int offsetConstant = 33-0; // OpsSwitch number for LSB of data value 095 // could also be pageAddress+8 096 097 OpSwCommand oneBit(String typeValue, int swNum, int data, boolean read) { 098 int bitNum = swNum-33; // is this a constant? 099 int bit = (data>>bitNum)&0x01; 100 String cv = typeValue+"."+Integer.toString(swNum); 101 var retval = new OpSwCommand(cv, bit, read); 102 retval.bit = bitNum; 103 return retval; 104 } 105 106 // programming interface 107 @Override 108 synchronized public void writeCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 109 log.debug("writeCV with {}", p); 110 _val = val; 111 useProgrammer(p); 112 pageAddress= -1; 113 parseCV(CV); 114 if (pageAddress == -1) { // this is pass through 115 state = ProgState.PROGRAMMING; 116 prog.writeCV(_cv, val, this); 117 } else { 118 commands = new ArrayDeque<OpSwCommand>(); 119 120 commands.addAll(addressToCommands(typeValue, pageNumber)); 121 122 for (int i = 46; i <= 48; i++) commands.add(oneBit(typeValue, i, val, false)); 123 124 for (int i = 33; i <= 45; i++) commands.add(oneBit(typeValue, i, val, false)); 125 126 for (int i = 62; i <= 64; i++) commands.add(oneBit(typeValue, i, val, false)); 127 128 for (int i = 49; i <= 61; i++) commands.add(oneBit(typeValue, i, val, false)); 129 130 state = ProgState.SENDWRITESEQUENCE; 131 prog.writeCV(commands.getFirst().cv, commands.getFirst().value, this); 132 } 133 } 134 135 @Override 136 synchronized public void readCV(String CV, jmri.ProgListener p) throws jmri.ProgrammerException { 137 readCV(CV, p, 0); 138 } 139 140 int accumulation; // accumulate the read result 141 @Override 142 synchronized public void readCV(String CV, jmri.ProgListener p, int val) throws jmri.ProgrammerException { 143 log.debug("readCV with {}", p); 144 useProgrammer(p); 145 pageAddress= -1; 146 accumulation = 0; 147 parseCV(CV); 148 if (pageAddress == -1) { 149 state = ProgState.PROGRAMMING; 150 prog.readCV(_cv, this, val); 151 } else { 152 commands = new ArrayDeque<OpSwCommand>(); 153 154 commands.addAll(addressToCommands(typeValue, pageNumber)); 155 156 for (int i = 46; i <= 48; i++) commands.add(oneBit(typeValue, i, val, true)); 157 158 for (int i = 33; i <= 45; i++) commands.add(oneBit(typeValue, i, val, true)); 159 160 for (int i = 62; i <= 64; i++) commands.add(oneBit(typeValue, i, val, true)); 161 162 for (int i = 49; i <= 61; i++) commands.add(oneBit(typeValue, i, val, true)); 163 164 state = ProgState.SENDREADSEQUENCE; 165 prog.writeCV(commands.getFirst().cv, commands.getFirst().value, this); // first address bit written 166 } 167 } 168 169 @Override 170 synchronized public void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 171 useProgrammer(p); 172 pageAddress= -1; 173 parseCV(CV); 174 if (pageAddress == -1) { 175 state = ProgState.PROGRAMMING; 176 prog.confirmCV(_cv, val, this); 177 } else { 178 commands = new ArrayDeque<OpSwCommand>(); 179 180 commands.addAll(addressToCommands(typeValue, pageNumber)); 181 182 for (int i = 46; i <= 48; i++) commands.add(oneBit(typeValue, i, val, true)); 183 184 for (int i = 33; i <= 45; i++) commands.add(oneBit(typeValue, i, val, true)); 185 186 for (int i = 62; i <= 64; i++) commands.add(oneBit(typeValue, i, val, true)); 187 188 for (int i = 49; i <= 61; i++) commands.add(oneBit(typeValue, i, val, true)); 189 190 state = ProgState.SENDWRITESEQUENCE; 191 prog.confirmCV(commands.getFirst().cv, commands.getFirst().value, this); 192 } 193 } 194 195 private jmri.ProgListener _usingProgrammer = null; 196 197 // internal method to remember who's using the programmer 198 protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException { 199 log.debug("useProgrammer for {} with {}", p, _usingProgrammer, new Exception("traceback")); 200 // test for only one! 201 if (_usingProgrammer != null && _usingProgrammer != p) { 202 log.debug("programmer already in use by {}", _usingProgrammer); 203 throw new jmri.ProgrammerException("programmer in use"); 204 } else { 205 _usingProgrammer = p; 206 } 207 } 208 209 enum ProgState { 210 211 PROGRAMMING, // doing last read/write, next reply is end; typically a single operation 212 NOTPROGRAMMING, // idle 213 SENDWRITESEQUENCE, 214 SENDREADSEQUENCE 215 } 216 217 public static int delayInterval = 10; // public static so can be changed in a script 218 219 /** {@inheritDoc} 220 * Note this assumes that there's only one phase to the operation 221 */ 222 @Override 223 synchronized public void programmingOpReply(int value, int status) { 224 log.debug("notifyProgListenerEnd value {} status {}", value, status); 225 226 if (_usingProgrammer == null) { 227 log.error("No listener to notify, reset and ignore"); 228 state = ProgState.NOTPROGRAMMING; 229 return; 230 } 231 232 // Complete processing later 233 final int myValue = value; 234 final int myStatus = status; 235 ThreadingUtil.runOnGUIDelayed(() -> { 236 processProgrammingOpReply(myValue, myStatus); 237 }, delayInterval); 238 } 239 240 // After a delay, this processes the reply on the Layout Thread 241 protected void processProgrammingOpReply(int value, int status) { 242 if (status != OK) { 243 log.debug("Reset and pass abort up"); 244 jmri.ProgListener temp = _usingProgrammer; 245 _usingProgrammer = null; // done 246 state = ProgState.NOTPROGRAMMING; 247 commands.clear(); 248 temp.programmingOpReply(value, status); 249 return; 250 } 251 252 switch (state) { 253 case PROGRAMMING: 254 // the programmingOpReply handler might send an immediate reply, so 255 // clear the current listener _first_ 256 jmri.ProgListener temp = _usingProgrammer; 257 _usingProgrammer = null; // done 258 state = ProgState.NOTPROGRAMMING; 259 commands.clear(); // just in case 260 temp.programmingOpReply(value, status); 261 break; 262 263 case SENDWRITESEQUENCE: 264 // drop last-sent item 265 commands.pollFirst(); 266 // and send next 267 var next = commands.peekFirst(); 268 if (next == null) { 269 // operation is done 270 // the programmingOpReply handler might send an immediate reply, so 271 // clear the current listener _first_ 272 temp = _usingProgrammer; 273 _usingProgrammer = null; // done 274 state = ProgState.NOTPROGRAMMING; 275 commands.clear(); // just in case 276 log.debug("_usingProgrammer: {}", _usingProgrammer); 277 temp.programmingOpReply(accumulation, status); 278 return; 279 280 } else { 281 // send that one 282 try { 283 prog.writeCV(next.cv, next.value, this); 284 } catch (jmri.ProgrammerException e) { 285 log.error("Exception doing write OpSw", e); 286 } 287 } 288 return; 289 290 case SENDREADSEQUENCE: 291 // drop last-sent item 292 var last = commands.pollFirst(); 293 // if last was a read, accumulate value 294 if (last.read) { 295 accumulation = accumulation | ((value&0x01)<<last.bit); 296 } 297 // and send next 298 next = commands.peekFirst(); 299 if (next == null) { 300 // operation is done 301 // the programmingOpReply handler might send an immediate reply, so 302 // clear the current listener _first_ 303 temp = _usingProgrammer; 304 _usingProgrammer = null; // done 305 state = ProgState.NOTPROGRAMMING; 306 commands.clear(); // just in case 307 log.debug("_usingProgrammer: {}", _usingProgrammer); 308 temp.programmingOpReply(accumulation, status); 309 return; 310 311 } else { 312 // send that one 313 try { 314 if (next.read) { 315 prog.readCV(next.cv, this, next.value); 316 } else { 317 prog.writeCV(next.cv, next.value, this); 318 } 319 } catch (jmri.ProgrammerException e) { 320 log.error("Exception doing write OpSw", e); 321 } 322 } 323 return; 324 325 default: 326 log.error("Unexpected state on reply: {}", state); 327 // clean up as much as possible 328 _usingProgrammer = null; 329 state = ProgState.NOTPROGRAMMING; 330 commands.clear(); 331 return; 332 } 333 } 334 335 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DigitraxOpSwWordFacade.class); 336 337}