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    /** {@inheritDoc}
190     * Note this assumes that there's only one phase to the operation
191     */
192    @Override
193    synchronized public void programmingOpReply(int value, int status) {
194        if (log.isDebugEnabled()) {
195            log.debug("notifyProgListenerEnd value {} status {}", value, status);
196        }
197
198        if (_usingProgrammer == null) {
199            log.error("No listener to notify, reset and ignore");
200            state = ProgState.NOTPROGRAMMING;
201            return;
202        }
203        
204        // Complete processing later so that WOWDecoder will go through a complete power on reset and not brown out between CV read/writes
205        int interval = 150;
206        ActionListener taskPerformer = new ActionListener() {
207            final int myValue = value;
208            final int myStatus = status;
209            @Override
210            public void actionPerformed(ActionEvent evt) {
211                processProgrammingOpReply(myValue, myStatus);
212            }
213        };
214        Timer t = new Timer(interval, taskPerformer );
215        t.setRepeats(false);
216        t.start();
217    }
218    
219    // After a Swing delay, this processes the reply
220    protected void processProgrammingOpReply(int value, int status) {
221        if (status != OK ) {
222            // pass abort up
223            log.debug("Reset and pass abort up");
224            jmri.ProgListener temp = _usingProgrammer;
225            _usingProgrammer = null; // done
226            state = ProgState.NOTPROGRAMMING;
227            temp.programmingOpReply(value, status);
228            return;
229        }
230
231        switch (state) {
232            case DOSIFORREAD:
233                try {
234                    state = ProgState.DOSTROBEFORREAD;
235                    prog.writeCV(indexSI, valueSI, this);
236                } catch (jmri.ProgrammerException e) {
237                    log.error("Exception doing write SI for read", e);
238                }
239                break;
240            case DOSTROBEFORREAD:
241                try {
242                    state = ProgState.DOREADFIRST;
243                    prog.writeCV(readStrobe, readOffset, this);
244                } catch (jmri.ProgrammerException e) {
245                    log.error("Exception doing write strobe for read", e);
246                }
247                break;
248            case DOREADFIRST:
249                try {
250                    state = ProgState.FINISHREAD;
251                    prog.readCV(valMSB, this, _startMSB);
252                } catch (jmri.ProgrammerException e) {
253                    log.error("Exception doing read first", e);
254                }
255                break;
256            case FINISHREAD:
257                try {
258                    state = ProgState.PROGRAMMING;
259                    if (valuePI != -1 && valueSI == -1) {
260                        upperByte = 0;
261                        prog.readCV(indexSI, this, _startVal);
262                    } else {
263                        upperByte = value;
264                        prog.readCV(valLSB, this, _startLSB);
265                    }
266                } catch (jmri.ProgrammerException e) {
267                    log.error("Exception doing final read", e);
268                }
269                break;
270
271            case DOMSBFORREAD:
272                try {
273                    state = ProgState.DOLSBFORREAD;
274                    prog.writeCV(valMSB, valueMSB, this);
275                } catch (jmri.ProgrammerException e) {
276                    log.error("Exception doing write strobe for read", e);
277                }
278                break;
279            case DOLSBFORREAD:
280                try {
281                    state = ProgState.FINISHREAD;
282                    prog.writeCV(valLSB, valueLSB, this);
283                } catch (jmri.ProgrammerException e) {
284                    log.error("Exception doing write strobe for read", e);
285                }
286                break;
287
288            case DOSIFORWRITE:
289                if (valueSI != -1) {
290                    // writing SI index after PI
291                    try {
292                        state = ProgState.DOWRITEFIRST;
293                        prog.writeCV(indexSI, valueSI, this);
294                    } catch (jmri.ProgrammerException e) {
295                        log.error("Exception doing write SI for write", e);
296                    }
297                } else {
298                    // writing data after PI
299                    try {
300                        state = ProgState.DOWRITEFIRST;
301                        prog.writeCV(indexSI, _val, this);
302                    } catch (jmri.ProgrammerException e) {
303                        log.error("Exception doing write SI for write", e);
304                    }
305                }
306                break;
307            case DOWRITEFIRST:
308                if (valueSI != -1) {
309                    // write upper data
310                    try {
311                        state = ProgState.FINISHWRITE;
312                        prog.writeCV(valMSB, _val / 256, this);
313                    } catch (jmri.ProgrammerException e) {
314                        log.error("Exception doing write MSB for write", e);
315                    }
316                } else {
317                    // write 2nd index
318                    try {
319                        state = ProgState.FINISHWRITE;
320                        prog.writeCV(valMSB, valueMSB, this);
321                    } catch (jmri.ProgrammerException e) {
322                        log.error("Exception doing write MSB for write", e);
323                    }
324                }
325                break;
326            case FINISHWRITE:
327                if (valueSI != -1) {
328                    try {
329                        state = ProgState.PROGRAMMING;
330                        prog.writeCV(valLSB, _val & 255, this);
331                    } catch (jmri.ProgrammerException e) {
332                        log.error("Exception doing final write", e);
333                    }
334                } else {
335                    try {
336                        state = ProgState.PROGRAMMING;
337                        prog.writeCV(valLSB, valueLSB, this);
338                    } catch (jmri.ProgrammerException e) {
339                        log.error("Exception doing final write", e);
340                    }
341                }
342                break;
343
344            case PROGRAMMING:
345                // the programmingOpReply handler might send an immediate reply, so
346                // clear the current listener _first_
347                jmri.ProgListener temp = _usingProgrammer;
348                _usingProgrammer = null; // done
349                state = ProgState.NOTPROGRAMMING;
350                temp.programmingOpReply(upperByte * 256 + value, status);
351                break;
352
353            default:
354                log.error("Unexpected state on reply: {}", state);
355                // clean up as much as possible
356                _usingProgrammer = null;
357                state = ProgState.NOTPROGRAMMING;
358                break;
359        }
360    }
361
362    private final static Logger log = LoggerFactory.getLogger(TwoIndexTcsProgrammerFacade.class);
363
364}