org.openhab.binding.myq.internal.MyqBinding.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.myq.internal.MyqBinding.java

Source

/**
 * Copyright (c) 2010-2019 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.myq.internal;

import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.StringUtils;
import org.openhab.binding.myq.MyqBindingProvider;
import org.openhab.core.binding.AbstractBinding;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.types.UpDownType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.TypeParser;
import org.openhab.core.types.UnDefType;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * this class polls the Chamberlain MyQ API and sends updates to the event bus
 * of configured items in openHAB
 *
 * @author Scott Hanson
 * @author Dan Cunningham
 * @since 1.8.0
 */
public class MyqBinding extends AbstractBinding<MyqBindingProvider> {
    private static final Logger logger = LoggerFactory.getLogger(MyqBinding.class);

    /**
     * The BundleContext. This is only valid when the bundle is ACTIVE. It is
     * set in the activate() method and must not be accessed anymore once the
     * deactivate() method was called or before activate() was called.
     */
    @SuppressWarnings("unused")
    private BundleContext bundleContext;

    /**
     * The myqData. This object stores the connection data and makes API
     * requests
     */
    private MyqData myqOnlineData = null;

    /**
     * the refresh interval which is used to poll values from the myq server
     * (optional, defaults to 60000ms)
     */
    private long refreshInterval = 60000;

    /**
     * We use our own polling service so we can adjust the polling rate during
     * periods of activity when a device has been sent a command or is in motion
     */
    private ScheduledExecutorService pollService = Executors.newSingleThreadScheduledExecutor();

    /**
     * The regular polling task
     */
    private ScheduledFuture<?> pollFuture;

    /**
     * This task will reset the poll interval back to normal after a rapid poll
     * cycle
     */
    private ScheduledFuture<?> pollResetFuture;

    /**
     * When polling quickly, how often do we poll
     */
    private int rapidRefresh = 2000;

    /**
     * Cap the time we poll rapidly to not overwhelm the servers with api
     * requests.
     */
    private static int MAX_RAPID_REFRESH = 30 * 1000;

    /**
     * If our login credentials are invalid then we will stop api requests until
     * our configuration is changed
     */
    private boolean invalidCredentials;

    /**
     * Use Craftman URL and APPID
     */
    private boolean useCraftman = false;

    /**
     * Called by the SCR to activate the component with its configuration read
     * from CAS
     *
     * @param bundleContext
     *            BundleContext of the Bundle that defines this component
     * @param configuration
     *            Configuration properties for this component obtained from the
     *            ConfigAdmin service
     */
    public void activate(final BundleContext bundleContext, final Map<String, Object> configuration) {
        this.bundleContext = bundleContext;
        modified(configuration);
    }

    /**
     * Called by the SCR when the configuration of a binding has been changed
     * through the ConfigAdmin service.
     *
     * @param configuration
     *            Updated configuration properties
     */
    public void modified(final Map<String, Object> configuration) {

        String refreshIntervalString = Objects.toString(configuration.get("refresh"), null);
        if (StringUtils.isNotBlank(refreshIntervalString)) {
            refreshInterval = Long.parseLong(refreshIntervalString);
        }

        String quickrefreshIntervalString = Objects.toString(configuration.get("quickrefresh"), null);
        if (StringUtils.isNotBlank(quickrefreshIntervalString)) {
            rapidRefresh = Integer.parseInt(quickrefreshIntervalString);
        }

        // update the internal configuration accordingly
        String usernameString = Objects.toString(configuration.get("username"), null);
        String passwordString = Objects.toString(configuration.get("password"), null);

        String appId = Objects.toString(configuration.get("appId"), null);
        if (StringUtils.isBlank(appId)) {
            appId = MyqData.DEFAULT_APP_ID;
        }

        int timeout = MyqData.DEFAUALT_TIMEOUT;
        String timeoutString = Objects.toString(configuration.get("timeout"), null);
        if (StringUtils.isNotBlank(timeoutString)) {
            timeout = Integer.parseInt(timeoutString);
        }

        String craftmanString = Objects.toString(configuration.get("craftman"), null);
        if (StringUtils.isNotBlank(craftmanString)) {
            useCraftman = Boolean.parseBoolean(craftmanString);
        }

        // reinitialize connection object if username and password is changed
        if (StringUtils.isNotBlank(usernameString) && StringUtils.isNotBlank(passwordString)) {
            myqOnlineData = new MyqData(usernameString, passwordString, appId, timeout, useCraftman);

            invalidCredentials = false;
            schedulePoll(refreshInterval);
        }
    }

    /**
     * Called by the SCR to deactivate the component when either the
     * configuration is removed or mandatory references are no longer satisfied
     * or the component has simply been stopped.
     *
     * @param reason
     *            Reason code for the deactivation:<br>
     *            <ul>
     *            <li>0  Unspecified
     *            <li>1  The component was disabled
     *            <li>2  A reference became unsatisfied
     *            <li>3  A configuration was changed
     *            <li>4  A configuration was deleted
     *            <li>5  The component was disposed
     *            <li>6  The bundle was stopped
     *            </ul>
     */
    public void deactivate(final int reason) {
        this.bundleContext = null;
        // deallocate resources here that are no longer needed and
        // should be reset when activating this binding again

        if (pollFuture != null && !pollFuture.isCancelled()) {
            pollFuture.cancel(true);
        }

        if (pollResetFuture != null && !pollResetFuture.isCancelled()) {
            pollResetFuture.cancel(true);
        }
    }

    /**
     * Poll for device changes
     */
    private void poll() {
        if (invalidCredentials || this.myqOnlineData == null) {
            logger.trace("Invalid Account Credentials");
            return;
        }

        try {
            // Get myQ Data
            MyqDeviceData myqStatus = myqOnlineData.getMyqData();

            for (MyqBindingProvider provider : providers) {
                for (String mygItemName : provider.getInBindingItemNames()) {
                    MyqBindingConfig deviceConfig = getConfigForItemName(mygItemName);
                    if (deviceConfig != null) {
                        MyqDevice device = myqStatus.getDevice(deviceConfig.deviceIndex);
                        if (device != null) {
                            if (device instanceof GarageDoorDevice) {
                                GarageDoorDevice garageopener = (GarageDoorDevice) device;
                                State newState = UnDefType.UNDEF;
                                if (!deviceConfig.attribute.isEmpty()
                                        && garageopener.hasAttribute(deviceConfig.attribute)) {
                                    newState = TypeParser.parseState(deviceConfig.acceptedDataTypes,
                                            garageopener.getAttribute(deviceConfig.attribute));
                                    if (newState == null) {
                                        newState = UnDefType.UNDEF;
                                    }
                                } else {
                                    for (Class<? extends State> type : deviceConfig.acceptedDataTypes) {
                                        if (OpenClosedType.class == type) {
                                            if (garageopener.getStatus().isClosed()) {
                                                newState = OpenClosedType.CLOSED;
                                                break;
                                            } else {
                                                newState = OpenClosedType.OPEN;
                                                break;
                                            }
                                        } else if (UpDownType.class == type) {
                                            if (garageopener.getStatus().isClosed()) {
                                                newState = UpDownType.DOWN;
                                                break;
                                            } else if (garageopener.getStatus().isOpen()) {
                                                newState = UpDownType.UP;
                                                break;
                                            }
                                        } else if (OnOffType.class == type) {
                                            if (garageopener.getStatus().isClosed()) {
                                                newState = OnOffType.OFF;
                                                break;
                                            } else {
                                                newState = OnOffType.ON;
                                                break;
                                            }
                                        } else if (PercentType.class == type) {
                                            if (garageopener.getStatus().isClosed()) {
                                                newState = PercentType.HUNDRED;
                                                break;
                                            } else if (garageopener.getStatus().isOpen()) {
                                                newState = PercentType.ZERO;
                                                break;
                                            } else if (garageopener.getStatus().inMotion()) {
                                                newState = new PercentType(50);
                                                break;
                                            }
                                        } else if (StringType.class == type) {
                                            newState = new StringType(garageopener.getStatus().getLabel());
                                            break;
                                        }
                                    }
                                }
                                eventPublisher.postUpdate(mygItemName, newState);

                                // make sure we are polling frequently
                                if (garageopener.getStatus().inMotion()) {
                                    beginRapidPoll(false);
                                }
                            } else if (device instanceof LampDevice) {
                                LampDevice lampDevice = (LampDevice) device;
                                State newState = UnDefType.UNDEF;
                                if (!deviceConfig.attribute.isEmpty()
                                        && lampDevice.hasAttribute(deviceConfig.attribute)) {
                                    newState = TypeParser.parseState(deviceConfig.acceptedDataTypes,
                                            lampDevice.getAttribute(deviceConfig.attribute));
                                    if (newState == null) {
                                        newState = UnDefType.UNDEF;
                                    }
                                } else {
                                    for (Class<? extends State> type : deviceConfig.acceptedDataTypes) {
                                        if (OnOffType.class == type) {
                                            newState = lampDevice.getState();
                                            break;
                                        }
                                    }
                                }
                                eventPublisher.postUpdate(mygItemName, newState);
                            }
                        }
                    }
                }
            }
        } catch (InvalidLoginException e) {
            logger.error("Could not log in, please check your credentials.", e);
            invalidCredentials = true;
        } catch (IOException e) {
            logger.error("Could not connect to MyQ service", e);
        }
    }

    /**
     * @{inheritDoc}
     */
    @Override
    public void internalReceiveCommand(String itemName, Command command) {
        super.internalReceiveCommand(itemName, command);
        logger.trace("MyQ binding received command '{}' for item '{}'", command, itemName);
        if (myqOnlineData != null) {
            computeCommandForItem(command, itemName);
        } else {
            logger.warn("Command '{}' for item '{}' not sent", command, itemName);
        }
    }

    /**
     * Checks whether the command is value and if the deviceID exists then get
     * status of Garage Door Opener and send command to change it's state
     * opposite of its current state
     *
     * @param command
     *            The command from the openHAB bus.
     * @param itemName
     *            The name of the targeted item.
     */
    private void computeCommandForItem(Command command, String itemName) {

        MyqBindingConfig deviceConfig = getConfigForItemName(itemName);

        if (invalidCredentials || deviceConfig == null) {
            return;
        }

        try {
            MyqDeviceData myqStatus = myqOnlineData.getMyqData();
            MyqDevice device = myqStatus.getDevice(deviceConfig.deviceIndex);
            if (device != null) {
                if (device instanceof GarageDoorDevice) {
                    GarageDoorDevice garageopener = (GarageDoorDevice) device;
                    if (command.equals(OnOffType.ON) || command.equals(UpDownType.UP)) {
                        myqOnlineData.executeMyQCommand(garageopener.getDeviceId(), "desireddoorstate", 1);
                        beginRapidPoll(true);
                    } else if (command.equals(OnOffType.OFF) || command.equals(UpDownType.DOWN)) {
                        myqOnlineData.executeMyQCommand(garageopener.getDeviceId(), "desireddoorstate", 0);
                        beginRapidPoll(true);
                    } else {
                        logger.warn("Unknown command {}", command);
                    }
                } else if (device instanceof LampDevice) {
                    LampDevice lampModule = (LampDevice) device;
                    if (command.equals(OnOffType.ON)) {
                        myqOnlineData.executeMyQCommand(lampModule.getDeviceId(), "desiredlightstate", 1);
                        doFuturePoll(rapidRefresh);
                    } else if (command.equals(OnOffType.OFF)) {
                        myqOnlineData.executeMyQCommand(lampModule.getDeviceId(), "desiredlightstate", 0);
                        doFuturePoll(rapidRefresh);
                    } else {
                        logger.warn("Unknown command {}", command);
                    }
                }
            } else {
                logger.warn("no MyQ device found with index: {}", deviceConfig.deviceIndex);
            }
        } catch (InvalidLoginException e) {
            logger.error("Could not log in, please check your credentials.", e);
            invalidCredentials = true;
        } catch (IOException e) {
            logger.error("Could not connect to MyQ service", e);
        }
    }

    /**
     * get item config based on item name(copied from HUE binding)
     */
    private MyqBindingConfig getConfigForItemName(String itemName) {
        for (MyqBindingProvider provider : providers) {
            if (provider.getItemConfig(itemName) != null) {
                return provider.getItemConfig(itemName);
            }
        }
        return null;
    }

    /**
     * Schedule our polling task
     *
     * @param millis
     */
    private void schedulePoll(long millis) {

        if (pollFuture != null && !pollFuture.isCancelled()) {
            pollFuture.cancel(false);
        }

        logger.trace("rapidRefreshFuture scheduling for {} millis", millis);
        // start polling at the RAPID_REFRESH_SECS interval
        pollFuture = pollService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                poll();
            }
        }, 0, millis, TimeUnit.MILLISECONDS);
    }

    /**
     * Schedule the task to reset out poll rate in a future time
     */
    private void scheduleFuturePollReset() {
        // stop rapid polling after MAX_RAPID_REFRESH_SECS
        pollResetFuture = pollService.schedule(new Runnable() {
            @Override
            public void run() {
                logger.trace("rapidRefreshFutureEnd stopping");
                schedulePoll(refreshInterval);
            }
        }, MAX_RAPID_REFRESH, TimeUnit.MILLISECONDS);
    }

    /**
     * Start rapid polling
     *
     * @param restart
     *            if already running, otherwise ignore.
     */
    private void beginRapidPoll(boolean restart) {
        if (restart && pollResetFuture != null) {
            pollResetFuture.cancel(true);
            pollResetFuture = null;
        }

        if (pollResetFuture == null || pollResetFuture.isCancelled()) {
            schedulePoll(rapidRefresh);
            scheduleFuturePollReset();
        }
    }

    /**
     * schedule a Poll in the near future
     */
    private void doFuturePoll(long millis) {
        pollResetFuture = pollService.schedule(new Runnable() {
            @Override
            public void run() {
                logger.trace("do schedule poll");
                poll();
            }
        }, millis, TimeUnit.MILLISECONDS);
    }
}