Java tutorial
/** * Copyright (c) 2010-2019 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0 * * SPDX-License-Identifier: EPL-2.0 */ package org.openhab.binding.ntp.internal.handler; import static org.openhab.binding.ntp.internal.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.time.Instant; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; 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 * @author Erdoan Hadzhiyusein - Adapted the class to work with the new DateTimeType */ public class NtpHandler extends BaseThingHandler { private final Logger logger = LoggerFactory.getLogger(NtpHandler.class); /** timeout for requests to the NTP server */ private static final int NTP_TIMEOUT = 30000; 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 DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern(DATE_PATTERN_WITH_TZ); private final LocaleProvider localeProvider; ScheduledFuture<?> refreshJob; /** NTP host */ private String hostname; /** NTP server port */ private BigDecimal port; /** 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()); Configuration config = getThing().getConfiguration(); hostname = config.get(PROPERTY_NTP_SERVER_HOST).toString(); port = (BigDecimal) config.get(PROPERTY_NTP_SERVER_PORT); refreshInterval = (BigDecimal) config.get(PROPERTY_REFRESH_INTERVAL); refreshNtp = (BigDecimal) config.get(PROPERTY_REFRESH_NTP); refreshNtpCount = 0; try { Object timeZoneConfigValue = config.get(PROPERTY_TIMEZONE); if (timeZoneConfigValue != null) { timeZone = TimeZone.getTimeZone(timeZoneConfigValue.toString()); } else { timeZone = TimeZone.getDefault(); logger.debug("{} using default TZ '{}', because configuration property '{}' is null.", getThing().getUID(), timeZone, PROPERTY_TIMEZONE); } } catch (Exception e) { timeZone = TimeZone.getDefault(); logger.debug("{} using default TZ '{}' due to an occurred exception: ", getThing().getUID(), timeZone, e); } try { Object localeStringConfigValue = config.get(PROPERTY_LOCALE); if (localeStringConfigValue != null) { locale = new Locale(localeStringConfigValue.toString()); } else { locale = localeProvider.getLocale(); logger.debug("{} using default locale '{}', because configuration property '{}' is null.", getThing().getUID(), locale, PROPERTY_LOCALE); } } catch (Exception e) { locale = localeProvider.getLocale(); logger.debug("{} using default locale '{}' due to an occurred exception: ", getThing().getUID(), locale, e); } dateTimeChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_DATE_TIME); stringChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_STRING); try { Channel stringChannel = getThing().getChannel(stringChannelUID.getId()); if (stringChannel != null) { Configuration cfg = stringChannel.getConfiguration(); String dateTimeFormatString = cfg.get(PROPERTY_DATE_TIME_FORMAT).toString(); if (!(dateTimeFormatString == null || dateTimeFormatString.isEmpty())) { dateTimeFormat = DateTimeFormatter.ofPattern(dateTimeFormatString); } else { logger.debug("No format set in channel config for {}. Using default format.", stringChannelUID); dateTimeFormat = DateTimeFormatter.ofPattern(DATE_PATTERN_WITH_TZ); } } else { logger.debug("Missing channel: '{}'", stringChannelUID.getId()); } } catch (RuntimeException ex) { logger.debug("No channel config or invalid format for {}. Using default format. ({})", stringChannelUID, ex.getMessage()); dateTimeFormat = DateTimeFormatter.ofPattern(DATE_PATTERN_WITH_TZ); } SDF.setTimeZone(timeZone); dateTimeFormat.withZone(timeZone.toZoneId()); logger.debug( "Initialized NTP handler '{}' with configuration: host '{}', refresh interval {}, timezone {}, locale {}.", getThing().getUID(), hostname, refreshInterval, timeZone, locale); startAutomaticRefresh(); } catch (Exception ex) { logger.error("Error occurred while initializing NTP handler: {}", ex.getMessage(), ex); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.conf-error-init-handler"); } } @Override public void dispose() { refreshJob.cancel(true); super.dispose(); } private void startAutomaticRefresh() { refreshJob = scheduler.scheduleWithFixedDelay(() -> { try { refreshTimeDate(); } catch (Exception e) { logger.debug("Exception occurred during refresh: {}", e.getMessage(), e); } }, 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(), timeOffset); refreshNtpCount = refreshNtp.intValue(); } else { networkTimeInMillis = System.currentTimeMillis() + timeOffset; refreshNtpCount--; } ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochMilli(networkTimeInMillis), timeZone.toZoneId()); updateState(dateTimeChannelUID, new DateTimeType(zoned)); updateState(stringChannelUID, new StringType(dateTimeFormat.format(zoned))); } 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, port.intValue()); timeInfo.computeDetails(); long serverMillis = timeInfo.getReturnTime() + timeInfo.getOffset(); logger.debug("{} Got time update from host '{}': {}.", getThing().getUID(), hostname, SDF.format(new Date(serverMillis))); updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE); return serverMillis; } catch (UnknownHostException uhe) { logger.debug( "{} The given hostname '{}' of the timeserver is unknown -> returning current sytem time instead. ({})", getThing().getUID(), hostname, uhe.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/offline.comm-error-unknown-host [\"" + (hostname == null ? "null" : hostname) + "\"]"); } catch (IOException ioe) { logger.debug( "{} Couldn't establish network connection to host '{}' -> returning current sytem time instead. ({})", getThing().getUID(), hostname, ioe.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/offline.comm-error-connection [\"" + (hostname == null ? "null" : hostname) + "\"]"); } return System.currentTimeMillis(); } @Override public void channelLinked(ChannelUID channelUID) { refreshTimeDate(); } }