org.openhab.binding.ihc.internal.IhcBinding.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.ihc.internal.IhcBinding.java

Source

/**
 * Copyright (c) 2010-2015, openHAB.org and others.
 *
 * 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.ihc.internal;

import java.util.ArrayList;
import java.util.Dictionary;
import java.util.EventObject;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.lang.StringUtils;
import org.openhab.binding.ihc.IhcBindingProvider;
import org.openhab.binding.ihc.ws.IhcClient;
import org.openhab.binding.ihc.ws.IhcClient.ConnectionState;
import org.openhab.binding.ihc.ws.IhcEnumValue;
import org.openhab.binding.ihc.ws.IhcEventListener;
import org.openhab.binding.ihc.ws.IhcExecption;
import org.openhab.binding.ihc.ws.datatypes.*;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.binding.BindingChangeListener;
import org.openhab.core.binding.BindingProvider;
import org.openhab.core.items.Item;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.Type;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * IhcBinding order runtime value notifications from IHC / ELKO LS controller
 * and post values to the openHAB event bus when notification is received.
 * 
 * Binding also polls resources from controller where interval is configured.
 * 
 * @author Pauli Anttila
 * @author Simon Merschjohann
 * @since 1.1.0
 */
public class IhcBinding extends AbstractActiveBinding<IhcBindingProvider>
        implements ManagedService, IhcEventListener, BindingChangeListener {

    private static final Logger logger = LoggerFactory.getLogger(IhcBinding.class);

    private long refreshInterval = 1000;

    /** Holds runtime notification reorder timeout in milliseconds */
    private final int NOTIFICATIONS_REORDER_WAIT_TIME = 2000;

    /** Holds time stamps in seconds when binding items states are refreshed */
    private Map<String, Long> lastUpdateMap = new HashMap<String, Long>();

    /** IHC / ELKO LS Controller client */
    private static IhcClient ihc = null;

    /** IP address of IHC / ELKO LS Controller */
    private static String ip = null;

    /** User name for controller authentication */
    private static String username = null;

    /** Password for controller authentication */
    private static String password = null;

    /**
     * Path for IHC / ELKO lS project file, if it's empty/null project is
     * download from controller
     */
    private static String projectFile = null;

    /** Path for resource's dump. Dump is useful to find out resource id's. */
    private static String dumpResourceFile = null;

    /** Timeout for controller communication */
    private static int timeout = 5000;

    /**
     * Store current state of the controller, use to recognize when controller
     * state is changed
     */
    private WSControllerState controllerState = null;

    /**
     * Reminder to slow down resource value notification ordering from
     * controller.
     */
    private NotificationsRequestReminder reminder = null;
    private boolean reconnectRequest = false;
    private boolean valueNotificationRequest = false;

    @Override
    protected String getName() {
        return "IHC / ELKO LS refresh and notification listener service";
    }

    @Override
    protected long getRefreshInterval() {
        return refreshInterval;
    }

    public void activate(ComponentContext componentContext) {
    }

    public void deactivate(ComponentContext componentContext) {
        disconnect();
    }

    protected boolean isReconnectRequestActivated() {
        synchronized (IhcBinding.class) {
            return reconnectRequest;
        }
    }

    protected void setReconnectRequest(boolean reconnect) {
        synchronized (IhcBinding.class) {
            this.reconnectRequest = reconnect;
        }
    }

    protected boolean isValueNotificationRequestActivated() {
        synchronized (IhcBinding.class) {
            return valueNotificationRequest;
        }
    }

    protected void setValueNotificationRequest(boolean valueNotificationRequest) {
        synchronized (IhcBinding.class) {
            this.valueNotificationRequest = valueNotificationRequest;
        }
    }

    /**
     * Initialize IHC client and open connection to IHC / ELKO LS controller.
     * 
     */
    public void connect() throws IhcExecption {

        if (StringUtils.isNotBlank(ip) && StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) {

            logger.info("Connecting to IHC / ELKO LS controller [IP='{}' Username='{}' Password='{}'].",
                    new Object[] { ip, username, "******" });

            ihc = new IhcClient(ip, username, password, timeout);
            ihc.setProjectFile(projectFile);
            ihc.setDumpResourceInformationToFile(dumpResourceFile);
            ihc.openConnection();
            controllerState = ihc.getControllerState();
            ihc.addEventListener(this);

        } else {
            logger.warn(
                    "Couldn't connect to IHC controller because of missing connection parameters [IP='{}' Username='{}' Password='{}'].",
                    new Object[] { ip, username, "******" });
        }
    }

    /**
     * Disconnect connection to IHC / ELKO LS controller.
     * 
     */
    public void disconnect() {
        if (ihc != null) {
            try {
                ihc.removeEventListener(this);
                ihc.closeConnection();
            } catch (IhcExecption e) {
                logger.error("Couldn't close connection to IHC controller", e);
            }
        }
    }

    /**
     * @{inheritDoc
     */
    @Override
    public void execute() {

        if (ihc == null || isReconnectRequestActivated()) {

            try {
                if (ihc != null) {
                    disconnect();
                }
                connect();
                setReconnectRequest(false);
                enableResourceValueNotifications();
            } catch (IhcExecption e) {
                logger.warn("Can't open connection to controller", e);
                return;
            }
        }

        if (ihc != null) {

            if (isValueNotificationRequestActivated()) {
                try {
                    enableResourceValueNotifications();
                } catch (IhcExecption e) {
                    logger.warn("Can't enable resource value notifications from controller", e);
                }
            }

            // Poll all requested resources from controller
            for (IhcBindingProvider provider : providers) {
                for (String itemName : provider.getItemNames()) {

                    int resourceId = provider.getResourceIdForInBinding(itemName);
                    int itemRefreshInterval = provider.getRefreshInterval(itemName) * 1000;

                    if (resourceId > 0 && itemRefreshInterval > 0) {

                        Long lastUpdateTimeStamp = lastUpdateMap.get(itemName);
                        if (lastUpdateTimeStamp == null) {
                            lastUpdateTimeStamp = 0L;
                        }

                        long age = System.currentTimeMillis() - lastUpdateTimeStamp;
                        boolean needsUpdate = age >= itemRefreshInterval;

                        if (needsUpdate) {
                            logger.debug("Item '{}' is about to be refreshed now", itemName);

                            try {
                                WSResourceValue resourceValue = null;

                                try {
                                    resourceValue = ihc.resourceQuery(resourceId);
                                } catch (IhcExecption e) {
                                    logger.warn("Value could not be read from controller - retrying one time.", e);

                                    try {
                                        resourceValue = ihc.resourceQuery(resourceId);
                                    } catch (IhcExecption ex) {
                                        logger.error("Communication error", ex);
                                        logger.debug("Reconnection request");
                                        setReconnectRequest(true);
                                    }
                                }

                                if (resourceValue != null) {
                                    Class<? extends Item> itemType = provider.getItemType(itemName);
                                    State value = IhcDataConverter.convertResourceValueToState(itemType,
                                            resourceValue);
                                    eventPublisher.postUpdate(itemName, value);
                                }

                            } catch (Exception e) {
                                logger.error("Error occured during resource query", e);
                            }

                            lastUpdateMap.put(itemName, System.currentTimeMillis());
                        }
                    }
                }
            }
        } else {
            logger.warn("Controller is not initialized => refresh cycle aborted!");
        }

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void allBindingsChanged(BindingProvider provider) {
        logger.debug("allBindingsChanged");
        setValueNotificationRequest(true);
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    public void bindingChanged(BindingProvider provider, String itemName) {
        logger.trace("bindingChanged {}", itemName);
        if (reminder != null) {
            reminder.cancel();
            reminder = null;
        }

        reminder = new NotificationsRequestReminder(NOTIFICATIONS_REORDER_WAIT_TIME);
    }

    /**
     * Used to slow down resource value notification ordering process. All
     * resource values need to be ordered by one request from the controller,
     * therefore wait that all binding items are loaded.
     */
    private class NotificationsRequestReminder {
        Timer timer;

        public NotificationsRequestReminder(int milliseconds) {
            timer = new Timer();
            timer.schedule(new RemindTask(), milliseconds);
        }

        public void cancel() {
            timer.cancel();
        }

        class RemindTask extends TimerTask {

            public void run() {
                logger.debug("Timer: enableResourceValueNotifications");
                setValueNotificationRequest(true);
                timer.cancel();
            }
        }
    }

    public void updated(Dictionary<String, ?> config) throws ConfigurationException {

        logger.debug("Configuration updated, config {}", config != null ? true : false);

        if (config != null) {
            ip = (String) config.get("ip");
            username = (String) config.get("username");
            password = (String) config.get("password");
            timeout = Integer.parseInt((String) config.get("timeout"));
            projectFile = (String) config.get("projectFile");
            dumpResourceFile = (String) config.get("dumpResourceFile");
            setProperlyConfigured(true);
            setReconnectRequest(true);
        }
    }

    @Override
    protected void internalReceiveCommand(String itemName, Command command) {
        updateResource(itemName, command, false);
    }

    @Override
    public void internalReceiveUpdate(String itemName, State newState) {
        updateResource(itemName, newState, true);
    }

    /**
     * Update resource value to IHC controller.
     */
    private void updateResource(String itemName, Type type, boolean updateOnlyExclusiveOutBinding) {

        if (itemName != null) {
            Command cmd = null;
            try {
                cmd = (Command) type;
            } catch (Exception e) {
            }

            IhcBindingProvider provider = findFirstMatchingBindingProvider(itemName, cmd);

            if (provider == null) {
                // command not configured, skip
                return;
            }

            if (updateOnlyExclusiveOutBinding && provider.hasInBinding(itemName)) {
                logger.trace("Ignore in binding update for item '{}'", itemName);
                return;
            }

            logger.debug("Received update/command (item='{}', state='{}', class='{}')",
                    new Object[] { itemName, type.toString(), type.getClass().toString() });

            if (ihc == null) {
                logger.warn("Controller is not initialized, abort resource value update for item '{}'!", itemName);
                return;
            }

            if (ihc.getConnectionState() != ConnectionState.CONNECTED) {
                logger.warn("Connection to controller is not ok, abort resource value update for item '{}'!",
                        itemName);
                return;
            }

            try {
                int resourceId = provider.getResourceId(itemName, (Command) type);

                logger.trace("found resourceId {} (item='{}', state='{}', class='{}')",
                        new Object[] { new Integer(resourceId).toString(), itemName, type.toString(),
                                type.getClass().toString() });

                if (resourceId > 0) {
                    WSResourceValue value = ihc.getResourceValueInformation(resourceId);

                    ArrayList<IhcEnumValue> enumValues = null;

                    if (value instanceof WSEnumValue) {

                        enumValues = ihc.getEnumValues(((WSEnumValue) value).getDefinitionTypeID());
                    }

                    // check if configuration has a custom value defined
                    // (0->OFF, 1->ON, >1->trigger)
                    // if that is the case, the type will be overridden with a
                    // new type

                    Integer val = provider.getValue(itemName, (Command) type);
                    boolean trigger = false;
                    if (val != null) {
                        if (val == 0) {
                            type = OnOffType.OFF;
                        } else if (val == 1) {
                            type = OnOffType.ON;
                        } else {
                            trigger = true;
                        }
                    } else {
                        // the original type is kept
                    }

                    if (!trigger) {
                        value = IhcDataConverter.convertCommandToResourceValue(type, value, enumValues);

                        boolean result = updateResource(value);

                        if (result == true) {
                            logger.debug("Item updated '{}' succesfully sent", itemName);
                        } else {
                            logger.error("Item '{}' update failed", itemName);
                        }
                    } else {
                        value = IhcDataConverter.convertCommandToResourceValue(OnOffType.ON, value, enumValues);

                        boolean result = updateResource(value);

                        if (result == true) {
                            logger.debug("Item '{}' trigger started", itemName);

                            Thread.sleep(val);

                            value = IhcDataConverter.convertCommandToResourceValue(OnOffType.OFF, value,
                                    enumValues);

                            result = updateResource(value);

                            if (result == true) {
                                logger.debug("Item '{}' trigger completed", itemName);
                            } else {
                                logger.error("Item '{}' trigger stop failed", itemName);
                            }

                        } else {
                            logger.error("Item '{}' update failed", itemName);
                        }
                    }

                } else {
                    logger.error("resourceId invalid");
                }

            } catch (IhcExecption e) {
                logger.error("Can't update Item '{}' value ", itemName, e);
            } catch (Exception e) {
                logger.error("Error occured during item update", e);
            }
        }

    }

    /**
     * Update resource value to IHC controller.
     */
    private boolean updateResource(WSResourceValue value) throws IhcExecption {
        boolean result = false;

        try {
            result = ihc.resourceUpdate(value);

        } catch (IhcExecption e) {

            logger.warn("Value could not be set - retrying one time: {}", e.getMessage());

            result = ihc.resourceUpdate(value);
        }

        return result;
    }

    /**
     * Find the first matching {@link IhcBindingProvider} according to
     * <code>itemName</code> and <code>command</code>.
     * 
     * @param itemName
     * 
     * @return the matching binding provider or <code>null</code> if no binding
     *         provider could be found
     */
    private IhcBindingProvider findFirstMatchingBindingProvider(String itemName, Command type) {
        IhcBindingProvider firstMatchingProvider = null;

        for (IhcBindingProvider provider : this.providers) {
            if (provider.getResourceId(itemName, type) > 0 || provider.getResourceId(itemName, null) > 0) {
                firstMatchingProvider = provider;
                break;
            }
        }

        return firstMatchingProvider;
    }

    /**
     * Order resource value notifications from IHC controller.
     */
    private void enableResourceValueNotifications() throws IhcExecption {

        logger.debug("Subscripe resource runtime value notifications");

        if (ihc != null) {

            if (ihc.getConnectionState() != ConnectionState.CONNECTED) {
                logger.debug("Controller is connecting, abort subscribe");
                return;
            }

            List<Integer> resourceIdList = new ArrayList<Integer>();

            for (IhcBindingProvider provider : providers) {
                for (String itemName : provider.getItemNames()) {
                    resourceIdList.add(provider.getResourceIdForInBinding(itemName));
                }
            }

            if (resourceIdList.size() > 0) {
                logger.debug("Enable runtime notfications for {} resources", resourceIdList.size());

                try {
                    ihc.enableRuntimeValueNotifications(resourceIdList);
                } catch (IhcExecption e) {
                    logger.debug("Reconnection request");
                    setReconnectRequest(true);
                }
            }
        } else {
            logger.warn("Controller is not initialized!");
            logger.debug("Reconnection request");
            setReconnectRequest(true);
        }

        setValueNotificationRequest(false);
    }

    @Override
    public void statusUpdateReceived(EventObject event, WSControllerState state) {

        logger.trace("Controller state {}", state.getState());

        if (controllerState.getState().equals(state.getState()) == false) {
            logger.info("Controller state change detected ({} -> {})", controllerState.getState(),
                    state.getState());

            if (controllerState.getState().equals(IhcClient.CONTROLLER_STATE_INITIALIZE)
                    || state.getState().equals(IhcClient.CONTROLLER_STATE_READY)) {

                logger.debug("Reconnection request");
                setReconnectRequest(true);
            }

            controllerState.setState(state.getState());
        }

    }

    @Override
    public void resourceValueUpdateReceived(EventObject event, WSResourceValue value) {

        for (IhcBindingProvider provider : providers) {
            for (String itemName : provider.getItemNames()) {

                int resourceId = provider.getResourceIdForInBinding(itemName);

                if (value.getResourceID() == resourceId) {

                    if (!provider.hasInBinding(itemName)) {

                        logger.trace("{} has no inbinding...skip update to OpenHAB bus", itemName);

                    } else {

                        Class<? extends Item> itemType = provider.getItemType(itemName);
                        State state = IhcDataConverter.convertResourceValueToState(itemType, value);

                        logger.trace("Received resource value update (item='{}', state='{}')",
                                new Object[] { itemName, state });

                        eventPublisher.postUpdate(itemName, state);

                    }
                }

            }
        }
    }

    @Override
    public void errorOccured(EventObject event, IhcExecption e) {
        logger.warn("Error occured on communication to IHC controller: {}", e.getMessage());

        logger.debug("Reconnection request");
        setReconnectRequest(true);
    }
}