001package jmri.jmrix.lenz.li100; 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 Lenz 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 017 * <li>Send Request for Service Mode Results request 018 * <li>Wait for results reply, interpret 019 * <li>Send Resume Operations request 020 * <li>Wait for Normal Operations Resumed broadcast 021 * </ul> 022 * 023 * @author Bob Jacobsen Copyright (c) 2002, 2007 024 * @author Paul Bender Copyright (c) 2003, 2004, 2005, 2009 025 * @author Giorgio Terdina Copyright (c) 2007 026 */ 027public class LI100XNetProgrammer extends XNetProgrammer { 028 029 private static final int RETURNSENT = 3; 030 031 // save the last XpressNet message for retransmission after a 032 // communication error.. 033 private XNetMessage lastRequestMessage = null; 034 035 private int _error = 0; 036 037 public LI100XNetProgrammer(XNetTrafficController tc) { 038 super(tc); 039 } 040 041 /** 042 * {@inheritDoc} 043 */ 044 @Override 045 public synchronized void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 046 final int CV = Integer.parseInt(CVname); 047 log.debug("writeCV {} listens {}", CV, p); 048 useProgrammer(p); 049 _progRead = false; 050 // set new state & save values 051 progState = REQUESTSENT; 052 _val = val; 053 _cv = 0xffff & CV; 054 055 try { 056 // start the error timer 057 restartTimer(XNetProgrammerTimeout); 058 059 // format and send message to go to program mode 060 if (getMode().equals(ProgrammingMode.PAGEMODE)) { 061 XNetMessage msg = XNetMessage.getWritePagedCVMsg(CV, val); 062 msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE); 063 lastRequestMessage = new XNetMessage(msg); 064 controller().sendXNetMessage(msg, this); 065 } else if (getMode().equals(ProgrammingMode.DIRECTBITMODE) || getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) { 066 XNetMessage msg = XNetMessage.getWriteDirectCVMsg(CV, val); 067 msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE); 068 lastRequestMessage = new XNetMessage(msg); 069 controller().sendXNetMessage(msg, this); 070 } else { // register mode by elimination 071 XNetMessage msg = XNetMessage.getWriteRegisterMsg(registerFromCV(CV), val); 072 msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE); 073 lastRequestMessage = new XNetMessage(msg); 074 controller().sendXNetMessage(msg, this); 075 } 076 } catch (jmri.ProgrammerException e) { 077 progState = NOTPROGRAMMING; 078 throw e; 079 } 080 } 081 082 /** 083 * {@inheritDoc} 084 */ 085 @Override 086 public synchronized void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 087 readCV(CV, p); 088 } 089 090 /** 091 * {@inheritDoc} 092 */ 093 @Override 094 public synchronized void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException { 095 final int CV = Integer.parseInt(CVname); 096 log.debug("readCV {} listens {}", CV, p); 097 098 if (!getCanRead()) { 099 // should not invoke this if cant read, but if done anyway set NotImplemented error 100 notifyProgListenerEnd(p,CV,jmri.ProgListener.NotImplemented); 101 return; 102 } 103 104 useProgrammer(p); 105 _progRead = true; 106 // set new state 107 progState = REQUESTSENT; 108 _cv = 0xffff & CV; 109 try { 110 // start the error timer 111 restartTimer(XNetProgrammerTimeout); 112 113 // format and send message to go to program mode 114 if (getMode() == ProgrammingMode.PAGEMODE) { 115 XNetMessage msg = XNetMessage.getReadPagedCVMsg(CV); 116 msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE); 117 lastRequestMessage = new XNetMessage(msg); 118 controller().sendXNetMessage(msg, this); 119 } else if (getMode().equals(ProgrammingMode.DIRECTBITMODE) || getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) { 120 XNetMessage msg = XNetMessage.getReadDirectCVMsg(CV); 121 msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE); 122 lastRequestMessage = new XNetMessage(msg); 123 controller().sendXNetMessage(msg, this); 124 } else { // register mode by elimination 125 XNetMessage msg = XNetMessage.getReadRegisterMsg(registerFromCV(CV)); 126 msg.setNeededMode(jmri.jmrix.AbstractMRTrafficController.NORMALMODE); 127 lastRequestMessage = new XNetMessage(msg); 128 controller().sendXNetMessage(msg, this); 129 } 130 } catch (jmri.ProgrammerException e) { 131 progState = NOTPROGRAMMING; 132 throw e; 133 } 134 } 135 136 /** 137 * {@inheritDoc} 138 */ 139 @Override 140 public synchronized void message(XNetReply m) { 141 if (m.getElement(0) == XNetConstants.CS_INFO 142 && m.getElement(1) == XNetConstants.BC_SERVICE_MODE_ENTRY) { 143 if (!_service_mode) { 144 // the command station is in service mode. An "OK" 145 // message can trigger a request for service mode 146 // results if progrstate is REQUESTSENT. 147 log.debug("change _service_mode to true"); 148 _service_mode = true; 149 } else { // _service_mode == true 150 // Since we get this message as both a broadcast and 151 // a directed message, ignore the message if we're 152 //already in the indicated mode 153 log.debug("_service_mode already true"); 154 return; 155 } 156 } 157 if (m.getElement(0) == XNetConstants.CS_INFO 158 && m.getElement(1) == XNetConstants.BC_NORMAL_OPERATIONS) { 159 if (_service_mode) { 160 // the command station is not in service mode. An 161 // "OK" message can not trigger a request for service 162 // mode results if progrstate is REQUESTSENT. 163 log.debug("change _service_mode to false"); 164 _service_mode = false; 165 } else { // _service_mode == false 166 // Since we get this message as both a broadcast and 167 // a directed message, ignore the message if we're 168 //already in the indicated mode 169 log.debug("_service_mode already false"); 170 return; 171 } 172 } 173 174 if (progState == NOTPROGRAMMING) { 175 // we get the complete set of replies now, so ignore these 176 } else if (progState == REQUESTSENT) { 177 log.debug("reply in REQUESTSENT state"); 178 // see if reply is the acknowledge of program mode; if not, wait for next 179 if ((_service_mode && m.isOkMessage()) 180 || (m.getElement(0) == XNetConstants.CS_INFO 181 && (m.getElement(1) == XNetConstants.BC_SERVICE_MODE_ENTRY 182 || m.getElement(1) == XNetConstants.PROG_CS_READY))) { 183 stopTimer(); 184 185 if (!getCanRead()) { 186 // on systems like the Roco MultiMaus 187 // (which does not support reading) 188 // let a timeout occur so the system 189 // has time to write data to the 190 // decoder 191 restartTimer(SHORT_TIMEOUT); 192 return; 193 } 194 195 // here ready to request the results 196 progState = INQUIRESENT; 197 //start the error timer 198 restartTimer(XNetProgrammerTimeout); 199 200 controller().sendXNetMessage(XNetMessage.getServiceModeResultsMsg(), 201 this); 202 } else if (m.getElement(0) == XNetConstants.CS_INFO 203 && m.getElement(1) == XNetConstants.CS_NOT_SUPPORTED) { 204 // programming operation not supported by this command station 205 progState = RETURNSENT; 206 _error = jmri.ProgListener.NotImplemented; 207 // create a request to exit service mode and 208 // send the message to the command station 209 controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(), 210 this); 211 } else if (m.getElement(0) == XNetConstants.CS_INFO 212 && m.getElement(1) == XNetConstants.BC_NORMAL_OPERATIONS) { 213 // We Exited Programming Mode early 214 log.debug("Service mode exited before sequence complete."); 215 progState = NOTPROGRAMMING; 216 stopTimer(); 217 notifyProgListenerEnd(_val, jmri.ProgListener.SequenceError); 218 } else if (m.getElement(0) == XNetConstants.CS_INFO 219 && m.getElement(1) == XNetConstants.PROG_SHORT_CIRCUIT) { 220 // We experienced a short Circuit on the Programming Track 221 log.error("Short Circuit While Programming Decoder"); 222 progState = RETURNSENT; 223 _error = jmri.ProgListener.ProgrammingShort; 224 // create a request to exit service mode and 225 // send the message to the command station 226 controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(), 227 this); 228 } else if (m.isCommErrorMessage()) { 229 // We experienced a communicatiosn error 230 if (m.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_TIMESLOT_ERROR) { 231 // If this is a Timeslot error, ignore it, 232 // otherwise report it as an error 233 } else if (!_service_mode) { 234 log.error("Communications error in REQUESTSENT state before entering service mode. Error: {}",m); 235 controller().sendXNetMessage(lastRequestMessage, this); 236 } else { 237 log.error("Communications error in REQUESTSENT state after entering service mode. Error: {}",m); 238 progState = RETURNSENT; 239 _error = jmri.ProgListener.CommError; 240 // create a request to exit service mode and 241 // send the message to the command station 242 controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(), 243 this); 244 } 245 } 246 } else if (progState == INQUIRESENT) { 247 log.debug("reply in INQUIRESENT state"); 248 // check for right message, else return 249 if (m.isPagedModeResponse()) { 250 // valid operation response, but does it belong to us? 251 try { 252 // we always save the cv number, but if 253 // we are using register mode, there is 254 // at least one case (CV29) where the value 255 // returned does not match the value we saved. 256 if (m.getServiceModeCVNumber() != _cv 257 && m.getServiceModeCVNumber() != registerFromCV(_cv)) { 258 log.debug(" result for CV {} expecting {}", m.getServiceModeCVNumber(), _cv); 259 return; 260 } 261 } catch (jmri.ProgrammerException e) { 262 progState = NOTPROGRAMMING; 263 notifyProgListenerEnd(_val, jmri.ProgListener.UnknownError); 264 } 265 // see why waiting 266 if (_progRead) { 267 // read was in progress - get return value 268 _val = m.getServiceModeCVValue(); 269 } 270 progState = RETURNSENT; 271 _error = jmri.ProgListener.OK; 272 // create a request to exit service mode and 273 // send the message to the command station 274 controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(), 275 this); 276 } else if (m.isDirectModeResponse()) { 277 // valid operation response, but does it belong to us? 278 if (m.getServiceModeCVNumber() != _cv) { 279 log.debug(" result for CV {} expecting {}", m.getServiceModeCVNumber(), _cv); 280 return; 281 } 282 283 // see why waiting 284 if (_progRead) { 285 // read was in progress - get return value 286 _val = m.getServiceModeCVValue(); 287 } 288 progState = RETURNSENT; 289 _error = jmri.ProgListener.OK; 290 stopTimer(); 291 // create a request to exit service mode and 292 // send the message to the command station 293 controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(), 294 this); 295 } else if (m.getElement(0) == XNetConstants.CS_INFO 296 && m.getElement(1) == XNetConstants.PROG_BYTE_NOT_FOUND) { 297 // "data byte not found", e.g. no reply 298 progState = RETURNSENT; 299 _error = jmri.ProgListener.NoLocoDetected; 300 // create a request to exit service mode and 301 // send the message to the command station 302 controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(), 303 this); 304 } else if (m.getElement(0) == XNetConstants.CS_INFO 305 && m.getElement(1) == XNetConstants.PROG_SHORT_CIRCUIT) { 306 // We experienced a short Circuit on the Programming Track 307 log.error("Short Circuit While Programming Decoder"); 308 progState = RETURNSENT; 309 _error = jmri.ProgListener.ProgrammingShort; 310 // create a request to exit service mode and 311 // send the message to the command station 312 controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(), 313 this); 314 } else if (m.getElement(0) == XNetConstants.CS_INFO 315 && m.getElement(1) == XNetConstants.BC_NORMAL_OPERATIONS) { 316 // We Exited Programming Mode early 317 log.error("Service mode exited before sequence complete."); 318 progState = NOTPROGRAMMING; 319 stopTimer(); 320 notifyProgListenerEnd(_val, jmri.ProgListener.SequenceError); 321 } else if (m.isCommErrorMessage()) { 322 // We experienced a communicatiosn error 323 if (m.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_TIMESLOT_ERROR) { 324 // If this is a Timeslot error, ignore it 325 } else if (_service_mode) { 326 // If we're in service mode, retry sending the 327 // result request. 328 log.error("Communications error in INQUIRESENT state while in service mode. Error: {}", m); 329 controller().sendXNetMessage(XNetMessage.getServiceModeResultsMsg(), 330 this); 331 } else { 332 //otherwise report it as an error 333 log.error("Communications error in INQUIRESENT state after exiting service mode. Error: {}", m); 334 progState = RETURNSENT; 335 _error = jmri.ProgListener.CommError; 336 // create a request to exit service mode and 337 // send the message to the command station 338 controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(), 339 this); 340 } 341 } else { 342 // nothing related to programming, ignore 343 } 344 345 } else if (progState == RETURNSENT) { 346 log.debug("reply in RETURNSENT state"); 347 if (m.getElement(0) == XNetConstants.CS_INFO 348 && m.getElement(1) == XNetConstants.BC_NORMAL_OPERATIONS) { 349 progState = NOTPROGRAMMING; 350 stopTimer(); 351 352 // We've exited service mode. Notify the programmer of any 353 // the results. 354 notifyProgListenerEnd(_val, _error); 355 356 } 357 } else { 358 log.debug("reply in un-decoded state"); 359 } 360 } 361 362 /** 363 * {@inheritDoc} 364 */ 365 @Override 366 public synchronized void message(XNetMessage l) { 367 // this class does not use outbound messages. 368 } 369 370 /** 371 * {@inheritDoc} 372 */ 373 @Override 374 protected synchronized void timeout() { 375 // if a timeout occurs, and we are not 376 // finished programming, we need to exit 377 // service mode. 378 if (progState != NOTPROGRAMMING) { 379 // we're programming, time to stop 380 log.debug("timeout!"); 381 382 progState = RETURNSENT; 383 if (!getCanRead()) { 384 _error = jmri.ProgListener.OK; //MultiMaus etc. 385 } else // perhaps no loco present? 386 { 387 _error = jmri.ProgListener.FailedTimeout; 388 } 389 390 controller().sendXNetMessage(XNetMessage.getExitProgModeMsg(), 391 this); 392 } 393 } 394 395 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LI100XNetProgrammer.class); 396 397}