001package jmri.implementation;
002
003import javax.annotation.Nonnull;
004import jmri.ProgListener;
005import jmri.Programmer;
006import jmri.jmrix.AbstractProgrammerFacade;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * Programmer facade which verifies each write via a read, if possible.
012 * <p>
013 * If the underlying programmer (1) can read and (2) is not already doing a read verify, 
014 * each write operation is followed by a readback.
015 * If the value doesn't match, an error is signaled.
016 * <p>
017 * State Diagram for read and write operations  (click to magnify):
018 * <a href="doc-files/VerifyWriteProgrammerFacade-State-Diagram.png"><img src="doc-files/VerifyWriteProgrammerFacade-State-Diagram.png" alt="UML State diagram" height="50%" width="50%"></a>
019 *
020 * @see jmri.implementation.ProgrammerFacadeSelector
021 *
022 * @author Bob Jacobsen Copyright (C) 2017
023 */
024 
025 /*
026 * @startuml jmri/implementation/doc-files/VerifyWriteProgrammerFacade-State-Diagram.png
027 * [*] --> NOTPROGRAMMING 
028 * NOTPROGRAMMING --> READING: readCV()\n(read CV)
029 * READING --> NOTPROGRAMMING: OK reply received\n(return status and value)
030 * NOTPROGRAMMING --> FINISHWRITE: writeCV()\n(write CV)
031 * FINISHWRITE --> FINISHREAD: OK reply & getCanRead()\n(read CV)
032 * FINISHWRITE --> NOTPROGRAMMING: OK reply received && !getCanRead()\n(return OK status and value)
033 * FINISHREAD --> NOTPROGRAMMING: OK reply & value matches\n(return OK status reply and value)
034 * FINISHREAD --> NOTPROGRAMMING: OK reply & value not match\n(return error status reply and value)
035 * READING --> NOTPROGRAMMING : Error reply received
036 * FINISHWRITE --> NOTPROGRAMMING : Error reply received
037 * FINISHREAD --> NOTPROGRAMMING : Error reply received
038 * @enduml
039*/
040
041
042public class VerifyWriteProgrammerFacade extends AbstractProgrammerFacade implements ProgListener {
043
044    /**
045     * @param prog    the programmer to which this facade is attached
046     */
047    public VerifyWriteProgrammerFacade(Programmer prog) {
048        super(prog);
049    }
050
051    // members for handling the programmer interface
052    int _val;   // remember the value being read/written for confirmative reply
053    String _cv; // remember the cv number being read/written
054    
055    // programming interface
056    @Override
057    synchronized public void writeCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
058        _val = val;
059        _cv = CV;
060        useProgrammer(p);
061
062        // write value first
063        state = ProgState.FINISHWRITE;
064        prog.writeCV(CV, val, this);
065    }
066
067    @Override
068    synchronized public void readCV(String CV, jmri.ProgListener p) throws jmri.ProgrammerException {
069        _cv = CV;
070        useProgrammer(p);
071
072        state = ProgState.READING;
073        prog.readCV(CV, this);
074    }
075
076    /**
077     * This facade ensures that {@link jmri.Programmer.WriteConfirmMode#ReadAfterWrite}
078     * is done, so long as it has permission to read the CV after writing.
079     */
080    @Override
081    @Nonnull
082    public WriteConfirmMode getWriteConfirmMode(String addr) {
083        if ( prog.getCanRead(addr) ) {
084            return WriteConfirmMode.ReadAfterWrite;
085        } else {
086             return prog.getWriteConfirmMode(addr);
087        }
088    }
089
090    private jmri.ProgListener _usingProgrammer = null;
091
092    // internal method to remember who's using the programmer
093    protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
094        // test for only one!
095        if (_usingProgrammer != null && _usingProgrammer != p) {
096            log.info("programmer already in use by {}", _usingProgrammer);
097            throw new jmri.ProgrammerException("programmer in use");
098        } else {
099            _usingProgrammer = p;
100        }
101    }
102
103    /**
104     * State machine for VerifyWriteProgrammerFacade  (click to magnify):
105     * <a href="doc-files/VerifyWriteProgrammerFacade-State-Diagram.png"><img src="doc-files/VerifyWriteProgrammerFacade-State-Diagram.png" alt="UML State diagram" height="50%" width="50%"></a>
106     */
107    enum ProgState {
108        /** Waiting for response to read, will end next */
109        READING, 
110        /** Waiting for response to write, issue verify read next */
111        FINISHWRITE,
112        /** Waiting for response to verify read, will end next */
113        FINISHREAD,
114        /** No current operation */
115        NOTPROGRAMMING
116    }
117    ProgState state = ProgState.NOTPROGRAMMING;
118
119    // Get notified of the result from the underlying programmer, and work
120    // through the state machine for needed requests
121    @Override
122    public void programmingOpReply(int value, int status) {
123        log.debug("notifyProgListenerEnd value {} status {} ", value, status);
124
125        if (status != OK ) {
126            // pass abort up
127            jmri.ProgListener temp = _usingProgrammer;
128            _usingProgrammer = null; // done
129            state = ProgState.NOTPROGRAMMING;
130            temp.programmingOpReply(value, status);
131            return;
132        }
133        
134        if (_usingProgrammer == null) {
135            log.error("No listener to notify, reset and ignore");
136            state = ProgState.NOTPROGRAMMING;
137            return;
138        }
139
140        jmri.ProgListener temp = _usingProgrammer;
141        
142        switch (state) {
143            case FINISHWRITE:
144                // write completed, can we do read, and is it not already being done?
145                if (prog.getCanRead(_cv) && ! prog.getWriteConfirmMode(_cv).equals(WriteConfirmMode.ReadAfterWrite) ) {
146                    state = ProgState.FINISHREAD;
147                    try {
148                        prog.readCV(_cv, this);
149                    } catch (jmri.ProgrammerException e) {
150                        // pass abort up
151                        _usingProgrammer = null; // done
152                        state = ProgState.NOTPROGRAMMING;
153                        temp.programmingOpReply(value, ProgListener.ConfirmFailed);
154                        return;         
155                    }
156                    break;
157                }
158                // can't read or it's already being done
159                // deliberately fall through to normal completion
160                //$FALL-THROUGH$
161            case READING: // done, forward the return code and data
162                // the programmingOpReply handler might send an immediate reply, so
163                // clear the current listener _first_
164                _usingProgrammer = null; // done
165                state = ProgState.NOTPROGRAMMING;
166                temp.programmingOpReply(value, status);
167                break;
168                
169            case FINISHREAD:
170                _usingProgrammer = null; // done
171                state = ProgState.NOTPROGRAMMING;
172                // check if we got it right
173                if (value == _val) {
174                    // ok, reply OK
175                    temp.programmingOpReply(value, status);
176                } else {
177                    // error, reply error
178                    temp.programmingOpReply(value, ProgListener.ConfirmFailed);
179                }
180                break;
181
182            default:
183                log.error("Unexpected state on reply: {}", state);
184                // clean up as much as possible
185                _usingProgrammer = null;
186                state = ProgState.NOTPROGRAMMING;
187                break;
188        }
189    }
190
191    private final static Logger log = LoggerFactory.getLogger(VerifyWriteProgrammerFacade.class);
192
193}