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