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