/** * Copyright (c) 2014-2017 by the respective copyright holders. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.smarthome.binding.ntp.handler; import static org.eclipse.smarthome.binding.ntp.NtpBindingConstants.*; import java.io.IOException; import java.math.BigDecimal; import java.net.InetAddress; import java.net.UnknownHostException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.TimeZone; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.apache.commons.net.ntp.NTPUDPClient; import org.apache.commons.net.ntp.TimeInfo; import org.eclipse.smarthome.config.core.Configuration; import org.eclipse.smarthome.core.i18n.LocaleProvider; import org.eclipse.smarthome.core.library.types.DateTimeType; import org.eclipse.smarthome.core.library.types.StringType; import org.eclipse.smarthome.core.thing.Channel; import org.eclipse.smarthome.core.thing.ChannelUID; import org.eclipse.smarthome.core.thing.Thing; import org.eclipse.smarthome.core.thing.ThingStatus; import org.eclipse.smarthome.core.thing.ThingStatusDetail; import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; import org.eclipse.smarthome.core.types.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * The NTP Refresh Service polls the configured timeserver with a configurable * interval and posts a new event of type ({@link DateTimeType}. * * The {@link NtpHandler} is responsible for handling commands, which are sent * to one of the channels. * * @author Marcel Verpaalen - Initial contribution OH2 ntp binding * @author Thomas.Eichstaedt-Engelen OH1 ntp binding (getTime routine) * @author Markus Rathgeb - Add locale provider */ public class NtpHandler extends BaseThingHandler { private Logger logger = LoggerFactory.getLogger(NtpHandler.class); /** timeout for requests to the NTP server */ private static final int NTP_TIMEOUT = 10000; public static final String DATE_PATTERN_WITH_TZ = "yyyy-MM-dd HH:mm:ss z"; /** for logging purposes */ private final DateFormat SDF = new SimpleDateFormat(DATE_PATTERN_WITH_TZ); /** for publish purposes */ private DateFormat dateTimeFormat = new SimpleDateFormat(DATE_PATTERN_WITH_TZ); private final LocaleProvider localeProvider; ScheduledFuture<?> refreshJob; /** NTP host */ private String hostname; /** refresh interval */ private BigDecimal refreshInterval; /** NTP refresh frequency */ private BigDecimal refreshNtp = new BigDecimal(0); /** Timezone */ private TimeZone timeZone; /** Locale */ private Locale locale; /** NTP refresh counter */ private int refreshNtpCount = 0; /** NTP system time delta */ private long timeOffset; private ChannelUID dateTimeChannelUID; private ChannelUID stringChannelUID; public NtpHandler(final Thing thing, final LocaleProvider localeProvider) { super(thing); this.localeProvider = localeProvider; } @Override public void handleCommand(ChannelUID channelUID, Command command) { // No specific commands tied to this, but we will trigger an update this.refreshNtpCount = 0; refreshTimeDate(); } @Override public void initialize() { try { logger.debug("Initializing NTP handler for '{}'.", getThing().getUID().toString()); Configuration config = getThing().getConfiguration(); hostname = (String) config.get(PROPERTY_NTP_SERVER); refreshInterval = (BigDecimal) config.get(PROPERTY_REFRESH_INTERVAL); refreshNtp = (BigDecimal) config.get(PROPERTY_REFRESH_NTP); refreshNtpCount = 0; try { timeZone = TimeZone.getTimeZone((String) config.get(PROPERTY_TIMEZONE)); } catch (Exception e) { timeZone = TimeZone.getDefault(); logger.debug("{} using default TZ: {}", getThing().getUID().toString(), timeZone); } try { String localeString = (String) config.get(PROPERTY_LOCALE); locale = new Locale(localeString); } catch (Exception e) { locale = localeProvider.getLocale(); logger.debug("{} using default locale: {}", getThing().getUID().toString(), locale); } dateTimeChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_DATE_TIME); stringChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_STRING); try { Channel stringChannel = getThing().getChannel(stringChannelUID.getId()); Configuration cfg = stringChannel.getConfiguration(); String dateTimeFormatString = (String) cfg.get(PROPERTY_DATE_TIME_FORMAT); if (!(dateTimeFormatString == null || dateTimeFormatString.isEmpty())) { dateTimeFormat = new SimpleDateFormat(dateTimeFormatString); logger.debug("Could not format {} with DateFormat '{}', using default format.", getThing().getUID().toString(), dateTimeFormatString); } } catch (Exception ex) { logger.debug("No channel config or invalid format for {}. Using default format. ({})", stringChannelUID, ex.getMessage()); dateTimeFormat = new SimpleDateFormat(DATE_PATTERN_WITH_TZ); } SDF.setTimeZone(timeZone); dateTimeFormat.setTimeZone(timeZone); logger.debug( "Initialized NTP handler '{}' with configuration: host '{}', refresh interval {}, timezone {}, locale {}.", getThing().getUID().toString(), hostname, refreshInterval, timeZone, locale); startAutomaticRefresh(); } catch (Exception ex) { String msg = "Error occurred while initializing NTP handler: " + ex.getMessage(); logger.error(msg, ex); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg); } } @Override public void dispose() { refreshJob.cancel(true); super.dispose(); } private void startAutomaticRefresh() { Runnable runnable = new Runnable() { @Override public void run() { try { refreshTimeDate(); } catch (Exception e) { logger.debug("Exception occurred during execution: {}", e.getMessage(), e); } } }; refreshJob = scheduler.scheduleAtFixedRate(runnable, 0, refreshInterval.intValue(), TimeUnit.SECONDS); } private synchronized void refreshTimeDate() { if (timeZone != null && locale != null) { long networkTimeInMillis; if (refreshNtpCount <= 0) { networkTimeInMillis = getTime(hostname); timeOffset = networkTimeInMillis - System.currentTimeMillis(); logger.debug("{} delta system time: {}", getThing().getUID().toString(), timeOffset); refreshNtpCount = refreshNtp.intValue(); } else { networkTimeInMillis = System.currentTimeMillis() + timeOffset; refreshNtpCount--; } Calendar calendar = Calendar.getInstance(timeZone, locale); calendar.setTimeInMillis(networkTimeInMillis); updateState(dateTimeChannelUID, new DateTimeType(calendar)); updateState(stringChannelUID, new StringType(dateTimeFormat.format(calendar.getTime()))); } else { logger.debug("Not refreshing, since we do not seem to be initialized yet"); } } /** * Queries the given timeserver <code>hostname</code> and returns the time * in milliseconds. * * @param hostname * the timeserver to query * @return the time in milliseconds or the current time of the system if an * error occurs. */ public long getTime(String hostname) { try { NTPUDPClient timeClient = new NTPUDPClient(); timeClient.setDefaultTimeout(NTP_TIMEOUT); InetAddress inetAddress = InetAddress.getByName(hostname); TimeInfo timeInfo = timeClient.getTime(inetAddress); logger.debug("{} Got time update from: {} : {}", getThing().getUID().toString(), hostname, SDF.format(new Date(timeInfo.getReturnTime()))); updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE); return timeInfo.getReturnTime(); } catch (UnknownHostException uhe) { String msg = getThing().getUID().toString() + " the given hostname '" + hostname + "' of the timeserver is unknown -> returning current sytem time instead."; logger.warn(msg); updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR, msg); } catch (IOException ioe) { String msg = getThing().getUID().toString() + " couldn't establish network connection [host '" + hostname + "'] -> returning current sytem time instead."; logger.warn(msg); updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR, msg); } return System.currentTimeMillis(); } @Override public void channelLinked(ChannelUID channelUID) { refreshTimeDate(); } }