001package jmri.jmrix.roco.z21; 002 003import jmri.ProgListener; 004import jmri.jmrix.lenz.XNetConstants; 005import jmri.jmrix.lenz.XNetMessage; 006import jmri.jmrix.lenz.XNetReply; 007import jmri.jmrix.lenz.XNetTrafficController; 008import jmri.jmrix.loconet.LnConstants; 009import jmri.jmrix.loconet.LocoNetListener; 010import jmri.jmrix.loconet.LocoNetMessage; 011import jmri.jmrix.loconet.LnTrafficController; 012 013/** 014 * Provides an Ops mode programming interface for Roco Z21 Currently only Byte 015 * mode is implemented, though XpressNet also supports bit mode writes for POM 016 * 017 * @see jmri.Programmer 018 * @author Paul Bender Copyright (C) 2018 019 */ 020public class Z21XNetOpsModeProgrammer extends jmri.jmrix.lenz.XNetOpsModeProgrammer implements LocoNetListener { 021 022 private int _cv; 023 private LnTrafficController lnTC; 024 025 public Z21XNetOpsModeProgrammer(int pAddress, XNetTrafficController controller) { 026 this(pAddress,controller,null); 027 } 028 029 public Z21XNetOpsModeProgrammer(int pAddress, XNetTrafficController controller,LnTrafficController lntc) { 030 super(pAddress,controller); 031 // connect to listen 032 controller.addXNetListener(~0, 033 this); 034 lnTC = lntc; 035 if(lnTC!=null) { 036 lnTC.addLocoNetListener(~0,this); 037 } 038 } 039 040 /** 041 * {@inheritDoc} 042 * 043 * Send an ops-mode write request to the Xpressnet. 044 */ 045 @Override 046 synchronized public void writeCV(String CVname, int val, ProgListener p) { 047 final int CV = Integer.parseInt(CVname); 048 XNetMessage msg = XNetMessage.getWriteOpsModeCVMsg(mAddressHigh, mAddressLow, CV, val); 049 msg.setBroadcastReply(); // reply comes through a loconet message. 050 tc.sendXNetMessage(msg, this); 051 /* we need to save the programer and value so we can send messages 052 back to the screen when the programming screen when we receive 053 something from the command station */ 054 progListener = p; 055 _cv = 0xffff & CV; 056 value = val; 057 progState = REQUESTSENT; 058 restartTimer(msg.getTimeout()); 059 } 060 061 /** 062 * {@inheritDoc} 063 */ 064 @Override 065 synchronized public void readCV(String CVname, ProgListener p) { 066 final int CV = Integer.parseInt(CVname); 067 XNetMessage msg = XNetMessage.getVerifyOpsModeCVMsg(mAddressHigh, mAddressLow, CV, value); 068 /* we need to save the programer so we can send messages 069 back to the programming screen when we receive 070 something from the command station */ 071 progListener = p; 072 _cv = 0xffff & CV; 073 tc.sendXNetMessage(msg, this); 074 progState = REQUESTSENT; 075 restartTimer(msg.getTimeout()); 076 } 077 078 /** 079 * {@inheritDoc} 080 */ 081 @Override 082 synchronized public void confirmCV(String CVname, int val, ProgListener p) { 083 int CV = Integer.parseInt(CVname); 084 XNetMessage msg = XNetMessage.getVerifyOpsModeCVMsg(mAddressHigh, mAddressLow, CV, val); 085 tc.sendXNetMessage(msg, this); 086 /* we need to save the programer so we can send messages 087 back to the programming screen when we receive 088 something from the command station */ 089 progListener = p; 090 _cv = 0xffff & CV; 091 progState = REQUESTSENT; 092 restartTimer(msg.getTimeout()); 093 } 094 095 /** 096 * {@inheritDoc} 097 */ 098 @Override 099 synchronized public void message(XNetReply l) { 100 if (progState == NOTPROGRAMMING) { 101 // We really don't care about any messages unless we send a 102 // request, so just ignore anything that comes in 103 } else if (progState == REQUESTSENT) { 104 if (l.isOkMessage()) { 105 // Before we set the programmer state to not programming, 106 // delay for a short time to give the decoder a chance to 107 // process the request. 108 new jmri.util.WaitHandler(this,250); 109 progState = NOTPROGRAMMING; 110 stopTimer(); 111 notifyProgListenerEnd(progListener, value, jmri.ProgListener.OK); 112 } else if (l.getElement(0) == Z21Constants.LAN_X_CV_RESULT_XHEADER 113 && l.getElement(1) == Z21Constants.LAN_X_CV_RESULT_DB0) { 114 // valid operation response, but does it belong to us? 115 int sent_cv = (l.getElement(2) << 8) + l.getElement(3) + 1; 116 if (sent_cv != _cv) { 117 return; // not for us. 118 } 119 value = l.getElement(4); 120 progState = NOTPROGRAMMING; 121 stopTimer(); 122 // if this was a read, we cached the value earlier. If its a 123 // write, we're to return the original write value 124 notifyProgListenerEnd(progListener, value, jmri.ProgListener.OK); 125 } else { 126 /* this is an error */ 127 if (l.isRetransmittableErrorMsg()) { 128 // just ignore this, since we are retransmitting 129 // the message. 130 } else if (l.getElement(0) == XNetConstants.CS_INFO 131 && l.getElement(1) == XNetConstants.PROG_BYTE_NOT_FOUND) { 132 // "data byte not found", e.g. no reply 133 progState = NOTPROGRAMMING; 134 stopTimer(); 135 notifyProgListenerEnd(progListener, value, jmri.ProgListener.NoLocoDetected); 136 } else if (l.getElement(0) == XNetConstants.CS_INFO 137 && l.getElement(1) == XNetConstants.CS_NOT_SUPPORTED) { 138 progState = NOTPROGRAMMING; 139 stopTimer(); 140 notifyProgListenerEnd(progListener, value, jmri.ProgListener.NotImplemented); 141 } else { 142 /* this is an unknown error */ 143 progState = NOTPROGRAMMING; 144 stopTimer(); 145 notifyProgListenerEnd(progListener, value, jmri.ProgListener.UnknownError); 146 } 147 } 148 } 149 } 150 151 /** 152 * {@inheritDoc} 153 */ 154 @Override 155 synchronized public void message(LocoNetMessage m){ 156 // the Roco Z21 responds to Operations mode write requests with a 157 // LocoNet message. 158 log.debug("LocoNet message received: {}", m); 159 160 int slot = m.getElement(2); // slot number for this request 161 162 if(slot == LnConstants.PRG_SLOT && progState == REQUESTSENT) { 163 // we are programming, and this is a programming slot message, 164 // so let's see if it is for us. 165 log.debug("Right message slot and programming"); 166 167 // the following 8 lines and assignment of val were copied 168 // from the loconet monitor. 169 int hopsa = m.getElement(5); // Ops mode - 7 high address bits 170 // of loco to program 171 int lopsa = m.getElement(6); // Ops mode - 7 low address bits of 172 // loco to program 173 int cvh = m.getElement(8); // hi 3 bits of CV# and msb of data7 174 int cvl = m.getElement(9); // lo 7 bits of CV# 175 int data7 = m.getElement(10); // 7 bits of data to program, msb 176 int cvNumber = (((((cvh & LnConstants.CVH_CV8_CV9) >> 3) | (cvh & LnConstants.CVH_CV7)) * 128) + (cvl & 0x7f)) + 1; 177 int address = hopsa * 128 + lopsa; 178 179 // if we attempt to verify the cvNumber, this fails for 180 // multiple writes from the Symbolic Programmer. 181 if(address!=mAddress || cvNumber != _cv ){ 182 log.debug("message for address {} expecting {}; cv {} expecting {}", 183 address,mAddress,cvNumber,_cv); 184 return; // not for us 185 } 186 187 int val = -1; 188 189 if ((m.getElement(2) & 0x20) != 0) { 190 val = (((cvh & LnConstants.CVH_D7) << 6) | (data7 & 0x7f)); 191 } 192 193 log.debug("received value {} for cv {} on address {}",val,cvNumber,address); 194 195 // successful read if LACK return status is not 0x7F 196 int code = ProgListener.OK; 197 if ((m.getElement(2) == 0x7f)) { 198 code = ProgListener.UnknownError; 199 } 200 201 progState = NOTPROGRAMMING; 202 stopTimer(); 203 log.debug("sending code {} val {} to programmer",code,val); 204 notifyProgListenerEnd(progListener, val, code); 205 } 206 } 207 208 209 // initialize logging 210 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Z21XNetOpsModeProgrammer.class); 211 212}