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        read(status,0,0,0);
290    }
291    public void read(JLabel status, long number, long total, long cvReadStartTime) {
292        log.debug("read call with Cv number {} and programmer {}", _num, mProgrammer); // NOI18N
293
294        setToRead(false);
295        // get a programmer reference and write
296        _status = status;
297
298        if (status != null) {
299            if (total == 0) {
300                status.setText(
301                        java.text.MessageFormat.format(
302                                Bundle.getMessage("StateReadingCV"),
303                                new Object[]{"" + _num}));
304
305            } else {
306                long remaining = ((System.currentTimeMillis() - cvReadStartTime) / number * (total - number)) / 1000;
307                String units = "seconds";
308                if (remaining > 60) {
309                    remaining = remaining / 60;
310                    units = "minutes";
311                }
312                status.setText(
313                        java.text.MessageFormat.format(
314                                Bundle.getMessage("StateReadingCVincltime"),
315                                new Object[]{"" + _num, number, total, remaining, units}));
316            }
317        }
318
319        if (mProgrammer != null) {
320            setBusy(true);
321            _reading = true;
322            _confirm = false;
323            try {
324                mProgrammer.readCV(_num, this, this.getValue());
325            } catch (Exception e) {
326                if (status != null) {
327                    status.setText(
328                            java.text.MessageFormat.format(
329                                    Bundle.getMessage("StateExceptionDuringRead"),
330                                    new Object[]{e.toString()}));
331                }
332
333                log.warn("Exception during CV read", e); // NOI18N
334                setBusy(false);
335            }
336        } else {
337            if (status != null) {
338                status.setText(Bundle.getMessage("StateNoProgrammer"));
339            }
340            log.error("No programmer available!"); // NOI18N
341        }
342    }
343
344    public void confirm(JLabel status) {
345        log.debug("confirm call with Cv number {}", _num); // NOI18N
346
347        // get a programmer reference and write
348        _status = status;
349
350        if (status != null) {
351            status.setText(
352                    java.text.MessageFormat.format(
353                            Bundle.getMessage("StateConfirmingCV"),
354                            new Object[]{"" + _num}));
355        }
356
357        if (mProgrammer != null) {
358            setBusy(true);
359            _reading = false;
360            _confirm = true;
361            try {
362                mProgrammer.confirmCV(_num, _value, this);
363            } catch (Exception e) {
364                if (status != null) {
365                    status.setText(
366                            java.text.MessageFormat.format(
367                                    Bundle.getMessage("StateExceptionDuringConfirm"),
368                                    new Object[]{e.toString()}));
369                }
370                log.warn("Exception during CV confirm", e); // NOI18N
371                setBusy(false);
372            }
373        } else {
374            if (status != null) {
375                status.setText(Bundle.getMessage("StateNoProgrammer"));
376            }
377            log.error("No programmer available!"); // NOI18N
378        }
379    }
380
381    public void write(JLabel status) {
382        log.debug("write call with Cv number {}", _num); // NOI18N
383
384        setToWrite(false);
385        // get a programmer reference and write
386        _status = status;
387
388        if (status != null) {
389            status.setText(
390                    java.text.MessageFormat.format(
391                            Bundle.getMessage("StateWritingCV"),
392                            new Object[]{"" + _num}));
393        }
394        if (mProgrammer != null) {
395            setBusy(true);
396            _reading = false;
397            _confirm = false;
398            try {
399                setState(ValueState.UNKNOWN);
400                mProgrammer.writeCV(_num, _value, this);
401            } catch (Exception e) {
402                setState(ValueState.UNKNOWN);
403                if (status != null) {
404                    status.setText(
405                            java.text.MessageFormat.format(
406                                    Bundle.getMessage("StateExceptionDuringWrite"),
407                                    new Object[]{e.toString()}));
408                }
409                log.warn("Exception during write CV '{}' to '{}'", _num, _value, e); // NOI18N
410                setBusy(false);
411            }
412        } else {
413            if (status != null) {
414                status.setText(Bundle.getMessage("StateNoProgrammer"));
415            }
416            log.error("No programmer available!"); // NOI18N
417        }
418    }
419
420    @Override
421    public void programmingOpReply(int value, int retval) {
422        if (log.isDebugEnabled()) {
423            log.debug("CV progOpReply for CV {} with retval {} during {}", _num, retval, _reading ? "read sequence" : (_confirm ? "confirm sequence" : "write sequence"));  // NOI18N
424        }
425        if (!_busy) {
426            log.error("opReply when not busy!"); // NOI18N
427        }
428        boolean oldBusy = _busy;
429        if (retval == OK) {
430            if (_status != null) {
431                _status.setText(Bundle.getMessage("StateOK"));
432            }
433            if (_reading) {
434                // set & notify value directly to avoid state going to EDITED
435                _value = value;
436                _tableEntry.setText(Integer.toString(value));
437                notifyValueChange(value);
438                setState(ValueState.READ);
439                log.debug("CV setting not busy on end read"); // NOI18N
440                _busy = false;
441                notifyBusyChange(oldBusy, _busy);
442            } else if (_confirm) {
443                // _value doesn't change, just the state, and save the value read
444                _decoderValue = value;
445                // does the decoder value match the file value
446                if (value == _value) {
447                    setState(ValueState.SAME);
448                } else {
449                    setState(ValueState.DIFFERENT);
450                }
451                _busy = false;
452                notifyBusyChange(oldBusy, _busy);
453            } else {  // writing
454                setState(ValueState.STORED);
455                _busy = false;
456                notifyBusyChange(oldBusy, _busy);
457            }
458        } else {
459            if (_status != null) {
460                _status.setText(
461                        java.text.MessageFormat.format(
462                                Bundle.getMessage("StateProgrammerError"),
463                                new Object[]{mProgrammer.decodeErrorCode(retval)}));
464            }
465
466            // delay setting not Busy to ensure that the message appears to the user
467            javax.swing.Timer timer = new javax.swing.Timer(1000, new java.awt.event.ActionListener() {
468                @Override
469                public void actionPerformed(java.awt.event.ActionEvent e) {
470                    errorTimeout();
471                }
472            });
473            timer.setInitialDelay(1000);
474            timer.setRepeats(false);
475            timer.start();
476        }
477
478        log.debug("CV progOpReply end of handling CV {}", _num); // NOI18N
479    }
480
481    void errorTimeout() {
482        setState(ValueState.UNKNOWN);
483        log.debug("CV setting not busy on error reply"); // NOI18N
484        _busy = false;
485        notifyBusyChange(true, _busy);
486    }
487
488    // clean up connections when done
489    public void dispose() {
490        log.debug("dispose"); // NOI18N
491    }
492
493    // initialize logging
494    private static final Logger log = LoggerFactory.getLogger(CvValue.class);
495
496}