001package jmri.jmrix.roco.z21;
002
003import jmri.ProgrammingMode;
004import jmri.jmrix.lenz.XNetMessage;
005import jmri.jmrix.lenz.XNetProgrammer;
006import jmri.jmrix.lenz.XNetReply;
007import jmri.jmrix.lenz.XNetTrafficController;
008
009/**
010 * Z21 Programmer support for Lenz XpressNet.
011 * <p>
012 * The read operation state sequence is:
013 * <ul>
014 * <li>Send Register Mode / Paged mode /Direct Mode read request
015 * <li>Wait for Broadcast Service Mode Entry message
016 * <li>Send Request for Service Mode Results request
017 * <li>Wait for results reply, interpret
018 * <li>Send Resume Operations request
019 * <li>Wait for Normal Operations Resumed broadcast
020 * </ul>
021 *
022 * @author Paul Bender Copyright (c) 2014
023 */
024public class Z21XNetProgrammer extends XNetProgrammer {
025
026    public Z21XNetProgrammer(XNetTrafficController tc) {
027        super(tc);
028        // connect to listen
029        controller().addXNetListener(~0,
030                this);
031    }
032
033    /**
034     * {@inheritDoc}
035     * <p>
036     * Can we read from a specific CV in the specified mode? Answer may not be
037     * correct if the command station type and version sent by the command
038     * station mimics one of the known command stations.
039     */
040    @Override
041    public boolean getCanRead(String addr) {
042        if (log.isDebugEnabled()) {
043            log.debug("check mode {} CV {}", getMode(), addr);
044        }
045        if (!getCanRead()) {
046            return false; // check basic implementation first
047        }
048        // Multimaus cannot read CVs, unless Rocomotion interface is used, assume other Command Stations do.
049        // To be revised if and when a Rocomotion adapter is introduced!!!
050        if (controller().getCommandStation().getCommandStationType() == 0x10) {
051            return false;
052        }
053
054        if (getMode().equals(ProgrammingMode.DIRECTBITMODE) || getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) {
055            return true; // z21 allows us to specify the CV in 16 bits.
056        } else {
057            return Integer.parseInt(addr) <= 256;
058        }
059    }
060
061    /**
062     * {@inheritDoc}
063     * <p>
064     * Can we write to a specific CV in the specified mode? Answer may not be
065     * correct if the command station type and version sent by the command
066     * station mimics one of the known command stations.
067     */
068    @Override
069    public boolean getCanWrite(String addr) {
070        if (log.isDebugEnabled()) {
071            log.debug("check CV {}", addr);
072            log.debug("cs Type: {} CS Version: {}", controller().getCommandStation().getCommandStationType(), controller().getCommandStation().getCommandStationSoftwareVersion());
073        }
074        if (!getCanWrite()) {
075            return false; // check basic implementation first
076        }
077        if (getMode().equals(ProgrammingMode.DIRECTBITMODE) || getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) {
078            return true; // z21 allows us to specify the CV in 16 bits.
079        } else {
080            return Integer.parseInt(addr) <= 256;
081        }
082    }
083
084    /**
085     * {@inheritDoc}
086     */
087    @Override
088    synchronized public void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
089        final int CV = Integer.parseInt(CVname);
090        if (getMode().equals(ProgrammingMode.DIRECTBITMODE)
091                || getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) {
092            if (log.isDebugEnabled()) {
093                log.debug("writeCV {} listens {}", CV, p);
094            }
095            useProgrammer(p);
096            _progRead = false;
097            // set new state & save values
098            progState = REQUESTSENT;
099            _val = val;
100            _cv = 0xffff & CV;
101
102            // start the error timer
103            restartTimer(XNetProgrammerTimeout);
104
105            XNetMessage msg = Z21XNetMessage.getZ21WriteDirectCVMsg(CV, val);
106            controller().sendXNetMessage(msg, this);
107        } else {
108            super.writeCV(CVname, val, p);
109        }
110    }
111
112    /**
113     * {@inheritDoc}
114     */
115    @Override
116    synchronized public void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException {
117        final int CV = Integer.parseInt(CVname);
118        if (getMode().equals(ProgrammingMode.DIRECTBITMODE)
119                || getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) {
120            if (log.isDebugEnabled()) {
121                log.debug("readCV {} listens {}", CV, p);
122            }
123
124            useProgrammer(p);
125            _cv = 0xffff & CV;
126            _progRead = true;
127            // set new state
128            progState = REQUESTSENT;
129            // start the error timer
130            restartTimer(XNetProgrammerTimeout);
131
132            // format and send message to go to program mode
133            XNetMessage msg = Z21XNetMessage.getZ21ReadDirectCVMsg(CV);
134            controller().sendXNetMessage(msg, this);
135        } else {
136            super.readCV(CVname, p);
137        }
138
139    }
140
141    /**
142     * {@inheritDoc}
143     */
144    @Override
145    synchronized public void message(XNetReply m) {
146        if (progState == NOTPROGRAMMING) {
147            // we get the complete set of replies now, so ignore these
148
149        } else if (progState == REQUESTSENT
150                || progState == INQUIRESENT) {
151            if (log.isDebugEnabled()) {
152                log.debug("reply in {} state", progState == REQUESTSENT ? "REQUESTSENT" : "INQUIRESENT");
153            }
154            if (m.getElement(0) == Z21Constants.LAN_X_CV_RESULT_XHEADER
155                    && m.getElement(1) == Z21Constants.LAN_X_CV_RESULT_DB0) {
156                // valid operation response, but does it belong to us?
157                int sent_cv = (m.getElement(2) << 8) + m.getElement(3) + 1;
158                if (sent_cv != _cv) {
159                    return; // not for us.
160                } // see why waiting
161                if (_progRead) {
162                    // read was in progress - get return value
163                    _val = m.getElement(4);
164                }
165                progState = NOTPROGRAMMING;
166                stopTimer();
167                // if this was a read, we cached the value earlier.  If its a
168                // write, we're to return the original write value
169                notifyProgListenerEnd(_val, jmri.ProgListener.OK);
170            } else {
171                super.message(m);
172            }
173        } else {
174            if (log.isDebugEnabled()) {
175                log.debug("reply in un-decoded state");
176            }
177        }
178    }
179
180    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Z21XNetProgrammer.class);
181
182}