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