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}