org.openhab.binding.somfytahoma.handler.SomfyTahomaBridgeHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.somfytahoma.handler.SomfyTahomaBridgeHandler.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.somfytahoma.handler;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.smarthome.config.core.status.ConfigStatusMessage;
import org.eclipse.smarthome.core.thing.*;
import org.eclipse.smarthome.core.thing.binding.ConfigStatusBridgeHandler;
import org.eclipse.smarthome.core.types.Command;
import org.openhab.binding.somfytahoma.internal.SomfyTahomaException;
import org.openhab.binding.somfytahoma.internal.config.SomfyTahomaConfig;
import org.openhab.binding.somfytahoma.internal.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import static org.openhab.binding.somfytahoma.SomfyTahomaBindingConstants.*;

/**
 * The {@link SomfyTahomaBridgeHandler} is responsible for handling commands, which are
 * sent to one of the channels.
 *
 * @author Ondrej Pecta - Initial contribution
 */
public class SomfyTahomaBridgeHandler extends ConfigStatusBridgeHandler {

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

    // Instantiate and configure the SslContextFactory
    private SslContextFactory sslContextFactory = new SslContextFactory();

    // Instantiate HttpClient with the SslContextFactory
    private HttpClient httpClient = new HttpClient(sslContextFactory);

    /**
     * Future to poll for updates
     */
    private ScheduledFuture<?> pollFuture;

    /**
     * Future to poll for status
     */
    private ScheduledFuture<?> statusFuture;

    /**
     * Our configuration
     */
    protected SomfyTahomaConfig thingConfig;

    // Gson & parser
    private final Gson gson = new Gson();

    public SomfyTahomaBridgeHandler(Bridge thing) {
        super(thing);
    }

    @Override
    public void handleCommand(ChannelUID channelUID, Command command) {
    }

    @Override
    public void initialize() {
        String thingUid = getThing().getUID().toString();
        thingConfig = getConfigAs(SomfyTahomaConfig.class);
        thingConfig.setThingUid(thingUid);

        httpClient.setFollowRedirects(false);

        scheduler.execute(() -> {
            login();
            initPolling();
            logger.debug("Initialize done...");
        });
    }

    /**
     * starts this things polling future
     */
    private void initPolling() {
        stopPolling();
        pollFuture = scheduler.scheduleWithFixedDelay(() -> {
            getTahomaUpdates();
        }, 10, thingConfig.getRefresh(), TimeUnit.SECONDS);

        statusFuture = scheduler.scheduleWithFixedDelay(() -> {
            updateTahomaStates();
        }, 60, thingConfig.getStatusTimeout(), TimeUnit.SECONDS);

    }

    public synchronized void login() {
        String url;

        if (StringUtils.isEmpty(thingConfig.getEmail()) || StringUtils.isEmpty(thingConfig.getPassword())) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                    "Can not access device as username and/or password are null");
            return;
        }

        try {
            if (httpClient.isStarted()) {
                httpClient.stop();
            }
            httpClient.start();

            url = TAHOMA_URL + "login";
            String urlParameters = "userId=" + thingConfig.getEmail() + "&userPassword="
                    + thingConfig.getPassword();

            ContentResponse response = sendRequestBuilder(url, HttpMethod.POST)
                    .content(new StringContentProvider(urlParameters),
                            "application/x-www-form-urlencoded; charset=UTF-8")
                    .send();

            logger.trace("Login response: {}", response.getContentAsString());
            SomfyTahomaLoginResponse data = gson.fromJson(response.getContentAsString(),
                    SomfyTahomaLoginResponse.class);
            if (data.isSuccess()) {
                logger.debug("SomfyTahoma version: {}", data.getVersion());
                updateStatus(ThingStatus.ONLINE);
            } else {
                logger.debug("Login response: {}", response.getContentAsString());
                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error logging in");
                throw new SomfyTahomaException(response.getContentAsString());
            }
        } catch (JsonSyntaxException e) {
            logger.debug("Received invalid data", e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data");
        } catch (SomfyTahomaException e) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                    "Unauthorized. Please check credentials");
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            logger.debug("Cannot get login cookie!", e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Cannot get login cookie");
        } catch (Exception e) {
            logger.debug("Cannot start http client", e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Cannot start http client");
        }

    }

    private ArrayList<SomfyTahomaEvent> getEvents() {
        String url;
        String line = "";

        try {
            url = TAHOMA_URL + "getEvents";

            line = sendDataToTahomaWithCookie(url, "");
            SomfyTahomaEvent[] array = gson.fromJson(line, SomfyTahomaEvent[].class);
            return new ArrayList<>(Arrays.asList(array));
        } catch (JsonSyntaxException e) {
            logger.debug("Received data: {} is not JSON", line, e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data");
        } catch (SomfyTahomaException e) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                    "Unauthorized. Please check credentials");
        } catch (ExecutionException e) {
            if (isAuthenticationChallenge(e)) {
                login();
            } else {
                logger.debug("Cannot get Tahoma events!", e);
                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
            }
        } catch (InterruptedException | TimeoutException e) {
            logger.debug("Cannot get Tahoma events!", e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
        }
        return new ArrayList<>();
    }

    @Override
    public void handleRemoval() {
        super.handleRemoval();
        logout();
    }

    @Override
    public Collection<ConfigStatusMessage> getConfigStatus() {
        return Collections.emptyList();
    }

    @Override
    public void dispose() {
        stopPolling();

        try {
            httpClient.stop();
        } catch (Exception e) {
            logger.error("Cannot stop http client", e);
        }
    }

    /**
     * Stops this thing's polling future
     */
    private void stopPolling() {
        if (pollFuture != null && !pollFuture.isCancelled()) {
            pollFuture.cancel(true);
            pollFuture = null;
        }
        if (statusFuture != null && !statusFuture.isCancelled()) {
            statusFuture.cancel(true);
            statusFuture = null;
        }
    }

    public List<SomfyTahomaActionGroup> listActionGroups() {
        String groups = getGroups();
        if (StringUtils.equals(groups, UNAUTHORIZED)) {
            groups = getGroups();
        }

        if (groups == null || groups.equals(UNAUTHORIZED)) {
            return Collections.emptyList();
        }

        try {
            SomfyTahomaActionGroupResponse data = gson.fromJson(groups, SomfyTahomaActionGroupResponse.class);
            return data.getActionGroups();
        } catch (JsonSyntaxException e) {
            logger.debug("Received data: {} is not JSON", groups, e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data");
        }
        return new ArrayList<>();
    }

    private String getGroups() {
        String url;

        try {
            url = TAHOMA_URL + "getActionGroups";
            String urlParameters = "";

            return sendDataToTahomaWithCookie(url, urlParameters);
        } catch (SomfyTahomaException e) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                    "Unauthorized. Please check credentials");
            return UNAUTHORIZED;
        } catch (ExecutionException e) {
            if (isAuthenticationChallenge(e)) {
                login();
                return UNAUTHORIZED;
            } else {
                logger.debug("Cannot send getActionGroups command!", e);
                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
            }
        } catch (InterruptedException | TimeoutException e) {
            logger.debug("Cannot send getActionGroups command!", e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
        }
        return null;
    }

    public SomfyTahomaSetup listDevices() {
        String url;
        String line = "";

        try {
            url = TAHOMA_URL + "getSetup";
            String urlParameters = "";

            line = sendDataToTahomaWithCookie(url, urlParameters);
            SomfyTahomaSetupResponse data = gson.fromJson(line, SomfyTahomaSetupResponse.class);
            return data.getSetup();
        } catch (JsonSyntaxException e) {
            logger.debug("Received data: {} is not JSON", line, e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data");
        } catch (SomfyTahomaException e) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                    "Unauthorized. Please check credentials");
        } catch (ExecutionException e) {
            if (isAuthenticationChallenge(e)) {
                login();
            } else {
                logger.debug("Cannot send getSetup command!", e);
                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
            }
        } catch (InterruptedException | TimeoutException e) {
            logger.debug("Cannot send getSetup command!", e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
        }
        return null;
    }

    private String getFormattedParameters(Collection<String> stateNames) {
        ArrayList<String> uniqueNames = new ArrayList<>();
        for (String name : stateNames) {
            if (!uniqueNames.contains(name)) {
                uniqueNames.add(name);
            }
        }
        StringBuilder sb = new StringBuilder("{\"name\": \"" + STATUS_STATE + "\"}");
        for (String name : uniqueNames) {
            sb.append(',');
            sb.append("{\"name\": \"").append(name).append("\"}");
        }
        logger.debug("Formatted parameters: {}", sb.toString());
        return sb.toString();
    }

    public List<SomfyTahomaState> getAllStates(Collection<String> stateNames, String deviceUrl) {
        String url;
        String line = "";

        logger.debug("Getting states for a device: {}", deviceUrl);
        try {
            url = TAHOMA_URL + "getStates";
            String urlParameters = "[{\"deviceURL\": \"" + deviceUrl + "\", \"states\": ["
                    + getFormattedParameters(stateNames) + "]}]";

            line = sendDataToTahomaWithCookie(url, urlParameters);

            SomfyTahomaStatesResponse data = gson.fromJson(line, SomfyTahomaStatesResponse.class);
            SomfyTahomaDeviceWithState device = data.getDevices().get(0);
            if (!device.hasStates()) {
                logger.debug("Device: {} has not returned any state", deviceUrl);
            }
            return device.getStates();
        } catch (JsonSyntaxException e) {
            logger.debug("Received data: {} is not JSON", line, e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data");
        } catch (SomfyTahomaException e) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                    "Unauthorized. Please check credentials");
        } catch (ExecutionException e) {
            if (isAuthenticationChallenge(e)) {
                login();
            } else {
                logger.debug("Cannot send getStates command!", e);
                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
            }
        } catch (InterruptedException | TimeoutException e) {
            logger.debug("Cannot send getStates command!", e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
        }

        return null;
    }

    private void getTahomaUpdates() {
        logger.debug("Getting Tahoma Updates...");
        if (ThingStatus.OFFLINE.equals(thing.getStatus())) {
            logger.debug("Doing relogin");
            login();
        }

        ArrayList<SomfyTahomaEvent> events = getEvents();
        logger.debug("Got total of {} events", events.size());
        for (SomfyTahomaEvent event : events) {
            processEvent(event);
        }
    }

    private void processEvent(SomfyTahomaEvent event) {
        switch (event.getName()) {
        case "DeviceStateChangedEvent":
            processStateChangedEvent(event);
            break;
        case "RefreshAllDevicesStatesCompletedEvent":
            scheduler.schedule(() -> {
                //force update thing states
                for (Thing th : getThing().getThings()) {
                    updateThingStates(th);
                }
            }, 1, TimeUnit.SECONDS);
            break;
        default:
            //ignore other states
        }
    }

    private void processStateChangedEvent(SomfyTahomaEvent event) {
        String deviceUrl = event.getDeviceUrl();
        ArrayList<SomfyTahomaState> states = event.getDeviceStates();
        logger.debug("States for device {} : {}", deviceUrl, states.toString());
        Thing thing = getThingByDeviceUrl(deviceUrl);

        if (thing != null) {
            logger.debug("Updating status of thing: {}", thing.getUID().getId());
            SomfyTahomaBaseThingHandler handler = (SomfyTahomaBaseThingHandler) thing.getHandler();

            if (handler != null) {
                // update thing status
                handler.updateThingStatus(states);
                handler.updateThingChannels(states);
            }
        } else {
            logger.debug("Thing handler is null, probably not bound thing.");
        }
    }

    private void updateTahomaStates() {
        logger.debug("Updating Tahoma States...");
        if (ThingStatus.OFFLINE.equals(thing.getStatus())) {
            logger.debug("Doing relogin");
            login();
        }

        //force Tahoma to ask for actual states
        refreshDeviceStates();
    }

    private void updateThingStates(Thing th) {
        SomfyTahomaBaseThingHandler handler = (SomfyTahomaBaseThingHandler) th.getHandler();
        for (Channel ch : th.getChannels()) {
            handler.updateChannelState(ch.getUID());
        }
    }

    private Thing getThingByDeviceUrl(String deviceUrl) {
        for (Thing th : getThing().getThings()) {
            String url = (String) th.getConfiguration().get("url");
            if (deviceUrl.equals(url)) {
                return th;
            }
        }
        return null;
    }

    private void logout() {
        try {
            sendGetToTahomaWithCookie(TAHOMA_URL + "logout");
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            logger.debug("Cannot send logout command!", e);
        }
    }

    private String sendDataToTahomaWithCookie(String url, String urlParameters)
            throws InterruptedException, ExecutionException, TimeoutException, SomfyTahomaException {
        logger.trace("Sending POST to Tahoma to url: {} with data: {}", url, urlParameters);
        ContentResponse response = sendRequestBuilder(url, HttpMethod.POST)
                .content(new StringContentProvider(urlParameters)).send();

        logger.trace("Response: {}", response.getContentAsString());
        if (response.getStatus() < 200 || response.getStatus() >= 300) {
            logger.error("Received error code: {}", response.getStatus());
            if (response.getStatus() == 404) {
                throw new SomfyTahomaException("Not logged in");
            }
        }
        return response.getContentAsString();
    }

    private String sendGetToTahomaWithCookie(String url)
            throws InterruptedException, ExecutionException, TimeoutException, SomfyTahomaException {
        return sendMethodToTahomaWithCookie(url, HttpMethod.GET);
    }

    private String sendPutToTahomaWithCookie(String url)
            throws InterruptedException, ExecutionException, TimeoutException, SomfyTahomaException {
        return sendMethodToTahomaWithCookie(url, HttpMethod.PUT);
    }

    private String sendDeleteToTahomaWithCookie(String url)
            throws InterruptedException, ExecutionException, TimeoutException, SomfyTahomaException {
        return sendMethodToTahomaWithCookie(url, HttpMethod.DELETE);
    }

    private String sendMethodToTahomaWithCookie(String url, HttpMethod method)
            throws InterruptedException, ExecutionException, TimeoutException, SomfyTahomaException {
        logger.trace("Sending {} to Tahoma to url: {}", method.asString(), url);
        ContentResponse response = sendRequestBuilder(url, method).send();

        logger.trace("Response: {}", response.getContentAsString());
        if (response.getStatus() < 200 || response.getStatus() >= 300) {
            logger.error("Received error code: {}", response.getStatus());
            if (response.getStatus() == 404) {
                throw new SomfyTahomaException("Not logged in");
            }
        }
        return response.getContentAsString();
    }

    private Request sendRequestBuilder(String url, HttpMethod method) {
        return httpClient.newRequest(url).method(method).header(HttpHeader.ACCEPT_LANGUAGE, "en-US,en")
                .header(HttpHeader.ACCEPT_ENCODING, "gzip, deflate").header("X-Requested-With", "XMLHttpRequest")
                .agent(TAHOMA_AGENT);
    }

    public void sendCommand(String io, String command, String params) {
        Boolean result = sendCommandInternal(io, command, params);
        if (result != null && !result) {
            sendCommandInternal(io, command, params);
        }
    }

    private Boolean sendCommandInternal(String io, String command, String params) {
        String url;
        String line = "";

        try {
            url = TAHOMA_URL + "apply";

            String value = params.equals("[]") ? command : params.replace("\"", "");
            String urlParameters = "{\"label\":\"" + getThingLabelByURL(io) + " - " + value
                    + " - OH2\",\"actions\":[{\"deviceURL\":\"" + io + "\",\"commands\":[{\"name\":\"" + command
                    + "\",\"parameters\":" + params + "}]}]}";

            line = sendDataToTahomaWithCookie(url, urlParameters);

            SomfyTahomaApplyResponse data = gson.fromJson(line, SomfyTahomaApplyResponse.class);

            if (!StringUtils.isEmpty(data.getExecId())) {
                logger.debug("Exec id: {}", data.getExecId());
            } else {
                logger.warn("Apply command response: {}", line);
                throw new SomfyTahomaException(line);
            }
            return true;
        } catch (JsonSyntaxException e) {
            logger.debug("Received data: {} is not JSON", line, e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data");
        } catch (SomfyTahomaException e) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                    "Unauthorized. Please check credentials");
            return false;
        } catch (ExecutionException e) {
            if (isAuthenticationChallenge(e)) {
                login();
                return false;
            } else {
                logger.debug("Cannot send apply command {} with params {}!", command, params, e);
                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
            }
        } catch (InterruptedException | TimeoutException e) {
            logger.debug("Cannot send apply command {} with params {}!", command, params, e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
        }
        return null;
    }

    private String getThingLabelByURL(String io) {
        Thing th = getThingByDeviceUrl(io);
        if (th != null) {
            if (th.getProperties().containsKey("label")) {
                //Return label from Tahoma
                return th.getProperties().get("label").replace("\"", "");
            }
            //Return label from OH2
            return th.getLabel() != null ? th.getLabel().replace("\"", "") : "";
        }
        return "null";
    }

    public String getCurrentExecutions(String type) {
        String execId = getCurrentExecutionsInternal(type);
        if (StringUtils.equals(execId, UNAUTHORIZED)) {
            execId = getCurrentExecutionsInternal(type);
        }
        return StringUtils.equals(execId, UNAUTHORIZED) ? null : execId;
    }

    private String getCurrentExecutionsInternal(String type) {
        String url;
        String line = "";

        try {
            url = TAHOMA_URL + "getCurrentExecutions";
            String urlParameters = "";

            line = sendDataToTahomaWithCookie(url, urlParameters);

            SomfyTahomaExecutionsResponse data = gson.fromJson(line, SomfyTahomaExecutionsResponse.class);
            for (SomfyTahomaExecution execution : data.getExecutions()) {
                String execId = execution.getId();
                SomfyTahomaActionGroup group = execution.getActionGroup();
                for (SomfyTahomaAction action : group.getActions()) {
                    if (action.getDeviceURL().equals(type)) {
                        return execId;
                    }
                }
            }
            return null;
        } catch (JsonSyntaxException e) {
            logger.debug("Received data: {} is not JSON", line, e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data");
        } catch (SomfyTahomaException e) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                    "Unauthorized. Please check credentials");
            return UNAUTHORIZED;
        } catch (ExecutionException e) {
            if (isAuthenticationChallenge(e)) {
                login();
                return UNAUTHORIZED;
            } else {
                logger.debug("Cannot send getCurrentExecutions command!", e);
                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
            }
        } catch (InterruptedException | TimeoutException e) {
            logger.debug("Cannot send getCurrentExecutions command!", e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
        }
        return null;
    }

    public void cancelExecution(String executionId) {
        Boolean result = cancelExecutionInternal(executionId);
        if (result != null && !result) {
            cancelExecutionInternal(executionId);
        }
    }

    private Boolean cancelExecutionInternal(String executionId) {
        String url;

        try {
            url = DELETE_URL + executionId;
            sendDeleteToTahomaWithCookie(url);
            return true;
        } catch (SomfyTahomaException e) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                    "Unauthorized. Please check credentials");
            return false;
        } catch (ExecutionException e) {
            if (isAuthenticationChallenge(e)) {
                login();
                return false;
            } else {
                logger.debug("Cannot cancel execution!", e);
                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
            }
        } catch (InterruptedException | TimeoutException e) {
            logger.debug("Cannot cancel execution!", e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
        }
        return null;
    }

    public String getTahomaVersion(String gatewayId) {
        String line = "";
        try {
            String url = SETUP_URL + gatewayId + "/version";
            line = sendGetToTahomaWithCookie(url);
            SomfyTahomaVersionResponse data = gson.fromJson(line, SomfyTahomaVersionResponse.class);
            logger.debug("Tahoma version: {}", data.getResult());

            return data.getResult();
        } catch (JsonSyntaxException e) {
            logger.debug("Received data: {} is not JSON", line, e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data");
        } catch (SomfyTahomaException e) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                    "Unauthorized. Please check credentials");
            return UNAUTHORIZED;
        } catch (ExecutionException e) {
            if (isAuthenticationChallenge(e)) {
                login();
                return UNAUTHORIZED;
            } else {
                logger.debug("Cannot get Tahoma gateway version!", e);
                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
            }
        } catch (InterruptedException | TimeoutException e) {
            logger.debug("Cannot get Tahoma gateway version!", e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
        }
        return null;
    }

    public void executeActionGroup(String id) {
        String execId = executeActionGroupInternal(id);
        if (UNAUTHORIZED.equals(execId)) {
            executeActionGroupInternal(id);
        }
    }

    public String executeActionGroupInternal(String id) {
        String line = "";
        try {
            String url = EXEC_URL + id;

            line = sendDataToTahomaWithCookie(url, "");
            SomfyTahomaApplyResponse data = gson.fromJson(line, SomfyTahomaApplyResponse.class);
            if (data.getExecId() == null) {
                logger.debug("Got empty exec response");
            }
            return data.getExecId();
        } catch (JsonSyntaxException e) {
            logger.debug("Received data: {} is not JSON", line, e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data");
        } catch (SomfyTahomaException e) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                    "Unauthorized. Please check credentials");
            return UNAUTHORIZED;
        } catch (ExecutionException e) {
            if (isAuthenticationChallenge(e)) {
                login();
                return UNAUTHORIZED;
            } else {
                logger.debug("Cannot exec execution group!", e);
                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
            }
        } catch (InterruptedException | TimeoutException e) {
            logger.debug("Cannot exec execution group!", e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
        }
        return null;
    }

    public void refreshDeviceStates() {
        try {
            sendGetToTahomaWithCookie(REFRESH_URL);
        } catch (SomfyTahomaException e) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                    "Unauthorized. Please check credentials");
        } catch (ExecutionException e) {
            if (isAuthenticationChallenge(e)) {
                login();
            } else {
                logger.debug("Cannot refresh device states!", e);
                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
            }
        } catch (InterruptedException | TimeoutException e) {
            logger.debug("Cannot refresh device states!", e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
        }
    }

    public String getTahomaStatus(String gatewayId) {
        String line = "";
        try {
            String url = SETUP_URL + gatewayId;
            line = sendGetToTahomaWithCookie(url);
            SomfyTahomaStatusResponse data = gson.fromJson(line, SomfyTahomaStatusResponse.class);
            if (data.getConnectivity() == null) {
                logger.warn("Got empty connectivity response");
                return "N/A";
            }
            logger.debug("Tahoma status: {}", data.getConnectivity().getStatus());
            return data.getConnectivity().getStatus();
        } catch (JsonSyntaxException e) {
            logger.debug("Received data: {} is not JSON", line, e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data");
        } catch (SomfyTahomaException e) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                    "Unauthorized. Please check credentials");
            return UNAUTHORIZED;
        } catch (ExecutionException e) {
            if (isAuthenticationChallenge(e)) {
                login();
                return UNAUTHORIZED;
            } else {
                logger.debug("Cannot get Tahoma gateway status!", e);
                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
            }
        } catch (InterruptedException | TimeoutException e) {
            logger.debug("Cannot get Tahoma gateway status!", e);
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
        }
        return null;
    }

    private boolean isAuthenticationChallenge(Exception ex) {
        return ex.getMessage().contains(AUTHENTICATION_CHALLENGE);
    }
}