001package jmri.implementation; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import java.util.ArrayList; 005import java.util.List; 006import javax.annotation.Nonnull; 007import jmri.AddressedProgrammer; 008import jmri.CommandStation; 009import jmri.InstanceManager; 010import jmri.NmraPacket; 011import jmri.ProgListener; 012import jmri.Programmer; 013import jmri.ProgrammerException; 014import jmri.ProgrammingMode; 015import jmri.jmrix.AbstractProgrammerFacade; 016import org.slf4j.Logger; 017import org.slf4j.LoggerFactory; 018 019/** 020 * Programmer facade for access to Accessory Decoder Ops Mode programming 021 * <p> 022 * (Eventually implements four modes, passing all others to underlying 023 * programmer: 024 * <ul> 025 * <li>OPSACCBYTEMODE 026 * <li>OPSACCBITMODE 027 * <li>OPSACCEXTBYTEMODE 028 * <li>OPSACCEXTBITMODE 029 * </ul> 030 * <p> 031 * Used through the String write/read/confirm interface. Accepts integers as 032 * addresses, but then emits NMRA DCC packets through the default CommandStation 033 * interface (which must be present) 034 * 035 * @see jmri.implementation.ProgrammerFacadeSelector 036 * 037 * @author Bob Jacobsen Copyright (C) 2014 038 * @author Andrew Crosland Copyright (C) 2021 039 */ 040// @ToDo("transform to annotations requires e.g. http://alchemy.grimoire.ca/m2/sites/ca.grimoire/todo-annotations/") 041// @ToDo("get address from underlyng programmer (which might require adding a new subclass structure to Programmer)") 042// @ToDo("finish mode handling; what gets passed through?") 043// @ToDo("read handling needs to be aligned with other ops mode programmers") 044// @ToDo("make sure jmri/jmrit/progsupport/ProgServiceModePane shows the modes, and that DP/DP3 displays them as it configures a decoder") 045public class AccessoryOpsModeProgrammerFacade extends AbstractProgrammerFacade implements ProgListener { 046 047 /** 048 * Programmer facade for access to Accessory Decoder Ops Mode programming. 049 * 050 * @param prog The (possibly already decorated) programmer we are 051 * piggybacking on. 052 * @param addrType A string. "accessory" or "output" causes the address to 053 * be interpreted as an 11 bit accessory output address. 054 * "decoder" causes the address to be interpreted as a 9 bit 055 * accessory decoder address "signal" causes the address to 056 * be interpreted as an 11 bit signal decoder address. 057 * @param delay A string representing the desired delay between 058 * programming operations, in milliseconds. 059 * @param baseProg The underlying undecorated Ops Mode Programmer we are 060 * piggybacking on. 061 */ 062 @SuppressFBWarnings(value = "DM_CONVERT_CASE", 063 justification = "parameter value is never localised") // NOI18N 064 public AccessoryOpsModeProgrammerFacade(Programmer prog, @Nonnull String addrType, int delay, AddressedProgrammer baseProg) { 065 super(prog); 066 log.debug("Constructing AccessoryOpsModeProgrammerFacade"); 067 this._usingProgrammer = null; 068 this.mode = prog.getMode(); 069 this.aprog = prog; 070 this._addrType = (addrType == null) ? "" : addrType.toLowerCase(); // NOI18N 071 this._delay = delay; 072 this._baseProg = baseProg; 073 } 074 075 // ops accessory mode can't read locally 076 ProgrammingMode mode; 077 078 @Override 079 @Nonnull 080 public List<ProgrammingMode> getSupportedModes() { 081 List<ProgrammingMode> ret = new ArrayList<>(); 082 ret.add(ProgrammingMode.OPSACCBYTEMODE); 083 ret.add(ProgrammingMode.OPSACCBITMODE); 084 ret.add(ProgrammingMode.OPSACCEXTBYTEMODE); 085 ret.add(ProgrammingMode.OPSACCEXTBITMODE); 086 return ret; 087 } 088 089 /** 090 * Don't pass this mode through, as the underlying doesn't have it (although 091 * we should check). 092 * 093 * @param p The desired programming mode 094 */ 095 @Override 096 public void setMode(ProgrammingMode p) { 097 } 098 099 Programmer aprog; 100 101 @Override 102 public boolean getCanRead() { 103 return prog.getCanRead(); 104 } 105 106 @Override 107 public boolean getCanRead(String addr) { 108 return prog.getCanRead(addr); 109 } 110 111 @Override 112 public boolean getCanWrite() { 113 return prog.getCanWrite(); 114 } 115 116 @Override 117 public boolean getCanWrite(String addr) { 118 return prog.getCanWrite(addr); 119 } 120 121 // members for handling the programmer interface 122 int _val; // remember the value being read/written for confirmative reply 123 String _cv; // remember the cv number being read/written 124 String _addrType; // remember the address type: ("decoder" or null) or ("accessory" or "output") 125 int _delay; // remember the programming delay, in milliseconds 126 AddressedProgrammer _baseProg; // remember the underlying programmer 127 128 // programming interface 129 @Override 130 public synchronized void writeCV(String cv, int val, ProgListener p) throws ProgrammerException { 131 log.debug("writeCV entry: ProgListener p is {}", p); 132 _val = val; 133 useProgrammer(p); 134 state = ProgState.PROGRAMMING; 135 byte[] b; 136 137 // Send DCC commands to implement prog.writeCV(cv, val, this); 138 switch (_addrType) { 139 case "accessory": 140 case "output": 141 // interpret address as accessory address 142 log.debug("Send an accDecoderPktOpsMode: address={}, cv={}, value={}", 143 _baseProg.getAddressNumber(), Integer.parseInt(cv), val); 144 b = NmraPacket.accDecoderPktOpsMode(_baseProg.getAddressNumber(), Integer.parseInt(cv), val); 145 break; 146 case "signal": 147 // interpret address as signal address 148 log.debug("Send an accSignalDecoderPktOpsMode: address={}, cv={}, value={}", 149 _baseProg.getAddressNumber(), Integer.parseInt(cv), val); 150 b = NmraPacket.accSignalDecoderPktOpsMode(_baseProg.getAddressNumber(), Integer.parseInt(cv), val); 151 break; 152 case "altsignal": 153 // interpret address as signal address using the alternative interpretation of S-9.2.1 154 log.debug("Send an altAccSignalDecoderPktOpsMode: address={}, cv={}, value={}", 155 _baseProg.getAddressNumber(), Integer.parseInt(cv), val); 156 b = NmraPacket.altAccSignalDecoderPktOpsMode(_baseProg.getAddressNumber(), Integer.parseInt(cv), val); 157 break; 158 case "decoder": 159 // interpet address as decoder address 160 log.debug("Send an accDecPktOpsMode: address={}, cv={}, value={}", 161 _baseProg.getAddressNumber(), Integer.parseInt(cv), val); 162 b = NmraPacket.accDecPktOpsMode(_baseProg.getAddressNumber(), Integer.parseInt(cv), val); 163 break; 164 case "legacy": 165 // interpet address as decoder address and send legacy packet 166 log.debug("Send an accDecPktOpsModeLegacy: address={}, cv={}, value={}", 167 _baseProg.getAddressNumber(), Integer.parseInt(cv), val); 168 b = NmraPacket.accDecPktOpsModeLegacy(_baseProg.getAddressNumber(), Integer.parseInt(cv), val); 169 break; 170 default: 171 log.error("Unknown Address Type \"{}\"", _addrType); 172 programmingOpReply(val, ProgListener.UnknownError); 173 return; 174 } 175 boolean ret = InstanceManager.getDefault(CommandStation.class).sendPacket(b, 2); // send two packets 176 if (!ret) { 177 log.error("Unable to program cv={}, value={}: Operation not implemented in command station", Integer.parseInt(cv), val); 178 programmingOpReply(val, ProgListener.NotImplemented); 179 return; 180 } 181 182 // set up a delayed completion reply 183 log.debug("delaying {} milliseconds for cv={}, value={}", _delay, Integer.parseInt(cv), val); 184 jmri.util.ThreadingUtil.runOnLayoutDelayed(() -> { 185 log.debug(" delay elapsed for cv={}, value={}", Integer.parseInt(cv), val); 186 programmingOpReply(val, ProgListener.OK); 187 }, _delay); 188 } 189 190 @Override 191 public synchronized void readCV(String cv, jmri.ProgListener p) throws jmri.ProgrammerException { 192 readCV(cv, p, 0); 193 } 194 195 @Override 196 public synchronized void readCV(String cv, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException { 197 useProgrammer(p); 198 state = ProgState.PROGRAMMING; 199 prog.readCV(cv, this, startVal); 200 } 201 202 @Override 203 public synchronized void confirmCV(String cv, int val, ProgListener p) throws ProgrammerException { 204 useProgrammer(p); 205 state = ProgState.PROGRAMMING; 206 prog.confirmCV(cv, val, this); 207 } 208 209 private transient volatile jmri.ProgListener _usingProgrammer; 210 211 /** 212 * Internal method to remember who's using the programmer. 213 * 214 * 215 * @param p the programmer 216 * @throws ProgrammerException if p is already in use 217 */ 218 protected synchronized void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException { 219 // test for only one! 220 log.debug("useProgrammer entry: _usingProgrammer is {}", _usingProgrammer); 221 if (_usingProgrammer != null && _usingProgrammer != p) { 222 if (log.isInfoEnabled()) { 223 log.info("programmer already in use by {}", _usingProgrammer); 224 } 225 throw new jmri.ProgrammerException("programmer in use"); 226 } else { 227 _usingProgrammer = p; 228 } 229 log.debug("useProgrammer exit: _usingProgrammer is {}", _usingProgrammer); 230 } 231 232 enum ProgState { 233 234 PROGRAMMING, NOTPROGRAMMING 235 } 236 ProgState state = ProgState.NOTPROGRAMMING; 237 238 // get notified of the final result 239 // Note this assumes that there's only one phase to the operation 240 @Override 241 public synchronized void programmingOpReply(int value, int status) { 242 log.debug("notifyProgListenerEnd value={}, status={}", value, status); 243 244 if (status != OK) { 245 // pass abort up 246 log.debug("Reset and pass abort up"); 247 jmri.ProgListener temp = _usingProgrammer; 248 _usingProgrammer = null; // done 249 state = ProgState.NOTPROGRAMMING; 250 temp.programmingOpReply(value, status); 251 return; 252 } 253 254 if (_usingProgrammer == null) { 255 log.error("No listener to notify, reset and ignore"); 256 state = ProgState.NOTPROGRAMMING; 257 return; 258 } 259 260 switch (state) { 261 case PROGRAMMING: 262 // the programmingOpReply handler might send an immediate reply, so 263 // clear the current listener _first_ 264 log.debug("going NOTPROGRAMMING after value {}, status={}", value, status); 265 jmri.ProgListener temp = _usingProgrammer; 266 _usingProgrammer = null; // done 267 state = ProgState.NOTPROGRAMMING; 268 temp.programmingOpReply(value, status); 269 break; 270 default: 271 log.error("Unexpected state on reply: {}", state); 272 // clean up as much as possible 273 _usingProgrammer = null; 274 state = ProgState.NOTPROGRAMMING; 275 276 } 277 } 278 279 private final static Logger log = LoggerFactory.getLogger(AccessoryOpsModeProgrammerFacade.class); 280 281}