/* * Copyright (C) 2011 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.ros.time; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import org.apache.commons.logging.Log; import org.apache.commons.net.ntp.NTPUDPClient; import org.apache.commons.net.ntp.TimeInfo; import org.ros.log.RosLogFactory; import org.ros.math.CollectionMath; import org.ros.message.Duration; import org.ros.message.Time; import java.io.IOException; import java.net.InetAddress; import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** * Provides NTP synchronized wallclock (actual) time. * * @author damonkohler@google.com (Damon Kohler) */ public class NtpTimeProvider implements TimeProvider { private static final Log log = RosLogFactory.getLog(NtpTimeProvider.class); private static final int SAMPLE_SIZE = 11; private final InetAddress host; private final ScheduledExecutorService scheduledExecutorService; private final WallTimeProvider wallTimeProvider; private final NTPUDPClient ntpClient; private long offset; private ScheduledFuture<?> scheduledFuture; /** * @param host * the NTP host to use */ public NtpTimeProvider(InetAddress host, ScheduledExecutorService scheduledExecutorService) { this.host = host; this.scheduledExecutorService = scheduledExecutorService; wallTimeProvider = new WallTimeProvider(); ntpClient = new NTPUDPClient(); offset = 0; scheduledFuture = null; } /** * Update the current time offset from the configured NTP host. * * @throws IOException */ public void updateTime() throws IOException { List<Long> offsets = Lists.newArrayList(); for (int i = 0; i < SAMPLE_SIZE; i++) { offsets.add(computeOffset()); } offset = CollectionMath.median(offsets); log.info(String.format("NTP time offset: %d ms", offset)); } private long computeOffset() throws IOException { if (log.isDebugEnabled()) { log.debug("Updating time offset from NTP server: " + host.getHostName()); } TimeInfo time; try { time = ntpClient.getTime(host); } catch (IOException e) { log.error("Failed to read time from NTP server: " + host.getHostName(), e); throw e; } time.computeDetails(); return time.getOffset(); } /** * Starts periodically updating the current time offset periodically. * * <p> * The first time update happens immediately. * * <p> * Note that errors thrown while periodically updating time will be logged but * not rethrown. * * @param period * time between updates * @param unit * unit of period */ public void startPeriodicUpdates(long period, TimeUnit unit) { scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { updateTime(); } catch (IOException e) { log.error("Periodic NTP update failed.", e); } } }, 0, period, unit); } /** * Stops periodically updating the current time offset. */ public void stopPeriodicUpdates() { Preconditions.checkNotNull(scheduledFuture); scheduledFuture.cancel(true); scheduledFuture = null; } @Override public Time getCurrentTime() { Time currentTime = wallTimeProvider.getCurrentTime(); return currentTime.add(Duration.fromMillis(offset)); } }