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}