001package jmri.jmrix.lenz; 002 003import java.util.ArrayList; 004import java.util.List; 005import javax.annotation.Nonnull; 006 007import jmri.ProgrammingMode; 008import jmri.jmrix.AbstractProgrammer; 009 010/** 011 * Convert the jmri.Programmer interface into commands for the 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 * <img src="doc-files/XPressNetProgrammer-StateDiagram.png" alt="UML State diagram"> 023 * <img src="doc-files/XPressNetProgrammer-SequenceDiagram.png" alt="UML Sequence diagram"> 024 * 025 * @author Bob Jacobsen Copyright (c) 2002, 2007 026 * @author Paul Bender Copyright (c) 2003-2010 027 * @author Giorgio Terdina Copyright (c) 2007 028 */ 029 030/* 031 * @startuml jmri/jmrix/lenz/doc-files/XPressNetProgrammer-StateDiagram.png 032 * state NormalMode{ 033 * [*] --> initialREQUESTSENT: readCV() 034 * [*] --> initialREQUESTSENT: writeCV() 035 * } 036 * [*] --> NormalMode 037 * state ServiceMode{ 038 * [*] --> INQUIRESENT 039 * REQUESTSENT --> INQUIRESENT : Command Successfully Received 040 * REQUESTSENT --> NOTPROGRAMMING : timeout() 041 * INQUIRESENT --> NOTPROGRAMMING : Result Received 042 * INQUIRESENT --> NOTPROGRAMMING : timeout() 043 * NOTPROGRAMMING --> REQUESTSENT : readCV() 044 * NOTPROGRAMMING --> REQUESTSENT : writeCV() 045 * NOTPROGRAMMING --> RequestNormalOps : timeout() 046 * RequestNormalOps --> [*] 047 * } 048 * NormalMode --> ServiceMode : Service Mode Entry Received 049 * ServiceMode --> [*] : Normal Operations Resumed 050 * @enduml 051 * 052 * @startuml jmri/jmrix/lenz/doc-files/XPressNetProgrammer-SequenceDiagram.png 053 * actor user 054 * control programmer 055 * user -> programmer:read/write CV 056 * programmer -> XNetProgrammer:readCV()/writeCV() 057 * XNetProgrammer -> CommandStation: Read/Write CV in appropriate mode. 058 * CommandStation -> XNetProgrammer: Service Mode Entry. 059 * XNetProgrammer -> CommandStation: Request Service Mode Results. 060 * CommandStation -> XNetProgrammer: Service Mode Result or Error Message 061 * XNetProgrammer -> programmer: CV Value or Error Message 062 * programmer -> user: CV value or Error Message 063 * loop 0 or more times 064 * user -> programmer:read/write CV 065 * programmer -> XNetProgrammer:readCV()/writeCV() 066 * XNetProgrammer -> CommandStation: Read/Write CV in appropriate mode. 067 * CommandStation -> XNetProgrammer: Command Successfully Received. 068 * XNetProgrammer -> CommandStation: Request Service Mode Results. 069 * CommandStation -> XNetProgrammer: Service Mode Result or Error Message 070 * XNetProgrammer -> programmer: CV Value or Error Message 071 * programmer -> user: CV value or Error Message 072 * end 073 * XNetProgrammer -> CommandStation: Resume Normal Operations 074 * CommandStation -> XNetProgrammer: Normal Operations Resumed 075 * @enduml 076 */ 077public class XNetProgrammer extends AbstractProgrammer implements XNetListener { 078 079 protected static final int XNetProgrammerTimeout = 90000; 080 081 // keep track of whether or not the command station is in service 082 // mode. Used for determining if "OK" message is an aproriate 083 // response to a request to a programming request. 084 protected boolean _service_mode = false; 085 086 public XNetProgrammer(XNetTrafficController tc) { 087 // error if more than one constructed? 088 089 _controller = tc; 090 091 // connect to listen 092 controller().addXNetListener(XNetInterface.CS_INFO 093 | XNetInterface.COMMINFO 094 | XNetInterface.INTERFACE, 095 this); 096 097 setMode(ProgrammingMode.DIRECTBYTEMODE); 098 } 099 100 /** 101 * {@inheritDoc} 102 */ 103 @Override 104 @Nonnull 105 public List<ProgrammingMode> getSupportedModes() { 106 List<ProgrammingMode> ret = new ArrayList<>(); 107 ret.add(ProgrammingMode.DIRECTBYTEMODE); 108 ret.add(ProgrammingMode.DIRECTBITMODE); 109 ret.add(ProgrammingMode.PAGEMODE); 110 ret.add(ProgrammingMode.REGISTERMODE); 111 return ret; 112 } 113 114 /** 115 * {@inheritDoc} 116 * 117 * Can we read from a specific CV in the specified mode? Answer may not be 118 * correct if the command station type and version sent by the command 119 * station mimics one of the known command stations. 120 */ 121 @Override 122 public boolean getCanRead(String addr) { 123 if (log.isDebugEnabled()) { 124 log.debug("check mode {} CV {}", getMode(), addr); 125 } 126 if (!getCanRead()) { 127 return false; // check basic implementation first 128 } 129 // Multimaus cannot read CVs, unless Rocomotion interface is used, assume other Command Stations do. 130 // To be revised if and when a Rocomotion adapter is introduced!!! 131 if (controller().getCommandStation().getCommandStationType() == 0x10) { 132 return false; 133 } 134 135 if (getMode().equals(ProgrammingMode.DIRECTBITMODE) || getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) { 136 switch (controller().getCommandStation().getCommandStationType()) { 137 case XNetConstants.CS_TYPE_LZ100: 138 if (controller().getCommandStation() 139 .getCommandStationSoftwareVersion() <= 3.5) { 140 return Integer.parseInt(addr) <= 256; 141 } else { 142 return Integer.parseInt(addr) <= 1024; 143 } 144 default: 145 return Integer.parseInt(addr) <= 256; 146 } 147 } else { 148 return Integer.parseInt(addr) <= 256; 149 } 150 } 151 152 /** 153 * {@inheritDoc} 154 * 155 * Can we write to a specific CV in the specified mode? Answer may not be 156 * correct if the command station type and version sent by the command 157 * station mimics one of the known command stations. 158 */ 159 @Override 160 public boolean getCanWrite(String addr) { 161 if (log.isDebugEnabled()) { 162 log.debug("check CV {} ", addr); 163 log.debug("Command Station Version {}", controller().getCommandStation().getVersionString()); 164 165 } 166 if (!getCanWrite()) { 167 return false; // check basic implementation first 168 } 169 if (getMode().equals(ProgrammingMode.DIRECTBITMODE) || getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) { 170 switch (controller().getCommandStation().getCommandStationType()) { 171 case XNetConstants.CS_TYPE_LZ100: 172 if (controller().getCommandStation() 173 .getCommandStationSoftwareVersion() <= 3.5) { 174 return Integer.parseInt(addr) <= 256; 175 } else { 176 return Integer.parseInt(addr) <= 1024; 177 } 178 default: 179 return Integer.parseInt(addr) <= 256; 180 } 181 } else { 182 return Integer.parseInt(addr) <= 256; 183 } 184 } 185 186 // members for handling the programmer interface 187 protected int progState = 0; 188 protected static final int NOTPROGRAMMING = 0; // is notProgramming 189 protected static final int REQUESTSENT = 1; // waiting reply to command to go into programming mode 190 protected static final int INQUIRESENT = 2; // read/write command sent, waiting reply 191 protected boolean _progRead = false; 192 protected int _val; // remember the value being read/written for confirmative reply 193 protected int _cv; // remember the cv being read/written 194 195 /** 196 * {@inheritDoc} 197 */ 198 @Override 199 public synchronized void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 200 final int CV = Integer.parseInt(CVname); 201 log.debug("writeCV {} listens {} ",CV,p); 202 useProgrammer(p); 203 _progRead = false; 204 // set new state & save values 205 progState = REQUESTSENT; 206 _val = val; 207 _cv = 0xffff & CV; 208 209 try { 210 // start the error timer 211 restartTimer(XNetProgrammerTimeout); 212 213 // format and send message to go to program mode 214 if (getMode().equals(ProgrammingMode.PAGEMODE)) { 215 XNetMessage msg = XNetMessage.getWritePagedCVMsg(CV, val); 216 controller().sendXNetMessage(msg, this); 217 } else if (getMode().equals(ProgrammingMode.DIRECTBITMODE) || getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) { 218 XNetMessage msg = XNetMessage.getWriteDirectCVMsg(CV, val); 219 controller().sendXNetMessage(msg, this); 220 } else { // register mode by elimination 221 XNetMessage msg = XNetMessage.getWriteRegisterMsg(registerFromCV(CV), val); 222 controller().sendXNetMessage(msg, this); 223 } 224 } catch (jmri.ProgrammerException e) { 225 progState = NOTPROGRAMMING; 226 throw e; 227 } 228 } 229 230 /** 231 * {@inheritDoc} 232 */ 233 @Override 234 public synchronized void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 235 readCV(CV, p); 236 } 237 238 /** 239 * {@inheritDoc} 240 */ 241 @Override 242 public synchronized void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException { 243 final int CV = Integer.parseInt(CVname); 244 log.debug("readCV {} listenes {}",CV,p); 245 // If can't read (e.g. multiMaus CS), this shouldnt be invoked, but 246 // still we need to do something rational by returning a NotImplemented error 247 if (!getCanRead()) { 248 notifyProgListenerEnd(p, CV, jmri.ProgListener.NotImplemented); 249 return; 250 } 251 useProgrammer(p); 252 _cv = 0xffff & CV; 253 _progRead = true; 254 // set new state 255 progState = REQUESTSENT; 256 try { 257 // start the error timer 258 restartTimer(XNetProgrammerTimeout); 259 260 // format and send message to go to program mode 261 if (getMode().equals(ProgrammingMode.PAGEMODE)) { 262 XNetMessage msg = XNetMessage.getReadPagedCVMsg(CV); 263 controller().sendXNetMessage(msg, this); 264 } else if (getMode().equals(ProgrammingMode.DIRECTBITMODE) || getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) { 265 XNetMessage msg = XNetMessage.getReadDirectCVMsg(CV); 266 controller().sendXNetMessage(msg, this); 267 } else { // register mode by elimination 268 XNetMessage msg = XNetMessage.getReadRegisterMsg(registerFromCV(CV)); 269 controller().sendXNetMessage(msg, this); 270 } 271 } catch (jmri.ProgrammerException e) { 272 progState = NOTPROGRAMMING; 273 throw e; 274 } 275 276 } 277 278 private jmri.ProgListener _usingProgrammer = null; 279 280 // internal method to remember who's using the programmer 281 protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException { 282 // test for only one! 283 if (_usingProgrammer != null && _usingProgrammer != p) { 284 log.info("programmer already in use by {}",_usingProgrammer); 285 throw new jmri.ProgrammerException("programmer in use"); 286 } else { 287 _usingProgrammer = p; 288 } 289 } 290 291 /** 292 * {@inheritDoc} 293 */ 294 @Override 295 public synchronized void message(XNetReply m) { 296 if (m.getElement(0) == XNetConstants.CS_INFO 297 && m.getElement(1) == XNetConstants.BC_SERVICE_MODE_ENTRY) { 298 if (!_service_mode) { 299 // the command station is in service mode. An "OK" 300 // message can trigger a request for service mode 301 // results if progrstate is REQUESTSENT. 302 _service_mode = true; 303 } else { // _ service_mode == true 304 // Since we get this message as both a broadcast and 305 // a directed message, ignore the message if we're 306 //already in the indicated mode 307 return; 308 } 309 } 310 if (m.getElement(0) == XNetConstants.CS_INFO 311 && m.getElement(1) == XNetConstants.BC_NORMAL_OPERATIONS) { 312 if (_service_mode) { 313 // the command station is not in service mode. An 314 // "OK" message can not trigger a request for service 315 // mode results if progrstate is REQUESTSENT. 316 _service_mode = false; 317 } else { // _service_mode == false 318 // Since we get this message as both a broadcast and 319 // a directed message, ignore the message if we're 320 //already in the indicated mode 321 return; 322 } 323 } 324 if (progState == NOTPROGRAMMING) { 325 // we get the complete set of replies now, so ignore these 326 327 } else if (progState == REQUESTSENT) { 328 if (log.isDebugEnabled()) { 329 log.debug("reply in REQUESTSENT state"); 330 } 331 // see if reply is the acknowledge of program mode; if not, wait for next 332 if ((_service_mode && ( m.isOkMessage() || m.isTimeSlotRestored() )) 333 || (m.getElement(0) == XNetConstants.CS_INFO 334 && (m.getElement(1) == XNetConstants.BC_SERVICE_MODE_ENTRY 335 || m.getElement(1) == XNetConstants.PROG_CS_READY))) { 336 if (!getCanRead()) { 337 // on systems like the Roco MultiMaus 338 // (which does not support reading) 339 // let a timeout occur so the system 340 // has time to write data to the 341 // decoder 342 restartTimer(SHORT_TIMEOUT); 343 return; 344 } 345 346 // here ready to request the results 347 progState = INQUIRESENT; 348 //start the error timer 349 restartTimer(XNetProgrammerTimeout); 350 351 controller().sendXNetMessage(XNetMessage.getServiceModeResultsMsg(), 352 this); 353 } else if (m.getElement(0) == XNetConstants.CS_INFO 354 && m.getElement(1) == XNetConstants.CS_NOT_SUPPORTED) { 355 // programming operation not supported by this command station 356 progState = NOTPROGRAMMING; 357 notifyProgListenerEnd(_val, jmri.ProgListener.NotImplemented); 358 } else if (m.getElement(0) == XNetConstants.CS_INFO 359 && m.getElement(1) == XNetConstants.BC_NORMAL_OPERATIONS) { 360 // We Exited Programming Mode early 361 log.error("Service mode exited before sequence complete."); 362 progState = NOTPROGRAMMING; 363 stopTimer(); 364 notifyProgListenerEnd(_val, jmri.ProgListener.SequenceError); 365 } else if (m.getElement(0) == XNetConstants.CS_INFO 366 && m.getElement(1) == XNetConstants.PROG_SHORT_CIRCUIT) { 367 // We experienced a short Circuit on the Programming Track 368 log.error("Short Circuit While Programming Decoder"); 369 progState = NOTPROGRAMMING; 370 stopTimer(); 371 notifyProgListenerEnd(_val, jmri.ProgListener.ProgrammingShort); 372 } else if (m.isTimeSlotErrorMessage()){ 373 // we just ignore timeslot errors in the programmer. 374 } else if (m.isCommErrorMessage()) { 375 // We experienced a communications error 376 if(_controller.hasTimeSlot()) { 377 // We have a timeslot, so report it as an error 378 log.error("Communications error in REQUESTSENT state while programming. Error: {}",m); 379 progState = NOTPROGRAMMING; 380 stopTimer(); 381 notifyProgListenerEnd(_val, jmri.ProgListener.CommError); 382 } 383 } 384 } else if (progState == INQUIRESENT) { 385 if (log.isDebugEnabled()) { 386 log.debug("reply in INQUIRESENT state"); 387 } 388 // check for right message, else return 389 if (m.isPagedModeResponse()) { 390 // valid operation response, but does it belong to us? 391 try { 392 // we always save the cv number, but if 393 // we are using register mode, there is 394 // at least one case (CV29) where the value 395 // returned does not match the value we saved. 396 if (m.getServiceModeCVNumber() != _cv 397 && m.getServiceModeCVNumber() != registerFromCV(_cv)) { 398 log.debug(" result for CV {} expecting {}",m.getServiceModeCVNumber(),_cv); 399 return; 400 } 401 } catch (jmri.ProgrammerException e) { 402 progState = NOTPROGRAMMING; 403 notifyProgListenerEnd(_val, jmri.ProgListener.UnknownError); 404 } 405 // see why waiting 406 if (_progRead) { 407 // read was in progress - get return value 408 _val = m.getServiceModeCVValue(); 409 } 410 progState = NOTPROGRAMMING; 411 stopTimer(); 412 // if this was a read, we cached the value earlier. 413 // If its a write, we're to return the original write value 414 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 415 } else if (m.isDirectModeResponse()) { 416 // valid operation response, but does it belong to us? 417 if (m.getServiceModeCVNumber() != _cv) { 418 log.debug(" CV read {} expecting {}",m.getServiceModeCVNumber(),_cv); 419 return; 420 } 421 422 // see why waiting 423 if (_progRead) { 424 // read was in progress - get return value 425 _val = m.getServiceModeCVValue(); 426 } 427 progState = NOTPROGRAMMING; 428 stopTimer(); 429 // if this was a read, we cached the value earlier. If its a 430 // write, we're to return the original write value 431 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 432 } else if (m.getElement(0) == XNetConstants.CS_SERVICE_MODE_RESPONSE 433 && (m.getElement(1) & 0x14) == (0x14)) { 434 // valid operation response, but does it belong to us? 435 int sent_cv = m.getServiceModeCVNumber(); 436 if (sent_cv != _cv && (sent_cv == 0 && _cv != 0x0400)) { 437 return; 438 } 439 // see why waiting 440 if (_progRead) { 441 // read was in progress - get return value 442 _val = m.getServiceModeCVValue(); 443 } 444 progState = NOTPROGRAMMING; 445 stopTimer(); 446 // if this was a read, we cached the value earlier. If its a 447 // write, we're to return the original write value 448 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 449 } else if (m.getElement(0) == XNetConstants.CS_INFO 450 && m.getElement(1) == XNetConstants.PROG_BYTE_NOT_FOUND) { 451 // "data byte not found", e.g. no reply 452 progState = NOTPROGRAMMING; 453 stopTimer(); 454 notifyProgListenerEnd(_val, jmri.ProgListener.NoLocoDetected); 455 } else if (m.getElement(0) == XNetConstants.CS_INFO 456 && m.getElement(1) == XNetConstants.BC_NORMAL_OPERATIONS) { 457 // We Exited Programming Mode early 458 log.error("Service Mode exited before sequence complete."); 459 progState = NOTPROGRAMMING; 460 stopTimer(); 461 notifyProgListenerEnd(_val, jmri.ProgListener.SequenceError); 462 } else if (m.getElement(0) == XNetConstants.CS_INFO 463 && m.getElement(1) == XNetConstants.PROG_SHORT_CIRCUIT) { 464 // We experienced a short Circuit on the Programming Track 465 log.error("Short Circuit While Programming Decoder"); 466 progState = NOTPROGRAMMING; 467 stopTimer(); 468 notifyProgListenerEnd(_val, jmri.ProgListener.ProgrammingShort); 469 } else if (m.getElement(0) == XNetConstants.CS_INFO 470 && m.getElement(1) == XNetConstants.PROG_CS_BUSY) { 471 // Command station indicated it was busy in 472 // programming mode, request results again 473 // (do not reset timer or change mode) 474 // NOTE: Currently only sent by OpenDCC. 475 controller().sendXNetMessage(XNetMessage.getServiceModeResultsMsg(), 476 this); 477 } else if (m.isTimeSlotErrorMessage()){ 478 // we just ignore timeslot errors in the programmer. 479 } else if (m.isCommErrorMessage()) { 480 // We experienced a communications error 481 if(_controller.hasTimeSlot()) { 482 // We have a timeslot, so report it as an error 483 log.error("Communications error in INQUIRESENT state while programming. Error: {}", m); 484 progState = NOTPROGRAMMING; 485 stopTimer(); 486 notifyProgListenerEnd(_val, jmri.ProgListener.CommError); 487 } 488 } else { 489 // nothing important, ignore 490 log.debug("Ignoring message {}",m); 491 } 492 } else { 493 if (log.isDebugEnabled()) { 494 log.debug("reply in un-decoded state"); 495 } 496 } 497 } 498 499 /** 500 * {@inheritDoc} 501 * 502 */ 503 @Override 504 public synchronized void message(XNetMessage l) { 505 // The prgrammer does not use outgoing XpressNet messges. 506 } 507 508 /** 509 * {@inheritDoc} 510 * 511 * Log and ignore 512 */ 513 @Override 514 public void notifyTimeout(XNetMessage msg) { 515 log.debug("Notified of timeout on message {}",msg); 516 } 517 518 /** 519 * Since the Lenz programming sequence requires several 520 * operations, we want to be able to check and see if we are 521 * currently programming before allowing the Traffic Controller 522 * to send a request to exit service mode. 523 * @return true if programmer busy, else false. 524 */ 525 public synchronized boolean programmerBusy() { 526 return (progState != NOTPROGRAMMING); 527 } 528 529 /** 530 * {@inheritDoc} 531 */ 532 @Override 533 protected synchronized void timeout() { 534 if (progState != NOTPROGRAMMING) { 535 // we're programming, time to stop 536 if (log.isDebugEnabled()) { 537 log.debug("timeout!"); 538 } 539 // perhaps no loco present? Fail back to end of programming 540 progState = NOTPROGRAMMING; 541 if (getCanRead()) { 542 notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout); 543 } else { 544 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 545 } 546 } 547 } 548 549 /** 550 * Internal method to notify of the final result 551 * @param value Value returned 552 * @param status Status of operation 553 */ 554 protected void notifyProgListenerEnd(int value, int status) { 555 log.debug("notifyProgListenerEnd value {} status {}.",value,status); 556 // programmingOpReply, called by noitfyProgListenerEnd 557 // in the super class, might send an immediate reply, so 558 // clear the current listener _first_ 559 jmri.ProgListener temp = _usingProgrammer; 560 _usingProgrammer = null; 561 notifyProgListenerEnd(temp,value, status); 562 } 563 564 XNetTrafficController _controller; 565 566 protected XNetTrafficController controller() { 567 return _controller; 568 } 569 570 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(XNetProgrammer.class); 571 572}