001package jmri.implementation; 002 003import jmri.ProgListener; 004import jmri.Programmer; 005import jmri.ProgrammerException; 006import jmri.jmrix.AbstractProgrammerFacade; 007import org.slf4j.Logger; 008import org.slf4j.LoggerFactory; 009 010/** 011 * Programmer facade for access to Accessory Decoder Ops Mode programming 012 * <p> 013 * (Eventually implements four modes, passing all others to underlying 014 * programmer: 015 * <ul> 016 * <li>OPSACCBYTEMODE 017 * <li>OPSACCBITMODE 018 * <li>OPSACCEXTBYTEMODE 019 * <li>OPSACCEXTBITMODE 020 * </ul> 021 * <p> 022 * Used through the String write/read/confirm interface. Accepts integers as 023 * addresses, but then emits NMRA DCC packets through the default CommandStation 024 * interface (which must be present) 025 * 026 * @see jmri.implementation.ProgrammerFacadeSelector 027 * 028 * @author Bob Jacobsen Copyright (C) 2014 029 * @author Andrew Crosland Copyright (C) 2021 030 */ 031// @ToDo("transform to annotations requires e.g. http://alchemy.grimoire.ca/m2/sites/ca.grimoire/todo-annotations/") 032// @ToDo("read handling needs to be aligned with other ops mode programmers") 033// @ToDo("make sure jmri/jmrit/progsupport/ProgServiceModePane shows the modes, and that DP/DP3 displays them as it configures a decoder") 034public class OpsModeDelayedProgrammerFacade extends AbstractProgrammerFacade implements ProgListener { 035 036 /** 037 * Programmer facade for access to Accessory Decoder Ops Mode programming. 038 * 039 * @param prog The Ops Mode Programmer we are piggybacking on. 040 * @param writeDelay A string representing the desired delay after a write 041 * operation, in milliseconds. 042 */ 043 public OpsModeDelayedProgrammerFacade(Programmer prog, int writeDelay) { 044 super(prog); 045 log.debug("Constructing OpsModeDelayedProgrammerFacade"); 046 this._usingProgrammer = null; 047 this.prog = prog; 048 this._readDelay = 0; 049 this._writeDelay = writeDelay; 050 } 051 052 // members for handling the programmer interface 053 int _val; // remember the value being read/written for confirmative reply 054 String _cv; // remember the cv number being read/written 055 String _addrType; // remember the address type: ("decoder" or null) or ("accessory" or "output") 056 int _readDelay; // remember the programming delay, in milliseconds 057 int _writeDelay; // remember the programming delay, in milliseconds 058 int _delay; // remember the programming delay, in milliseconds 059 060 // programming interface 061 @Override 062 public synchronized void writeCV(String cv, int val, ProgListener p) throws ProgrammerException { 063 log.debug("writeCV entry: ProgListener p is {}", p); 064 useProgrammer(p); 065 state = ProgState.WRITECOMMANDSENT; 066 prog.writeCV(cv, val, this); 067 } 068 069 @Override 070 public synchronized void readCV(String cv, jmri.ProgListener p) throws jmri.ProgrammerException { 071 readCV(cv, p, 0); 072 } 073 074 @Override 075 public synchronized void readCV(String cv, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException { 076 useProgrammer(p); 077 state = ProgState.READCOMMANDSENT; 078 prog.readCV(cv, this, startVal); 079 } 080 081 @Override 082 public synchronized void confirmCV(String cv, int val, ProgListener p) throws ProgrammerException { 083 useProgrammer(p); 084 state = ProgState.READCOMMANDSENT; 085 prog.confirmCV(cv, val, this); 086 } 087 088 private transient volatile jmri.ProgListener _usingProgrammer; 089 090 /** 091 * Internal method to remember who's using the programmer. 092 * 093 * 094 * @param p the programmer 095 * @throws ProgrammerException if p is already in use 096 */ 097 protected synchronized void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException { 098 // test for only one! 099 log.debug("useProgrammer entry: _usingProgrammer is {}", _usingProgrammer); 100 if (_usingProgrammer != null && _usingProgrammer != p) { 101 if (log.isInfoEnabled()) { 102 log.info("programmer already in use by {}", _usingProgrammer); 103 } 104 throw new jmri.ProgrammerException("programmer in use"); 105 } else { 106 _usingProgrammer = p; 107 } 108 log.debug("useProgrammer exit: _usingProgrammer is {}", _usingProgrammer); 109 } 110 111 enum ProgState { 112 113 READCOMMANDSENT, WRITECOMMANDSENT, NOTPROGRAMMING 114 } 115 ProgState state = ProgState.NOTPROGRAMMING; 116 117 // get notified of the final result 118 // Note this assumes that there's only one phase to the operation 119 @Override 120 public synchronized void programmingOpReply(int value, int status) { 121 log.debug("notifyProgListenerEnd value={}, status={}", value, status); 122 123 if (status != OK) { 124 // pass abort up 125 log.debug("Reset and pass abort up"); 126 jmri.ProgListener temp = _usingProgrammer; 127 _usingProgrammer = null; // done 128 state = ProgState.NOTPROGRAMMING; 129 temp.programmingOpReply(value, status); 130 return; 131 } 132 133 if (_usingProgrammer == null) { 134 log.error("No listener to notify, reset and ignore"); 135 state = ProgState.NOTPROGRAMMING; 136 return; 137 } 138 139 switch (state) { 140 case READCOMMANDSENT: 141 _delay = _readDelay; 142 break; 143 case WRITECOMMANDSENT: 144 _delay = _writeDelay; 145 break; 146 default: 147 log.error("Unexpected state on reply: {}", state); 148 // clean up as much as possible 149 _usingProgrammer = null; 150 state = ProgState.NOTPROGRAMMING; 151 } 152 153 log.debug("delaying {} milliseconds", _delay); 154 jmri.util.ThreadingUtil.runOnLayoutDelayed(() -> { 155 // the programmingOpReply handler might send an immediate reply, so 156 // clear the current listener _first_ 157 log.debug("going NOTPROGRAMMING after value {}, status={}", value, status); 158 jmri.ProgListener temp = _usingProgrammer; 159 _usingProgrammer = null; // done 160 state = ProgState.NOTPROGRAMMING; 161 log.debug("notifying value {} status {}", value, status); 162 temp.programmingOpReply(value, status); 163 }, _delay); 164 165 } 166 167 private final static Logger log = LoggerFactory.getLogger(OpsModeDelayedProgrammerFacade.class); 168 169}