001package jmri.jmrix.lenz.hornbyelite; 002 003import jmri.ProgrammingMode; 004import jmri.jmrix.lenz.XNetConstants; 005import jmri.jmrix.lenz.XNetMessage; 006import jmri.jmrix.lenz.XNetProgrammer; 007import jmri.jmrix.lenz.XNetReply; 008import jmri.jmrix.lenz.XNetTrafficController; 009 010/** 011 * Programmer support for Hornby Elite implementationn of XpressNet. 012 * <p> 013 * The read operation state sequence is: 014 * <ul> 015 * <li>Send Register Mode / Paged mode /Direct Mode read request 016 * <li>Wait for Broadcast Service Mode Entry message -- not happening on elite 017 * <li>Send Request for Service Mode Results request 018 * <li>Wait for results reply, interpret 019 * <li>Send Resume Operations request -- The Elite does not seem to require this 020 * step. 021 * <li>Wait for Normal Operations Resumed broadcast -- The Elite does not seem 022 * to require this step. 023 * </ul> 024 * 025 * @author Paul Bender Copyright (c) 2008 026 */ 027public class EliteXNetProgrammer extends XNetProgrammer { 028 029 // Message timeout lengths. These have been determined by 030 // experimentation, and may need to be adjusted 031 private static final int ELITEMESSAGETIMEOUT = 10000; 032 private static final int EliteXNetProgrammerTimeout = 20000; 033 034 public EliteXNetProgrammer(XNetTrafficController tc) { 035 super(tc); 036 } 037 038 /** 039 * {@inheritDoc} 040 */ 041 @Override 042 public synchronized void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 043 final int CV = Integer.parseInt(CVname); 044 log.debug("writeCV {} listens {}", CV, p); 045 useProgrammer(p); 046 _progRead = false; 047 // set new state & save values 048 progState = REQUESTSENT; 049 _val = val; 050 _cv = 0xffff & CV; 051 052 try { 053 // start the error timer 054 restartTimer(EliteXNetProgrammerTimeout); 055 056 // format and send message to go to program mode 057 if (getMode().equals(ProgrammingMode.PAGEMODE)) { 058 XNetMessage msg = XNetMessage.getWritePagedCVMsg(CV, val); 059 msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE); 060 msg.setTimeout(ELITEMESSAGETIMEOUT); 061 controller().sendXNetMessage(msg, this); 062 } else if (getMode().equals(ProgrammingMode.DIRECTBITMODE) || getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) { 063 XNetMessage msg = XNetMessage.getWriteDirectCVMsg(CV, val); 064 msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE); 065 msg.setTimeout(ELITEMESSAGETIMEOUT); 066 controller().sendXNetMessage(msg, this); 067 } else { // register mode by elimination 068 XNetMessage msg = XNetMessage.getWriteRegisterMsg(registerFromCV(CV), val); 069 msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE); 070 msg.setTimeout(ELITEMESSAGETIMEOUT); 071 controller().sendXNetMessage(msg, this); 072 } 073 } catch (jmri.ProgrammerException e) { 074 progState = NOTPROGRAMMING; 075 throw e; 076 } 077 078 } 079 080 /** 081 * {@inheritDoc} 082 */ 083 @Override 084 public synchronized void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 085 readCV(CV, p); 086 } 087 088 /** 089 * {@inheritDoc} 090 */ 091 @Override 092 public synchronized void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException { 093 final int CV = Integer.parseInt(CVname); 094 log.debug("readCV {} listens {}", CV, p); 095 096 if (!getCanRead()) { 097 // should not invoke this if cant read, but if done anyway set NotImplemented error 098 notifyProgListenerEnd(p,CV,jmri.ProgListener.NotImplemented); 099 return; 100 } 101 102 useProgrammer(p); 103 _progRead = true; 104 // set new state 105 progState = REQUESTSENT; 106 _cv = 0xffff & CV; 107 try { 108 // start the error timer 109 restartTimer(EliteXNetProgrammerTimeout); 110 111 // format and send message to go to program mode 112 if (getMode().equals(ProgrammingMode.PAGEMODE)) { 113 XNetMessage msg = XNetMessage.getReadPagedCVMsg(CV); 114 msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE); 115 msg.setTimeout(ELITEMESSAGETIMEOUT); 116 controller().sendXNetMessage(msg, this); 117 } else if (getMode().equals(ProgrammingMode.DIRECTBITMODE) || getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) { 118 XNetMessage msg = XNetMessage.getReadDirectCVMsg(CV); 119 msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE); 120 msg.setTimeout(ELITEMESSAGETIMEOUT); 121 controller().sendXNetMessage(msg, this); 122 } else { // register mode by elimination 123 XNetMessage msg = XNetMessage.getReadRegisterMsg(registerFromCV(CV)); 124 msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE); 125 msg.setTimeout(ELITEMESSAGETIMEOUT); 126 controller().sendXNetMessage(msg, this); 127 } 128 } catch (jmri.ProgrammerException e) { 129 progState = NOTPROGRAMMING; 130 throw e; 131 } 132 133 } 134 135 /** 136 * {@inheritDoc} 137 */ 138 @Override 139 public synchronized void message(XNetReply m) { 140 if (m.getElement(0) == XNetConstants.CS_INFO 141 && m.getElement(1) == XNetConstants.BC_SERVICE_MODE_ENTRY) { 142 if (!_service_mode ) { 143 // the command station is in service mode. An "OK" 144 // message can trigger a request for service mode 145 // results if progrstate is REQUESTSENT. 146 _service_mode = true; 147 } else { // _service_mode == true 148 // Since we get this message as both a broadcast and 149 // a directed message, ignore the message if we're 150 //already in the indicated mode 151 return; 152 } 153 } 154 if (m.getElement(0) == XNetConstants.CS_INFO 155 && m.getElement(1) == XNetConstants.BC_NORMAL_OPERATIONS) { 156 if (_service_mode) { 157 // the command station is not in service mode. An 158 // "OK" message can not trigger a request for service 159 // mode results if progrstate is REQUESTSENT. 160 _service_mode = false; 161 } else { // _service_mode == false 162 // Since we get this message as both a broadcast and 163 // a directed message, ignore the message if we're 164 //already in the indicated mode 165 return; 166 } 167 } 168 169 if (progState == NOTPROGRAMMING) { 170 // we get the complete set of replies now, so ignore these 171 } else if (progState == REQUESTSENT) { 172 log.debug("reply in REQUESTSENT state"); 173 // see if reply is the acknowledge of program mode; if not, wait for next 174 if ((_service_mode && m.isOkMessage()) 175 || (m.getElement(0) == XNetConstants.CS_INFO 176 && (m.getElement(1) == XNetConstants.BC_SERVICE_MODE_ENTRY 177 || m.getElement(1) == XNetConstants.PROG_CS_READY))) { 178 stopTimer(); 179 180 if (!getCanRead()) { 181 // should not read here if cant read, because read shouldnt be invoked, but still attempt to handle 182 log.debug("CV reading not supported, exiting REQUESTSENT state"); 183 stopTimer(); 184 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 185 } 186 } else if (m.getElement(0) == XNetConstants.CS_INFO 187 && m.getElement(1) == XNetConstants.CS_NOT_SUPPORTED) { 188 // programming operation not supported by this command station 189 progState = NOTPROGRAMMING; 190 notifyProgListenerEnd(_val, jmri.ProgListener.NotImplemented); 191 } else if (m.getElement(0) == XNetConstants.CS_INFO 192 && m.getElement(1) == XNetConstants.BC_NORMAL_OPERATIONS) { 193 // On the Elite, the broadcast exit service mode message 194 // needs to triger the request for results. 195 progState = INQUIRESENT; 196 //start the error timer 197 restartTimer(EliteXNetProgrammerTimeout); 198 XNetMessage resultMsg = XNetMessage.getServiceModeResultsMsg(); 199 resultMsg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE); 200 resultMsg.setTimeout(ELITEMESSAGETIMEOUT); 201 controller().sendXNetMessage(resultMsg, this); 202 } else if (m.getElement(0) == XNetConstants.CS_INFO 203 && m.getElement(1) == XNetConstants.PROG_SHORT_CIRCUIT) { 204 // We experienced a short Circuit on the Programming Track 205 log.error("Short Circuit While Programming Decoder"); 206 progState = NOTPROGRAMMING; 207 stopTimer(); 208 notifyProgListenerEnd(_val, jmri.ProgListener.ProgrammingShort); 209 } else if (m.isCommErrorMessage()) { 210 // We experienced a communicatiosn error 211 // If this is a Timeslot error, ignore it, 212 //otherwise report it as an error 213 if (m.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_TIMESLOT_ERROR) { 214 return; 215 } 216 log.error("Communications error in REQUESTSENT state while programming. Error: {}", m); 217 progState = NOTPROGRAMMING; 218 stopTimer(); 219 notifyProgListenerEnd(_val, jmri.ProgListener.CommError); 220 } 221 } else if (progState == INQUIRESENT) { 222 log.debug("reply in INQUIRESENT state"); 223 // check for right message, else return 224 if (m.isPagedModeResponse()) { 225 // valid operation response, but does it belong to us? 226 try { 227 // we always save the cv number, but if 228 // we are using register mode, there is 229 // at least one case (CV29) where the value 230 // returned does not match the value we saved. 231 if (m.getServiceModeCVNumber() != _cv 232 && m.getServiceModeCVNumber() != registerFromCV(_cv)) { 233 log.debug(" result for CV {} expecting {}", m.getServiceModeCVNumber(), _cv); 234 return; 235 } 236 } catch (jmri.ProgrammerException e) { 237 progState = NOTPROGRAMMING; 238 notifyProgListenerEnd(_val, jmri.ProgListener.UnknownError); 239 } 240 241 // see why waiting 242 if (_progRead) { 243 // read was in progress - get return value 244 _val = m.getServiceModeCVValue(); 245 } 246 progState = NOTPROGRAMMING; 247 stopTimer(); 248 // if this was a read, we cached the value earlier. 249 // If its a write, we're to return the original write value 250 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 251 } else if (m.isDirectModeResponse()) { 252 // valid operation response, but does it belong to us? 253 if (m.getServiceModeCVNumber() != _cv) { 254 log.debug(" result for CV {} expecting {}", m.getServiceModeCVNumber(), _cv); 255 return; 256 } 257 // see why waiting 258 if (_progRead) { 259 // read was in progress - get return value 260 _val = m.getServiceModeCVValue(); 261 } 262 progState = NOTPROGRAMMING; 263 stopTimer(); 264 // if this was a read, we cached the value earlier. If its a 265 // write, we're to return the original write value 266 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 267 } else if (m.getElement(0) == XNetConstants.CS_INFO 268 && m.getElement(1) == XNetConstants.PROG_BYTE_NOT_FOUND) { 269 // "data byte not found", e.g. no reply 270 progState = NOTPROGRAMMING; 271 stopTimer(); 272 notifyProgListenerEnd(_val, jmri.ProgListener.NoLocoDetected); 273 } else if (m.getElement(0) == XNetConstants.CS_INFO 274 && m.getElement(1) == XNetConstants.PROG_SHORT_CIRCUIT) { 275 // We experienced a short Circuit on the Programming Track 276 log.error("Short Circuit While Programming Decoder"); 277 progState = NOTPROGRAMMING; 278 stopTimer(); 279 notifyProgListenerEnd(_val, jmri.ProgListener.ProgrammingShort); 280 } else if (m.isCommErrorMessage()) { 281 // We experienced a communicatiosn error 282 // If this is a Timeslot error, ignore it, 283 //otherwise report it as an error 284 if (m.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_TIMESLOT_ERROR) { 285 return; 286 } 287 log.error("Communications error in INQUIRESENT state while programming. Error: {}", m); 288 progState = NOTPROGRAMMING; 289 stopTimer(); 290 notifyProgListenerEnd(_val, jmri.ProgListener.CommError); 291 } else { 292 // nothing important, ignore 293 } 294 } else { 295 log.debug("reply in un-decoded state"); 296 } 297 } 298 299 /** 300 * {@inheritDoc} 301 */ 302 @Override 303 public synchronized void message(XNetMessage l) { 304 // this class is not interested in messages to the command station. 305 } 306 307 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EliteXNetProgrammer.class); 308 309}