/** * 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.opensprinkler.internal; import static org.openhab.binding.opensprinkler.internal.OpenSprinklerBinding.OpenSprinklerMode.HTTP; import java.util.Arrays; import java.util.Dictionary; import org.apache.commons.lang.StringUtils; import org.openhab.binding.opensprinkler.OpenSprinklerBindingProvider; import org.openhab.core.binding.AbstractActiveBinding; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.types.Command; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.jonathangiles.opensprinkler.OpenSprinkler; import net.jonathangiles.opensprinkler.OpenSprinklerFactory; /** * Binding for OpenSprinkler. At present this only supports the * OpenSprinkler Pi connecting via GPIO, but perhaps in the future * will also support HTTP connections to both OpenSprinkler and * OpenSprinkler Pi. * * @author Jonathan Giles (http://www.jonathangiles.net) * @since 1.3.0 */ public class OpenSprinklerBinding extends AbstractActiveBinding<OpenSprinklerBindingProvider>implements ManagedService { private static String[] VALID_STATION_NUMBER_LIST = { "8", "16", "24", "32", "40", "48" }; private static String RAIN_SENSOR_CONTACT_VALUE = "rs"; private static final Logger logger = LoggerFactory.getLogger(OpenSprinklerBinding.class); private OpenSprinkler openSprinkler; // configuration properties private int numberOfStations = 8; private static long refreshInterval = 60000; private OpenSprinklerMode mode = null; private String url; private String password; /** * Represents the valid modes of the binding {@code gpio} and {@code http}. */ enum OpenSprinklerMode { GPIO, HTTP; } public OpenSprinklerBinding() { // no-op } @Override public void activate() { updateBinding(); super.activate(); } @Override public void deactivate() { // deallocate Resources here that are no longer needed and // should be reset when activating this binding again openSprinkler.closeConnection(); } /** * @{inheritDoc} */ @Override protected String getName() { return "OpenSprinkler Refresh Service"; } /** * @{inheritDoc} */ @Override protected long getRefreshInterval() { return refreshInterval; } /** * @{inheritDoc} */ @Override protected void execute() { if (openSprinkler == null) { logger.debug("State is not being updated with the OpenSprinkler device because access not initialized."); return; } logger.debug("State is being updated with the OpenSprinkler device."); /* Parse through all stations and update their state value */ for (int station = 0; station < numberOfStations; station++) { String stationItemName = findFirstMatchingItemName(station); logger.debug("Checking state of item: " + stationItemName); if (stationItemName != null) { if (openSprinkler.isStationOpen(station)) { eventPublisher.postUpdate(stationItemName, OnOffType.ON); } else { eventPublisher.postUpdate(stationItemName, OnOffType.OFF); } } } /* Find the rain sensor item and update it with an accurate value from open sprinkler if found */ String rainSensorItemName = findFirstMatchingItemName(RAIN_SENSOR_CONTACT_VALUE); logger.debug("Checking state of item: " + rainSensorItemName); if (rainSensorItemName != null) { if (openSprinkler.isRainDetected()) { eventPublisher.postUpdate(rainSensorItemName, OpenClosedType.OPEN); } else { eventPublisher.postUpdate(rainSensorItemName, OpenClosedType.CLOSED); } } } /** * @{inheritDoc} */ @Override protected void internalReceiveCommand(String itemName, Command command) { // the code being executed when a command was sent on the openHAB // event bus goes here. This method is only called if one of the // BindingProviders provide a binding for the given 'itemName'. if (command instanceof OnOffType) { final OnOffType switchCommand = (OnOffType) command; final OpenSprinklerBindingProvider bindingProvider = findFirstMatchingBindingProvider(itemName, command); final int station = bindingProvider.getStationNumber(itemName); if (station < 0 || station >= numberOfStations) { logger.warn("Station " + station + " is not in the valid [" + 0 + ".." + numberOfStations + "] range"); return; } switch (switchCommand) { case ON: openSprinkler.openStation(station); break; case OFF: openSprinkler.closeStation(station); break; } return; } if (command instanceof OpenClosedType) { // abort processing return; } logger.debug("Provided command " + command + " is not of type 'OnOffType' or 'OpenClosedType'"); } protected void addBindingProvider(OpenSprinklerBindingProvider bindingProvider) { super.addBindingProvider(bindingProvider); } protected void removeBindingProvider(OpenSprinklerBindingProvider bindingProvider) { super.removeBindingProvider(bindingProvider); } /** * {@inheritDoc} */ @Override public void updated(Dictionary<String, ?> config) throws ConfigurationException { if (config != null) { // to override the mode (gpio or http) one has to add a // parameter to openhab.cfg like openSprinkler:mode=[gpio|http] String modeString = (String) config.get("mode"); if (StringUtils.isNotBlank(modeString)) { try { mode = OpenSprinklerMode.valueOf(modeString.toUpperCase()); } catch (IllegalArgumentException iae) { throw new ConfigurationException("openSprinkler:mode", "Unknown OpenSprinkler mode " + mode + "! Valid modes are 'gpio' and 'http'. Please fix your openhab.cfg."); } } // to specify the http url one has to add a // parameter to openhab.cfg like openSprinkler:httpUrl=<url> url = (String) config.get("httpUrl"); // to specify the http password one has to add a // parameter to openhab.cfg like openSprinkler:httpPassword=<password> password = (String) config.get("httpPassword"); // to override the refresh rate of the rain sensor check one has to add a // parameter to openhab.cfg like openSprinkler:rsRefresh String rsRefreshRate = (String) config.get("refreshInterval"); if (StringUtils.isNotBlank(rsRefreshRate)) { refreshInterval = Long.parseLong(rsRefreshRate); } // to override the number of stations one has to add a // parameter to openhab.cfg like openSprinkler:numberOfStations=<count> String numberOfStationsString = (String) config.get("numberOfStations"); if (StringUtils.isNotBlank(numberOfStationsString)) { if (Arrays.asList(VALID_STATION_NUMBER_LIST).contains(numberOfStationsString)) { numberOfStations = Integer.parseInt(numberOfStationsString); } else { logger.warn(numberOfStationsString + " is not a valid number of stations OpenSprinkler supports. Defaulting to 8."); } } // read further config parameters here ... setProperlyConfigured(true); // then update the binding updateBinding(); } } /** * Find the first matching {@link OpenSprinklerBindingProvider} according to * <code>itemName</code> and <code>command</code>. * * @param itemName * @param command * * @return the matching binding provider or <code>null</code> if no binding * provider could be found */ private OpenSprinklerBindingProvider findFirstMatchingBindingProvider(String itemName, Command command) { OpenSprinklerBindingProvider firstMatchingProvider = null; for (OpenSprinklerBindingProvider provider : this.providers) { boolean match = provider.providesBindingFor(itemName); if (match) { firstMatchingProvider = provider; break; } } return firstMatchingProvider; } /** * Find the first matching provider name according to * <code>commandValue</code>. * * @param commandValue * * @return the matching binding provider or <code>null</code> if no binding * provider could be found */ private String findFirstMatchingItemName(String commandValue) { String firstMatchingName = null; for (OpenSprinklerBindingProvider provider : this.providers) { for (String itemName : provider.getItemNames()) { boolean match = provider.getCommand(itemName).equals(commandValue); if (match) { firstMatchingName = itemName; break; } } } return firstMatchingName; } /** * Find the first matching provider name according to * <code>stationValue</code>. * * @param stationValue * * @return the matching binding provider or <code>null</code> if no binding * provider could be found */ private String findFirstMatchingItemName(int stationValue) { String firstMatchingName = null; for (OpenSprinklerBindingProvider provider : this.providers) { for (String itemName : provider.getItemNames()) { boolean match = (provider.getStationNumber(itemName) == stationValue); if (match) { firstMatchingName = itemName; break; } } } return firstMatchingName; } private void updateBinding() { if (openSprinkler != null) { openSprinkler.closeConnection(); } if (mode == null) { return; } switch (mode) { case GPIO: openSprinkler = OpenSprinklerFactory.newGpioConnection(); break; case HTTP: openSprinkler = OpenSprinklerFactory.newHttpConnection(url, password); break; default: throw new IllegalStateException("Unknown OpenSprinkler mode: " + mode + "! Since it is checked while initialization already, this Exception should never be thrown!"); } openSprinkler.setNumberOfStations(numberOfStations); logger.info("OpenSprinkler binding running in " + mode + " mode" + (HTTP.equals(mode) ? " with url " + url : "") + ". Running with " + numberOfStations + " stations enabled and a refresh rate of " + refreshInterval + "."); } }