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-2025 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 log.debug(" using programmer node {}, isBoosterOn = {}", progNode, isBoosterOn); 165 if (isBoosterOn) { 166 startLongTimer(); 167 tc.sendBiDiBMessage(message, progNode); 168 } 169 else { 170 // if the booster of OFF, return immediately without waiting for the timeout. 171 log.warn("BiDiB Booster is switched off!"); 172 progState = NOTPROGRAMMING; 173 notifyProgListenerEnd(_val, jmri.ProgListener.NoAck); 174 } 175 } 176 else { 177 log.warn("no prog node available!"); 178 progState = NOTPROGRAMMING; 179 notifyProgListenerEnd(_val, jmri.ProgListener.NotImplemented); 180 } 181 } 182 183 private jmri.ProgListener _usingProgrammer = null; 184 185 // internal method to remember who's using the programmer 186 protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException { 187 // test for only one! 188 if (_usingProgrammer != null && _usingProgrammer != p) { 189 if (log.isInfoEnabled()) { 190 log.info("programmer already in use by {}", _usingProgrammer); 191 } 192 throw new jmri.ProgrammerException("programmer in use"); 193 } else { 194 _usingProgrammer = p; 195 } 196 } 197 198 199 private void createProgrammerListener() { 200 // create BiDiB message listener 201 MessageListener messageListener = new DefaultMessageListener() { 202 //TODO implement retries somewhow... 203 @Override 204 public void csProgState( 205 byte[] address, int messageNum, CommandStationProgState commandStationProgState, int remainingTime, int cvNumber, int cvData) { 206 if (progState == NOTPROGRAMMING) { 207 // we get the complete set of replies now, so ignore these 208 if (log.isDebugEnabled()) { 209 log.debug("reply in NOTPROGRAMMING state"); 210 } 211 } else if (progState == COMMANDSENT) { 212 log.debug("node addr: {}, msg node addr: {}", progNode.getAddr(), address); 213 if (NodeUtils.isAddressEqual(progNode.getAddr(), address) && _cv == cvNumber) { 214 log.info("GLOBAL PROGRAMMER CS_PROG_STATE was signalled, node addr: {}, state: {}, CV: {}, value: {}, remaining time: {}", 215 address, commandStationProgState.getType(), cvNumber, cvData, remainingTime); 216 if ( (commandStationProgState.getType() & 0x80) != 0) { //bit 7 = 1 means operation has finished 217 stopTimer(); 218 progState = NOTPROGRAMMING; 219 if ( (commandStationProgState.getType() & 0x40) == 0) {//bit 6 = 0 means OK 220 log.debug(" prog ok"); 221 if (_progRead) { 222 // read was in progress - get return value 223 _val = cvData; 224 } 225 // if this was a read, we retrieved the value above. If its a 226 // write, we're to return the original write value 227 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 228 } 229 else { //not ok - return error 230 if (commandStationProgState == CommandStationProgState.PROG_NO_LOCO ) { 231 log.debug(" error: no loco detected"); 232 notifyProgListenerEnd(_val, jmri.ProgListener.NoLocoDetected); 233 } 234 else if (commandStationProgState == CommandStationProgState.PROG_STOPPED) { 235 log.debug(" error: user aborted"); 236 notifyProgListenerEnd(_val, jmri.ProgListener.UserAborted); 237 } 238 else if (commandStationProgState == CommandStationProgState.PROG_NO_ANSWER) { 239 log.debug(" error: no answer"); 240 // hack for BiDiB simulator - it does not report CV8 (manufacturer) and CV7 (decoder version) 241 // JMRI identify needs them, so we use return CV8=238 (NMRA Reserved) and CV7=42 (you know...) 242 if ( _progRead && (cvNumber == 8 || cvNumber == 7)) { 243 //if (cvNumber == 8) _val = 238; 244 //if (cvNumber == 7) _val = 42; 245 if (cvNumber == 8) _val = 145; 246 if (cvNumber == 7) _val = 26; 247 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 248 } 249 else { 250 _val = 0; 251 log.warn(" error: no answer, CV probably not implemented"); 252 notifyProgListenerEnd(_val, jmri.ProgListener.NoAck); 253 //notifyProgListenerEnd(_val, jmri.ProgListener.NotImplemented); 254 //notifyProgListenerEnd(_val, jmri.ProgListener.OK); 255 } 256 } 257 else if (commandStationProgState == CommandStationProgState.PROG_SHORT) { 258 log.warn(" error: programming short"); 259 notifyProgListenerEnd(_val, jmri.ProgListener.ProgrammingShort); 260 } 261 else if (commandStationProgState == CommandStationProgState.PROG_VERIFY_FAILED) { 262 log.warn(" error: verify failed"); 263 notifyProgListenerEnd(_val, jmri.ProgListener.ConfirmFailed); 264 } 265 else { 266 log.warn(" error: unknown error"); 267 notifyProgListenerEnd(_val, jmri.ProgListener.UnknownError); 268 } 269 } 270 } 271 else { 272 log.debug(" not finished..."); 273 // not finished - ignore so far... 274 } 275 } 276 } 277 } 278 @Override 279 public void boosterState(byte[] address, int messageNum, BoosterState state, BoosterControl control) { 280 Node node = tc.getNodeByAddr(address); 281 log.info("BOOSTER STATE was signalled: {}, control: {}", state.getType(), control.getType()); 282 if (node != null && node == progNode) { 283 isBoosterOn = ((state.getType() & 0x80) == 0x80); 284 } 285 } 286 }; 287 tc.addMessageListener(messageListener); 288 } 289 290 /** 291 * {@inheritDoc} 292 * 293 * Internal routine to handle a timeout 294 */ 295 @Override 296 protected synchronized void timeout() { 297 if (progState != NOTPROGRAMMING) { 298 // we're programming, time to stop 299 if (log.isDebugEnabled()) { 300 log.debug("timeout!"); 301 } 302 // perhaps no loco present? Fail back to end of programming 303 progState = NOTPROGRAMMING; 304 cleanup(); 305 notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout); 306 307 tc.checkProgMode(false, progNode); //be sure PROG mode is switched off 308 tc.setCurrentGlobalProgrammerNode(null); //invalidate, so the progNode must be evaluated again the next time 309 } 310 } 311 312 // Internal method to cleanup in case of a timeout. Separate routine 313 // so it can be changed in subclasses. 314 void cleanup() { 315 } 316 317 // internal method to notify of the final result 318 protected void notifyProgListenerEnd(int value, int status) { 319 if (log.isDebugEnabled()) { 320 log.debug("notifyProgListenerEnd value {} status {}", value, status); 321 } 322 // the programmingOpReply handler might send an immediate reply, so 323 // clear the current listener _first_ 324 jmri.ProgListener temp = _usingProgrammer; 325 _usingProgrammer = null; 326 notifyProgListenerEnd(temp, value, status); 327 } 328 329 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BiDiBProgrammer.class); 330 331}