001package jmri.jmrix.loconet; 002 003import java.util.Date; 004import jmri.PowerManager; 005import jmri.implementation.DefaultClockControl; 006 007import org.slf4j.Logger; 008import org.slf4j.LoggerFactory; 009 010/** 011 * Implementation of the Hardware Fast Clock for LocoNet. 012 * <p> 013 * This module is based on a GUI module developed by Bob Jacobsen and Alex 014 * Shepherd to correct the LocoNet fast clock rate and synchronize it with the 015 * internal JMRI fast clock Timebase. The methods that actually send, correct, 016 * or receive information from the LocoNet hardware are repackaged versions of 017 * their code. 018 * <p> 019 * The LocoNet Fast Clock is controlled by the user via the Fast Clock Setup GUI 020 * that is accessed from the JMRI Tools menu. 021 * <p> 022 * For this implementation, "synchronize" implies "correct", since the two 023 * clocks run at a different rate. 024 * <p> 025 * Some of the message formats used in this class are Copyright Digitrax, Inc. 026 * and used with permission as part of the JMRI project. That permission does 027 * not extend to uses in other software products. If you wish to use this code, 028 * algorithm or these message formats outside of JMRI, please contact Digitrax 029 * Inc for separate permission. 030 * <hr> 031 * This file is part of JMRI. 032 * <p> 033 * JMRI is free software; you can redistribute it and/or modify it under the 034 * terms of version 2 of the GNU General Public License as published by the Free 035 * Software Foundation. See the "COPYING" file for a copy of this license. 036 * <p> 037 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 038 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 039 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 040 * 041 * @author Dave Duchamp Copyright (C) 2007 042 * @author Bob Jacobsen, Alex Shepherd 043 */ 044public class LnClockControl extends DefaultClockControl implements SlotListener { 045 046 047 /** 048 * Create a ClockControl object for a LocoNet clock. 049 * 050 * @param scm the LocoNet System Connection Memo to associate with this 051 * Clock Control object 052 */ 053 public LnClockControl(LocoNetSystemConnectionMemo scm) { 054 this(scm.getSlotManager(), scm.getLnTrafficController(), scm.getPowerManager()); 055 } 056 057 /** 058 * Create a ClockControl object for a LocoNet clock. 059 * 060 * @param sm the Slot Manager associated with this object 061 * @param tc the Traffic Controller associated with this object 062 * @param pm the PowerManager associated with this object 063 */ 064 public LnClockControl(SlotManager sm, LnTrafficController tc, LnPowerManager pm) { 065 super(); 066 067 this.sm = sm; 068 this.tc = tc; 069 this.pm = pm; 070 071 // listen for updated slot contents 072 if (sm != null) { 073 sm.addSlotListener(this); 074 } else { 075 log.error("No LocoNet connection available, LnClockControl can't function"); 076 } 077 078 // Get internal timebase 079 clock = jmri.InstanceManager.getDefault(jmri.Timebase.class); 080 // Create a Timebase listener for Minute change events from the internal clock 081 minuteChangeListener = new java.beans.PropertyChangeListener() { 082 @Override 083 public void propertyChange(java.beans.PropertyChangeEvent e) { 084 newMinute(); 085 } 086 }; 087 clock.addMinuteChangeListener(minuteChangeListener); 088 } 089 090 final SlotManager sm; 091 final LnTrafficController tc; 092 final LnPowerManager pm; 093 094 /* Operational variables */ 095 jmri.Timebase clock = null; 096 java.beans.PropertyChangeListener minuteChangeListener = null; 097 /* current values of clock variables */ 098 private int curDays = 0; 099 private int curHours = 0; 100 private int curMinutes = 0; 101 private int curFractionalMinutes = 900; 102 private int curRate = 1; 103 private int savedRate = 1; 104 /* current options and flags */ 105 private boolean setInternal = false; // true if LocoNet Clock is the master 106 private boolean synchronizeWithInternalClock = false; 107 private boolean inSyncWithInternalFastClock = false; 108 private boolean timebaseErrorReported = false; 109 private boolean correctFastClock = false; 110 private boolean readInProgress = false; 111 /* constants */ 112 final static long MSECPERHOUR = 3600000; 113 final static long MSECPERMINUTE = 60000; 114 final static double CORRECTION = 915.0; 115 116 /** 117 * Accessor routines 118 * @return the associated name 119 */ 120 @Override 121 public String getHardwareClockName() { 122 return (Bundle.getMessage("LocoNetFastClockName")); 123 } 124 125 @Override 126 public boolean canCorrectHardwareClock() { 127 return true; 128 } 129 130 @Override 131 public void setRate(double newRate) { 132 if (curRate == 0) { 133 savedRate = (int) newRate; // clock stopped case 134 } else { 135 curRate = (int) newRate; // clock running case 136 savedRate = curRate; 137 } 138 setClock(); 139 } 140 141 @Override 142 public boolean requiresIntegerRate() { 143 return true; 144 } 145 146 @Override 147 public double getRate() { 148 return curRate; 149 } 150 151 @SuppressWarnings("deprecation") // Date.getHours, Date.getMinutes 152 @Override 153 public void setTime(Date now) { 154 curDays = now.getDate(); 155 curHours = now.getHours(); 156 curMinutes = now.getMinutes(); 157 setClock(); 158 } 159 160 @SuppressWarnings("deprecation") // Date.getTime, Date.getHours 161 @Override 162 public Date getTime() { 163 Date tem = clock.getTime(); 164 int cHours = tem.getHours(); 165 long cNumMSec = tem.getTime(); 166 long nNumMSec = ((cNumMSec / MSECPERHOUR) * MSECPERHOUR) - (cHours * MSECPERHOUR) 167 + (curHours * MSECPERHOUR) + (curMinutes * MSECPERMINUTE); 168 // Work out how far through the current fast minute we are 169 // and add that on to the time. 170 nNumMSec += (long) (((CORRECTION - curFractionalMinutes) / CORRECTION * MSECPERMINUTE)); 171 return (new Date(nNumMSec)); 172 } 173 174 @Override 175 public void startHardwareClock(Date now) { 176 curRate = savedRate; 177 setTime(now); 178 } 179 180 @Override 181 public void stopHardwareClock() { 182 savedRate = curRate; 183 curRate = 0; 184 setClock(); 185 } 186 187 @SuppressWarnings("deprecation") // Date.getDate, Date.getHours 188 @Override 189 public void initializeHardwareClock(double rate, Date now, boolean getTime) { 190 synchronizeWithInternalClock = clock.getSynchronize(); 191 correctFastClock = clock.getCorrectHardware(); 192 setInternal = !clock.getInternalMaster(); 193 if (!setInternal && !synchronizeWithInternalClock && !correctFastClock) { 194 // No request to interact with hardware fast clock - ignore call 195 return; 196 } 197 if (rate == 0.0) { 198 if (curRate != 0) { 199 savedRate = curRate; 200 } 201 curRate = 0; 202 } else { 203 savedRate = (int) rate; 204 if (curRate != 0) { 205 curRate = savedRate; 206 } 207 } 208 curDays = now.getDate(); 209 curHours = now.getHours(); 210 curMinutes = now.getMinutes(); 211 if (!getTime) { 212 setTime(now); 213 } 214 if (getTime || synchronizeWithInternalClock || correctFastClock) { 215 inSyncWithInternalFastClock = false; 216 initiateRead(); 217 } 218 } 219 220 /** 221 * Requests read of the LocoNet fast clock 222 */ 223 public void initiateRead() { 224 if (!readInProgress) { 225 sm.sendReadSlot(LnConstants.FC_SLOT); 226 readInProgress = true; 227 } 228 } 229 230 /** 231 * Corrects the LocoNet Fast Clock 232 */ 233 @SuppressWarnings("deprecation") // Date.getDate, Date.getHours, Date.getMinutes 234 public void newMinute() { 235 // ignore if waiting on LocoNet clock read 236 if (!inSyncWithInternalFastClock) { 237 return; 238 } 239 if (correctFastClock || synchronizeWithInternalClock) { 240 // get time from the internal clock 241 Date now = clock.getTime(); 242 // skip the correction if minutes is 0 because Logic Rail Clock displays incorrectly 243 // if a correction is sent at zero minutes. 244 if (now.getMinutes() != 0) { 245 // Set the Fast Clock Day to the current Day of the month 1-31 246 curDays = now.getDate(); 247 // Update current time 248 curHours = now.getHours(); 249 curMinutes = now.getMinutes(); 250 long millis = now.getTime(); 251 // How many ms are we into the fast minute as we want to sync the 252 // Fast Clock Master Frac_Mins to the right 65.535 ms tick 253 long elapsedMS = millis % MSECPERMINUTE; 254 double frac_min = elapsedMS / (double) MSECPERMINUTE; 255 curFractionalMinutes = (int) CORRECTION - (int) (CORRECTION * frac_min); 256 setClock(); 257 } 258 } else if (setInternal) { 259 inSyncWithInternalFastClock = false; 260 initiateRead(); 261 } 262 } 263 264 /** 265 * Handle changed slot contents, due to clock changes. Can get here three 266 * ways: 1) clock slot as a result of action by a throttle and 2) clock slot 267 * responding to a read from this module 3) a slot not involving the clock 268 * changing. 269 * 270 * @param s the LocoNetSlot object which has been changed 271 */ 272 @SuppressWarnings("deprecation") // Date.getTime, Date.getHours 273 @Override 274 public void notifyChangedSlot(LocoNetSlot s) { 275 // only watch the clock slot 276 if (s.getSlot() != LnConstants.FC_SLOT) { 277 return; 278 } 279 // if don't need to know, simply return 280 if (!correctFastClock && !synchronizeWithInternalClock && !setInternal) { 281 return; 282 } 283 if (log.isDebugEnabled()) { 284 log.debug("slot update {}", s); 285 } 286 // update current clock variables from the new slot contents 287 curDays = s.getFcDays(); 288 curHours = s.getFcHours(); 289 curMinutes = s.getFcMinutes(); 290 int temRate = s.getFcRate(); 291 // reject the new rate if different and not resetting the internal clock 292 if ((temRate != curRate) && !setInternal) { 293 setRate(curRate); 294 } // keep the new rate if different and resetting the internal clock 295 else if ((temRate != curRate) && setInternal) { 296 try { 297 clock.userSetRate(temRate); 298 } catch (jmri.TimebaseRateException e) { 299 if (!timebaseErrorReported) { 300 timebaseErrorReported = true; 301 log.warn("Time base exception on setting rate from LocoNet"); 302 } 303 } 304 } 305 curFractionalMinutes = s.getFcFracMins(); 306 // we calculate a new msec value for a specific hour/minute 307 // in the current day, then set that. 308 Date tem = clock.getTime(); 309 int cHours = tem.getHours(); 310 long cNumMSec = tem.getTime(); 311 long nNumMSec = ((cNumMSec / MSECPERHOUR) * MSECPERHOUR) - (cHours * MSECPERHOUR) 312 + (curHours * MSECPERHOUR) + (curMinutes * MSECPERMINUTE); 313 // set the internal timebase based on the LocoNet clock 314 if (readInProgress && !inSyncWithInternalFastClock) { 315 // Work out how far through the current fast minute we are 316 // and add that on to the time. 317 nNumMSec += (long) (((CORRECTION - curFractionalMinutes) / CORRECTION * MSECPERMINUTE)); 318 clock.setTime(new Date(nNumMSec)); 319 } else if (setInternal) { 320 // unsolicited time change from the LocoNet 321 clock.setTime(new Date(nNumMSec)); 322 } 323 // Once we have done everything else set the flag to say we are in sync 324 inSyncWithInternalFastClock = true; 325 } 326 327 /** 328 * Push current Clock Control parameters out to LocoNet slot. 329 */ 330 private void setClock() { 331 if (setInternal || synchronizeWithInternalClock || correctFastClock) { 332 // we are allowed to send commands to the fast clock 333 LocoNetSlot s = sm.slot(LnConstants.FC_SLOT); 334 335 // load time 336 s.setFcDays(curDays); 337 s.setFcHours(curHours); 338 s.setFcMinutes(curMinutes); 339 s.setFcRate(curRate); 340 s.setFcFracMins(curFractionalMinutes); 341 342 // set other content 343 // power (GTRK_POWER, 0x01 bit in byte 7) 344 boolean power = true; 345 if (pm != null) { 346 power = (pm.getPower() == PowerManager.ON); 347 } else { 348 jmri.util.LoggingUtil.warnOnce(log, "Can't access power manager for fast clock"); 349 } 350 s.setTrackStatus(s.getTrackStatus() & (~LnConstants.GTRK_POWER) ); 351 if (power) s.setTrackStatus(s.getTrackStatus() | LnConstants.GTRK_POWER); 352 353 // and write 354 tc.sendLocoNetMessage(s.writeSlot()); 355 } 356 } 357 358 public void dispose() { 359 // Drop LocoNet connection 360 if (sm != null) { 361 sm.removeSlotListener(this); 362 } 363 364 // Remove ourselves from the Timebase minute rollover event 365 if (minuteChangeListener != null) { 366 clock.removeMinuteChangeListener(minuteChangeListener); 367 minuteChangeListener = null; 368 } 369 } 370 371 private final static Logger log = LoggerFactory.getLogger(LnClockControl.class); 372 373}