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}