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}