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}