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 value; 027 protected jmri.ProgListener progListener = null; 028 029 // possible states. 030 static protected final int NOTPROGRAMMING = 0; // is notProgramming 031 static protected final int REQUESTSENT = 1; // read/write command sent, waiting reply 032 033 protected XNetTrafficController tc; 034 035 public XNetOpsModeProgrammer(int pAddress, XNetTrafficController controller) { 036 tc = controller; 037 if (log.isDebugEnabled()) { 038 log.debug("Creating Ops Mode Programmer for Address {}", pAddress); 039 } 040 mAddressLow = LenzCommandStation.getDCCAddressLow(pAddress); 041 mAddressHigh = LenzCommandStation.getDCCAddressHigh(pAddress); 042 mAddress = pAddress; 043 if (log.isDebugEnabled()) { 044 log.debug("High Address: {} Low Address: {}", mAddressHigh, mAddressLow); 045 } 046 // register as a listener 047 tc.addXNetListener(XNetInterface.COMMINFO | XNetInterface.CS_INFO, this); 048 } 049 050 /** 051 * {@inheritDoc} 052 * 053 * Send an ops-mode write request to the Xpressnet. 054 */ 055 @Override 056 synchronized public void writeCV(String CVname, int val, ProgListener p) throws ProgrammerException { 057 final int CV = Integer.parseInt(CVname); 058 XNetMessage msg = XNetMessage.getWriteOpsModeCVMsg(mAddressHigh, mAddressLow, CV, val); 059 tc.sendXNetMessage(msg, this); 060 /* we need to save the programer and value so we can send messages 061 back to the screen when the programming screen when we receive 062 something from the command station */ 063 progListener = p; 064 value = val; 065 progState = REQUESTSENT; 066 restartTimer(msg.getTimeout()); 067 } 068 069 /** 070 * {@inheritDoc} 071 */ 072 @Override 073 synchronized public void readCV(String CVname, ProgListener p) throws ProgrammerException { 074 final int CV = Integer.parseInt(CVname); 075 XNetMessage msg = XNetMessage.getVerifyOpsModeCVMsg(mAddressHigh, mAddressLow, CV, value); 076 tc.sendXNetMessage(msg, this); 077 /* We can trigger a read to an LRC120, but the information is not 078 currently sent back to us via the XpressNet */ 079 notifyProgListenerEnd(p,CV,jmri.ProgListener.NotImplemented); 080 } 081 082 /** 083 * {@inheritDoc} 084 */ 085 @Override 086 public void confirmCV(String CVname, int val, ProgListener p) throws ProgrammerException { 087 int CV = Integer.parseInt(CVname); 088 XNetMessage msg = XNetMessage.getVerifyOpsModeCVMsg(mAddressHigh, mAddressLow, CV, val); 089 tc.sendXNetMessage(msg, this); 090 /* We can trigger a read to an LRC120, but the information is not 091 currently sent back to us via the XpressNet */ 092 notifyProgListenerEnd(p,val,jmri.ProgListener.NotImplemented); 093 } 094 095 /** 096 * {@inheritDoc} 097 * 098 * Types implemented here. 099 */ 100 @Override 101 @Nonnull 102 public List<ProgrammingMode> getSupportedModes() { 103 List<ProgrammingMode> ret = new ArrayList<>(); 104 ret.add(ProgrammingMode.OPSBYTEMODE); 105 return ret; 106 } 107 108 /** 109 * {@inheritDoc} 110 * 111 * Can this ops-mode programmer read back values? 112 * Indirectly we can, though this requires an external display 113 * (a Lenz LRC120) and enabling railcom. 114 * 115 * @return true to allow us to trigger an ops mode read 116 */ 117 @Override 118 public boolean getCanRead() { 119 // An operations mode read can be triggered on command 120 // stations which support Operations Mode Writes (LZ100, 121 // LZV100,MultiMouse). Whether or not the operation produces 122 // a result depends on additional external hardware (a booster 123 // with an enabled RailCom cutout (LV102 or similar) and a 124 // RailCom receiver circuit (LRC120 or similar)). 125 // We have no way of determining if the required external 126 // hardware is present, so we return true for all command 127 // stations on which the Operations Mode Programmer is enabled. 128 129 // yes, we just call the superclass method. Leave this in place 130 // so the comments and javadoc above make sense. 131 return super.getCanRead(); 132 } 133 134 135 /** 136 * {@inheritDoc} 137 */ 138 @Override 139 synchronized public void message(XNetReply l) { 140 if (progState == NOTPROGRAMMING) { 141 // We really don't care about any messages unless we send a 142 // request, so just ignore anything that comes in 143 } else if (progState == REQUESTSENT) { 144 if (l.isOkMessage()) { 145 // Before we set the programmer state to not programming, 146 // delay for a short time to give the decoder a chance to 147 // process the request. 148 new jmri.util.WaitHandler(this,250); 149 progState = NOTPROGRAMMING; 150 stopTimer(); 151 notifyProgListenerEnd(progListener,value,jmri.ProgListener.OK); 152 } else { 153 /* this is an error */ 154 if (l.isRetransmittableErrorMsg()) { 155 // just ignore this, since we are retransmitting 156 // the message. 157 } else if (l.getElement(0) == XNetConstants.CS_INFO 158 && l.getElement(1) == XNetConstants.CS_NOT_SUPPORTED) { 159 progState = NOTPROGRAMMING; 160 stopTimer(); 161 notifyProgListenerEnd(progListener,value,jmri.ProgListener.NotImplemented); 162 } else { 163 /* this is an unknown error */ 164 progState = NOTPROGRAMMING; 165 stopTimer(); 166 notifyProgListenerEnd(progListener,value,jmri.ProgListener.UnknownError); 167 } 168 } 169 } 170 } 171 172 /** 173 * {@inheritDoc} 174 */ 175 @Override 176 public boolean getLongAddress() { 177 return true; 178 } 179 180 /** 181 * {@inheritDoc} 182 */ 183 @Override 184 public int getAddressNumber() { 185 return mAddress; 186 } 187 188 /** 189 * {@inheritDoc} 190 */ 191 @Override 192 public String getAddress() { 193 return "" + getAddressNumber() + " " + getLongAddress(); 194 } 195 196 /** 197 * {@inheritDoc} 198 */ 199 @Override 200 public synchronized void message(XNetMessage l) { 201 } 202 203 /** 204 * {@inheritDoc} 205 * 206 * Handle a timeout notification 207 */ 208 @Override 209 public void notifyTimeout(XNetMessage msg) { 210 if (log.isDebugEnabled()) { 211 log.debug("Notified of timeout on message{}", msg.toString()); 212 } 213 } 214 215 /** 216 * {@inheritDoc} 217 */ 218 @Override 219 synchronized protected void timeout() { 220 if (progState != NOTPROGRAMMING) { 221 // we're programming, time to stop 222 if (log.isDebugEnabled()) { 223 log.debug("timeout!"); 224 } 225 // perhaps no loco present? Fail back to end of programming 226 progState = NOTPROGRAMMING; 227 if (getCanRead()) { 228 notifyProgListenerEnd(progListener,value,jmri.ProgListener.FailedTimeout); 229 } else { 230 notifyProgListenerEnd(progListener,value,jmri.ProgListener.OK); 231 } 232 } 233 } 234 235 // initialize logging 236 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(XNetOpsModeProgrammer.class); 237 238}