org.eclipse.smarthome.binding.ntp.handler.NtpHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.smarthome.binding.ntp.handler.NtpHandler.java

Source

/**
 * 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();
    }

}