001package jmri.jmrix.zimo;
002
003import java.util.ArrayList;
004import java.util.List;
005import javax.annotation.Nonnull;
006
007import jmri.ProgrammingMode;
008import jmri.jmrix.AbstractProgrammer;
009
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013/**
014 * Programmer support for Zimo Mx-1. Currently paged mode is implemented.
015 * <p>
016 * The read operation state sequence is:
017 * <ul>
018 * <li>Reset Mx-1
019 * <li>Send paged mode read/write request
020 * <li>Wait for results reply, interpret
021 * <li>Send Resume Operations request
022 * <li>Wait for Normal Operations Resumed broadcast
023 * </ul>
024 *
025 * @author Bob Jacobsen Copyright (c) 2002
026 *
027 * Adapted by Sip Bosch for use with zimo Mx-1
028 *
029 */
030public class Mx1Programmer extends AbstractProgrammer implements Mx1Listener {
031
032    protected Mx1TrafficController tc;
033
034    protected Mx1Programmer(Mx1TrafficController _tc) {
035        this.tc = _tc;
036        SHORT_TIMEOUT = 4000; // length default timeout
037        // connect to listen
038        log.info("Mx1TrafficController: {}", this.tc);
039        if(this.tc!=null)
040            this.tc.addMx1Listener(~0, this);
041    }
042
043    /**
044     * {@inheritDoc}
045     *
046     * Types implemented here.
047     */
048    @Override
049    @Nonnull
050    public List<ProgrammingMode> getSupportedModes() {
051        List<ProgrammingMode> ret = new ArrayList<ProgrammingMode>();
052        ret.add(ProgrammingMode.PAGEMODE);
053        return ret;
054    }
055
056    // members for handling the programmer interface
057    int progState = 0;
058    boolean firstTime = true;
059    static final int NOTPROGRAMMING = 0; // is notProgramming
060    static final int INQUIRESENT = 2; // read/write command sent, waiting reply
061    boolean _progRead = false;
062    int _val; // remember the value being read/written for confirmative reply
063    int _cv;  // remember the cv being read/written
064
065    /**
066     * {@inheritDoc}
067     */
068    @Override
069    synchronized public void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
070        final int CV = Integer.parseInt(CVname);
071        if (log.isDebugEnabled()) {
072            log.debug("writeCV {} listens {}", CV, p);
073        }
074        useProgrammer(p);
075        _progRead = false;
076        // set new state & save values
077        progState = INQUIRESENT;
078        _val = val;
079        _cv = CV;
080        // start the error timer
081        startShortTimer();
082        // format and send message to go to program mode
083        if (getMode() == ProgrammingMode.PAGEMODE) {
084            if (tc.getProtocol() == Mx1Packetizer.ASCII) {
085                if (firstTime) {
086                    tc.sendMx1Message(tc.getCommandStation().resetModeMsg(), this);
087                    firstTime = false;
088                }
089                tc.sendMx1Message(tc.getCommandStation().getWritePagedCVMsg(CV, val), this);
090            } else {
091                tc.sendMx1Message(Mx1Message.getDecProgCmd(0, _cv, val, true), this);
092            }
093        }
094    }
095
096    /**
097     * {@inheritDoc}
098     */
099    @Override
100    public void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
101        readCV(CV, p);
102    }
103
104    /**
105     * {@inheritDoc}
106     */
107    @Override
108    synchronized public void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException {
109        final int CV = Integer.parseInt(CVname);
110        if (log.isDebugEnabled()) {
111            log.debug("readCV {} listens {}", CV, p);
112        }
113        useProgrammer(p);
114        _progRead = true;
115        // set new state
116        progState = INQUIRESENT;
117        _cv = CV;
118        // start the error timer
119        startShortTimer();
120        // format and send message to go to program mode
121        if (getMode() == ProgrammingMode.PAGEMODE) {
122            if (tc.getProtocol() == Mx1Packetizer.ASCII) {
123                if (firstTime) {
124                    tc.sendMx1Message(tc.getCommandStation().resetModeMsg(), this);
125                    firstTime = false;
126                }
127                tc.sendMx1Message(tc.getCommandStation().getReadPagedCVMsg(CV), this);
128            } else {
129                tc.sendMx1Message(Mx1Message.getDecProgCmd(0, _cv, -1, true), this);
130            }
131        }
132    }
133
134    private jmri.ProgListener _usingProgrammer = null;
135
136    // internal method to remember who's using the programmer
137    protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
138        // test for only one!
139        if (_usingProgrammer != null && _usingProgrammer != p) {
140            if (log.isInfoEnabled()) {
141                log.info("programmer already in use by {}", _usingProgrammer);
142            }
143            throw new jmri.ProgrammerException("programmer in use");
144        } else {
145            _usingProgrammer = p;
146            return;
147        }
148    }
149
150    /**
151     * {@inheritDoc}
152     */
153    @Override
154    synchronized public void message(Mx1Message m) {
155        if (progState == NOTPROGRAMMING) {
156            // we get the complete set of replies now, so ignore these
157            return;
158        } else if (progState == INQUIRESENT) {
159            if (log.isDebugEnabled()) {
160                log.debug("reply in INQUIRESENT state");
161            }
162            if (tc.getProtocol() == Mx1Packetizer.ASCII) {
163                //check for right message, else return
164                if (m.getElement(0) == 0x51 && m.getElement(1) == 0x4E
165                        && m.getElement(2) == 0x30 && m.getElement(3) == 0x30) {
166                    // valid operation response
167                    // see why waiting
168                    if (_progRead) {
169                        // read was in progress - get return value
170                        // convert asci into ebcdic
171                        int highVal = ascToBcd(m.getElement(6));
172                        highVal = highVal * 16 & 0xF0;
173                        int lowVal = ascToBcd(m.getElement(7));
174                        _val = (highVal | lowVal);
175                    }
176                    progState = NOTPROGRAMMING;
177                    stopTimer();
178                    // if this was a read, we cached the value earlier.  If its a
179                    // write, we're to return the original write value
180                    notifyProgListenerEnd(_val, jmri.ProgListener.OK);
181                    tc.sendMx1Message(tc.getCommandStation().resetModeMsg(), this);
182                    return;
183                    // faulty message
184                } else {
185                    progState = NOTPROGRAMMING;
186                    stopTimer();
187                    tc.sendMx1Message(tc
188                            .getCommandStation().resetModeMsg(), this);
189                    notifyProgListenerEnd(_val, jmri.ProgListener.NoLocoDetected);
190                    return;
191                }
192            } else {
193                if (m.getPrimaryMessage() == Mx1Message.PROGCMD && m.getMessageType() == Mx1Message.REPLY2) {
194                    if (_progRead) {
195                        _val = m.getCvValue();
196                    }
197                    progState = NOTPROGRAMMING;
198                    stopTimer();
199                    // if this was a read, we cached the value earlier.  If its a
200                    // write, we're to return the original write value
201                    notifyProgListenerEnd(_val, jmri.ProgListener.OK);
202                    /*tc.sendMx1Message(tc.getCommandStation().resetModeMsg(), this);*/
203                    return;
204                }
205            }
206        }
207    }
208
209    /**
210     * {@inheritDoc}
211     *
212     * Internal routine to handle a timeout
213     */
214    @Override
215    synchronized protected void timeout() {
216        if (progState != NOTPROGRAMMING) {
217            // we're programming, time to stop
218            if (log.isDebugEnabled()) {
219                log.debug("timeout!");
220            }
221            // perhaps no loco present? Fail back to end of programming
222            progState = NOTPROGRAMMING;
223            if (tc.getProtocol() == Mx1Packetizer.ASCII) {
224                tc.sendMx1Message(tc.getCommandStation().resetModeMsg(),
225                        this);
226            }
227            notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout);
228        }
229    }
230
231    // internal method to notify of the final result
232    protected void notifyProgListenerEnd(int value, int status) {
233        if (log.isDebugEnabled()) {
234            log.debug("notifyProgListenerEnd value {} status {}", value, status);
235        }
236        // the programmingOpReply handler might send an immediate reply, so
237        // clear the current listener _first_
238        jmri.ProgListener temp = _usingProgrammer;
239        _usingProgrammer = null;
240        notifyProgListenerEnd(temp, value, status);
241    }
242
243    public int ascToBcd(int hex) {
244        switch (hex) {
245            case 0x46:
246                return 0x0F;
247            case 0x45:
248                return 0x0E;
249            case 0x65:
250                return 0x0E;
251            case 0x44:
252                return 0x0D;
253            case 0x43:
254                return 0x0C;
255            case 0x42:
256                return 0x0B;
257            case 0x41:
258                return 0x0A;
259            case 0x39:
260                return 0x09;
261            case 0x38:
262                return 0x08;
263            case 0x37:
264                return 0x07;
265            case 0x36:
266                return 0x06;
267            case 0x35:
268                return 0x05;
269            case 0x34:
270                return 0x04;
271            case 0x33:
272                return 0x03;
273            case 0x32:
274                return 0x02;
275            case 0x31:
276                return 0x01;
277            default:
278                return 0x00;
279        }
280    }
281
282    private final static Logger log = LoggerFactory.getLogger(Mx1Programmer.class);
283
284}