001package jmri.jmrix.lenz; 002 003import java.util.ArrayList; 004import java.util.List; 005import javax.annotation.Nonnull; 006 007import jmri.AddressedProgrammer; 008import jmri.ProgListener; 009import jmri.ProgrammerException; 010import jmri.ProgrammingMode; 011 012/** 013 * Provides an Ops mode programming interface for XpressNet Currently only Byte 014 * mode is implemented, though XpressNet also supports bit mode writes for POM 015 * 016 * @see jmri.Programmer 017 * @author Paul Bender Copyright (C) 2003-2010 018 * @author Girgio Terdina Copyright (C) 2007 019 */ 020public class XNetOpsModeProgrammer extends jmri.jmrix.AbstractProgrammer implements XNetListener, AddressedProgrammer { 021 022 protected final int mAddressHigh; 023 protected final int mAddressLow; 024 protected final int mAddress; 025 protected int progState = NOTPROGRAMMING; 026 protected int progMethod = WRITE; 027 protected int value; 028 protected jmri.ProgListener progListener = null; 029 030 // possible states. 031 static protected final int NOTPROGRAMMING = 0; // is notProgramming 032 static protected final int REQUESTSENT = 1; // read/write command sent, waiting reply 033 static protected final int RESULTREQUESTED = 2; // result request sent, waiting reply 034 static protected final int WRITE = 0; // write mode 035 static protected final int READ = 1; // read mode 036 037 protected XNetTrafficController tc; 038 039 public XNetOpsModeProgrammer(int pAddress, XNetTrafficController controller) { 040 tc = controller; 041 if (log.isDebugEnabled()) { 042 log.debug("Creating Ops Mode Programmer for Address {}", pAddress); 043 } 044 mAddressLow = LenzCommandStation.getDCCAddressLow(pAddress); 045 mAddressHigh = LenzCommandStation.getDCCAddressHigh(pAddress); 046 mAddress = pAddress; 047 if (log.isDebugEnabled()) { 048 log.debug("High Address: {} Low Address: {}", mAddressHigh, mAddressLow); 049 } 050 // register as a listener 051 tc.addXNetListener(XNetInterface.COMMINFO | XNetInterface.CS_INFO, this); 052 } 053 054 /** 055 * {@inheritDoc} 056 * 057 * Send an ops-mode write request to the Xpressnet. 058 */ 059 @Override 060 synchronized public void writeCV(String CVname, int val, ProgListener p) throws ProgrammerException { 061 final int CV = Integer.parseInt(CVname); 062 XNetMessage msg = XNetMessage.getWriteOpsModeCVMsg(mAddressHigh, mAddressLow, CV, val); 063 tc.sendXNetMessage(msg, this); 064 /* we need to save the programer and value so we can send messages 065 back to the screen when the programming screen when we receive 066 something from the command station */ 067 progListener = p; 068 value = val; 069 progState = REQUESTSENT; 070 progMethod = WRITE; 071 restartTimer(msg.getTimeout()); 072 } 073 074 /** 075 * {@inheritDoc} 076 */ 077 @Override 078 synchronized public void readCV(String CVname, ProgListener p) throws ProgrammerException { 079 final int CV = Integer.parseInt(CVname); 080 XNetMessage msg = XNetMessage.getVerifyOpsModeCVMsg(mAddressHigh, mAddressLow, CV, value); 081 tc.sendXNetMessage(msg, this); 082 /* we need to save the programer and value so we can send messages 083 back to the screen when the programming screen when we receive 084 something from the command station */ 085 progListener = p; 086 progState = REQUESTSENT; 087 progMethod = READ; 088 restartTimer(msg.getTimeout()); 089 } 090 091 /** 092 * {@inheritDoc} 093 */ 094 @Override 095 public void confirmCV(String CVname, int val, ProgListener p) throws ProgrammerException { 096 int CV = Integer.parseInt(CVname); 097 XNetMessage msg = XNetMessage.getVerifyOpsModeCVMsg(mAddressHigh, mAddressLow, CV, val); 098 tc.sendXNetMessage(msg, this); 099 /* we need to save the programer and value so we can send messages 100 back to the screen when the programming screen when we receive 101 something from the command station */ 102 progListener = p; 103 value = val; 104 progState = REQUESTSENT; 105 progMethod = READ; 106 restartTimer(msg.getTimeout()); 107 } 108 109 /** 110 * {@inheritDoc} 111 * 112 * Types implemented here. 113 */ 114 @Override 115 @Nonnull 116 public List<ProgrammingMode> getSupportedModes() { 117 List<ProgrammingMode> ret = new ArrayList<>(); 118 ret.add(ProgrammingMode.OPSBYTEMODE); 119 return ret; 120 } 121 122 /** 123 * {@inheritDoc} 124 * 125 * Can this ops-mode programmer read back values? 126 * Indirectly we can, though this requires an external display 127 * (a Lenz LRC120) and enabling railcom. 128 * 129 * @return true to allow us to trigger an ops mode read 130 */ 131 @Override 132 public boolean getCanRead() { 133 // An operations mode read can be triggered on command 134 // stations which support Operations Mode Writes (LZ100, 135 // LZV100, MultiMouse). Whether or not the operation produces 136 // a result depends on additional external hardware (a booster 137 // with an enabled RailCom cutout (LV102 or similar) and a 138 // RailCom receiver circuit (LRC120 or similar)). 139 // We have no way of determining if the required external 140 // hardware is present, so we return true for all command 141 // stations on which the Operations Mode Programmer is enabled. 142 143 // yes, we just call the superclass method. Leave this in place 144 // so the comments and javadoc above make sense. 145 return super.getCanRead(); 146 } 147 148 149 /** 150 * {@inheritDoc} 151 */ 152 @Override 153 synchronized public void message(XNetReply l) { 154 if (progState == NOTPROGRAMMING) { 155 // We really don't care about any messages unless we send a 156 // request, so just ignore anything that comes in 157 } else if (progState == REQUESTSENT) { 158 if (l.isOkMessage()) { 159 // Before we set the programmer state to not programming, 160 // delay for a short time to give the decoder a chance to 161 // process the request. 162 if (progMethod == WRITE) { 163 new jmri.util.WaitHandler(this, 250); 164 progState = NOTPROGRAMMING; 165 stopTimer(); 166 notifyProgListenerEnd(progListener, value, jmri.ProgListener.OK); 167 } else if (progMethod == READ) { 168 stopTimer(); 169 new jmri.util.WaitHandler(this, 1000); // XPressNet documentations say to wait up to 0.5 to 1 second before requesting the result 170 progState = RESULTREQUESTED; 171 XNetMessage msg = XNetMessage.getOpsModeResultsMsg(); 172 tc.sendXNetMessage(msg, this); 173 restartTimer(msg.getTimeout()); 174 } 175 } else { 176 handleXNetError(l); 177 } 178 } else if(progState == RESULTREQUESTED) { 179 if (l.isOpsModeResultMessage()) { 180 // We got a result message, so we can stop the timer 181 int address = l.getOpsModeResultAddress(); 182 if (address != mAddress && address != 0) { 183 // This is not the address we are looking for, so ignore it 184 if (log.isDebugEnabled()) { 185 log.debug("Received result message for address {}, expected {}", address, mAddress); 186 } 187 return; 188 } 189 if (log.isDebugEnabled()) { 190 log.debug("Received result message: {}", l); 191 } 192 if (address == 0) { 193 progState = NOTPROGRAMMING; 194 stopTimer(); 195 // this indicates that there was no result from the read, so notify the programmer listener of that 196 notifyProgListenerEnd(progListener, value, ProgListener.NoAck); 197 } else { 198 progState = NOTPROGRAMMING; 199 stopTimer(); 200 // Notify the listener of the result 201 notifyProgListenerEnd(progListener, l.getOpsModeResultValue(), jmri.ProgListener.OK); 202 } 203 } else { 204 handleXNetError(l); 205 } 206 } 207 } 208 209 private void handleXNetError(XNetReply l) { 210 /* this is an error */ 211 if (l.isRetransmittableErrorMsg()) { 212 // just ignore this, since we are retransmitting 213 // the message. 214 } else if (l.getElement(0) == XNetConstants.CS_INFO 215 && l.getElement(1) == XNetConstants.CS_NOT_SUPPORTED) { 216 progState = NOTPROGRAMMING; 217 stopTimer(); 218 notifyProgListenerEnd(progListener,value, ProgListener.NotImplemented); 219 } else { 220 /* this is an unknown error */ 221 progState = NOTPROGRAMMING; 222 stopTimer(); 223 notifyProgListenerEnd(progListener,value, ProgListener.UnknownError); 224 } 225 } 226 227 /** 228 * {@inheritDoc} 229 */ 230 @Override 231 public boolean getLongAddress() { 232 return true; 233 } 234 235 /** 236 * {@inheritDoc} 237 */ 238 @Override 239 public int getAddressNumber() { 240 return mAddress; 241 } 242 243 /** 244 * {@inheritDoc} 245 */ 246 @Override 247 public String getAddress() { 248 return "" + getAddressNumber() + " " + getLongAddress(); 249 } 250 251 /** 252 * {@inheritDoc} 253 */ 254 @Override 255 public synchronized void message(XNetMessage l) { 256 } 257 258 /** 259 * {@inheritDoc} 260 * 261 * Handle a timeout notification 262 */ 263 @Override 264 public void notifyTimeout(XNetMessage msg) { 265 if (log.isDebugEnabled()) { 266 log.debug("Notified of timeout on message{}", msg.toString()); 267 } 268 } 269 270 /** 271 * {@inheritDoc} 272 */ 273 @Override 274 synchronized protected void timeout() { 275 if (progState != NOTPROGRAMMING) { 276 // we're programming, time to stop 277 if (log.isDebugEnabled()) { 278 log.debug("timeout!"); 279 } 280 // perhaps no loco present? Fail back to end of programming 281 progState = NOTPROGRAMMING; 282 if (getCanRead()) { 283 notifyProgListenerEnd(progListener,value,jmri.ProgListener.FailedTimeout); 284 } else { 285 notifyProgListenerEnd(progListener,value,jmri.ProgListener.OK); 286 } 287 } 288 } 289 290 @Override 291 public Configurator getConfigurator() { 292 return new XNetOpsConfigurator(); 293 } 294 295 /** 296 * This class is used by tests. 297 */ 298 public static class XNetOpsConfigurator implements Configurator { 299 } 300 301 // initialize logging 302 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(XNetOpsModeProgrammer.class); 303 304}