org.openhab.binding.plum.PlumActiveBinding.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.plum.PlumActiveBinding.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.plum;

import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.json.JSONObject;
import org.openhab.binding.plum.internal.PlumTCPStreamListener;
import org.openhab.binding.plum.internal.PlumUtilities;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.binding.BindingProvider;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Mike Nemat
 */

public class PlumActiveBinding extends AbstractActiveBinding<PlumBindingProvider> implements ManagedService {
    private static final Logger logger = LoggerFactory.getLogger(PlumActiveBinding.class);
    private HashMap<String, String> m_config = null;
    private long m_refreshInterval = 60000L;
    private boolean m_isActive = false; // state of binding
    private Map<String, Integer> currentLlidLevels = new HashMap<String, Integer>();

    // Mapping of Plum IP addreses to Threads monitoring them
    private static Map<String, Thread> threads = new HashMap<String, Thread>();
    private static Map<String, PlumTCPStreamListener> plumTCPStreams = new HashMap<String, PlumTCPStreamListener>();

    /**
     * Constructor
     */
    public PlumActiveBinding() {

    }

    /**
     * Inherited from AbstractBinding. This method is invoked by the framework
     * whenever a command is coming from openhab, i.e. a switch is flipped via
     * the GUI or other controls. The binding translates this openhab command
     * into a message to the modem. {@inheritDoc}
     */

    @Override
    public void internalReceiveCommand(String itemName, Command command) {
        logger.info("Item: {} got command {}", itemName, command);

        if (!(isProperlyConfigured() && m_isActive)) {
            logger.debug("not ready to handle commands yet, returning.");
            return;
        }
        boolean commandHandled = false;
        for (PlumBindingProvider provider : providers) {
            if (provider.providesBindingFor(itemName)) {
                commandHandled = true;
                PlumBindingConfig c = provider.getPlumBindingConfig(itemName);
                if (c == null) {
                    logger.warn("could not find config for item {}", itemName);
                } else {
                    sendCommand(c, command);
                }
            }
        }

        if (!commandHandled) {
            logger.warn("No converter found for item = {}, command = {}, ignoring.", itemName, command.toString());
        }
    }

    /**
     * Send command to Plum device
     * 
     * @param c
     *            item binding configuration
     * @param command
     *            The command to be sent
     */
    private void sendCommand(PlumBindingConfig c, Command command) {

        if (command instanceof OnOffType || command instanceof PercentType) {
            int val = -1;
            if (command instanceof OnOffType) {
                if (((OnOffType) command).equals(OnOffType.ON)) {
                    val = 255;
                } else if (((OnOffType) command).equals(OnOffType.OFF)) {
                    val = 0;
                }

            } else if (command instanceof PercentType) {
                val = (int) ((((PercentType) command).doubleValue() / 100) * 255);
            }
            if (val > -1) {
                Map<String, Object> args = new HashMap<String, Object>();
                args.put("level", val);
                args.put("llid", c.getLlid());
                try {
                    sendHttpCommand(c.getIpAddr(), "setLogicalLoadLevel", args);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        } else if (command instanceof IncreaseDecreaseType) {
            Integer val = currentLlidLevels.get(c.getLlid());
            if (val == null) {
                logger.warn("You must wait for the first poll (60s after startup) before controlling dimmers");
                return;
            }
            if (command.equals(IncreaseDecreaseType.INCREASE)) {
                if (255 - val <= 13) {
                    val = 255;
                } else {
                    val += 13;
                }
            } else if (command.equals(IncreaseDecreaseType.DECREASE)) {
                if (val <= 13) {
                    val = 0;
                } else {
                    val -= 13;
                }
            }
            Map<String, Object> args = new HashMap<String, Object>();
            args.put("level", val);
            args.put("llid", c.getLlid());
            try {
                sendHttpCommand(c.getIpAddr(), "setLogicalLoadLevel", args);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

        }
    }

    private String sendHttpCommand(String ip, String apiCall, Map<String, Object> args) throws Exception {

        HttpClient httpclient = PlumUtilities.getHttpClient();
        // Prepare a request object
        HttpPost httppost = new HttpPost("https://" + ip + ":8443/v2/" + apiCall);
        httppost.setHeader("User-Agent", "Plum/2.3.0 (iPhone; iOS 9.2.1; Scale/2.00)");
        httppost.setHeader("X-Plum-House-Access-Token", PlumUtilities.sha256(m_config.get("house_token")));
        httppost.setHeader("Content-type", "application/json");

        JSONObject json = new JSONObject();
        for (Entry<String, Object> kv : args.entrySet()) {
            json.put(kv.getKey(), kv.getValue());
        }

        StringEntity se = new StringEntity(json.toString());
        se.setContentEncoding("UTF-8");

        httppost.setEntity(se);

        // Execute the request
        HttpResponse response = httpclient.execute(httppost);

        // Examine the response status
        logger.info("Plum HTTP Request to IP " + ip + " returned: " + response.getStatusLine());

        // Get hold of the response entity
        HttpEntity entity = response.getEntity();
        String responseString = null;
        if (entity != null) {
            responseString = EntityUtils.toString(entity, "UTF-8");
        }

        // If the response does not enclose an entity, there is no need
        // to worry about connection release
        if (entity != null) {
            httpclient.getConnectionManager().shutdown();
        }

        return responseString;
    }

    /**
     * Inherited from AbstractBinding. Activates the binding. There is nothing
     * we can do at this point if we don't have the configuration information,
     * which usually comes in later, when updated() is called. {@inheritDoc}
     */
    @Override
    public void activate() {
        logger.debug("activating binding");

        m_isActive = true;

        initialize();
    }

    /**
     * Inherited from AbstractBinding. Deactivates the binding. The Controller
     * is stopped and the serial interface is closed as well. {@inheritDoc}
     */
    @Override
    public void deactivate() {
        logger.debug("deactivating binding!");
        shutdown();
        m_isActive = false;
    }

    /**
     * Inherited from AbstractActiveBinding. {@inheritDoc}
     */
    @Override
    protected String getName() {
        return "Plum";
    }

    /**
     * Inherited from AbstractActiveBinding. Periodically called by the
     * framework to execute a refresh of the binding. {@inheritDoc}
     */
    @Override
    protected void execute() {
        PlumGenericBindingProvider p = (PlumGenericBindingProvider) providers.iterator().next();
        // Only call getLLM once per LLID.
        Map<String, List<PlumBindingConfig>> configByLLID = new HashMap<String, List<PlumBindingConfig>>();
        for (PlumBindingConfig c : p.getPlumBindingConfigs()) {
            List<PlumBindingConfig> configs = configByLLID.get(c.getLlid());
            if (configs == null) {
                configs = new ArrayList<PlumBindingConfig>();
                configByLLID.put(c.getLlid(), configs);
            }
            configs.add(c);
        }

        for (String llid : configByLLID.keySet()) {
            Map<String, Object> args = new HashMap<String, Object>();
            args.put("llid", llid);
            String httpReply = null;
            JSONObject jsonParsed = null;
            String ipAddr = configByLLID.get(llid).get(0).getIpAddr();
            try {
                logger.info("Polling Plum HTTP " + ipAddr);
                httpReply = sendHttpCommand(ipAddr, "getLogicalLoadMetrics", args);
                logger.trace("Got Plum HTTP: " + httpReply);
                jsonParsed = new JSONObject(httpReply);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            for (PlumBindingConfig c : configByLLID.get(llid)) {

                if (c.getType().equals("dimmer") || c.getType().equals("switch")) {
                    int level = jsonParsed.getInt("level");
                    int lightpads = jsonParsed.getJSONArray("lightpad_metrics").length();
                    if (level > 0 && lightpads > 1) {
                        // Work around the behaviour where level is the sum
                        // of n
                        // levels where n = # of lightpads in a llid. Divide
                        // levels
                        // by n.
                        level = (int) ((float) level / lightpads);
                    }
                    currentLlidLevels.put(llid, level);
                    if (level == 0) {
                        publishState(c.getName(), OnOffType.OFF);
                    } else if (level == 255) {
                        publishState(c.getName(), OnOffType.ON);
                    } else {
                        publishState(c.getName(), new PercentType((int) (100 * ((float) level / 255))));
                    }
                } else if (c.getType().equals("powermeter")) {
                    int power = jsonParsed.getInt("power");
                    publishState(c.getName(), new DecimalType(power));
                }

            }

        }
    }

    private void publishState(String name, State state) {
        eventPublisher.postUpdate(name, state);
    }

    /**
     * Inherited from AbstractActiveBinding. Returns the refresh interval (time
     * between calls to execute()) in milliseconds. {@inheritDoc}
     */
    @Override
    protected long getRefreshInterval() {
        return m_refreshInterval;
    }

    /**
     * Inherited from AbstractActiveBinding. This method is called by the
     * framework whenever there are changes to a binding configuration.
     * 
     * @param provider
     *            the binding provider where the binding has changed
     * @param itemName
     *            the item name for which the binding has changed
     */
    @Override
    public void bindingChanged(BindingProvider provider, String itemName) {
        super.bindingChanged(provider, itemName);
        initialize();
    }

    /**
     * Initialize the binding: initialize the driver etc
     */
    private void initialize() {

        logger.debug("initializing...");

        PlumGenericBindingProvider p = (PlumGenericBindingProvider) providers.iterator().next();
        // Create threads by IP
        Map<String, Set<PlumBindingConfig>> configByIP = new HashMap<String, Set<PlumBindingConfig>>();
        for (PlumBindingConfig c : p.getPlumBindingConfigs()) {
            Set<PlumBindingConfig> configs = configByIP.get(c.getIpAddr());
            if (configs == null) {
                configs = new HashSet<PlumBindingConfig>();
                configByIP.put(c.getIpAddr(), configs);
            }
            configs.add(c);
        }

        for (String ip : configByIP.keySet()) {
            Set<PlumBindingConfig> configs = configByIP.get(ip);
            if (threads.get(ip) == null) {
                PlumTCPStreamListener stream = new PlumTCPStreamListener(eventPublisher, configs,
                        currentLlidLevels);
                Thread t = new Thread(stream);
                t.start();
                threads.put(ip, t);
                plumTCPStreams.put(ip, stream);
            } else {
                plumTCPStreams.get(ip).getConfigs().addAll(configs);
            }
        }
    }

    /**
     * Clean up all state.
     */
    private void shutdown() {
        logger.debug("shutting down binding");
        for (Thread t : threads.values()) {
            t.stop();
            t.destroy();
        }

    }

    @Override
    public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
        logger.warn(
                "The Plum Binding does not support runtime updates. Restart OpenHAB for item changes to take effect");
        HashMap<String, String> newConfig = new HashMap<String, String>();
        if (m_config == null) {
            logger.debug("seems like our configuration has been erased, will reset everything!");

            // turn config into new HashMap
            for (Enumeration<String> e = properties.keys(); e.hasMoreElements();) {
                String key = e.nextElement();
                String value = properties.get(key).toString();
                newConfig.put(key, value);
            }

            m_config = newConfig;

            if (m_config.containsKey("refresh")) {
                try {
                    m_refreshInterval = Long.parseLong(m_config.get("refresh"));
                    setProperlyConfigured(true);
                } catch (NumberFormatException e) {
                    logger.error(
                            "Are you sure you provided a numerical value for the openhab.cfg option plum:refresh?");
                }
            }
        }

    }
}