001package jmri.implementation;
002import java.awt.event.ActionEvent;
003import java.awt.event.ActionListener;
004import javax.swing.Timer;
005import jmri.ProgListener;
006import jmri.Programmer;
007import jmri.jmrix.AbstractProgrammerFacade;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * Programmer facade for single index multi-CV access.
013 * <p>
014 * Used through the String write/read/confirm interface. Accepts address
015 * formats:
016 * <ul>
017 * <li> T2CV.11.12 <br>
018 * The write operation writes 11 to the first index CV (201), 12 to the 2nd
019 * index CV (202), then writes the data to CV 203 (MSB) and 204 (LSB).<br>
020 * The read operation is slightly different, writing 111 (100+11) to CV201,
021 * then 12 to the 2nd index CV (202), then writes 100 to CV204, then reads the
022 * two values from CV203 and CV204.
023 * <li> T3CV.11.12.13 <br>
024 * The write operation writes 11 to the first index CV (201), the data to the
025 * 2nd index CV (202), then writes 12 to CV203 and 13 to CV204.<br>
026 * The read operation writes 11 to CV201, then 12 to CV203, then 13 to CV204,
027 * then reads from CV202.
028 * </ul>
029 * All others pass through to the next facade or programmer. E.g. 123 will do a
030 * write/read/confirm to 123, or some other facade can provide "normal" indexed
031 * addressing.
032 *
033 * @see jmri.implementation.ProgrammerFacadeSelector
034 *
035 * @author Bob Jacobsen Copyright (C) 2013, 2016
036 * @author Andrew Crosland Copyright (C) 2021
037 */
038public class TwoIndexTcsProgrammerFacade extends AbstractProgrammerFacade implements ProgListener {
039
040    /**
041     * @param prog the programmer this facade is attached to
042     */
043    public TwoIndexTcsProgrammerFacade(Programmer prog) {
044        super(prog);
045    }
046
047    // these could be constructor arguments, but until there's another decoder
048    // this weird, for simplicity we leave them as constants
049    static final String indexPI = "201";
050    static final String indexSI = "202";
051    static final String valMSB = "203";
052    static final String valLSB = "204";
053    static final String readStrobe = "204";  // CV that has to be written before read
054    static final String format2Flag = "T2CV"; // flag to indicate this type of CV
055    static final String format3Flag = "T3CV"; // flag to indicate this type of CV
056    static final int readOffset = 100;
057
058    // members for handling the programmer interface
059    int _val; // remember the value being read/written for confirmative reply
060    String _cv; // remember the cv number being read/written
061    int valuePI;   //  value to write to PI or -1
062    int valueSI;   //  value to write to SI or -1
063    int valueMSB;  //  value to write to MSB or -1
064    int valueLSB;  //  value to write to LSB or -1
065    int _startVal; // Current CV value hint
066    int _startMSB;
067    int _startLSB;
068
069    private void parseCV(String cv) throws IllegalArgumentException {
070        valuePI = -1;
071        valueSI = -1;
072        if (cv.contains(".")) {
073            String[] splits = cv.split("\\.");
074            if (splits.length == 3 && splits[0].equals(format2Flag)) {
075                valuePI = Integer.parseInt(splits[1]);
076                valueSI = Integer.parseInt(splits[2]);
077            } else if (splits.length == 4 && splits[0].equals(format3Flag)) {
078                valuePI = Integer.parseInt(splits[1]);
079                valueMSB = Integer.parseInt(splits[2]);
080                valueLSB = Integer.parseInt(splits[3]);
081            } else {
082                _cv = cv;  // this is a pass through operation
083            }
084        } else {
085            _cv = cv;
086        }
087    }
088
089    // programming interface
090    @Override
091    synchronized public void writeCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
092        _val = val;
093        useProgrammer(p);
094        parseCV(CV);
095        upperByte = 0;
096        if (valuePI == -1) { // this is pass through
097            state = ProgState.PROGRAMMING;
098            prog.writeCV(_cv, val, this);
099        } else {
100            // write index first
101            state = ProgState.DOSIFORWRITE;
102            prog.writeCV(indexPI, valuePI, this);
103        }
104    }
105
106    @Override
107    synchronized public void readCV(String CV, jmri.ProgListener p) throws jmri.ProgrammerException {
108       readCV(CV, p, 0);
109    }
110
111    @Override
112    synchronized public void readCV(String CV, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException {
113        useProgrammer(p);
114        parseCV(CV);
115        _startVal = startVal;
116        _startMSB = startVal / 256;
117        _startLSB = startVal % 256;
118        upperByte = 0;
119        if (valuePI == -1) {
120            state = ProgState.PROGRAMMING;
121            prog.readCV(_cv, this, startVal);
122        } else {
123            // write index first; 2nd operation depends on type
124            if (valueSI == -1) {
125                state = ProgState.DOMSBFORREAD;
126            } else {
127                state = ProgState.DOSIFORREAD;
128            }
129            prog.writeCV(indexPI, valuePI + readOffset, this);
130        }
131    }
132
133    @Override
134    synchronized public void confirmCV(String CV, int startVal, jmri.ProgListener p) throws jmri.ProgrammerException {
135        useProgrammer(p);
136        parseCV(CV);
137        _startVal = startVal;
138        _startMSB = startVal/256;
139        _startLSB = startVal%256;
140        upperByte = 0;
141        if (valuePI == -1) {
142            state = ProgState.PROGRAMMING;
143            prog.confirmCV(_cv, startVal, this);
144        } else {
145            // write index first; 2nd operation depends on type
146            if (valueSI == -1) {
147                state = ProgState.DOMSBFORREAD;
148            } else {
149                state = ProgState.DOSIFORREAD;
150            }
151            prog.writeCV(indexPI, valuePI + readOffset, this);
152        }
153    }
154    
155    private jmri.ProgListener _usingProgrammer = null;
156
157    // internal method to remember who's using the programmer
158    protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
159        // test for only one!
160        if (_usingProgrammer != null && _usingProgrammer != p) {
161            if (log.isInfoEnabled()) {
162                log.info("programmer already in use by {}", _usingProgrammer);
163            }
164            throw new jmri.ProgrammerException("programmer in use");
165        } else {
166            _usingProgrammer = p;
167        }
168    }
169
170    enum ProgState {
171
172        PROGRAMMING, // doing last read/write, next reply is end
173        DOSIFORREAD, // reading, write to SI next
174        DOSTROBEFORREAD,// reading, write to strobe CV next
175        DOMSBFORREAD, // reading, write to MSB next
176        DOLSBFORREAD, // reading, write to LSB next
177        DOREADFIRST, // reading, get MSB next
178        FINISHREAD, // reading, read CV (LSB) next
179        DOSIFORWRITE, // writing, write to SI next
180        DOWRITEFIRST, // writing, write CV (MSB) next
181        FINISHWRITE, // writing, write CV (LSB) next
182        NOTPROGRAMMING  // idle, doing nothing, no reply expected
183    }
184
185    ProgState state = ProgState.NOTPROGRAMMING;
186
187    int upperByte;
188
189    public static int delayInterval = 10; // public static so can be changed in a script
190    
191    /** {@inheritDoc}
192     * Note this assumes that there's only one phase to the operation
193     */
194    @Override
195    synchronized public void programmingOpReply(int value, int status) {
196        if (log.isDebugEnabled()) {
197            log.debug("notifyProgListenerEnd value {} status {}", value, status);
198        }
199
200        if (_usingProgrammer == null) {
201            log.error("No listener to notify, reset and ignore");
202            state = ProgState.NOTPROGRAMMING;
203            return;
204        }
205        
206        // Complete processing later 
207        // originally installed so that WOWDecoder will go through a complete power on reset and not brown out between CV read/writes
208        ActionListener taskPerformer = new ActionListener() {
209            final int myValue = value;
210            final int myStatus = status;
211            @Override
212            public void actionPerformed(ActionEvent evt) {
213                processProgrammingOpReply(myValue, myStatus);
214            }
215        };
216        Timer t = new Timer(delayInterval, taskPerformer );
217        t.setRepeats(false);
218        t.start();
219    }
220    
221    // After a Swing delay, this processes the reply
222    protected void processProgrammingOpReply(int value, int status) {
223        if (status != OK && state != ProgState.DOREADFIRST) {  // we accept errors on the CV204 write due to CS-105 behavior with TCS decoders.
224            // pass abort up
225            log.debug("Reset and pass abort up");
226            jmri.ProgListener temp = _usingProgrammer;
227            _usingProgrammer = null; // done
228            state = ProgState.NOTPROGRAMMING;
229            temp.programmingOpReply(value, status);
230            return;
231        }
232
233        switch (state) {
234            case DOSIFORREAD:
235                try {
236                    state = ProgState.DOSTROBEFORREAD;
237                    prog.writeCV(indexSI, valueSI, this);
238                } catch (jmri.ProgrammerException e) {
239                    log.error("Exception doing write SI for read", e);
240                }
241                break;
242            case DOSTROBEFORREAD:
243                try {
244                    state = ProgState.DOREADFIRST;
245                    prog.writeCV(readStrobe, 0, this);
246                } catch (jmri.ProgrammerException e) {
247                    log.error("Exception doing write strobe for read", e);
248                }
249                break;
250            case DOREADFIRST:
251                try {
252                    state = ProgState.FINISHREAD;
253                    prog.readCV(valMSB, this, _startMSB);
254                } catch (jmri.ProgrammerException e) {
255                    log.error("Exception doing read first", e);
256                }
257                break;
258            case FINISHREAD:
259                try {
260                    state = ProgState.PROGRAMMING;
261                    if (valuePI != -1 && valueSI == -1) {
262                        upperByte = 0;
263                        prog.readCV(indexSI, this, _startVal);
264                    } else {
265                        upperByte = value;
266                        prog.readCV(valLSB, this, _startLSB);
267                    }
268                } catch (jmri.ProgrammerException e) {
269                    log.error("Exception doing final read", e);
270                }
271                break;
272
273            case DOMSBFORREAD:
274                try {
275                    state = ProgState.DOLSBFORREAD;
276                    prog.writeCV(valMSB, valueMSB, this);
277                } catch (jmri.ProgrammerException e) {
278                    log.error("Exception doing write strobe for read", e);
279                }
280                break;
281            case DOLSBFORREAD:
282                try {
283                    state = ProgState.FINISHREAD;
284                    prog.writeCV(valLSB, valueLSB, this);
285                } catch (jmri.ProgrammerException e) {
286                    log.error("Exception doing write strobe for read", e);
287                }
288                break;
289
290            case DOSIFORWRITE:
291                if (valueSI != -1) {
292                    // writing SI index after PI
293                    try {
294                        state = ProgState.DOWRITEFIRST;
295                        prog.writeCV(indexSI, valueSI, this);
296                    } catch (jmri.ProgrammerException e) {
297                        log.error("Exception doing write SI for write", e);
298                    }
299                } else {
300                    // writing data after PI
301                    try {
302                        state = ProgState.DOWRITEFIRST;
303                        prog.writeCV(indexSI, _val, this);
304                    } catch (jmri.ProgrammerException e) {
305                        log.error("Exception doing write SI for write", e);
306                    }
307                }
308                break;
309            case DOWRITEFIRST:
310                if (valueSI != -1) {
311                    // write upper data
312                    try {
313                        state = ProgState.FINISHWRITE;
314                        prog.writeCV(valMSB, _val / 256, this);
315                    } catch (jmri.ProgrammerException e) {
316                        log.error("Exception doing write MSB for write", e);
317                    }
318                } else {
319                    // write 2nd index
320                    try {
321                        state = ProgState.FINISHWRITE;
322                        prog.writeCV(valMSB, valueMSB, this);
323                    } catch (jmri.ProgrammerException e) {
324                        log.error("Exception doing write MSB for write", e);
325                    }
326                }
327                break;
328            case FINISHWRITE:
329                if (valueSI != -1) {
330                    try {
331                        state = ProgState.PROGRAMMING;
332                        prog.writeCV(valLSB, _val & 255, this);
333                    } catch (jmri.ProgrammerException e) {
334                        log.error("Exception doing final write", e);
335                    }
336                } else {
337                    try {
338                        state = ProgState.PROGRAMMING;
339                        prog.writeCV(valLSB, valueLSB, this);
340                    } catch (jmri.ProgrammerException e) {
341                        log.error("Exception doing final write", e);
342                    }
343                }
344                break;
345
346            case PROGRAMMING:
347                // the programmingOpReply handler might send an immediate reply, so
348                // clear the current listener _first_
349                jmri.ProgListener temp = _usingProgrammer;
350                _usingProgrammer = null; // done
351                state = ProgState.NOTPROGRAMMING;
352                temp.programmingOpReply(upperByte * 256 + value, status);
353                break;
354
355            default:
356                log.error("Unexpected state on reply: {}", state);
357                // clean up as much as possible
358                _usingProgrammer = null;
359                state = ProgState.NOTPROGRAMMING;
360                break;
361        }
362    }
363
364    private final static Logger log = LoggerFactory.getLogger(TwoIndexTcsProgrammerFacade.class);
365
366}