001package jmri.jmrix.bidib; 002 003import java.util.ArrayList; 004import java.util.List; 005import javax.annotation.Nonnull; 006import jmri.Programmer; 007 008import jmri.ProgrammingMode; 009import jmri.jmrix.AbstractProgrammer; 010import org.bidib.jbidibc.core.DefaultMessageListener; 011import org.bidib.jbidibc.core.MessageListener; 012import org.bidib.jbidibc.messages.enums.CommandStationProgState; 013import org.bidib.jbidibc.messages.Node; 014import org.bidib.jbidibc.messages.enums.BoosterControl; 015import org.bidib.jbidibc.messages.enums.BoosterState; 016import org.bidib.jbidibc.messages.enums.CommandStationPt; 017import org.bidib.jbidibc.messages.message.BidibCommandMessage; 018import org.bidib.jbidibc.messages.message.CommandStationProgMessage; 019import org.bidib.jbidibc.messages.utils.NodeUtils; 020 021/** 022 * Convert the jmri.Programmer interface into BiDiB. 023 * <P> 024 * This has two states: NOTPROGRAMMING, and COMMANDSENT. The transitions to and 025 * from programming mode are now handled in the TrafficController code. 026 * 027 * @author Bob Jacobsen Copyright (C) 2001, 2016 028 * @author Eckart Meyer Copyright (C) 2019-2023 029 */ 030public class BiDiBProgrammer extends AbstractProgrammer { 031 032 protected BiDiBTrafficController tc; 033 protected Node progNode; //the BiDiB progNode to sent the MSG_CS_PROG message to 034 private boolean isBoosterOn = false; 035 036// @SuppressWarnings("OverridableMethodCallInConstructor") 037 public BiDiBProgrammer(BiDiBTrafficController tc) { 038 this.tc = tc; 039 super.SHORT_TIMEOUT = 4000; 040 progNode = tc.getCurrentGlobalProgrammerNode(); 041 log.debug("global programmer node: {}", progNode); 042 043 if (getSupportedModes().size() > 0) { 044 setMode(getSupportedModes().get(0)); 045 } 046 047 createProgrammerListener(); 048 } 049 050 /** 051 * {@inheritDoc} 052 * 053 * BiDiB programming modes available depend on settings 054 */ 055 @Override 056 @Nonnull 057 public List<ProgrammingMode> getSupportedModes() { 058 List<ProgrammingMode> ret = new ArrayList<>(); 059 if (tc == null) { 060 log.warn("getSupportedModes called with null tc", new Exception("traceback")); 061 } 062 java.util.Objects.requireNonNull(tc, "TrafficController reference needed"); 063 064 ret.add(ProgrammingMode.DIRECTBYTEMODE); 065 //ret.add(ProgrammingMode.DIRECTBITMODE); //TODO! BiDiB should be able to do this! 066 return ret; 067 } 068 069 // getCanRead/getCanWrite: BiDiB protocol allows CVs from 1...1024 - this is the default implementation 070 071 /** 072 * {@inheritDoc} 073 * 074 * The default implementation does not check for cv > 1024 - not neccessary? We do it here anywhere 075 */ 076 @Override 077 public boolean getCanWrite(String cv) { 078 if (!getCanWrite()) { 079 return false; // check basic implementation first 080 } 081 return Integer.parseInt(cv) <= 1024; 082 } 083 084 /** 085 * {@inheritDoc} 086 */ 087 @Nonnull 088 @Override 089 public Programmer.WriteConfirmMode getWriteConfirmMode(String addr) { 090 return WriteConfirmMode.DecoderReply; 091 } 092 093 // members for handling the programmer interface 094 int progState = 0; 095 static final int NOTPROGRAMMING = 0;// is notProgramming 096 static final int COMMANDSENT = 2; // read/write command sent, waiting reply 097 static final int COMMANDSENT_2 = 4; // ops programming mode, send msg twice 098 boolean _progRead = false; 099 int _val; // remember the value being read/written for confirmative reply 100 int _cv; // remember the cv being read/written 101 102 /** 103 * {@inheritDoc} 104 */ 105 @Override 106 public synchronized void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 107 final int CV = Integer.parseInt(CVname); 108 log.info("write mode: {}, CV={}, val={}", getMode().getStandardName(), CV, val); 109 if (log.isDebugEnabled()) { 110 log.debug("writeCV {} listens {}", CV, p); 111 } 112 useProgrammer(p); 113 if (!getCanWrite(CVname)) { 114 throw new jmri.ProgrammerException("CV number not supported"); 115 } 116 if (progNode == null) { 117 throw new jmri.ProgrammerException("No Global Programmer node found!"); 118 } 119 _progRead = false; 120 // set state 121 progState = COMMANDSENT; 122 _val = val; 123 _cv = CV; 124 125//TODO bit mode ?? 126 sendBiDiBMessage(new CommandStationProgMessage(CommandStationPt.BIDIB_CS_PROG_WR_BYTE, _cv, _val)); 127 } 128 129 /** 130 * {@inheritDoc} 131 */ 132 @Override 133 public void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 134 readCV(CV, p); 135 } 136 137 /** 138 * {@inheritDoc} 139 */ 140 @Override 141 public synchronized void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException { 142 final int CV = Integer.parseInt(CVname); 143 log.info("read mode: {}, CV={}", getMode().getStandardName(), CV); 144 if (log.isDebugEnabled()) { 145 log.debug("readCV {} listens {}", CV, p); 146 } 147 useProgrammer(p); 148 if (!getCanRead(CVname)) { 149 throw new jmri.ProgrammerException("CV number not supported"); 150 } 151 _progRead = true; 152 153 // set commandPending state 154 progState = COMMANDSENT; 155 _cv = CV; 156 157//TODO bit mode ?? 158 sendBiDiBMessage(new CommandStationProgMessage(CommandStationPt.BIDIB_CS_PROG_RD_BYTE, _cv, 0)); 159 } 160 161 private void sendBiDiBMessage(BidibCommandMessage message) { 162 progNode = tc.getCurrentGlobalProgrammerNode(); //the global programmer progNode may have changed TODO: make the progNode user selectable! 163 if (progNode != null) { 164 if (isBoosterOn) { 165 startLongTimer(); 166 tc.sendBiDiBMessage(message, progNode); 167 } 168 else { 169 // if the booster of OFF, return immediately without waiting for the timeout. 170 progState = NOTPROGRAMMING; 171 notifyProgListenerEnd(_val, jmri.ProgListener.NoAck); 172 } 173 } 174 else { 175 progState = NOTPROGRAMMING; 176 notifyProgListenerEnd(_val, jmri.ProgListener.NotImplemented); 177 } 178 } 179 180 private jmri.ProgListener _usingProgrammer = null; 181 182 // internal method to remember who's using the programmer 183 protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException { 184 // test for only one! 185 if (_usingProgrammer != null && _usingProgrammer != p) { 186 if (log.isInfoEnabled()) { 187 log.info("programmer already in use by {}", _usingProgrammer); 188 } 189 throw new jmri.ProgrammerException("programmer in use"); 190 } else { 191 _usingProgrammer = p; 192 } 193 } 194 195 196 private void createProgrammerListener() { 197 // create BiDiB message listener 198 MessageListener messageListener = new DefaultMessageListener() { 199 //TODO implement retries somewhow... 200 @Override 201 public void csProgState( 202 byte[] address, int messageNum, CommandStationProgState commandStationProgState, int remainingTime, int cvNumber, int cvData) { 203 if (progState == NOTPROGRAMMING) { 204 // we get the complete set of replies now, so ignore these 205 if (log.isDebugEnabled()) { 206 log.debug("reply in NOTPROGRAMMING state"); 207 } 208 } else if (progState == COMMANDSENT) { 209 log.debug("node addr: {}, msg node addr: {}", progNode.getAddr(), address); 210 if (NodeUtils.isAddressEqual(progNode.getAddr(), address) && _cv == cvNumber) { 211 log.info("GLOBAL PROGRAMMER CS_PROG_STATE was signalled, node addr: {}, state: {}, CV: {}, value: {}, remaining time: {}", 212 address, commandStationProgState.getType(), cvNumber, cvData, remainingTime); 213 if ( (commandStationProgState.getType() & 0x80) != 0) { //bit 7 = 1 means operation has finished 214 stopTimer(); 215 progState = NOTPROGRAMMING; 216 if ( (commandStationProgState.getType() & 0x40) == 0) {//bit 6 = 0 means OK 217 log.debug(" prog ok"); 218 if (_progRead) { 219 // read was in progress - get return value 220 _val = cvData; 221 } 222 // if this was a read, we retrieved the value above. If its a 223 // write, we're to return the original write value 224 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 225 } 226 else { //not ok - return error 227 if (commandStationProgState == CommandStationProgState.PROG_NO_LOCO ) { 228 log.debug(" error: no loco detected"); 229 notifyProgListenerEnd(_val, jmri.ProgListener.NoLocoDetected); 230 } 231 else if (commandStationProgState == CommandStationProgState.PROG_STOPPED) { 232 log.debug(" error: user aborted"); 233 notifyProgListenerEnd(_val, jmri.ProgListener.UserAborted); 234 } 235 else if (commandStationProgState == CommandStationProgState.PROG_NO_ANSWER) { 236 log.debug(" error: no answer"); 237 // hack for BiDiB simulator - it does not report CV8 (manufacturer) and CV7 (decoder version) 238 // JMRI identify needs them, so we use return CV8=238 (NMRA Reserved) and CV7=42 (you know...) 239 if ( _progRead && (cvNumber == 8 || cvNumber == 7)) { 240 //if (cvNumber == 8) _val = 238; 241 //if (cvNumber == 7) _val = 42; 242 if (cvNumber == 8) _val = 145; 243 if (cvNumber == 7) _val = 26; 244 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 245 } 246 else { 247 _val = 0; 248 log.warn(" error: no answer, CV probably not implemented"); 249 notifyProgListenerEnd(_val, jmri.ProgListener.NoAck); 250 //notifyProgListenerEnd(_val, jmri.ProgListener.NotImplemented); 251 //notifyProgListenerEnd(_val, jmri.ProgListener.OK); 252 } 253 } 254 else if (commandStationProgState == CommandStationProgState.PROG_SHORT) { 255 log.warn(" error: programming short"); 256 notifyProgListenerEnd(_val, jmri.ProgListener.ProgrammingShort); 257 } 258 else if (commandStationProgState == CommandStationProgState.PROG_VERIFY_FAILED) { 259 log.warn(" error: verify failed"); 260 notifyProgListenerEnd(_val, jmri.ProgListener.ConfirmFailed); 261 } 262 else { 263 log.warn(" error: unknown error"); 264 notifyProgListenerEnd(_val, jmri.ProgListener.UnknownError); 265 } 266 } 267 } 268 else { 269 log.debug(" not finished..."); 270 // not finished - ignore so far... 271 } 272 } 273 } 274 } 275 @Override 276 public void boosterState(byte[] address, int messageNum, BoosterState state, BoosterControl control) { 277 Node node = tc.getNodeByAddr(address); 278 log.info("BOOSTER STATE was signalled: {}, control: {}", state.getType(), control.getType()); 279 if (node != null && node == progNode) { 280 isBoosterOn = ((state.getType() & 0x80) == 0x80); 281 } 282 } 283 }; 284 tc.addMessageListener(messageListener); 285 } 286 287 /** 288 * {@inheritDoc} 289 * 290 * Internal routine to handle a timeout 291 */ 292 @Override 293 protected synchronized void timeout() { 294 if (progState != NOTPROGRAMMING) { 295 // we're programming, time to stop 296 if (log.isDebugEnabled()) { 297 log.debug("timeout!"); 298 } 299 // perhaps no loco present? Fail back to end of programming 300 progState = NOTPROGRAMMING; 301 cleanup(); 302 notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout); 303 304 tc.checkProgMode(false, progNode); //be sure PROG mode is switched off 305 tc.setCurrentGlobalProgrammerNode(null); //invalidate, so the progNode must be evaluated again the next time 306 } 307 } 308 309 // Internal method to cleanup in case of a timeout. Separate routine 310 // so it can be changed in subclasses. 311 void cleanup() { 312 } 313 314 // internal method to notify of the final result 315 protected void notifyProgListenerEnd(int value, int status) { 316 if (log.isDebugEnabled()) { 317 log.debug("notifyProgListenerEnd value {} status {}", value, status); 318 } 319 // the programmingOpReply handler might send an immediate reply, so 320 // clear the current listener _first_ 321 jmri.ProgListener temp = _usingProgrammer; 322 _usingProgrammer = null; 323 notifyProgListenerEnd(temp, value, status); 324 } 325 326 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BiDiBProgrammer.class); 327 328}