001package jmri.implementation; 002 003import jmri.ProgListener; 004import jmri.Programmer; 005import jmri.jmrix.AbstractProgrammerFacade; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009/** 010 * Programmer facade, at this point just an example. 011 * <p> 012 * This is for decoders that have an alternate high-CV access method for command 013 * stations that can't address all 1024. It falls back to that mode if the CS 014 * can't directly address an requested CV address. In the fall back, CVs from 0 015 * to "top" are addressed directly. Above the top CV, the upper part of the 016 * address is written to a specific CV, followed by an operation to just the 017 * lower part of the address. The upper and lower parts are calculated using a 018 * supplied modulus, e.g. 100, and an indicator value that's added in. Finally, 019 * the specific CV is reset to zero to end the offset operation. 020 * <p> 021 * For example, to write the value N to CV xyy, this will do (modulo = 100, 022 * indicator = 200): 023 * <ul> 024 * <li>Write 200+x*10 to CV7 where 10 is cvFactor, 200 is indicator and 7 is 025 * addrCV 026 * <li>Write N to CVyy 027 * <li>Write 0 to CV7 028 * </ul> 029 * <p> 030 * This method is used by some Zimo decoders 031 * 032 * @see jmri.implementation.ProgrammerFacadeSelector 033 * 034 * @author Bob Jacobsen Copyright (C) 2013 035 * @author Andrew Crosland Copyright (C) 2021 036 */ 037public class ResettingOffsetHighCvProgrammerFacade extends AbstractProgrammerFacade implements ProgListener { 038 039 /** 040 * @param prog the programmer this facade is attached to 041 * @param top CVs above this use the indirect method 042 * @param addrCV CV to which the high part of address is to be written 043 * @param cvFactor CV to which the low part of address is to be written 044 * @param modulo modulus for determining high/low address parts 045 * @param indicator value added to calculation to split high and low parts 046 */ 047 public ResettingOffsetHighCvProgrammerFacade(Programmer prog, String top, String addrCV, String cvFactor, String modulo, String indicator) { 048 super(prog); 049 this.top = Integer.parseInt(top); 050 this.addrCV = addrCV; 051 this.cvFactor = Integer.parseInt(cvFactor); 052 this.modulo = Integer.parseInt(modulo); 053 this.indicator = Integer.parseInt(indicator); 054 } 055 056 int top; 057 String addrCV; 058 int cvFactor; 059 int modulo; 060 int indicator; 061 062 // members for handling the programmer interface 063 int _val; // remember the value being read/written for confirmative reply 064 int _cv; // remember the cv being read/written 065 int _startVal; // remember the starting value (hint) 066 067 @Override 068 public void writeCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 069 log.debug("start writeCV"); 070 _cv = Integer.parseInt(CV); 071 _val = val; 072 useProgrammer(p); 073 if (prog.getCanWrite(CV) || _cv <= top) { 074 state = ProgState.PROGRAMMING; 075 prog.writeCV(CV, val, this); 076 } else { 077 // write index first 078 state = ProgState.FINISHWRITE; 079 prog.writeCV(addrCV, (_cv / modulo) * cvFactor + indicator, this); 080 } 081 } 082 083 @Override 084 public void readCV(String CV, jmri.ProgListener p) throws jmri.ProgrammerException { 085 readCV(CV, p, 0); 086 } 087 088 @Override 089 public void readCV(String CV, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException { 090 log.debug("start readCV"); 091 _cv = Integer.parseInt(CV); 092 _startVal = startVal; 093 useProgrammer(p); 094 if (prog.getCanRead(CV) || _cv <= top) { 095 state = ProgState.PROGRAMMING; 096 prog.readCV(CV, this, startVal); 097 } else { 098 // write index first 099 state = ProgState.FINISHREAD; 100 prog.writeCV(addrCV, (_cv / modulo) * cvFactor + indicator, this); 101 } 102 } 103 104 private jmri.ProgListener _usingProgrammer = null; 105 106 // internal method to remember who's using the programmer 107 protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException { 108 // test for only one! 109 if (_usingProgrammer != null && _usingProgrammer != p) { 110 if (log.isInfoEnabled()) { 111 log.info("programmer already in use by {}", _usingProgrammer); 112 } 113 throw new jmri.ProgrammerException("programmer in use"); 114 } else { 115 _usingProgrammer = p; 116 } 117 } 118 119 enum ProgState { 120 121 PROGRAMMING, FINISHREAD, FINISHWRITE, RESET, NOTPROGRAMMING 122 } 123 ProgState state = ProgState.NOTPROGRAMMING; 124 125 // get notified of the final result 126 // Note this assumes that there's only one phase to the operation 127 @Override 128 public void programmingOpReply(int value, int status) { 129 if (log.isDebugEnabled()) { 130 log.debug("notifyProgListenerEnd value {} status {}", value, status); 131 } 132 133 if (status != OK ) { 134 // pass abort up 135 log.debug("Reset and pass abort up"); 136 jmri.ProgListener temp = _usingProgrammer; 137 _usingProgrammer = null; // done 138 state = ProgState.NOTPROGRAMMING; 139 temp.programmingOpReply(value, status); 140 return; 141 } 142 143 if (_usingProgrammer == null) { 144 log.error("No listener to notify, reset and ignore"); 145 state = ProgState.NOTPROGRAMMING; 146 return; 147 } 148 149 switch (state) { 150 case FINISHREAD: 151 try { 152 state = ProgState.RESET; 153 prog.readCV(""+(_cv % modulo), this, _startVal); 154 } catch (jmri.ProgrammerException e) { 155 log.error("Exception doing final read", e); 156 } 157 break; 158 case FINISHWRITE: 159 try { 160 state = ProgState.RESET; 161 prog.writeCV(""+(_cv % modulo), _val, this); 162 } catch (jmri.ProgrammerException e) { 163 log.error("Exception doing final write", e); 164 } 165 break; 166 case RESET: 167 try { 168 state = ProgState.PROGRAMMING; 169 prog.writeCV(addrCV, 0, this); 170 } catch (jmri.ProgrammerException e) { 171 log.error("Exception doing reset write", e); 172 } 173 break; 174 case PROGRAMMING: 175 // the programmingOpReply handler might send an immediate reply, so 176 // clear the current listener _first_ 177 jmri.ProgListener temp = _usingProgrammer; 178 _usingProgrammer = null; // done 179 state = ProgState.NOTPROGRAMMING; 180 temp.programmingOpReply(value, status); 181 break; 182 default: 183 log.error("Unexpected state on reply: {}", state); 184 // clean up as much as possible 185 _usingProgrammer = null; 186 state = ProgState.NOTPROGRAMMING; 187 188 } 189 190 } 191 192 // Access to full address space provided by this. 193 @Override 194 public boolean getCanRead() { 195 return true; 196 } 197 198 @Override 199 public boolean getCanRead(String addr) { 200 return Integer.parseInt(addr) <= 1024; 201 } 202 203 @Override 204 public boolean getCanWrite() { 205 return true; 206 } 207 208 @Override 209 public boolean getCanWrite(String addr) { 210 return Integer.parseInt(addr) <= 1024; 211 } 212 213 private final static Logger log = LoggerFactory.getLogger(ResettingOffsetHighCvProgrammerFacade.class); 214 215}