001package jmri.jmrit.symbolicprog;
002
003import java.awt.Color;
004import javax.swing.JLabel;
005import javax.swing.JTextField;
006import jmri.ProgListener;
007import jmri.Programmer;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * Encapsulate a single CV value and provide programming access to the decoder.
013 * <p>
014 * Since this is a single CV in a single decoder, the Programmer used to get
015 * access is part of the state. This allows us to specify a specific ops-mode
016 * programmer aimed at a particular decoder.
017 * <p>
018 * There are three relevant parameters: Busy, Value, State. Busy == true means
019 * that a read or write operation is going on. When it transitions to "false",
020 * the operation is complete, and the Value and State are stable. During a read
021 * operation, Value changes before State, so you can assume that Value is stable
022 * if notified of a State change.
023 *
024 * @author Bob Jacobsen Copyright (C) 2001, 2003, 2004, 2013
025 * @author Howard G. Penny Copyright (C) 2005
026 * @author Andrew Crosland (C) 2021
027 */
028public class CvValue extends AbstractValue implements ProgListener {
029
030    public CvValue(String num, Programmer pProgrammer) {
031        _num = num;
032        mProgrammer = pProgrammer;
033        _tableEntry = new JTextField("0", 3);
034        _defaultColor = _tableEntry.getBackground();
035        _tableEntry.setBackground(ValueState.UNKNOWN.getColor());
036    }
037
038    public CvValue(String num, String cvName, Programmer pProgrammer) {
039        _num = num;
040        _cvName = cvName;
041        if (cvName == null) {
042            log.error("cvName == null in ctor num: {}", num); // NOI18N
043        }
044        mProgrammer = pProgrammer;
045        _tableEntry = new JTextField("0", 3);
046        _defaultColor = _tableEntry.getBackground();
047        _tableEntry.setBackground(ValueState.UNKNOWN.getColor());
048    }
049
050    @Override
051    public String toString() {
052        return "CvValue _num=" + _num + " _cvName=" + _cvName;
053    }
054
055    void setProgrammer(Programmer p) {
056        mProgrammer = p;
057    }
058
059    public String number() {
060        return _num;
061    }
062    private final String _num;
063
064    public String cvName() {
065        return _cvName;
066    }
067    private String _cvName = "";
068
069    private JLabel _status = null;
070
071    private Programmer mProgrammer;
072
073    public int getValue() {
074        return _value;
075    }
076
077    Color getDefaultColor() {
078        return _defaultColor;
079    }
080
081    Color getColor() {
082        return _tableEntry.getBackground();
083    }
084
085    protected void notifyValueChange(int value) {
086        prop.firePropertyChange("Value", null, value);
087    }
088
089
090    /**
091     * Edit a new value into the CV. Fires listeners
092     * <p>
093     * Only use this for external edits, e.g. set from a GUI.
094     * Not for internal uses, as it sets the state to EDITED.
095     * @param value new CV value.
096     */
097    public void setValue(int value) {
098        log.debug("CV {} value changed from {} to {}", number(), _value, value); // NOI18N
099
100        setState(ValueState.EDITED);
101        if (_value != value) {
102            _value = value;
103            _tableEntry.setText("" + value);
104            notifyValueChange(value);
105        }
106    }
107    private int _value = 0;
108
109    /**
110     * Get the decoder value read during compare.
111     *
112     * @return _decoderValue
113     */
114    public int getDecoderValue() {
115        return _decoderValue;
116    }
117
118    private int _decoderValue = 0;
119
120    public ValueState getState() {
121        return _state;
122    }
123
124    /**
125     * Set state value and send notification.Also sets GUI color as needed.
126     * @param state new state, e.g READ, UNKNOWN, SAME.
127     */
128    public void setState(ValueState state) {
129        if (log.isDebugEnabled()) {  // stateToString overhead
130            log.debug("cv {} set state from {} to {}", number(), _state.name(), state.name()); // NOI18N
131        }
132        ValueState oldstate = _state;
133        _state = state;
134        setColor(state.getColor());
135        if (oldstate != state) {
136            prop.firePropertyChange("State", oldstate, state);
137        }
138    }
139
140    private ValueState _state = ValueState.UNKNOWN;
141
142    // read, write operations
143    public boolean isBusy() {
144        return _busy;
145    }
146
147    /**
148     * Set the busy state and send notification. Should be used _only_ if this
149     * is the only thing changing.
150     */
151    private void setBusy(boolean busy) {
152        log.debug("setBusy from {} to {} state {}", _busy, busy, _state); // NOI18N
153
154        boolean oldBusy = _busy;
155        _busy = busy;
156        notifyBusyChange(oldBusy, busy);
157    }
158
159    /**
160     * Notify of changes to the busy state.
161     */
162    private void notifyBusyChange(boolean oldBusy, boolean newBusy) {
163        log.debug("notifyBusyChange from {} to {} current state {}", oldBusy, newBusy, _state); // NOI18N
164
165        if (oldBusy != newBusy) {
166            prop.firePropertyChange("Busy",
167                    oldBusy ? Boolean.TRUE : Boolean.FALSE,
168                    newBusy ? Boolean.TRUE : Boolean.FALSE);
169        }
170    }
171    private boolean _busy = false;
172
173    // color management
174    Color _defaultColor;
175
176    @Override
177    void setColor(Color c) {
178        if (c != null) {
179            _tableEntry.setBackground(c);
180        } else {
181            _tableEntry.setBackground(_defaultColor);
182        }
183        //prop.firePropertyChange("Value", null, null);
184    }
185
186    // object for Table entry
187    JTextField _tableEntry = null;
188
189    JTextField getTableEntry() {
190        return _tableEntry;
191    }
192
193    /**
194     * Set bean keeping track of whether this CV is intended to be read-only.
195     * Does not otherwise affect behaviour! Default is "false".
196     * @param is read-only flag, true or false.
197     */
198    public void setReadOnly(boolean is) {
199        _readOnly = is;
200    }
201
202    private boolean _readOnly = false;
203
204    /**
205     * Retrieve bean keeping track of whether this CV is intended to be
206     * read-only. Does not otherwise affect behaviour! Default is "false".
207     * @return read-only flag.
208     */
209    public boolean getReadOnly() {
210        return _readOnly;
211    }
212
213    /**
214     * Set bean keeping track of whether this CV is intended to be used as
215     * info-only. Does not otherwise affect behaviour! Default is "false".
216     * @param is info-only flag, true or false.
217     */
218    public void setInfoOnly(boolean is) {
219        _infoOnly = is;
220    }
221
222    private boolean _infoOnly = false;
223
224    /**
225     * Retrieve bean keeping track of whether this CV is intended to be used as
226     * info-only. Does not otherwise affect behaviour! Default is "false".
227     * @return write-only flag.
228     */
229    public boolean getInfoOnly() {
230        return _infoOnly;
231    }
232
233    /**
234     * Set bean keeping track of whether this CV is intended to be used as
235     * write-only. Does not otherwise affect behaviour! Default is "false".
236     * @param is write-only flag, true or false.
237     */
238    public void setWriteOnly(boolean is) {
239        _writeOnly = is;
240    }
241
242    private boolean _writeOnly = false;
243
244    /**
245     * Retrieve bean keeping track of whether this CV is intended to be used as
246     * write-only.
247     * <p>
248     * Does not otherwise affect behaviour!
249     * Default is "false".
250     * @return write-only flag.
251     */
252    public boolean getWriteOnly() {
253        return _writeOnly;
254    }
255
256    @Override
257    public void setToRead(boolean state) {
258        if (getInfoOnly() || getWriteOnly()) {
259            state = false;
260        }
261        _toRead = state;
262    }
263
264    @Override
265    public boolean isToRead() {
266        return _toRead;
267    }
268    private boolean _toRead = false;
269
270    @Override
271    public void setToWrite(boolean state) {
272        if (getInfoOnly() || getReadOnly()) {
273            state = false;
274        }
275        _toWrite = state;
276    }
277
278    @Override
279    public boolean isToWrite() {
280        return _toWrite;
281    }
282    private boolean _toWrite = false;
283
284    // read, write support
285    private boolean _reading = false;
286    private boolean _confirm = false;
287
288    public void read(JLabel status) {
289        log.debug("read call with Cv number {} and programmer {}", _num, mProgrammer); // NOI18N
290
291        setToRead(false);
292        // get a programmer reference and write
293        _status = status;
294
295        if (status != null) {
296            status.setText(
297                    java.text.MessageFormat.format(
298                            Bundle.getMessage("StateReadingCV"),
299                            new Object[]{"" + _num}));
300        }
301
302        if (mProgrammer != null) {
303            setBusy(true);
304            _reading = true;
305            _confirm = false;
306            try {
307                //mProgrammer.readCV(_num, this);
308                mProgrammer.readCV(_num, this, this.getValue());
309            } catch (Exception e) {
310                if (status != null) {
311                    status.setText(
312                            java.text.MessageFormat.format(
313                                    Bundle.getMessage("StateExceptionDuringRead"),
314                                    new Object[]{e.toString()}));
315                }
316
317                log.warn("Exception during CV read", e); // NOI18N
318                setBusy(false);
319            }
320        } else {
321            if (status != null) {
322                status.setText(Bundle.getMessage("StateNoProgrammer"));
323            }
324            log.error("No programmer available!"); // NOI18N
325        }
326    }
327
328    public void confirm(JLabel status) {
329        log.debug("confirm call with Cv number {}", _num); // NOI18N
330
331        // get a programmer reference and write
332        _status = status;
333
334        if (status != null) {
335            status.setText(
336                    java.text.MessageFormat.format(
337                            Bundle.getMessage("StateConfirmingCV"),
338                            new Object[]{"" + _num}));
339        }
340
341        if (mProgrammer != null) {
342            setBusy(true);
343            _reading = false;
344            _confirm = true;
345            try {
346                mProgrammer.confirmCV(_num, _value, this);
347            } catch (Exception e) {
348                if (status != null) {
349                    status.setText(
350                            java.text.MessageFormat.format(
351                                    Bundle.getMessage("StateExceptionDuringConfirm"),
352                                    new Object[]{e.toString()}));
353                }
354                log.warn("Exception during CV confirm", e); // NOI18N
355                setBusy(false);
356            }
357        } else {
358            if (status != null) {
359                status.setText(Bundle.getMessage("StateNoProgrammer"));
360            }
361            log.error("No programmer available!"); // NOI18N
362        }
363    }
364
365    public void write(JLabel status) {
366        log.debug("write call with Cv number {}", _num); // NOI18N
367
368        setToWrite(false);
369        // get a programmer reference and write
370        _status = status;
371
372        if (status != null) {
373            status.setText(
374                    java.text.MessageFormat.format(
375                            Bundle.getMessage("StateWritingCV"),
376                            new Object[]{"" + _num}));
377        }
378        if (mProgrammer != null) {
379            setBusy(true);
380            _reading = false;
381            _confirm = false;
382            try {
383                setState(ValueState.UNKNOWN);
384                mProgrammer.writeCV(_num, _value, this);
385            } catch (Exception e) {
386                setState(ValueState.UNKNOWN);
387                if (status != null) {
388                    status.setText(
389                            java.text.MessageFormat.format(
390                                    Bundle.getMessage("StateExceptionDuringWrite"),
391                                    new Object[]{e.toString()}));
392                }
393                log.warn("Exception during write CV '{}' to '{}'", _num, _value, e); // NOI18N
394                setBusy(false);
395            }
396        } else {
397            if (status != null) {
398                status.setText(Bundle.getMessage("StateNoProgrammer"));
399            }
400            log.error("No programmer available!"); // NOI18N
401        }
402    }
403
404    @Override
405    public void programmingOpReply(int value, int retval) {
406        if (log.isDebugEnabled()) {
407            log.debug("CV progOpReply for CV {} with retval {} during {}", _num, retval, _reading ? "read sequence" : (_confirm ? "confirm sequence" : "write sequence"));  // NOI18N
408        }
409        if (!_busy) {
410            log.error("opReply when not busy!"); // NOI18N
411        }
412        boolean oldBusy = _busy;
413        if (retval == OK) {
414            if (_status != null) {
415                _status.setText(Bundle.getMessage("StateOK"));
416            }
417            if (_reading) {
418                // set & notify value directly to avoid state going to EDITED
419                _value = value;
420                _tableEntry.setText(Integer.toString(value));
421                notifyValueChange(value);
422                setState(ValueState.READ);
423                log.debug("CV setting not busy on end read"); // NOI18N
424                _busy = false;
425                notifyBusyChange(oldBusy, _busy);
426            } else if (_confirm) {
427                // _value doesn't change, just the state, and save the value read
428                _decoderValue = value;
429                // does the decoder value match the file value
430                if (value == _value) {
431                    setState(ValueState.SAME);
432                } else {
433                    setState(ValueState.DIFFERENT);
434                }
435                _busy = false;
436                notifyBusyChange(oldBusy, _busy);
437            } else {  // writing
438                setState(ValueState.STORED);
439                _busy = false;
440                notifyBusyChange(oldBusy, _busy);
441            }
442        } else {
443            if (_status != null) {
444                _status.setText(
445                        java.text.MessageFormat.format(
446                                Bundle.getMessage("StateProgrammerError"),
447                                new Object[]{mProgrammer.decodeErrorCode(retval)}));
448            }
449
450            // delay setting not Busy to ensure that the message appears to the user
451            javax.swing.Timer timer = new javax.swing.Timer(1000, new java.awt.event.ActionListener() {
452                @Override
453                public void actionPerformed(java.awt.event.ActionEvent e) {
454                    errorTimeout();
455                }
456            });
457            timer.setInitialDelay(1000);
458            timer.setRepeats(false);
459            timer.start();
460        }
461
462        log.debug("CV progOpReply end of handling CV {}", _num); // NOI18N
463    }
464
465    void errorTimeout() {
466        setState(ValueState.UNKNOWN);
467        log.debug("CV setting not busy on error reply"); // NOI18N
468        _busy = false;
469        notifyBusyChange(true, _busy);
470    }
471
472    // clean up connections when done
473    public void dispose() {
474        log.debug("dispose"); // NOI18N
475    }
476
477    // initialize logging
478    private final static Logger log = LoggerFactory.getLogger(CvValue.class);
479
480}