All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jamierf.evohome.EvohomeClient Maven / Gradle / Ivy

package com.jamierf.evohome;

import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import javax.ws.rs.core.MediaType;

import org.apache.http.auth.UsernamePasswordCredentials;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.ListenableFuture;
import com.jamierf.evohome.api.QuickActionRequest;
import com.jamierf.evohome.api.SessionRequest;
import com.jamierf.evohome.api.SetTemperatureRequest;
import com.jamierf.evohome.model.Device;
import com.jamierf.evohome.model.Gateway;
import com.jamierf.evohome.model.Location;
import com.jamierf.evohome.model.QuickAction;
import com.jamierf.evohome.model.Session;
import com.jamierf.evohome.model.State;
import com.jamierf.evohome.model.Task;
import com.jamierf.evohome.model.TaskStatus;
import com.jamierf.evohome.model.Temperature;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.GenericType;

// https://github.com/watchforstock/evohome-client/
public class EvohomeClient {

    public static final URI DEFAULT_API_ROOT = URI.create("https://rs.alarmnet.com");

    private static final String SESSION_API = "/TotalConnectComfort/WebAPI/api/Session";
    private static final String LOCATIONS_API = "/TotalConnectComfort/WebAPI/api/locations";
    private static final String GATEWAY_API = "/TotalConnectComfort/WebAPI/api/gateways";
    private static final String TASK_STATUS_API = "/TotalConnectComfort/WebAPI/api/commTasks";
    private static final String SET_QUICK_ACTION_API = "/TotalConnectComfort/WebAPI/api/evoTouchSystems";
    private static final String SET_TEMPERATURE_API = "/TotalConnectComfort/WebAPI/api/devices/%s/thermostat/changeableValues/heatSetpoint";

    private static final String APPLICATION_ID = "91db1612-73fd-4500-91b2-e63b069b185c"; // the mobile app
    private static final String SESSION_ID_HEADER = "SessionId";

    private static final Logger LOG = LoggerFactory.getLogger(EvohomeClient.class);
    private static final int SESSION_EXPIRATION_HOURS = 24;

    public static EvohomeClientBuilder builder(final Client client) {
        return new EvohomeClientBuilder(client);
    }

    private final Client client;
    private final URI apiRoot;
    private final TaskRunner taskRunner;
    private final UsernamePasswordCredentials credentials;
    private final Location location;

    private final LoadingCache sessionCache;

    public EvohomeClient(final Client client, final String username, final String password,
                          final Predicate locationMatcher, final URI apiRoot) {
        this.client = client;
        this.apiRoot = apiRoot;

        taskRunner = new TaskRunner(Executors.newCachedThreadPool(), this);
        credentials = new UsernamePasswordCredentials(username, password);

        sessionCache = CacheBuilder.newBuilder()
                .expireAfterWrite(SESSION_EXPIRATION_HOURS, TimeUnit.HOURS)
                .build(new CacheLoader() {
                    @Override
                    public Session load(final UsernamePasswordCredentials key) throws Exception {
                        return getSession(key.getUserName(), key.getPassword());
                    }
                });

        location = FluentIterable.from(getLocations().values())
                .firstMatch(locationMatcher)
                .orNull();
        if (location == null) {
            throw new IllegalStateException("No locations found.");
        }
    }

    public Optional getDevice(final String zone) {
        return Optional.fromNullable(getDevices().get(zone));
    }

    protected Session getSession() {
        try {
            return sessionCache.get(credentials);
        } catch (ExecutionException e) {
            LOG.warn("Failed to create session", e);
            throw Throwables.propagate(e.getCause());
        }
    }

    private Session getSession(final String username, final String password) {
        return client.resource(apiRoot)
                .path(SESSION_API)
                .type(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .post(Session.class, new SessionRequest(username, password, APPLICATION_ID));
    }

    protected Map getLocations() {
        final Session session = getSession();
        final Collection locations = client.resource(apiRoot)
                .path(LOCATIONS_API)
                .queryParam("userId", String.valueOf(session.getUser().getId()))
                .queryParam("allData", "True")
                .header(SESSION_ID_HEADER, session.getId())
                .accept(MediaType.APPLICATION_JSON)
                .get(new GenericType>() {
                });
        return Maps.uniqueIndex(locations, Location::getName);
    }

    public Map getDevices() {
        final Optional result = Optional.fromNullable(getLocations().get(location.getName()));
        final Collection devices = result.transform(Location::getDevices).or(Collections.emptyList());
        return Maps.uniqueIndex(devices, Device::getName);
    }

    public Map getGateways() {
        final Session session = getSession();
        final Collection gateways = client.resource(apiRoot)
                .path(GATEWAY_API)
                .queryParam("locationId", String.valueOf(location.getId()))
                .queryParam("allData", "False")
                .header(SESSION_ID_HEADER, session.getId())
                .accept(MediaType.APPLICATION_JSON)
                .get(new GenericType>() {
                });
        return Maps.uniqueIndex(gateways, Gateway::getMac);
    }

    protected TaskStatus getTaskStatus(final Task task) {
        final Session session = getSession();
        return client.resource(apiRoot)
                .path(TASK_STATUS_API)
                .queryParam("commTaskId", String.valueOf(task.getId()))
                .header(SESSION_ID_HEADER, session.getId())
                .accept(MediaType.APPLICATION_JSON)
                .get(TaskStatus.class);
    }

    public ListenableFuture setQuickAction(final QuickAction action, final Optional until) {
        final Session session = getSession();
        final Task task = Iterables.getOnlyElement(client.resource(apiRoot)
                .path(SET_QUICK_ACTION_API)
                .queryParam("locationId", String.valueOf(location.getId()))
                .header(SESSION_ID_HEADER, session.getId())
                .type(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .put(new GenericType>() {
                },
                        new QuickActionRequest(action, until)));
        return taskRunner.toFuture(task);
    }

    public ListenableFuture setTemperature(final Device device, final Temperature temperature, final Optional until) {
        final Session session = getSession();
        final Task task = Iterables.getOnlyElement(client.resource(apiRoot)
                .path(String.format(SET_TEMPERATURE_API, device.getId()))
                .header(SESSION_ID_HEADER, session.getId())
                .type(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .put(new GenericType>() {
                },
                        new SetTemperatureRequest(temperature, until)));
        return taskRunner.toFuture(task);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy