/** * Copyright (c) 2010-2016 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.openhab.binding.netatmo.internal; import static org.apache.commons.lang.StringUtils.isNotBlank; import static org.openhab.binding.netatmo.internal.NetatmoGenericBindingProvider.*; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import org.openhab.binding.netatmo.NetatmoBindingProvider; import org.openhab.binding.netatmo.internal.authentication.OAuthCredentials; import org.openhab.binding.netatmo.internal.camera.NetatmoCameraBinding; import org.openhab.binding.netatmo.internal.weather.NetatmoPressureUnit; import org.openhab.binding.netatmo.internal.weather.NetatmoUnitSystem; import org.openhab.binding.netatmo.internal.weather.NetatmoWeatherBinding; import org.openhab.core.binding.AbstractActiveBinding; import org.openhab.core.binding.BindingProvider; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Binding that gets measurements from the Netatmo API every couple of minutes. * * @author Andreas Brenk * @author Thomas.Eichstaedt-Engelen * @author Gaƫl L'hopital * @author Rob Nielsen * @author Ing. Peter Weiss * @since 1.4.0 */ public class NetatmoBinding extends AbstractActiveBinding<NetatmoBindingProvider> implements ManagedService { private static final String DEFAULT_USER_ID = "DEFAULT_USER"; private static final long DEFAULT_GRANULARITY = 5000; private static final long DEFAULT_REFRESH = 300000; private static final Logger logger = LoggerFactory.getLogger(NetatmoBinding.class); protected static final String CONFIG_CLIENT_ID = "clientid"; protected static final String CONFIG_CLIENT_SECRET = "clientsecret"; protected static final String CONFIG_GRANULARITY = "granularity"; protected static final String CONFIG_REFRESH = "refresh"; protected static final String CONFIG_REFRESH_TOKEN = "refreshtoken"; protected static final String CONFIG_PRESSURE_UNIT = "pressureunit"; protected static final String CONFIG_UNIT_SYSTEM = "unitsystem"; /** * the interval which is used to call the execute() method */ private long granularity = DEFAULT_GRANULARITY; /** * The refresh interval which is used to poll values from the Netatmo server */ private long refreshInterval = DEFAULT_REFRESH; /** * The next time to poll this instance. Initially 0 so pollTimeExpired() initially returns true. */ private final AtomicLong pollTime = new AtomicLong(0); private static Map<String, OAuthCredentials> credentialsCache = new HashMap<String, OAuthCredentials>(); private final NetatmoCameraBinding cameraBinding = new NetatmoCameraBinding(); private final NetatmoWeatherBinding weatherBinding = new NetatmoWeatherBinding(); /** * {@inheritDoc} */ @Override protected String getName() { return "Netatmo Refresh Service"; } /** * {@inheritDoc} */ @Override protected long getRefreshInterval() { return this.granularity; } /** * /** * {@inheritDoc} */ @SuppressWarnings("incomplete-switch") @Override protected void execute() { if (pollTimeExpired()) { logger.debug("Querying Netatmo API"); for (String userid : credentialsCache.keySet()) { // Check if weather and/or camera items are configured boolean bWeather = false; boolean bCamera = false; for (final NetatmoBindingProvider provider : providers) { for (final String itemName : provider.getItemNames()) { final String sItemType = provider.getItemType(itemName); if (NETATMO_WEATHER.equals(sItemType)) { bWeather = true; } else if (NETATMO_CAMERA.equals(sItemType)) { bCamera = true; } if (bWeather && bCamera) { break; } } } if (!bWeather && !bCamera) { logger.debug("There are not any items configured for this binding!"); continue; } OAuthCredentials oauthCredentials = getOAuthCredentials(userid); oauthCredentials.setWeather(bWeather); oauthCredentials.setCamera(bCamera); if (oauthCredentials.noAccessToken()) { // initial run after a restart, so get an access token first oauthCredentials.refreshAccessToken(); } // Start the execution of the weather station and/or camera binding if (bWeather) { weatherBinding.execute(oauthCredentials, this.providers, this.eventPublisher); } if (bCamera) { cameraBinding.execute(oauthCredentials, this.providers, this.eventPublisher); } // schedule the next poll at the standard refresh interval schedulePoll(this.refreshInterval); } } } /** * {@inheritDoc} */ @Override public void allBindingsChanged(BindingProvider provider) { if (provider instanceof NetatmoBindingProvider) { schedulePoll(0); } super.allBindingsChanged(provider); } /** * Returns the cached {@link OAuthCredentials} for the given {@code userid}. * If their is no such cached {@link OAuthCredentials} element, the cache is * searched with the {@code DEFAULT_USER}. If there is still no cached * element found {@code NULL} is returned. * * @param userid * the userid to find the {@link OAuthCredentials} * @return the cached {@link OAuthCredentials} or {@code NULL} */ public static OAuthCredentials getOAuthCredentials(String userid) { if (credentialsCache.containsKey(userid)) { return credentialsCache.get(userid); } else { return credentialsCache.get(DEFAULT_USER_ID); } } protected void addBindingProvider(NetatmoBindingProvider bindingProvider) { super.addBindingProvider(bindingProvider); } protected void removeBindingProvider(NetatmoBindingProvider bindingProvider) { super.removeBindingProvider(bindingProvider); } /** * {@inheritDoc} */ @Override public void updated(final Dictionary<String, ?> config) throws ConfigurationException { if (config != null) { final String granularityString = (String) config.get(CONFIG_GRANULARITY); if (isNotBlank(granularityString)) { this.granularity = Long.parseLong(granularityString); } final String refreshIntervalString = (String) config.get(CONFIG_REFRESH); if (isNotBlank(refreshIntervalString)) { this.refreshInterval = Long.parseLong(refreshIntervalString); } Enumeration<String> configKeys = config.keys(); while (configKeys.hasMoreElements()) { String configKey = configKeys.nextElement(); // the config-key enumeration contains additional keys that we // don't want to process here ... if (CONFIG_GRANULARITY.equals(configKey) || CONFIG_REFRESH.equals(configKey) || "service.pid".equals(configKey)) { continue; } String userid; String configKeyTail; if (configKey.contains(".")) { String[] keyElements = configKey.split("\\."); userid = keyElements[0]; configKeyTail = keyElements[1]; } else { userid = DEFAULT_USER_ID; configKeyTail = configKey; } OAuthCredentials credentials = credentialsCache.get(userid); if (credentials == null) { credentials = new OAuthCredentials(); credentialsCache.put(userid, credentials); } String value = (String) config.get(configKeyTail); if (CONFIG_CLIENT_ID.equals(configKeyTail)) { credentials.setClientId(value); } else if (CONFIG_CLIENT_SECRET.equals(configKeyTail)) { credentials.setClientSecret(value); } else if (CONFIG_REFRESH_TOKEN.equals(configKeyTail)) { credentials.setRefreshToken(value); } else if (CONFIG_PRESSURE_UNIT.equals(configKeyTail)) { try { weatherBinding.setPressureUnit(NetatmoPressureUnit.fromString(value)); } catch (IllegalArgumentException e) { throw new ConfigurationException(configKey, "the value '" + value + "' is not valid for the configKey '" + configKey + "'"); } } else if (CONFIG_UNIT_SYSTEM.equals(configKeyTail)) { try { weatherBinding.setUnitSystem(NetatmoUnitSystem.fromString(value)); } catch (IllegalArgumentException e) { throw new ConfigurationException(configKey, "the value '" + value + "' is not valid for the configKey '" + configKey + "'"); } } else { throw new ConfigurationException(configKey, "the given configKey '" + configKey + "' is unknown"); } } setProperlyConfigured(true); } } /** * Return true if this instance is at or past the time to poll. * * @return if this instance is at or past the time to poll. */ private boolean pollTimeExpired() { return System.currentTimeMillis() >= this.pollTime.get(); } /** * Record the earliest time in the future at which we are allowed to poll this instance. * * @param future * the number of milliseconds in the future */ private void schedulePoll(long future) { this.pollTime.set(System.currentTimeMillis() + future); } }