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