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}