001package jmri.jmrix.nce.clockmon; 002 003import java.awt.*; 004import java.awt.event.ActionEvent; 005import java.beans.PropertyChangeEvent; 006import java.text.DecimalFormat; 007import java.util.ArrayList; 008import java.util.Date; 009 010import javax.swing.*; 011 012import org.slf4j.Logger; 013import org.slf4j.LoggerFactory; 014 015import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 016import jmri.*; 017import jmri.jmrix.nce.*; 018import jmri.util.swing.JmriJOptionPane; 019 020/** 021 * Frame displaying and programming a NCE clock monitor. 022 * <p> 023 * Some of the message formats used in this class are Copyright NCE Inc. and 024 * used with permission as part of the JMRI project. That permission does not 025 * extend to uses in other software products. If you wish to use this code, 026 * algorithm or these message formats outside of JMRI, please contact NCE Inc 027 * for separate permission. 028 * 029 * Notes: 030 * 031 * 1. the commands for time don't include seconds so I had to use memory write 032 * to sync nce clock. 033 * 034 * 2. I tried fiddling with the internal nce clock loop values, didn't work. 035 * 036 * 3. to sync nce to internal clock: A. set an alarm about 5 seconds before next 037 * minute B. read nce clock C. compute error and record last X errors for 038 * correction calc D. adjust nce clock as needed E. reset alarm after next 039 * internal minute ticks 040 * 041 * 4. to sync internal to nce clock A. every so often, read nce clock and 042 * compare to internal B. compute error and record last X errors for correction 043 * calc C. adjust internal clock rate factor as needed 044 * 045 * 5. The clock message only seems to go out to the throttles on the tic of the 046 * minute. 047 * 048 * 6. The nce clock must be left running, or it doesn't tic and 049 * therefore doesn't go out over the bus. 050 * 051 * @author Ken Cameron Copyright (C) 2007, 2023 052 * derived from loconet.clockmonframe by Bob Jacobson Copyright (C) 2003 053 */ 054public class ClockMonPanel extends jmri.jmrix.nce.swing.NcePanel implements NceListener { 055 056 public static final int CS_CLOCK_MEM_SIZE = 0x10; 057 public static final int CS_CLOCK_SCALE = 0x00; 058 public static final int CS_CLOCK_TICK = 0x01; 059 public static final int CS_CLOCK_SECONDS = 0x02; 060 public static final int CS_CLOCK_MINUTES = 0x03; 061 public static final int CS_CLOCK_HOURS = 0x04; 062 public static final int CS_CLOCK_AMPM = 0x05; 063 public static final int CS_CLOCK_1224 = 0x06; 064 public static final int CS_CLOCK_STATUS = 0x0D; 065 public static final int CMD_CLOCK_SET_TIME_SIZE = 0x03; 066 public static final int CMD_CLOCK_SET_PARAM_SIZE = 0x02; 067 public static final int CMD_CLOCK_SET_RUN_SIZE = 0x01; 068 public static final int CMD_CLOCK_SET_REPLY_SIZE = 0x01; 069 public static final int CMD_MEM_SET_REPLY_SIZE = 0x01; 070 public static final int MAX_ERROR_ARRAY = 4; 071 public static final double MIN_POLLING_INTERVAL = 1.0; 072 public static final double MAX_POLLING_INTERVAL = 120; 073 public static final int CLOCKRATIO_MIN = 0; 074 public static final int CLOCKRATIO_MAX = 15; 075 public static final double DEFAULT_POLLING_INTERVAL = 5; 076 public static final double TARGET_SYNC_DELAY = 55; 077 public static final int SYNCMODE_OFF = 0; //0 - clocks independent 078 public static final int SYNCMODE_NCE_MASTER = 1; //1 - NCE sets Internal 079 public static final int SYNCMODE_INTERNAL_MASTER = 2; //2 - Internal sets NCE 080 public static final int WAIT_CMD_EXECUTION = 1000; 081 private static final long MAX_SECONDS_IN_DAY = 24 * 3600; 082 private static final double ESTIMATED_NCE_RATE_FACTOR = 0.92; 083 DecimalFormat fiveDigits = new DecimalFormat("0.00000"); 084 DecimalFormat fourDigits = new DecimalFormat("0.0000"); 085 DecimalFormat threeDigits = new DecimalFormat("0.000"); 086 DecimalFormat twoDigits = new DecimalFormat("0.00"); 087 088 private int waiting = 0; 089 private int clockMode = SYNCMODE_OFF; 090 private boolean waitingForCmdRead = false; 091 private boolean waitingForCmdStop = false; 092 private boolean waitingForCmdStart = false; 093 private boolean waitingForCmdRatio = false; 094 private boolean waitingForCmdTime = false; 095 private boolean waitingForCmd1224 = false; 096 private boolean updateTimeFromRead = false; 097 private boolean updateRatioFromRead = false; 098 private boolean updateFormatFromRead = false; 099 private boolean updateStatusFromRead = false; 100 private NceReply lastClockReadPacket = null; 101 //private Date lastClockReadAtTime; 102 private int nceLastHour; 103 private int nceLastMinute; 104 private int nceLastSecond; 105 private int nceLastRatio; 106 private boolean nceLastAmPm; 107 private boolean nceLast1224; 108 private boolean nceLastRunning; 109 private double internalLastRatio; 110 private boolean internalLastRunning; 111 private double pollingInterval = DEFAULT_POLLING_INTERVAL; 112 private final ArrayList<Double> priorDiffs = new ArrayList<>(); 113 private final ArrayList<Double> priorOffsetErrors = new ArrayList<>(); 114 private final ArrayList<Double> priorCorrections = new ArrayList<>(); 115 private double syncInterval = TARGET_SYNC_DELAY; 116 private int internalSyncInitStateCounter = 0; 117 private int internalSyncRunStateCounter = 0; 118 private double ncePidGainPv = 0.04; 119 private double ncePidGainIv = 0.01; 120 private double ncePidGainDv = 0.005; 121 private final double intPidGainPv = 0.02; 122 private final double intPidGainIv = 0.001; 123 private final double intPidGainDv = 0.01; 124 125 private final double rateChgMinimum = 0.001; 126 127 private int nceSyncInitStateCounter = 0; // NCE master sync initialzation state machine 128 private int nceSyncRunStateCounter = 0; // NCE master sync runtime state machine 129 private int alarmDisplayStateCounter = 0; // manages the display update from the alarm 130 131 Timebase internalClock; 132 Timer timerDisplayUpdate = null; 133 Timer alarmSyncUpdate = null; 134 135 JTextField hours = new JTextField(" 00"); 136 JTextField minutes = new JTextField(" 00"); 137 JTextField seconds = new JTextField(" 00"); 138 139 JTextField rateNce = new JTextField(" 1"); 140 JTextField amPm = new JTextField(2); 141 JCheckBox twentyFour = new JCheckBox(Bundle.getMessage("CheckBox24HourFormat")); 142 JTextField status = new JTextField(10); 143 144 JRadioButton setSyncModeNceMaster = new JRadioButton(Bundle.getMessage("ClockModeNCE")); 145 JRadioButton setSyncModeInternalMaster = new JRadioButton(Bundle.getMessage("ClockModeInternal")); 146 JRadioButton setSyncModeOff = new JRadioButton(Bundle.getMessage("ClockModeIndependent")); 147 148 JTextField internalDisplayStatus = new JTextField(60); 149 150 JTextField nceDisplayStatus = new JTextField(60); 151 152 JTextField pollingSpeed = new JTextField(5); 153 154 JTextField ncePidGainP = new JTextField(7); 155 JTextField ncePidGainI = new JTextField(7); 156 JTextField ncePidGainD = new JTextField(7); 157 JTextField intPidGainP = new JTextField(7); 158 JTextField intPidGainI = new JTextField(7); 159 JTextField intPidGainD = new JTextField(7); 160 161 transient java.beans.PropertyChangeListener minuteChangeListener; 162 163 JButton setSyncButton = new JButton(Bundle.getMessage("SetSyncMode")); 164 JButton setClockButton = new JButton(Bundle.getMessage("SetHoursMinutes")); 165 JButton setRatioButton = new JButton(Bundle.getMessage("SetRatio")); 166 JButton set1224Button = new JButton(Bundle.getMessage("Set12/24Mode")); 167 JButton setStopNceButton = new JButton(Bundle.getMessage("StopNceClock")); 168 JButton setStartNceButton = new JButton(Bundle.getMessage("StartNceClock")); 169 JButton readButton = new JButton(Bundle.getMessage("ReadAll")); 170 JButton setPollingSpeedButton = new JButton(Bundle.getMessage("SetInterfaceUpdRate")); 171 JButton setPidButton = new JButton(Bundle.getMessage("SetPid")); 172 173 private NceTrafficController tc = null; 174 175 public ClockMonPanel() { 176 super(); 177 } 178 179 /** 180 * {@inheritDoc} 181 */ 182 @Override 183 public void initContext(Object context) { 184 if (context instanceof NceSystemConnectionMemo) { 185 try { 186 initComponents((NceSystemConnectionMemo) context); 187 } catch (Exception e) { 188 log.error("NceClockMon initContext failed"); // NOI18N 189 } 190 } 191 } 192 193 /** 194 * {@inheritDoc} 195 */ 196 @Override 197 public String getHelpTarget() { 198 return "package.jmri.jmrix.nce.clockmon.ClockMonFrame"; 199 } 200 201 /** 202 * {@inheritDoc} 203 */ 204 @Override 205 public String getTitle() { 206 StringBuilder x = new StringBuilder(); 207 if (memo != null) { 208 x.append(memo.getUserName()); 209 } else { 210 x.append("NCE_"); 211 } 212 x.append(": "); 213 x.append(Bundle.getMessage("TitleNceClockMonitor")); 214 return x.toString(); 215 } 216 217 /** 218 * The minimum frame size for font size 16 219 */ 220 @Override 221 public Dimension getMinimumDimension() { 222 return new Dimension(600, 600); 223 } 224 225 /** 226 * {@inheritDoc} 227 */ 228 @Override 229 public void initComponents(NceSystemConnectionMemo m) { 230 this.memo = m; 231 this.tc = m.getNceTrafficController(); 232 233 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 234 235 // Internal Clock Info Panel 236 JPanel panel = new JPanel(); 237 panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); 238 JPanel pane2 = new JPanel(); 239 GridBagLayout gLayout = new GridBagLayout(); 240 GridBagConstraints gConstraints = new GridBagConstraints(); 241 242 javax.swing.border.Border pane2Border = BorderFactory.createEtchedBorder(); 243 javax.swing.border.Border pane2Titled = BorderFactory.createTitledBorder(pane2Border, 244 Bundle.getMessage("InternalClockStatusBorderText")); 245 pane2.setBorder(pane2Titled); 246 pane2.add(internalDisplayStatus); 247 internalDisplayStatus.setEditable(false); 248 internalDisplayStatus.setBorder(BorderFactory.createEmptyBorder()); 249 add(pane2); 250 251 // NCE Clock Info Panel 252 pane2 = new JPanel(); 253 pane2Border = BorderFactory.createEtchedBorder(); 254 pane2Titled = BorderFactory.createTitledBorder(pane2Border, 255 Bundle.getMessage("NceClockStatusBorderText")); 256 pane2.setBorder(pane2Titled); 257 pane2.add(nceDisplayStatus); 258 nceDisplayStatus.setEditable(false); 259 nceDisplayStatus.setBorder(BorderFactory.createEmptyBorder()); 260 add(pane2); 261 262 // setting time items 263 pane2 = new JPanel(); 264 pane2Border = BorderFactory.createEtchedBorder(); 265 pane2Titled = BorderFactory.createTitledBorder(pane2Border, 266 Bundle.getMessage("SetClockValuesBorderText")); 267 pane2.setBorder(pane2Titled); 268 pane2.add(new JLabel(Bundle.getMessage("LabelTime"))); 269 pane2.add(hours); 270 hours.setToolTipText("0 - 23"); 271 pane2.add(new JLabel(Bundle.getMessage("LabelTimeSep"))); 272 pane2.add(minutes); 273 minutes.setToolTipText("0 - 59"); 274 pane2.add(new JLabel(Bundle.getMessage("LabelTimeSep"))); 275 pane2.add(seconds); 276 seconds.setToolTipText("0 - 59"); 277 seconds.setEditable(false); 278 pane2.add(new JLabel(" ")); 279 pane2.add(amPm); 280 amPm.setEditable(false); 281 pane2.add(new JLabel(" ")); 282 pane2.add(setClockButton); 283 add(pane2); 284 285 // set clock ratio items 286 pane2 = new JPanel(); 287 pane2Border = BorderFactory.createEtchedBorder(); 288 pane2Titled = BorderFactory.createTitledBorder(pane2Border, 289 Bundle.getMessage("SetClockRatioBorderText")); 290 pane2.setBorder(pane2Titled); 291 pane2.add(new JLabel(Bundle.getMessage("LabelRatio"))); 292 pane2.add(rateNce); 293 rateNce.setToolTipText(CLOCKRATIO_MIN + " - "+ CLOCKRATIO_MAX); 294 pane2.add(new JLabel(Bundle.getMessage("LabelToOne"))); 295 pane2.add(setRatioButton); 296 add(pane2); 297 298 // add 12/24 clock options 299 pane2 = new JPanel(); 300 pane2Border = BorderFactory.createEtchedBorder(); 301 pane2Titled = BorderFactory.createTitledBorder(pane2Border, 302 Bundle.getMessage("SetClock12/24ModeBorderText")); 303 pane2.setBorder(pane2Titled); 304 pane2.add(twentyFour); 305 pane2.add(new JLabel(" ")); 306 pane2.add(set1224Button); 307 add(pane2); 308 309 // pane2 = new JPanel(); 310 // pane2.setLayout(new BoxLayout(pane2, BoxLayout.X_AXIS)); 311 // pane2.add(new JLabel(" ")); 312 // pane2.add(status); 313 // add(pane2); 314 pane2 = new JPanel(); 315 pane2Border = BorderFactory.createEtchedBorder(); 316 pane2Titled = BorderFactory.createTitledBorder(pane2Border, 317 Bundle.getMessage("InterfaceCommandBorderText")); 318 pane2.setBorder(pane2Titled); 319 pane2.setLayout(gLayout); 320 gConstraints.gridx = 0; 321 gConstraints.gridy = 0; 322 gConstraints.gridwidth = 1; 323 gConstraints.gridheight = 1; 324 gConstraints.ipadx = 10; 325 gConstraints.ipady = 1; 326 gConstraints.insets = new Insets(1, 1, 1, 1); 327 pane2.add(setStartNceButton, gConstraints); 328 gConstraints.gridx++; 329 pane2.add(setStopNceButton, gConstraints); 330 gConstraints.gridx++; 331 pane2.add(readButton, gConstraints); 332 333 ButtonGroup modeGroup = new ButtonGroup(); 334 modeGroup.add(setSyncModeInternalMaster); 335 modeGroup.add(setSyncModeNceMaster); 336 modeGroup.add(setSyncModeOff); 337 338 gConstraints.gridx = 0; 339 gConstraints.gridy++; 340 gConstraints.gridwidth = 3; 341 pane2.add(setSyncModeNceMaster, gConstraints); 342 gConstraints.gridy++; 343 pane2.add(setSyncModeInternalMaster, gConstraints); 344 gConstraints.gridy++; 345 pane2.add(setSyncModeOff, gConstraints); 346 gConstraints.gridy++; 347 pane2.add(setSyncButton, gConstraints); 348 setSyncModeInternalMaster.setEnabled(true); 349 setSyncModeNceMaster.setEnabled(true); 350 if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) { // needs memory commands to sync 351 setSyncModeInternalMaster.setEnabled(false); 352 setSyncModeNceMaster.setEnabled(false); 353 } 354 add(pane2); 355 356 // add polling speed 357 pane2 = new JPanel(); 358 pane2Border = BorderFactory.createEtchedBorder(); 359 pane2Titled = BorderFactory.createTitledBorder(pane2Border, 360 Bundle.getMessage("InterfaceUpdRateBorderText")); 361 pane2.setBorder(pane2Titled); 362 pane2.add(new JLabel(Bundle.getMessage("InterfaceUpdRate"))); 363 pane2.add(new JLabel(" ")); 364 pane2.add(pollingSpeed); 365 pollingSpeed.setText("" + pollingInterval); 366 pollingSpeed.setToolTipText(MIN_POLLING_INTERVAL + " - " + MAX_POLLING_INTERVAL); 367 pane2.add(new JLabel(" ")); 368 pane2.add(new JLabel(Bundle.getMessage("InterfaceUpdRateSufix"))); 369 pane2.add(new JLabel(" ")); 370 pane2.add(setPollingSpeedButton); 371 add(pane2); 372 373// // add PID values 374// gLayout = new GridBagLayout(); 375// gConstraints = new GridBagConstraints(); 376// pane2 = new JPanel(); 377// pane2Border = BorderFactory.createEtchedBorder(); 378// pane2Titled = BorderFactory.createTitledBorder(pane2Border, 379// Bundle.getMessage("InterfacePidBorderText")); 380// pane2.setBorder(pane2Titled); 381// pane2.setLayout(gLayout); 382// gConstraints.gridx = 0; 383// gConstraints.gridy = 0; 384// gConstraints.gridwidth = 1; 385// gConstraints.gridheight = 1; 386// gConstraints.ipadx = 10; 387// gConstraints.ipady = 1; 388// gConstraints.insets = new Insets(3, 3, 3, 3); 389// pane2.add(new JLabel(Bundle.getMessage("InterfacePidNce")), gConstraints); 390// gConstraints.gridx++; 391// pane2.add(new JLabel(Bundle.getMessage("InterfacePidGainP")), gConstraints); 392// gConstraints.gridx++; 393// pane2.add(ncePidGainP, gConstraints); 394// gConstraints.gridx++; 395// pane2.add(new JLabel(Bundle.getMessage("InterfacePidGainI")), gConstraints); 396// gConstraints.gridx++; 397// pane2.add(ncePidGainI, gConstraints); 398// gConstraints.gridx++; 399// pane2.add(new JLabel(Bundle.getMessage("InterfacePidGainD")), gConstraints); 400// gConstraints.gridx++; 401// pane2.add(ncePidGainD, gConstraints); 402// gConstraints.gridx++; 403// gConstraints.gridheight = 2; 404// pane2.add(setPidButton, gConstraints); 405// gConstraints.gridheight = 0; 406// gConstraints.gridx = 0; 407// gConstraints.gridy = 1; 408// pane2.add(new JLabel(Bundle.getMessage("InterfacePidInt")), gConstraints); 409// gConstraints.gridx++; 410// pane2.add(new JLabel(Bundle.getMessage("InterfacePidGainP")), gConstraints); 411// gConstraints.gridx++; 412// pane2.add(intPidGainP, gConstraints); 413// gConstraints.gridx++; 414// pane2.add(new JLabel(Bundle.getMessage("InterfacePidGainI")), gConstraints); 415// gConstraints.gridx++; 416// pane2.add(intPidGainI, gConstraints); 417// gConstraints.gridx++; 418// pane2.add(new JLabel(Bundle.getMessage("InterfacePidGainD")), gConstraints); 419// gConstraints.gridx++; 420// pane2.add(intPidGainD, gConstraints); 421// ncePidGainP.setText(fiveDigits.format(ncePidGainPv)); 422// ncePidGainI.setText(fiveDigits.format(ncePidGainIv)); 423// ncePidGainD.setText(fiveDigits.format(ncePidGainDv)); 424// intPidGainP.setText(fiveDigits.format(intPidGainPv)); 425// intPidGainI.setText(fiveDigits.format(intPidGainIv)); 426// intPidGainD.setText(fiveDigits.format(intPidGainDv)); 427// add(pane2); 428 // install "read" button handler 429 readButton.addActionListener((ActionEvent a) -> { 430 issueReadAllRequest(); 431 }); 432 // install "set" button handler 433 setClockButton.addActionListener((ActionEvent a) -> { 434 issueClockSet(Integer.parseInt(hours.getText().trim()), 435 Integer.parseInt(minutes.getText().trim()), 436 Integer.parseInt(seconds.getText().trim()) 437 ); 438 }); 439 // install "stop" clock button handler 440 setStopNceButton.addActionListener((ActionEvent a) -> { 441 issueClockStop(); 442 }); 443 // install "start" clock button handler 444 setStartNceButton.addActionListener((ActionEvent a) -> { 445 issueClockStart(); 446 }); 447 // install set fast clock ratio 448 setRatioButton.addActionListener((ActionEvent a) -> { 449 changeNceClockRatio(); 450 }); 451 // install set 12/24 button 452 set1224Button.addActionListener((ActionEvent a) -> { 453 issueClock1224(twentyFour.isSelected()); 454 }); 455 // install Sync Change Clock button 456 setSyncButton.addActionListener((ActionEvent a) -> { 457 changeSyncMode(); 458 }); 459 460 // install "setPolling" button handler 461 setPollingSpeedButton.addActionListener((ActionEvent a) -> { 462 changePollingSpeed(Double.parseDouble(pollingSpeed.getText().trim())); 463 }); 464 465 // install "setPid" button handler 466 setPidButton.addActionListener((ActionEvent a) -> { 467 changePidValues(); 468 }); 469 470 if (clockMode == SYNCMODE_OFF) { 471 setSyncModeOff.setSelected(true); 472 } 473 if (clockMode == SYNCMODE_INTERNAL_MASTER) { 474 setSyncModeInternalMaster.setSelected(true); 475 } 476 if (clockMode == SYNCMODE_NCE_MASTER) { 477 setSyncModeNceMaster.setSelected(true); 478 } 479 this.setSize(400, 300); 480 481 // Create a timebase listener for the Minute change events 482 internalClock = InstanceManager.getNullableDefault(jmri.Timebase.class); 483 if (internalClock == null) { 484 log.error("No Timebase Instance; clock will not run"); // NOI18N 485 return; 486 } 487 minuteChangeListener = (PropertyChangeEvent e) -> { 488 newInternalMinute(); 489 }; 490 internalClock.addMinuteChangeListener(minuteChangeListener); 491 492 // start display alarm timer 493 alarmDisplayUpdateHandler(); 494 } 495 496 // ignore replies 497 @Override 498 public void message(NceMessage m) { 499 log.error("clockmon message received: {}", m); 500 } // NOI18N 501 502 @Override 503 public void reply(NceReply r) { 504 log.trace("nceReplyCatcher() waiting: {} watingForRead: {} waitingForCmdTime: {} waitingForCmd1224: {} waitingForCmdRatio: {} waitingForCmdStop: {} waitingForCmdStart: {}", 505 waiting, waitingForCmdRead, waitingForCmdTime, waitingForCmd1224, waitingForCmdRatio, waitingForCmdStop, waitingForCmdStart); 506 if (waiting <= 0) { 507 log.debug("unexpected response"); 508 return; 509 } 510 waiting--; 511 if (waitingForCmdRead && r.getNumDataElements() == CS_CLOCK_MEM_SIZE) { 512 readClockPacket(r); 513 waitingForCmdRead = false; 514 callStateMachines(); 515 return; 516 } 517 if (waitingForCmdTime) { 518 if (r.getNumDataElements() != CMD_CLOCK_SET_REPLY_SIZE) { 519 log.error("NCE clock command reply, invalid length:{}", r.getNumDataElements()); 520 return; 521 } else { 522 waitingForCmdTime = false; 523 if (r.getElement(0) != NceMessage.NCE_OKAY) { 524 log.error("NCE set clock replied: {}", r.getElement(0)); 525 } 526 callStateMachines(); 527 return; 528 } 529 } 530 if (r.getNumDataElements() != CMD_CLOCK_SET_REPLY_SIZE) { 531 log.error("NCE clock command reply, invalid length:{}", r.getNumDataElements()); 532 return; 533 } else { 534 if (waitingForCmd1224) { 535 waitingForCmd1224 = false; 536 if (r.getElement(0) != NceMessage.NCE_OKAY) { 537 log.error("NCE set clock 12/24 replied:{}", r.getElement(0)); 538 } 539 callStateMachines(); 540 return; 541 } 542 if (waitingForCmdRatio) { 543 waitingForCmdRatio = false; 544 if (r.getElement(0) != NceMessage.NCE_OKAY) { 545 log.error("NCE clock ratio cmd replied:{}", r.getElement(0)); 546 } 547 callStateMachines(); 548 return; 549 } 550 if (waitingForCmdStop) { 551 waitingForCmdStop = false; 552 if (r.getElement(0) != NceMessage.NCE_OKAY) { 553 log.error("NCE clock stop cmd replied:{}", r.getElement(0)); 554 } 555 callStateMachines(); 556 return; 557 } 558 if (waitingForCmdStart) { 559 waitingForCmdStart = false; 560 if (r.getElement(0) != NceMessage.NCE_OKAY) { 561 log.error("NCE clock start cmd replied:{}", r.getElement(0)); 562 } 563 callStateMachines(); 564 return; 565 } 566 } 567 log.debug("unexpected response"); 568 } 569 570 private void callStateMachines() { 571 if (internalSyncInitStateCounter > 0) { 572 internalSyncInitStates(); 573 } 574 if (internalSyncRunStateCounter > 0) { 575 internalSyncRunStates(); 576 } 577 if (nceSyncInitStateCounter > 0) { 578 nceSyncInitStates(); 579 } 580 if (nceSyncRunStateCounter > 0) { 581 nceSyncRunStates(); 582 } 583 if (alarmDisplayStateCounter > 0) { 584 alarmDisplayStates(); 585 } 586 } 587 588 private void readClockPacket(NceReply r) { 589 NceReply priorClockReadPacket = lastClockReadPacket; 590 int priorNceRatio = nceLastRatio; 591 boolean priorNceRunning = nceLastRunning; 592 lastClockReadPacket = r; 593 //lastClockReadAtTime = internalClock.getTime(); 594 //log.debug("readClockPacket - at time: " + lastClockReadAtTime); 595 nceLastHour = r.getElement(CS_CLOCK_HOURS) & 0xFF; 596 nceLastMinute = r.getElement(CS_CLOCK_MINUTES) & 0xFF; 597 nceLastSecond = r.getElement(CS_CLOCK_SECONDS) & 0xFF; 598 nceLast1224 = r.getElement(CS_CLOCK_1224) == 1; 599 nceLastAmPm = r.getElement(CS_CLOCK_AMPM) == 'A'; 600 int sc = r.getElement(CS_CLOCK_SCALE) & 0xFF; 601 if (sc > 0) { 602 nceLastRatio = 250 / sc; 603 } 604 if (clockMode == SYNCMODE_NCE_MASTER) { 605 if (priorClockReadPacket != null && priorNceRatio != nceLastRatio) { 606 log.debug("NCE Change Rate from cab: prior vs last: {} vs {}", priorNceRatio, nceLastRatio); 607 rateNce.setText("" + nceLastRatio); 608 nceSyncInitStateCounter = 1; 609 nceSyncInitStates(); 610 } 611 } 612 nceLastRunning = r.getElement(CS_CLOCK_STATUS) != 1; 613 if (clockMode == SYNCMODE_NCE_MASTER) { 614 if (priorClockReadPacket != null && priorNceRunning != nceLastRunning) { 615 log.debug("NCE Stop/Start: prior vs last: {} vs {}", priorNceRunning, nceLastRunning); 616 if (nceLastRunning) { 617 nceSyncInitStateCounter = 1; 618 } else { 619 nceSyncInitStateCounter = -1; 620 } 621 nceSyncInitStates(); 622 internalClock.setRun(nceLastRunning); 623 } 624 } 625 updateSettingsFromNce(); 626 } 627 628 private void alarmDisplayUpdateHandler() { 629 if (pollingInterval < MIN_POLLING_INTERVAL || pollingInterval > MAX_POLLING_INTERVAL) { 630 JmriJOptionPane.showMessageDialog(this, 631 Bundle.getMessage("DIALOG_PolingIntOutOfRange", MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, pollingInterval), 632 Bundle.getMessage("DIALOG_NceClockMon"), 633 JmriJOptionPane.ERROR_MESSAGE); 634 pollingInterval = DEFAULT_POLLING_INTERVAL; 635 } 636 // initialize things if not running 637 alarmSetup(); 638 alarmDisplayStates(); 639 updateInternalClockDisplay(); 640 } 641 642 private void alarmSetup() { 643 // initialize things if not running 644 if (timerDisplayUpdate == null) { 645 timerDisplayUpdate = new Timer((int) (pollingInterval * 1000.0), (ActionEvent e) -> { 646 alarmDisplayUpdateHandler(); 647 }); 648 } 649 timerDisplayUpdate.setInitialDelay((1 * 1000)); 650 timerDisplayUpdate.setRepeats(true); // in case we run by 651 timerDisplayUpdate.start(); 652 alarmDisplayStateCounter = 1; 653 } 654 655 private void alarmSyncInit() { 656 // initialize things if not running 657 int delay = 1000; 658 if (alarmSyncUpdate == null) { 659 alarmSyncUpdate = new Timer(delay, (ActionEvent e) -> { 660 alarmSyncHandler(); 661 }); 662 if (clockMode == SYNCMODE_INTERNAL_MASTER) { 663 delay = (int) (syncInterval * 1000 / nceLastRatio); 664 alarmSyncUpdate.setRepeats(false); 665 } 666 if (clockMode == SYNCMODE_NCE_MASTER) { 667 delay = 10 * 1000; 668 alarmSyncUpdate.setRepeats(true); 669 } 670 alarmSyncUpdate.setInitialDelay(delay); 671 alarmSyncUpdate.setDelay(delay); 672 alarmSyncUpdate.stop(); 673 } 674 } 675 676 @SuppressWarnings("deprecation") // Date.getTime 677 private void alarmSyncStart() { 678 // initialize things if not running 679 Date now = internalClock.getTime(); 680 if (alarmSyncUpdate == null) { 681 alarmSyncInit(); 682 } 683 int delay = 60 * 1000; 684 if (clockMode == SYNCMODE_INTERNAL_MASTER) { 685 if (syncInterval - 3 - now.getSeconds() <= 0) { 686 delay = 10; // basically trigger right away 687 } else { 688 delay = (int) ((syncInterval - now.getSeconds()) * 1000 / internalClock.getRate()); 689 } 690 } 691 if (clockMode == SYNCMODE_NCE_MASTER) { 692 delay = 10 * 1000; 693 } 694 alarmSyncUpdate.setDelay(delay); 695 alarmSyncUpdate.setInitialDelay(delay); 696 alarmSyncUpdate.start(); 697 log.trace("alarmSyncStart delay: {} @ {}", delay, now); 698 } 699 700 private void alarmSyncHandler() { 701 if (clockMode == SYNCMODE_INTERNAL_MASTER) { 702 internalSyncRunStateCounter = 1; 703 internalSyncRunStates(); 704 } 705 if (clockMode == SYNCMODE_NCE_MASTER) { 706 if (nceSyncRunStateCounter == 0) { 707 nceSyncRunStateCounter = 1; 708 nceSyncRunStates(); 709 } 710 } 711 if (clockMode == SYNCMODE_OFF) { 712 alarmSyncUpdate.stop(); 713 } 714 if (alarmDisplayStateCounter == 0) { 715 alarmDisplayStateCounter = 1; 716 alarmDisplayStates(); 717 } 718 } 719 720 private void alarmDisplayStates() { 721 int priorState; 722 do { 723 log.trace("alarmDisplayStates: before: {} {}", alarmDisplayStateCounter, internalClock.getTime()); 724 priorState = alarmDisplayStateCounter; 725 switch (alarmDisplayStateCounter) { 726 case 0: 727 // inactive 728 break; 729 case 1: 730 // issue nce read 731 internalClockStatusCheck(); 732 issueReadOnlyRequest(); 733 alarmDisplayStateCounter++; 734 break; 735 case 2: 736 // wait for update 737 if (!waitingForCmdRead) { 738 alarmDisplayStateCounter++; 739 } 740 break; 741 case 3: 742 // update clock display 743 alarmDisplayStateCounter = 0; 744 updateNceClockDisplay(); 745 updateInternalClockDisplay(); 746 break; 747 default: 748 log.warn("Unexpected alarmDisplayStateCounter {} in alarmDisplayStates", alarmDisplayStateCounter); 749 break; 750 } 751 log.trace("alarmDisplayStates: after: {} {}", alarmDisplayStateCounter, internalClock.getTime()); 752 } while (priorState != alarmDisplayStateCounter); 753 } 754 755 private double getNceTime() { 756 double nceTime = 0; 757 if (lastClockReadPacket != null) { 758 nceTime = (lastClockReadPacket.getElement(CS_CLOCK_HOURS) * 3600) 759 + (lastClockReadPacket.getElement(CS_CLOCK_MINUTES) * 60) 760 + lastClockReadPacket.getElement(CS_CLOCK_SECONDS) 761 + (lastClockReadPacket.getElement(CS_CLOCK_TICK) * 0.25); 762 } 763 return (nceTime); 764 } 765 766 @SuppressWarnings("deprecation") // Date.getTime 767 private Date getNceDate() { 768 Date now = internalClock.getTime(); 769 if (lastClockReadPacket != null) { 770 now.setHours(lastClockReadPacket.getElement(CS_CLOCK_HOURS)); 771 now.setMinutes(lastClockReadPacket.getElement(CS_CLOCK_MINUTES)); 772 now.setSeconds(lastClockReadPacket.getElement(CS_CLOCK_SECONDS)); 773 } 774 return (now); 775 } 776 777 @SuppressWarnings("deprecation") // Date.getTime 778 private double getIntTime() { 779 Date now = internalClock.getTime(); 780 int ms = (int) (now.getTime() % 1000); 781 int ss = now.getSeconds(); 782 int mm = now.getMinutes(); 783 int hh = now.getHours(); 784 log.trace("getIntTime: {}:{}:{}.{}", hh, mm, ss, ms); 785 return ((hh * 60 * 60) + (mm * 60) + ss + (ms / 1000)); 786 } 787 788 private void changeNceClockRatio() { 789 String newRatioStr = rateNce.getText().trim(); 790 try { 791 int newRatio = Integer.parseInt(newRatioStr); 792 if ((newRatio <= CLOCKRATIO_MIN) || (newRatio > CLOCKRATIO_MAX)) { 793 throw new NumberFormatException(); 794 } 795 issueClockRatio(newRatio); 796 } catch (NumberFormatException e) { 797 JmriJOptionPane.showMessageDialog(this, 798 Bundle.getMessage("DIALOG_InvalidRatio", newRatioStr, CLOCKRATIO_MIN, CLOCKRATIO_MAX), 799 Bundle.getMessage("DIALOG_NceClockMon"), 800 JmriJOptionPane.ERROR_MESSAGE); 801 } 802 } 803 804 @SuppressWarnings("deprecation") // Date.getTime 805 private void internalSyncInitStates() { 806 Date now = internalClock.getTime(); 807 int priorState; 808 do { 809 if (internalSyncInitStateCounter != 0) { 810 log.trace("internalSyncInitStates begin: {} @ {}", internalSyncInitStateCounter, now); 811 } 812 priorState = internalSyncInitStateCounter; 813 switch (internalSyncInitStateCounter) { 814 case 0: 815 // do nothing, idle state 816 break; 817 case -1: 818 // cleanup, halt state 819 alarmSyncUpdate.stop(); 820 internalSyncInitStateCounter = 0; 821 internalSyncRunStateCounter = 0; 822 setClockButton.setEnabled(true); 823 setRatioButton.setEnabled(true); 824 set1224Button.setEnabled(true); 825 setStopNceButton.setEnabled(true); 826 setStartNceButton.setEnabled(true); 827 break; 828 case -3: 829 // stopping from internal clock 830 internalSyncRunStateCounter = 0; 831 alarmSyncUpdate.stop(); 832 issueClockStop(); 833 internalSyncInitStateCounter++; 834 break; 835 case -2: 836 // waiting for nce to stop 837 if (!waitingForCmdStop) { 838 internalSyncInitStateCounter = 0; 839 } 840 break; 841 case 1: 842 // get current values + initialize all values for sync operations 843 priorDiffs.clear(); 844 priorCorrections.clear(); 845 priorOffsetErrors.clear(); 846 syncInterval = TARGET_SYNC_DELAY; 847 // disable NCE clock options 848 setClockButton.setEnabled(false); 849 setRatioButton.setEnabled(false); 850 set1224Button.setEnabled(false); 851 setStopNceButton.setEnabled(false); 852 setStartNceButton.setEnabled(false); 853 // stop NCE clock 854 issueClockStop(); 855 internalSyncInitStateCounter++; 856 break; 857 case 2: 858 if (!waitingForCmdStop) { 859 internalSyncInitStateCounter++; 860 } 861 break; 862 case 3: 863 // set NCE ratio, mode etc... 864 issueClockRatio((int) internalClock.getRate()); 865 internalSyncInitStateCounter++; 866 break; 867 case 4: 868 if (!waitingForCmdRatio) { 869 internalSyncInitStateCounter++; 870 } 871 break; 872 case 5: 873 issueClock1224(true); 874 internalSyncInitStateCounter++; 875 break; 876 case 6: 877 if (!waitingForCmd1224) { 878 internalSyncInitStateCounter++; 879 } 880 break; 881 case 7: 882 // set initial NCE time 883 // set NCE from internal settings 884 // start NCE clock 885 now = internalClock.getTime(); 886 issueClockSet(now.getHours(), now.getMinutes(), now.getSeconds()); 887 internalSyncInitStateCounter++; 888 break; 889 case 8: 890 if (!waitingForCmdTime) { 891 internalSyncInitStateCounter++; 892 } 893 break; 894 case 9: 895 issueClockStart(); 896 internalSyncInitStateCounter++; 897 break; 898 case 10: 899 if (!waitingForCmdStart) { 900 internalSyncInitStateCounter++; 901 } 902 break; 903 case 11: 904 issueReadOnlyRequest(); 905 internalSyncInitStateCounter++; 906 break; 907 case 12: 908 if (!waitingForCmdRead) { 909 internalSyncInitStateCounter++; 910 } 911 break; 912 case 13: 913 updateNceClockDisplay(); 914 updateInternalClockDisplay(); 915 alarmSyncStart(); 916 internalSyncInitStateCounter++; 917 break; 918 case 14: 919 // initialization complete 920 internalSyncInitStateCounter = 0; 921 internalSyncRunStateCounter = 1; 922 log.trace("internalSyncState: init done"); 923 break; 924 default: 925 internalSyncInitStateCounter = 0; 926 log.error("Uninitialized value: internalSyncInitStateCounter"); 927 break; 928 } 929 } while (priorState != internalSyncInitStateCounter); 930 } 931 932 @SuppressWarnings("deprecation") // Date.getTime 933 private void internalSyncRunStates() { 934 double intTime; 935 double nceTime; 936 double diffTime; 937 Date now = internalClock.getTime(); // Date.getTime 938 if (internalSyncRunStateCounter != 0) { 939 log.trace("internalSyncRunStates: {} @ {}", internalSyncRunStateCounter, now); 940 } 941 int priorState; 942 do { 943 priorState = internalSyncRunStateCounter; 944 switch (internalSyncRunStateCounter) { 945 case -1: 946 // turn off any sync parts 947 internalSyncInitStateCounter = -1; 948 internalSyncInitStates(); 949 break; 950 case 1: 951 internalClockStatusCheck(); 952 // alarm fired, issue fresh nce reads 953 issueReadOnlyRequest(); 954 internalSyncRunStateCounter++; 955 break; 956 case 2: 957 case 6: 958 if (!waitingForCmdRead) { 959 internalSyncRunStateCounter++; 960 } 961 break; 962 case 3: 963 // compute error 964 nceTime = getNceTime(); 965 intTime = getIntTime(); 966 diffTime = intTime - nceTime; 967 if (log.isTraceEnabled()) { 968 log.trace("syncStates2 begin. NCE: {}{}:{}{}:{}{} Internal: {}{}:{}{}:{}{} diff: {}", 969 nceLastHour / 10, nceLastHour - ((nceLastHour / 10) * 10), 970 nceLastMinute / 10, nceLastMinute - ((nceLastMinute / 10) * 10), 971 nceLastSecond / 10, nceLastSecond - ((nceLastSecond / 10) * 10), 972 now.getHours() / 10, now.getHours() - ((now.getHours() / 10) * 10), 973 now.getMinutes() / 10, now.getMinutes() - ((now.getMinutes() / 10) * 10), 974 now.getSeconds() / 10, now.getSeconds() - ((now.getSeconds() / 10) * 10), 975 diffTime); 976 } 977 // save error to array 978 while (priorDiffs.size() >= MAX_ERROR_ARRAY) { 979 priorDiffs.remove(0); 980 } 981 priorDiffs.add(diffTime); 982 recomputeInternalSync(); 983 issueClockSet( 984 now.getHours(), 985 now.getMinutes(), 986 (int) syncInterval 987 ); 988 internalSyncRunStateCounter++; 989 break; 990 case 4: 991 if (!waitingForCmdTime) { 992 internalSyncRunStateCounter++; 993 } 994 break; 995 case 5: 996 issueReadOnlyRequest(); 997 internalSyncRunStateCounter++; 998 break; 999 case 7: 1000 // compute offset delay 1001 intTime = now.getSeconds(); 1002 diffTime = TARGET_SYNC_DELAY - intTime; 1003 // save offset error to array 1004 while (priorOffsetErrors.size() >= MAX_ERROR_ARRAY) { 1005 priorOffsetErrors.remove(0); 1006 } 1007 priorOffsetErrors.add(diffTime); 1008 recomputeOffset(); 1009 if (log.isTraceEnabled()) { 1010 log.trace("syncState compute offset. NCE: {}{}:{}{}:{}{} Internal: {}{}:{}{}:{}{}", 1011 nceLastHour / 10, nceLastHour - ((nceLastHour / 10) * 10), 1012 nceLastMinute / 10, nceLastMinute - ((nceLastMinute / 10) * 10), 1013 nceLastSecond / 10, nceLastSecond - ((nceLastSecond / 10) * 10), 1014 now.getHours() / 10, now.getHours() - ((now.getHours() / 10) * 10), 1015 now.getMinutes() / 10, now.getMinutes() - ((now.getMinutes() / 10) * 10), 1016 now.getSeconds() / 10, now.getSeconds() - ((now.getSeconds() / 10) * 10)); 1017 } 1018 internalSyncRunStateCounter = 0; 1019 break; 1020 default: 1021 internalSyncRunStateCounter = 0; 1022 break; 1023 } 1024 } while (priorState != internalSyncRunStateCounter); 1025 } 1026 1027 @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY", justification = "testing for change from stored value") 1028 private void internalClockStatusCheck() { 1029 // if change to internal clock 1030 if (clockMode == SYNCMODE_INTERNAL_MASTER) { 1031 if (internalLastRunning != internalClock.getRun()) { 1032 if (internalClock.getRun()) { 1033 internalSyncInitStateCounter = 1; 1034 } else { 1035 internalSyncInitStateCounter = -3; 1036 } 1037 internalSyncInitStates(); 1038 } 1039 // next line is the FE_FLOATING_POINT_EQUALITY annotated above 1040 if (internalLastRatio != internalClock.getRate()) { 1041 internalSyncInitStateCounter = 1; 1042 internalSyncInitStates(); 1043 } 1044 } 1045 internalLastRunning = internalClock.getRun(); 1046 internalLastRatio = internalClock.getRate(); 1047 } 1048 1049 private void changePidValues() { 1050 double p = 0; 1051 double i = 0; 1052 double d = 0; 1053 boolean ok = true; 1054 try { 1055 p = Double.parseDouble(ncePidGainP.getText().trim()); 1056 } catch (NumberFormatException e) { 1057 log.error("Invalid value: {}", ncePidGainP.getText().trim()); 1058 ok = false; 1059 } 1060 try { 1061 i = Double.parseDouble(ncePidGainI.getText().trim()); 1062 } catch (NumberFormatException e) { 1063 log.error("Invalid value: {}", ncePidGainP.getText().trim()); 1064 ok = false; 1065 } 1066 try { 1067 d = Double.parseDouble(ncePidGainD.getText().trim()); 1068 } catch (NumberFormatException e) { 1069 log.error("Invalid value: {}", ncePidGainP.getText().trim()); 1070 ok = false; 1071 } 1072 if (ok) { 1073 if (p < 0) { 1074 p = 0; 1075 } 1076 if (p > 1) { 1077 p = 1; 1078 } 1079 if (i < 0) { 1080 i = 0; 1081 } 1082 if (d > 1) { 1083 d = 1; 1084 } 1085 ncePidGainPv = p; 1086 ncePidGainIv = i; 1087 ncePidGainDv = d; 1088 ncePidGainP.setText(fiveDigits.format(p)); 1089 ncePidGainI.setText(fiveDigits.format(i)); 1090 ncePidGainD.setText(fiveDigits.format(d)); 1091 } 1092 } 1093 1094 private void recomputeOffset() { 1095 1096 double sumDiff = 0; 1097 if (priorOffsetErrors.size() > 1) { 1098 sumDiff = priorOffsetErrors.get(0) + priorOffsetErrors.get(1); 1099 } 1100 double avgDiff = sumDiff / 2; 1101 syncInterval = syncInterval + avgDiff; 1102 if (syncInterval < 30) { 1103 syncInterval = 30; 1104 } 1105 if (syncInterval > 58) { 1106 syncInterval = 58; 1107 } 1108 if (log.isTraceEnabled()) { 1109 Date now = internalClock.getTime(); 1110 StringBuilder txt = new StringBuilder(""); 1111 for (int i = 0; i < priorOffsetErrors.size(); i++) { 1112 txt.append(" ").append(priorOffsetErrors.get(i).doubleValue()); 1113 } 1114 log.trace("priorOffsetErrors: {}", txt); 1115 log.trace("syncOffset: {} avgDiff: {} @ {}", syncInterval, avgDiff, now); 1116 } 1117 } 1118 1119 private void recomputeInternalSync() { 1120 //Date now = internalClock.getTime(); 1121 double sumDiff = 0; 1122 double currError = 0; 1123 //double diffError = 0; 1124 //double avgDiff = 0; 1125 if (priorDiffs.size() > 0) { 1126 currError = priorDiffs.get(priorDiffs.size() - 1); 1127 //diffError = priorDiffs.get(priorDiffs.size() - 1).doubleValue() - ((Double) priorDiffs.get(0)).doubleValue(); 1128 } 1129 for (int i = 0; i < priorDiffs.size(); i++) { 1130 sumDiff = sumDiff + priorDiffs.get(i); 1131 } 1132 double corrDiff = 0; 1133 if (priorCorrections.size() > 0) { 1134 corrDiff = priorCorrections.get(priorCorrections.size() - 1) - priorCorrections.get(0); 1135 } 1136 double pCorr = currError * intPidGainPv; 1137 double iCorr = sumDiff * intPidGainIv; 1138 double dCorr = corrDiff * intPidGainDv; 1139 double newRateAdj = pCorr + iCorr + dCorr; 1140 // save correction to array 1141 while (priorCorrections.size() >= MAX_ERROR_ARRAY) { 1142 priorCorrections.remove(0); 1143 } 1144 priorCorrections.add(newRateAdj); 1145 syncInterval = syncInterval + newRateAdj; 1146 if (syncInterval > 57) { 1147 syncInterval = 57; 1148 } 1149 if (syncInterval < 40) { 1150 syncInterval = 40; 1151 } 1152 if (log.isTraceEnabled()) { 1153 StringBuilder txt = new StringBuilder(""); 1154 for (int i = 0; i < priorDiffs.size(); i++) { 1155 txt.append(" ").append(priorDiffs.get(i)); 1156 } 1157 log.trace("priorDiffs: {}", txt); 1158 log.trace("syncInterval: {} pCorr: {} iCorr: {} dCorr: {}", 1159 syncInterval, fiveDigits.format(pCorr), fiveDigits.format(iCorr), fiveDigits.format(dCorr)); 1160 } 1161 } 1162 1163 private void recomputeNceSync() { 1164 //Date now = internalClock.getTime(); 1165 double sumDiff = 0; 1166 double currError = 0; 1167 double diffError = 0; 1168 if (priorDiffs.size() > 0) { 1169 currError = priorDiffs.get(priorDiffs.size() - 1); 1170 diffError = priorDiffs.get(priorDiffs.size() - 1) - priorDiffs.get(0); 1171 } 1172 for (int i = 0; i < priorDiffs.size(); i++) { 1173 sumDiff = sumDiff + priorDiffs.get(i); 1174 } 1175 double corrDiff = 0; 1176 if (priorCorrections.size() > 0) { 1177 corrDiff = priorCorrections.get(priorCorrections.size() - 1) - priorCorrections.get(0); 1178 } 1179 double pCorr = currError * ncePidGainPv; 1180 double iCorr = diffError * ncePidGainIv; 1181 double dCorr = corrDiff * ncePidGainDv; 1182 double newRateAdj = pCorr + iCorr + dCorr; 1183 // if (newRateAdj > 0.5) { 1184 // newRateAdj = 0.5; 1185 //} 1186 //if (newRateAdj < -0.5) { 1187 // newRateAdj = -0.5; 1188 // } 1189 // save correction to array 1190 while (priorCorrections.size() >= MAX_ERROR_ARRAY) { 1191 priorCorrections.remove(0); 1192 } 1193 priorCorrections.add(newRateAdj); 1194 double oldInternalRate = internalClock.getRate(); 1195 double newInternalRate = oldInternalRate + newRateAdj; 1196 if (Math.abs(currError) > 60) { 1197 // don't try to drift, just reset 1198 nceSyncInitStateCounter = 1; 1199 nceSyncInitStates(); 1200 } else if (Math.abs(oldInternalRate - newInternalRate) >= rateChgMinimum) { 1201 try { 1202 internalClock.setRate(newInternalRate); 1203 if (log.isDebugEnabled()) { 1204 log.debug("changing internal rate: {}", newInternalRate); 1205 } 1206 } catch (TimebaseRateException e) { 1207 log.error("recomputeNceSync: Failed setting new internal rate: {}", newInternalRate); 1208 // just set the internal to NCE and set the clock 1209 nceSyncInitStateCounter = 1; 1210 nceSyncInitStates(); 1211 } 1212 } 1213 if (log.isTraceEnabled()) { 1214 StringBuilder txt = new StringBuilder(""); 1215 for (int i = priorDiffs.size() - 1; i >= 0; i--) { 1216 txt.append(" ").append(threeDigits.format(priorDiffs.get(i))); 1217 } 1218 log.trace("priorDiffs: {}", txt); 1219 txt = new StringBuilder(""); 1220 for (int i = priorCorrections.size() - 1; i >= 0; i--) { 1221 txt.append(" ").append(threeDigits.format(priorCorrections.get(i))); 1222 } 1223 log.trace("priorCorrections: {}", txt); 1224 log.trace("currError: {} pCorr: {} iCorr: {} dCorr: {} newInternalRate: {}", 1225 fiveDigits.format(currError), fiveDigits.format(pCorr), fiveDigits.format(iCorr), fiveDigits.format(dCorr), threeDigits.format(newInternalRate)); 1226 } 1227 } 1228 1229 private void changePollingSpeed(double newInterval) { 1230 if (newInterval < MIN_POLLING_INTERVAL || newInterval > MAX_POLLING_INTERVAL) { 1231 JmriJOptionPane.showMessageDialog(this, 1232 Bundle.getMessage("DIALOG_PolingOutOfRange", newInterval, MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL), 1233 Bundle.getMessage("DIALOG_NceClockMon"), 1234 JmriJOptionPane.ERROR_MESSAGE); 1235 } else { 1236 pollingInterval = newInterval; 1237 pollingSpeed.setText("" + pollingInterval); 1238 if (timerDisplayUpdate == null) { 1239 alarmSetup(); 1240 } 1241 timerDisplayUpdate.setDelay((int) (pollingInterval * 1000)); 1242 } 1243 } 1244 1245 private void changeSyncMode() { 1246 int oldMode = clockMode; 1247 int newMode = SYNCMODE_OFF; 1248 if (setSyncModeNceMaster.isSelected() == true) { 1249 newMode = SYNCMODE_NCE_MASTER; 1250 } 1251 if (setSyncModeInternalMaster.isSelected() == true) { 1252 newMode = SYNCMODE_INTERNAL_MASTER; 1253 } 1254 if (internalClock != null) { 1255 log.debug("changeSyncMode(): New Mode: {} Old Mode: {}", newMode, oldMode); 1256 if (oldMode != newMode) { 1257 clockMode = SYNCMODE_OFF; 1258 // some change so, change settings 1259 if (oldMode == SYNCMODE_OFF) { 1260 if (newMode == SYNCMODE_INTERNAL_MASTER) { 1261 log.debug("starting Internal mode"); 1262 internalSyncInitStateCounter = 1; 1263 internalSyncRunStateCounter = 0; 1264 internalSyncInitStates(); 1265 clockMode = SYNCMODE_INTERNAL_MASTER; 1266 } 1267 if (newMode == SYNCMODE_NCE_MASTER) { 1268 log.debug("starting NCE mode"); 1269 nceSyncInitStateCounter = 1; 1270 nceSyncRunStateCounter = 0; 1271 nceSyncInitStates(); 1272 clockMode = SYNCMODE_NCE_MASTER; 1273 } 1274 } else { 1275 if (oldMode == SYNCMODE_NCE_MASTER) { 1276 // clear nce sync 1277 nceSyncInitStateCounter = -1; 1278 nceSyncInitStates(); 1279 internalSyncInitStateCounter = 1; 1280 internalSyncInitStates(); 1281 } 1282 if (oldMode == SYNCMODE_INTERNAL_MASTER) { 1283 // clear internal mode 1284 internalSyncInitStateCounter = -1; 1285 internalSyncInitStates(); 1286 nceSyncInitStateCounter = 1; 1287 nceSyncInitStates(); 1288 } 1289 } 1290 } 1291 } 1292 } 1293 1294 private void nceSyncInitStates() { 1295 int priorState; 1296 do { 1297 if (log.isTraceEnabled()) { 1298 log.trace("Before nceSyncInitStateCounter: {} {}", nceSyncInitStateCounter, internalClock.getTime()); 1299 } 1300 priorState = nceSyncInitStateCounter; 1301 switch (nceSyncInitStateCounter) { 1302 case -1: 1303 // turn all this off 1304 if (alarmSyncUpdate != null) { 1305 alarmSyncUpdate.stop(); 1306 } 1307 // clear any old records 1308 priorDiffs.clear(); 1309 priorCorrections.clear(); 1310 nceSyncInitStateCounter = 0; 1311 nceSyncRunStateCounter = 0; 1312 break; 1313 case 0: 1314 // idle state 1315 break; 1316 case 1: 1317 // first init step 1318 log.debug("Init/Reset NCE Clock Sync"); 1319 // make sure other state is off 1320 nceSyncRunStateCounter = 0; 1321 // stop internal clock 1322 internalClock.setRun(false); 1323 if (alarmSyncUpdate != null) { 1324 alarmSyncUpdate.stop(); 1325 } 1326 // clear any old records 1327 priorDiffs.clear(); 1328 priorCorrections.clear(); 1329 // request all current nce values 1330 issueReadOnlyRequest(); 1331 nceSyncInitStateCounter++; 1332 break; 1333 case 2: 1334 // make sure the read only has happened 1335 if (!waitingForCmdRead) { 1336 nceSyncInitStateCounter++; 1337 } 1338 break; 1339 case 3: 1340 // set ratio, modes etc... 1341 try { 1342 internalClock.setRate(nceLastRatio * ESTIMATED_NCE_RATE_FACTOR); 1343 } catch (TimebaseRateException e) { 1344 log.error("nceSyncInitStates: failed to set internal clock rate: {}", nceLastRatio); 1345 } 1346 // get time from NCE settings and set internal clock 1347 setInternalClockFromNce(); 1348 internalClock.setRun(true); 1349 nceSyncInitStateCounter = 0; // init is done 1350 nceSyncRunStateCounter = 1; 1351 nceSyncRunStates(); 1352 alarmSyncStart(); 1353 updateNceClockDisplay(); 1354 updateInternalClockDisplay(); 1355 break; 1356 default: 1357 log.warn("Unexpected nceSyncInitStateCounter {} in nceSyncInitStates", nceSyncInitStateCounter); 1358 break; 1359 } 1360 log.trace("After nceSyncInitStateCounter: {} {}", nceSyncInitStateCounter, internalClock.getTime()); 1361 } while (priorState != nceSyncInitStateCounter); 1362 } 1363 1364 private void nceSyncRunStates() { 1365 double intTime; 1366 double nceTime; 1367 double diffTime; 1368 if (log.isTraceEnabled()) { 1369 log.trace("Before nceSyncRunStateCounter: {} {}", nceSyncRunStateCounter, internalClock.getTime()); 1370 } 1371 int priorState; 1372 do { 1373 priorState = nceSyncRunStateCounter; 1374 switch (nceSyncRunStateCounter) { 1375 case 1: // issue read for nce time 1376 issueReadOnlyRequest(); 1377 nceSyncRunStateCounter++; 1378 break; 1379 case 2: 1380 // did read happen?? 1381 if (!waitingForCmdRead) { 1382 nceSyncRunStateCounter++; 1383 } 1384 break; 1385 case 3: // compare internal with nce time 1386 intTime = getIntTime(); 1387 nceTime = getNceTime(); 1388 diffTime = nceTime - intTime; 1389 // deal with end of day reset 1390 if (diffTime > MAX_SECONDS_IN_DAY / 2) { 1391 diffTime = MAX_SECONDS_IN_DAY + nceTime - intTime; 1392 } else if (diffTime < MAX_SECONDS_IN_DAY / -2) { 1393 diffTime = nceTime; 1394 } 1395 if (log.isDebugEnabled()) { 1396 log.debug("new diffTime: {} = {} - {}", diffTime, nceTime, intTime); 1397 } 1398 // save error to array 1399 while (priorDiffs.size() >= MAX_ERROR_ARRAY) { 1400 priorDiffs.remove(0); 1401 } 1402 priorDiffs.add(diffTime); 1403 recomputeNceSync(); 1404 // initialize things if not running 1405 if (alarmSyncUpdate == null) { 1406 alarmSyncInit(); 1407 } 1408 updateNceClockDisplay(); 1409 updateInternalClockDisplay(); 1410 nceSyncRunStateCounter++; 1411 break; 1412 case 4: 1413 // wait for next minute 1414 nceSyncRunStateCounter = 0; 1415 break; 1416 default: 1417 log.warn("Unexpected state {} in nceSyncRunStates", nceSyncRunStateCounter); 1418 break; 1419 } 1420 } while (priorState != nceSyncRunStateCounter); 1421 if (log.isTraceEnabled()) { 1422 log.trace("After nceSyncRunStateCounter: {} {}", nceSyncRunStateCounter, internalClock.getTime()); 1423 } 1424 } 1425 1426 private void setInternalClockFromNce() { 1427 Date newTime = getNceDate(); 1428 internalClock.setTime(newTime); 1429 log.debug("setInternalClockFromNce nceClock: {}", newTime); 1430 } 1431 1432 private void updateSettingsFromNce() { 1433 if (updateTimeFromRead == true) { 1434 hours.setText("" + (nceLastHour / 10) + (nceLastHour - ((nceLastHour / 10) * 10))); 1435 minutes.setText("" + (nceLastMinute / 10) + (nceLastMinute - ((nceLastMinute / 10) * 10))); 1436 seconds.setText("" + (nceLastSecond / 10) + (nceLastSecond - ((nceLastSecond / 10) * 10))); 1437 if (nceLast1224) { 1438 twentyFour.setSelected(true); 1439 amPm.setText(" "); 1440 } else { 1441 twentyFour.setSelected(false); 1442 if (nceLastAmPm) { 1443 amPm.setText(Bundle.getMessage("TagAm")); 1444 } else { 1445 amPm.setText(Bundle.getMessage("TagPm")); 1446 } 1447 } 1448 updateTimeFromRead = false; 1449 } 1450 if (updateRatioFromRead == true) { 1451 rateNce.setText("" + nceLastRatio); 1452 updateRatioFromRead = false; 1453 } 1454 if (updateFormatFromRead == true) { 1455 if (nceLast1224) { 1456 twentyFour.setSelected(true); 1457 } else { 1458 twentyFour.setSelected(false); 1459 } 1460 updateFormatFromRead = false; 1461 } 1462 if (updateStatusFromRead == true) { 1463 if (nceLastRunning) { 1464 status.setText(Bundle.getMessage("TagRunning")); 1465 } else { 1466 status.setText(Bundle.getMessage("TagStopped")); 1467 } 1468 } 1469 } 1470 1471 private void updateNceClockDisplay() { 1472 String txt = nceLastRunning ? Bundle.getMessage("TagRunning") : Bundle.getMessage("TagStopped"); 1473 txt = txt + " " 1474 + (nceLastHour / 10) + (nceLastHour - ((nceLastHour / 10) * 10)) + Bundle.getMessage("LabelTimeSep") 1475 + (nceLastMinute / 10) + (nceLastMinute - ((nceLastMinute / 10) * 10)) + Bundle.getMessage("LabelTimeSep") 1476 + (nceLastSecond / 10) + (nceLastSecond - ((nceLastSecond / 10) * 10)); 1477 if (!nceLast1224) { 1478 if (nceLastAmPm) { 1479 txt = txt + " " + Bundle.getMessage("TagAm"); 1480 } else { 1481 txt = txt + " " + Bundle.getMessage("TagPm"); 1482 } 1483 } 1484 txt = txt + " " + Bundle.getMessage("LabelRatio") + " " 1485 + nceLastRatio + Bundle.getMessage("LabelToOne"); 1486 if (clockMode == SYNCMODE_NCE_MASTER) { 1487 txt = txt + " " + Bundle.getMessage("TagIsNceMaster"); 1488 double intTime = getIntTime(); 1489 double nceTime = getNceTime(); 1490 double diffTime = nceTime - intTime; 1491 txt = txt + " " + Bundle.getMessage("ClockError"); 1492 txt = txt + " " + threeDigits.format(diffTime); 1493 log.trace("intTime: {} nceTime: {} diffTime: {}", intTime, nceTime, diffTime); 1494 } 1495 nceDisplayStatus.setText(txt); 1496 } 1497 1498 @SuppressWarnings("deprecation") // Date.getTime 1499 private void updateInternalClockDisplay() { 1500 String txt = internalClock.getRun() ? Bundle.getMessage("TagRunning") : Bundle.getMessage("TagStopped"); 1501 Date now = internalClock.getTime(); 1502 txt = txt + " " 1503 + (now.getHours() / 10) + (now.getHours() - ((now.getHours() / 10) * 10)) 1504 + Bundle.getMessage("LabelTimeSep") 1505 + (now.getMinutes() / 10) + (now.getMinutes() - ((now.getMinutes() / 10) * 10)) 1506 + Bundle.getMessage("LabelTimeSep") 1507 + (now.getSeconds() / 10) + (now.getSeconds() - ((now.getSeconds() / 10) * 10)); 1508 txt = txt + " " 1509 + Bundle.getMessage("LabelRatio") + " " 1510 + threeDigits.format(internalClock.getRate()) + Bundle.getMessage("LabelToOne"); 1511 if (clockMode == SYNCMODE_INTERNAL_MASTER) { 1512 txt = txt + " " + Bundle.getMessage("TagIsInternalMaster"); 1513 double intTime = getIntTime(); 1514 double nceTime = getNceTime(); 1515 double diffTime = nceTime - intTime; 1516 txt = txt + " " + Bundle.getMessage("ClockError"); 1517 txt = txt + " " + threeDigits.format(diffTime); 1518 } 1519 internalDisplayStatus.setText(txt); 1520 } 1521 1522 private void issueReadOnlyRequest() { 1523 if (!waitingForCmdRead) { 1524 byte[] cmd = jmri.jmrix.nce.NceBinaryCommand.accMemoryRead(tc.csm.getClockAddr()); 1525 NceMessage cmdNce = jmri.jmrix.nce.NceMessage.createBinaryMessage(tc, cmd, CS_CLOCK_MEM_SIZE); 1526 waiting++; 1527 waitingForCmdRead = true; 1528 tc.sendNceMessage(cmdNce, this); 1529 // log.debug("issueReadOnlyRequest at " + internalClock.getTime()); 1530 } 1531 } 1532 1533 private void issueReadAllRequest() { 1534 if (!waitingForCmdRead) { 1535 byte[] cmd = jmri.jmrix.nce.NceBinaryCommand.accMemoryRead(tc.csm.getClockAddr()); 1536 NceMessage cmdNce = jmri.jmrix.nce.NceMessage.createBinaryMessage(tc, cmd, CS_CLOCK_MEM_SIZE); 1537 waiting++; 1538 waitingForCmdRead = true; 1539 tc.sendNceMessage(cmdNce, this); 1540 } 1541 updateTimeFromRead = true; 1542 updateRatioFromRead = true; 1543 updateFormatFromRead = true; 1544 updateStatusFromRead = true; 1545 } 1546 1547 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification="was previously marked with @SuppressWarnings, reason unknown") 1548 private void issueReadTimeRequest() { 1549 if (!waitingForCmdRead) { 1550 byte[] cmd = jmri.jmrix.nce.NceBinaryCommand.accMemoryRead(tc.csm.getClockAddr()); 1551 NceMessage cmdNce = jmri.jmrix.nce.NceMessage.createBinaryMessage(tc, cmd, CS_CLOCK_MEM_SIZE); 1552 waiting++; 1553 waitingForCmdRead = true; 1554 tc.sendNceMessage(cmdNce, this); 1555 } 1556 updateTimeFromRead = true; 1557 } 1558 1559 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification="was previously marked with @SuppressWarnings, reason unknown") 1560 private void issueReadRatioRequest() { 1561 if (!waitingForCmdRead) { 1562 byte[] cmd = jmri.jmrix.nce.NceBinaryCommand.accMemoryRead(tc.csm.getClockAddr()); 1563 NceMessage cmdNce = jmri.jmrix.nce.NceMessage.createBinaryMessage(tc, cmd, CS_CLOCK_MEM_SIZE); 1564 waiting++; 1565 waitingForCmdRead = true; 1566 tc.sendNceMessage(cmdNce, this); 1567 } 1568 updateRatioFromRead = true; 1569 } 1570 1571 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification="was previously marked with @SuppressWarnings, reason unknown") 1572 private void issueReadFormatRequest() { 1573 if (!waitingForCmdRead) { 1574 byte[] cmd = jmri.jmrix.nce.NceBinaryCommand.accMemoryRead(tc.csm.getClockAddr()); 1575 NceMessage cmdNce = jmri.jmrix.nce.NceMessage.createBinaryMessage(tc, cmd, CS_CLOCK_MEM_SIZE); 1576 waiting++; 1577 waitingForCmdRead = true; 1578 tc.sendNceMessage(cmdNce, this); 1579 } 1580 updateFormatFromRead = true; 1581 } 1582 1583 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification="was previously marked with @SuppressWarnings, reason unknown") 1584 private void issueReadStatusRequest() { 1585 if (!waitingForCmdRead) { 1586 byte[] cmd = jmri.jmrix.nce.NceBinaryCommand.accMemoryRead(tc.csm.getClockAddr()); 1587 NceMessage cmdNce = jmri.jmrix.nce.NceMessage.createBinaryMessage(tc, cmd, CS_CLOCK_MEM_SIZE); 1588 waiting++; 1589 waitingForCmdRead = true; 1590 tc.sendNceMessage(cmdNce, this); 1591 } 1592 updateStatusFromRead = true; 1593 } 1594 1595 private void issueClockSet(int hh, int mm, int ss) { 1596 if ((hh < 0) || (hh > 23)) { 1597 JmriJOptionPane.showMessageDialog(this, 1598 Bundle.getMessage("DIALOG_BadHour", hh), 1599 Bundle.getMessage("DIALOG_NceClockMon"), 1600 JmriJOptionPane.ERROR_MESSAGE); 1601 return; 1602 } 1603 if ((mm < 0) || (mm > 59)) { 1604 JmriJOptionPane.showMessageDialog(this, 1605 Bundle.getMessage("DIALOG_BadMinute", mm), 1606 Bundle.getMessage("DIALOG_NceClockMon"), 1607 JmriJOptionPane.ERROR_MESSAGE); 1608 return; 1609 } 1610 if ((ss < 0) || (ss > 59)) { 1611 JmriJOptionPane.showMessageDialog(this, 1612 Bundle.getMessage("DIALOG_BadSecond", ss), 1613 Bundle.getMessage("DIALOG_NceClockMon"), 1614 JmriJOptionPane.ERROR_MESSAGE); 1615 return; 1616 } 1617 issueClockSetMem(hh, mm, ss); 1618 } 1619 1620 private void issueClockSetMem(int hh, int mm, int ss) { 1621 byte[] b = new byte[3]; 1622 b[0] = (byte) ss; 1623 b[1] = (byte) mm; 1624 b[2] = (byte) hh; 1625 byte[] cmd = jmri.jmrix.nce.NceBinaryCommand.accMemoryWriteN(tc.csm.getClockAddr() + CS_CLOCK_SECONDS, b); 1626 NceMessage cmdNce = jmri.jmrix.nce.NceMessage.createBinaryMessage(tc, cmd, CMD_MEM_SET_REPLY_SIZE); 1627 waiting++; 1628 waitingForCmdTime = true; 1629 tc.sendNceMessage(cmdNce, this); 1630 } 1631 1632 private void issueClockRatio(int r) { 1633 byte[] cmd = jmri.jmrix.nce.NceBinaryCommand.accSetClockRatio(r); 1634 NceMessage cmdNce = jmri.jmrix.nce.NceMessage.createBinaryMessage(tc, cmd, CMD_CLOCK_SET_REPLY_SIZE); 1635 waiting++; 1636 waitingForCmdRatio = true; 1637 tc.sendNceMessage(cmdNce, this); 1638 } 1639 1640 private void issueClock1224(boolean mode) { 1641 byte[] cmd = jmri.jmrix.nce.NceBinaryCommand.accSetClock1224(mode); 1642 NceMessage cmdNce = jmri.jmrix.nce.NceMessage.createBinaryMessage(tc, cmd, CMD_CLOCK_SET_REPLY_SIZE); 1643 waiting++; 1644 waitingForCmd1224 = true; 1645 tc.sendNceMessage(cmdNce, this); 1646 } 1647 1648 private void issueClockStop() { 1649 byte[] cmd = jmri.jmrix.nce.NceBinaryCommand.accStopClock(); 1650 NceMessage cmdNce = jmri.jmrix.nce.NceMessage.createBinaryMessage(tc, cmd, CMD_CLOCK_SET_REPLY_SIZE); 1651 waiting++; 1652 waitingForCmdStop = true; 1653 tc.sendNceMessage(cmdNce, this); 1654 } 1655 1656 private void issueClockStart() { 1657 byte[] cmd = jmri.jmrix.nce.NceBinaryCommand.accStartClock(); 1658 NceMessage cmdNce = jmri.jmrix.nce.NceMessage.createBinaryMessage(tc, cmd, CMD_CLOCK_SET_REPLY_SIZE); 1659 waiting++; 1660 waitingForCmdStart = true; 1661 tc.sendNceMessage(cmdNce, this); 1662 } 1663 1664 /** 1665 * Handles minute notifications for NCE Clock Monitor/Synchronizer 1666 */ 1667 public void newInternalMinute() { 1668 // if (log.isDebugEnabled()) { 1669 // log.debug("newInternalMinute clockMode: " + clockMode + " nceInit: " + nceSyncInitStateCounter + " nceRun: " + nceSyncRunStateCounter); 1670 //} 1671 //NCE clock is running 1672 if (lastClockReadPacket != null && lastClockReadPacket.getElement(CS_CLOCK_STATUS) == 0) { 1673 if (clockMode == SYNCMODE_INTERNAL_MASTER) { 1674 // start alarm timer 1675 alarmSyncStart(); 1676 } 1677 } 1678 } 1679 1680 // handle window closing event 1681 public void windowClosing(java.awt.event.WindowEvent e) { 1682 setVisible(false); 1683 if (timerDisplayUpdate != null) { 1684 timerDisplayUpdate.stop(); 1685 } 1686 //super.windowClosing(e); 1687 } 1688 1689 @Override 1690 public void dispose() { 1691 // stop alarm 1692 if (timerDisplayUpdate != null) { 1693 timerDisplayUpdate.stop(); 1694 timerDisplayUpdate = null; 1695 } 1696 // Remove ourselves from the Timebase minute rollover event 1697 InstanceManager.getDefault(jmri.Timebase.class).removeMinuteChangeListener(minuteChangeListener); 1698 minuteChangeListener = null; 1699 1700 // take apart the JFrame 1701 super.dispose(); 1702 } 1703 1704 /** 1705 * Nested class to create one of these using old-style defaults 1706 */ 1707 static public class Default extends jmri.jmrix.nce.swing.NceNamedPaneAction { 1708 1709 public Default() { 1710 super("Open NCE Clock Monitor", 1711 new jmri.util.swing.sdi.JmriJFrameInterface(), 1712 ClockMonPanel.class.getName(), 1713 jmri.InstanceManager.getDefault(NceSystemConnectionMemo.class)); 1714 } 1715 } 1716 1717 private final static Logger log = LoggerFactory.getLogger(ClockMonPanel.class); 1718 1719}