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