001package jmri.jmrix.mrc;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.text.DecimalFormat;
005import java.util.Date;
006import jmri.InstanceManager;
007import jmri.Timebase;
008import jmri.implementation.DefaultClockControl;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012/**
013 * Implementation of the Hardware Fast Clock for Mrc
014 * <p>
015 * This module is based on the NCE version.
016 * <br>
017 * <hr>
018 * This file is part of JMRI.
019 * <p>
020 * JMRI is free software; you can redistribute it and/or modify it under the
021 * terms of version 2 of the GNU General Public License as published by the Free
022 * Software Foundation. See the "COPYING" file for a copy of this license.
023 * <p>
024 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
025 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
026 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
027 *
028 * @author Ken Cameron Copyright (C) 2014
029 * @author Dave Duchamp Copyright (C) 2007
030 * @author Bob Jacobsen, Alex Shepherd
031 */
032public class MrcClockControl extends DefaultClockControl implements MrcTrafficListener {
033
034    /**
035     * Create a ClockControl object for a Mrc clock
036     * @param tc traffic control for connection
037     * @param prefix system prefix for connection
038     */
039    public MrcClockControl(MrcTrafficController tc, String prefix) {
040        super();
041        this.tc = tc;
042
043        // Create a timebase listener for the Minute change events
044        internalClock = InstanceManager.getNullableDefault(jmri.Timebase.class);
045        if (internalClock == null) {
046            log.error("No Internal Timebase Instance"); // NOI18N
047            return;
048        }
049        minuteChangeListener = new java.beans.PropertyChangeListener() {
050            @Override
051            public void propertyChange(java.beans.PropertyChangeEvent e) {
052                newInternalMinute();
053            }
054        };
055
056        internalClock.addMinuteChangeListener(minuteChangeListener);
057        tc.addTrafficListener(MrcInterface.CLOCK, this);
058    }
059    private MrcTrafficController tc = null;
060
061    /* constants, variables, etc */
062    private static final boolean DEBUG_SHOW_PUBLIC_CALLS = true; // enable debug for each public interface
063    private static final boolean DEBUG_SHOW_SYNC_CALLS = false; // enable debug for sync logic
064
065    public static final int CS_CLOCK_SCALE = 0x00;
066    public static final int CS_CLOCK_MINUTES = 0x03;
067    public static final int CS_CLOCK_HOURS = 0x04;
068    public static final int CS_CLOCK_AMPM = 0x05;
069    public static final int CS_CLOCK_1224 = 0x06;
070    public static final int CS_CLOCK_STATUS = 0x0D;
071    public static final int CMD_CLOCK_SET_TIME_SIZE = 0x03;
072    public static final int CMD_CLOCK_SET_PARAM_SIZE = 0x02;
073    public static final int CMD_CLOCK_SET_RUN_SIZE = 0x01;
074    public static final int CMD_CLOCK_SET_REPLY_SIZE = 0x01;
075    public static final int CMD_MEM_SET_REPLY_SIZE = 0x01;
076    public static final int MAX_ERROR_ARRAY = 4;
077    public static final double TARGET_SYNC_DELAY = 55;
078    public static final int SYNCMODE_OFF = 0;    //0 - clocks independent
079    public static final int SYNCMODE_MRC_MASTER = 1;  //1 - Mrc sets Internal
080    public static final int SYNCMODE_INTERNAL_MASTER = 2; //2 - Internal sets Mrc
081    public static final int WAIT_CMD_EXECUTION = 1000;
082
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 clockMode = SYNCMODE_OFF;
089    private MrcMessage lastClockReadPacket = null;
090    //private Date lastClockReadAtTime;
091    private int mrcLastHour;
092    private int mrcLastMinute;
093    private int mrcLastRatio;
094    private boolean mrcLastAmPm;
095    private boolean mrcLast1224;
096
097    private int mrcSyncInitStateCounter = 0; // MRC master sync initialization state machine
098    private int mrcSyncRunStateCounter = 0; // MRC master sync runtime state machine
099
100    Timebase internalClock;
101    javax.swing.Timer alarmSyncUpdate = null;
102    java.beans.PropertyChangeListener minuteChangeListener;
103
104    //  Filter reply messages for the clock poll
105    public void message(MrcMessage r) {
106        if ((r.getMessageClass() & MrcInterface.CLOCK) != MrcInterface.CLOCK) {
107            return;
108        }
109        if (r.getNumDataElements() != 6 || r.getElement(0) != 0 || r.getElement(1) != 1
110                || r.getElement(3) != 0 || r.getElement(5) != 0) {
111            // not a clock packet
112            return;
113        }
114        log.debug("MrcReply(len {})", r.getNumDataElements()); // NOI18N
115
116        readClockPacket(r);
117    }
118
119    @Override
120    public synchronized void notifyXmit(Date timestamp, MrcMessage m) {
121    }
122
123    @Override
124    public synchronized void notifyFailedXmit(Date timestamp, MrcMessage m) {
125    }
126
127    @Override
128    public synchronized void notifyRcv(Date timestamp, MrcMessage m) {
129        message(m);
130    }
131
132    /**
133     * name of Mrc clock
134     */
135    @Override
136    public String getHardwareClockName() {
137        if (DEBUG_SHOW_PUBLIC_CALLS) {
138            log.debug("getHardwareClockName"); // NOI18N
139        }
140        return (Bundle.getMessage("MrcClockName"));
141    }
142
143    /**
144     * Mrc clock runs stable enough
145     */
146    @Override
147    public boolean canCorrectHardwareClock() {
148        if (DEBUG_SHOW_PUBLIC_CALLS) {
149            log.debug("getHardwareClockName"); // NOI18N
150        }
151        return false;
152    }
153
154    /**
155     * Mrc clock supports 12/24 operation
156     */
157    @Override
158    public boolean canSet12Or24HourClock() {
159        if (DEBUG_SHOW_PUBLIC_CALLS) {
160            log.debug("canSet12Or24HourClock"); // NOI18N
161        }
162        return true;
163    }
164
165    /**
166     * sets Mrc clock speed, must be 1 to 60
167     */
168    @Override
169    public void setRate(double newRate) {
170        if (DEBUG_SHOW_PUBLIC_CALLS) {
171            log.debug("setRate: {}", newRate); // NOI18N
172        }
173        int newRatio = (int) newRate;
174        if (newRatio < 1 || newRatio > 60) {
175            log.error("Mrc clock ratio out of range:"); // NOI18N
176        } else {
177            issueClockRatio(newRatio);
178        }
179    }
180
181    /**
182     * Mrc only supports integer rates
183     */
184    @Override
185    public boolean requiresIntegerRate() {
186        if (DEBUG_SHOW_PUBLIC_CALLS) {
187            log.debug("requiresIntegerRate"); // NOI18N
188        }
189        return true;
190    }
191
192    /**
193     * last known ratio from Mrc clock
194     */
195    @Override
196    public double getRate() {
197        if (DEBUG_SHOW_PUBLIC_CALLS) {
198            log.debug("getRate: {}", mrcLastRatio); // NOI18N
199        }
200        return (mrcLastRatio);
201    }
202
203    /**
204     * set the time, the date part is ignored
205     */
206    @SuppressWarnings("deprecation") // Date.getHours, Date.getMinutes
207    @Override
208    public void setTime(Date now) {
209        if (DEBUG_SHOW_PUBLIC_CALLS) {
210            log.debug("setTime: {}", now); // NOI18N
211        }
212        issueClockTime(now.getHours(), now.getMinutes());
213    }
214
215    /**
216     * returns the current Mrc time, does not have a date component
217     */
218    @SuppressWarnings("deprecation") // Date.getTime
219    @Override
220    public Date getTime() {
221        Date now = internalClock.getTime();
222        if (lastClockReadPacket != null) {
223            if (mrcLast1224) { // is 24 hour mode
224                now.setHours(mrcLastHour);
225            } else {
226                if (mrcLastAmPm) { // is AM
227                    now.setHours(mrcLastHour);
228                } else {
229                    now.setHours(mrcLastHour + 12);
230                }
231            }
232            now.setMinutes(mrcLastMinute);
233            now.setSeconds(0);
234        }
235        if (DEBUG_SHOW_PUBLIC_CALLS) {
236            log.debug("getTime returning: {}", now); // NOI18N
237        }
238        return (now);
239    }
240
241    /**
242     * set Mrc clock and start clock
243     */
244    @SuppressWarnings("deprecation") // Date.getMinutes
245    @Override
246    public void startHardwareClock(Date now) {
247        if (DEBUG_SHOW_PUBLIC_CALLS) {
248            log.debug("startHardwareClock: {}", now); // NOI18N
249        }
250        issueClockTime(now.getHours(), now.getMinutes());
251    }
252
253    @SuppressFBWarnings(value="FE_FLOATING_POINT_EQUALITY", justification="testing for any change from previous value")
254    @Override
255    public void initializeHardwareClock(double rate, Date now, boolean getTime) {
256        // clockMode controls what we are doing: SYNCMODE_OFF, SYNCMODE_INTERNAL_MASTER, SYNCMODE_MRC_MASTER
257        boolean synchronizeWithInternalClock = internalClock.getSynchronize();
258        boolean correctFastClock = internalClock.getCorrectHardware();
259        boolean setInternal = !internalClock.getInternalMaster();
260        if (!setInternal && !synchronizeWithInternalClock && !correctFastClock) {
261            // No request to interact with hardware fast clock - ignore call
262            return;
263        }
264        int newRate = (int) rate;
265
266        // next line is the FE_FLOATING_POINT_EQUALITY annotated above
267        if (newRate != getRate()) {
268            setRate(rate);
269        }
270        if (!getTime) {
271            setTime(now);
272        }
273    }
274
275    /**
276     * stops any sync, removes listeners
277     */
278    public void dispose() {
279
280        // Remove ourselves from the timebase minute rollover event
281        if (minuteChangeListener != null) {
282            internalClock.removeMinuteChangeListener(minuteChangeListener);
283            minuteChangeListener = null;
284        }
285    }
286
287    /**
288     * Handles minute notifications for MRC Clock Monitor/Synchronizer
289     */
290    public void newInternalMinute() {
291        if (DEBUG_SHOW_SYNC_CALLS) {
292            log.debug("newInternalMinute clockMode: {} mrcInit: {} mrcRun: {}",
293                    clockMode, mrcSyncInitStateCounter, mrcSyncRunStateCounter); // NOI18N
294        }
295        // if sync and Internal is master
296        // clockMode - SYNCMODE_OFF, SYNCMODE_INTERNAL_MASTER, SYNCMODE_MRC_MASTER
297        if (clockMode == SYNCMODE_INTERNAL_MASTER) {
298            Date now = internalClock.getTime();
299            setTime(now);
300        }
301    }
302
303    @SuppressWarnings("deprecation") // Date.getTime
304    private void readClockPacket(MrcMessage r) {
305        lastClockReadPacket = r;
306        mrcLastHour = r.getElement(2) & 0x1F;
307        mrcLastMinute = r.getElement(4) & 0xFF;
308        if ((r.getElement(2) & 0xC0) == 0x80) {
309            mrcLast1224 = true;
310        } else {
311            mrcLast1224 = false;
312        }
313        if ((r.getElement(2) & 0xC0) == 0x0) {
314            mrcLastAmPm = true;
315        } else {
316            mrcLastAmPm = false;
317        }
318        Date now = internalClock.getTime();
319        if (mrcLast1224) { // is 24 hour mode
320            now.setHours(mrcLastHour);
321        } else {
322            if (mrcLastAmPm) { // is AM
323                now.setHours(mrcLastHour);
324            } else {
325                now.setHours(mrcLastHour + 12);
326            }
327        }
328        now.setMinutes(mrcLastMinute);
329        now.setSeconds(0);
330        if (clockMode == SYNCMODE_MRC_MASTER) {
331            internalClock.userSetTime(now);
332        }
333    }
334
335    private void issueClockRatio(int r) {
336        log.debug("sending ratio {} to mrc cmd station", r); // NOI18N
337        MrcMessage cmdMrc = jmri.jmrix.mrc.MrcMessage.setClockRatio(r);
338        tc.sendMrcMessage(cmdMrc);
339    }
340
341    private void issueClockTime(int hh, int mm) {
342        MrcMessage cmdMrc = jmri.jmrix.mrc.MrcMessage.setClockTime(hh, mm);
343        tc.sendMrcMessage(cmdMrc);
344    }
345
346    private final static Logger log = LoggerFactory.getLogger(MrcClockControl.class);
347}