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}