[warmup] Initial contribution (#8562)

Signed-off-by: James Melville <jamesmelville@gmail.com>
This commit is contained in:
James Melville 2021-05-13 14:37:05 +01:00 committed by GitHub
parent 18497a9436
commit db05079e6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 1603 additions and 0 deletions

View File

@ -305,6 +305,7 @@
/bundles/org.openhab.binding.vigicrues/ @clinique
/bundles/org.openhab.binding.vitotronic/ @steand
/bundles/org.openhab.binding.volvooncall/ @clinique
/bundles/org.openhab.binding.warmup/ @jamesmelville
/bundles/org.openhab.binding.weathercompany/ @mhilbush
/bundles/org.openhab.binding.weatherunderground/ @lolodomo
/bundles/org.openhab.binding.webthing/ @grro

View File

@ -1501,6 +1501,11 @@
<artifactId>org.openhab.binding.volvooncall</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.warmup</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.weathercompany</artifactId>

View File

@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@ -0,0 +1,112 @@
# Warmup Binding
This binding integrates the Warmup 4iE Thermostat https://www.warmup.co.uk/thermostats/smart/4ie-underfloor-heating, via the API at https://my.warmup.com/.
Any Warmup 4iE device(s) must be registered at https://my.warmup.com/ prior to usage.
This API is not known to be documented publicly.
The binding api implementation has been derived from the implementations at https://github.com/alyc100/SmartThingsPublic/blob/master/devicetypes/alyc100/warmup-4ie.src/warmup-4ie.groovy and https://github.com/alex-0103/warmup4IE/blob/master/warmup4ie/warmup4ie.py, and enhanced by inspecting the GraphQL endpoint.
## Supported Things
The Warmup binding supports the following thing types:
| Bridge | Label | Description |
|----------------|-------------------|----------------------------------------------------------------------------------------|
| `my-warmup` | My Warmup Account | The account credentials for my.warmup.com which acts as an API to the Warmup device(s) |
| Thing | Label | Description |
|----------|-------|----------------------------------------------------------------------------------------------------------------------|
| `room` | Room | A room containing an individual Warmup 4iE device which is a WiFi connected device which controls a heating circuit. |
### Room
The device is optimised for controlling underfloor heating (electric or hydronic), although it can also control central heating circuits.
The device reports the temperature from one of two thermostats, either a floor temperature probe or the air temperature at the device.
The separate temperatures do not appear to be reported through the API. It appears to be possible to configure two devices in a primary / secondary configuration, but it is not clear how this might be represented by the API and hasn't been implemented.
## Discovery
Once credentials are successfully added to the bridge, any rooms (devices) detected will be added as things to the inbox.
## Thing Configuration
### My Warmup Account
| config parameter | type | description | required | default |
|------------------|---------|-------------------------------------------------|----------|---------|
| username | String | Username for my.warmup.com | true | |
| password | String | Password for my.warmup.com | true | |
| refreshInterval | Integer | Interval in seconds between automatic refreshes | true | 300 |
### Room
Rooms are configured automatically with a Serial Number on discovery, or can be added manually using the "Device Number" from the device, excluding the last 3 characters. The only supported temperature change is an override, through a default duration configured on the thing. This defaults to 60 minutes.
| config parameter | type | description | required | default |
|------------------|---------|--------------------------------------------------------------------|----------|---------|
| serialNumber | String | Device Serial Number, excluding last 3 characters | true | |
| overrideDuration | Integer | Duration in minutes of override when target temperature is changed | true | 60 |
## Channels
| channel | type | description | read only |
|---------------------|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------|-----------|
| currentTemperature | Number:Temperature | Currently reported temperature | true |
| targetTemperature | Number:Temperature | Target temperature | false |
| overrideRemaining | Number:Time | Duration remaining of the configured override | true |
| runMode | String | Current operating mode of the thermostat, options listed below | true |
| frostProtectionMode | Switch | Toggles between the "Frost Protection" run mode and the previously configured "active" run mode (known options are either Fixed or Schedule) | false |
### Run Mode Statuses
These run mode statuses are defined for the API. The descriptions are based on inspection of the device behaviour and are not sourced from documentation.
| api value | ui name | description |
|------------|------------------|---------------------------------------------------------------------------------|
| not_set | Not Set | Unknown |
| off | Off | Device turned off |
| schedule | Schedule | Device target temperature running to a programmed schedule |
| override | Override | Target temperature overridden for the remaining duration in overrideRemaining |
| fixed | Fixed | Device target temperature set to a constant fixed value |
| anti_frost | Frost Protection | Device target temperature set to 7°C |
| holiday | Holiday | Device target temperature set to a constant fixed value for duration of holiday |
| fil_pilote | Fil Pilote | Unknown |
| gradual | Gradual | Unknown |
| relay | Relay | Unknown |
| previous | Previous | Unknown |
## Full Example
### .things file
```
Bridge warmup:my-warmup:MyWarmup [ username="test@example.com", password="test", refreshInterval=300 ]
{
room bathroom "Home - Bathroom" [ serialNumber="AABBCCDDEEFF", overrideDuration=60 ]
}
```
### .items file
```
Number:Temperature bathroom_temperature "Temperature [%.1f °C]" <temperature> (GF_Bathroom, Temperature) ["Temperature"] {channel="warmup:room:MyWarmup:bathroom:currentTemperature"}
Number:Temperature bathroom_setpoint "Set Point [%.1f °C]" <temperature> (GF_Bathroom) ["Set Point"] {channel="warmup:room:MyWarmup:bathroom:targetTemperature"}
Number:Time bathroom_overrideRemaining "Override Remaining [%d minutes]" (GF_Bathroom) {channel="warmup:room:MyWarmup:bathroom:overrideRemaining"}
String bathroom_runMode "Run Mode [%s]" (GF_Bathroom) {channel="warmup:room:MyWarmup:bathroom:runMode"}
Switch bathroom_frostProtection "Frost Protection Mode" (GF_Bathroom) {channel="warmup:room:MyWarmup:bathroom:frostProtectionMode"}
```
### Sitemap
```
Text label="Bathroom" {
Text item=bathroom_temperature
Setpoint item=bathroom_setpoint step=0.5
Text item=bathroom_overrideRemaining
Text item=bathroom_runMode
Switch item=bathroom_frostProtection
}
```

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.warmup</artifactId>
<name>openHAB Add-ons :: Bundles :: Warmup Binding</name>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.warmup-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-warmup" description="Warmup Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.warmup/${project.version}</bundle>
</feature>
</features>

View File

@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link WarmupBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author James Melville - Initial contribution
*/
@NonNullByDefault
public class WarmupBindingConstants {
private static final String BINDING_ID = "warmup";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "my-warmup");
public static final ThingTypeUID THING_TYPE_ROOM = new ThingTypeUID(BINDING_ID, "room");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BRIDGE, THING_TYPE_ROOM);
public static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPES_UIDS = Set.of(THING_TYPE_ROOM);
// Room Channel Ids
public static final String CHANNEL_CURRENT_TEMPERATURE = "currentTemperature";
public static final String CHANNEL_TARGET_TEMPERATURE = "targetTemperature";
public static final String CHANNEL_OVERRIDE_DURATION = "overrideRemaining";
public static final String CHANNEL_RUN_MODE = "runMode";
public static final String CHANNEL_FROST_PROTECTION_MODE = "frostProtectionMode";
public static final String CHANNEL_HEATING_TARGET = "heatingTarget";
public static final String CHANNEL_AIR_TEMPERATURE = "airTemperature";
public static final String CHANNEL_FLOOR_TEMPERATURE = "floorTemperature";
public static final String FROST_PROTECTION_MODE = "anti_frost";
// Property Labels
public static final String PROPERTY_ROOM_ID = "Id";
public static final String PROPERTY_ROOM_NAME = "Name";
public static final String PROPERTY_LOCATION_ID = "LocationId";
public static final String PROPERTY_LOCATION_NAME = "Location";
// Web Service Endpoints
public static final String APP_ENDPOINT = "https://api.warmup.com/apps/app/v1";
public static final String QUERY_ENDPOINT = "https://apil.warmup.com/graphql";
// Web Service Constants
public static final String USER_AGENT = "WARMUP_APP";
public static final String APP_TOKEN = "M=;He<Xtg\"$}4N%5k{$:PD+WA\"]D<;#PriteY|VTuA>_iyhs+vA\"4lic{6-LqNM:";
public static final String AUTH_METHOD = "userLogin";
public static final String AUTH_APP_ID = "WARMUP-APP-V001";
}

View File

@ -0,0 +1,190 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.api;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
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.http.HttpStatus;
import org.openhab.binding.warmup.internal.WarmupBindingConstants;
import org.openhab.binding.warmup.internal.handler.MyWarmupConfigurationDTO;
import org.openhab.binding.warmup.internal.model.auth.AuthRequestDTO;
import org.openhab.binding.warmup.internal.model.auth.AuthResponseDTO;
import org.openhab.binding.warmup.internal.model.query.QueryResponseDTO;
import org.openhab.core.library.types.OnOffType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* The {@link MyWarmupApi} class contains code specific to calling the My Warmup API.
*
* @author James Melville - Initial contribution
*/
@NonNullByDefault
public class MyWarmupApi {
private static final Gson GSON = new Gson();
private final Logger logger = LoggerFactory.getLogger(MyWarmupApi.class);
private final HttpClient httpClient;
private MyWarmupConfigurationDTO configuration;
private @Nullable String authToken;
/**
* Construct the API client
*
* @param httpClient HttpClient to make HTTP Calls
* @param configuration Thing configuration which contains API credentials
*/
public MyWarmupApi(final HttpClient httpClient, MyWarmupConfigurationDTO configuration) {
this.httpClient = httpClient;
this.configuration = configuration;
}
/**
* Update the configuration, trigger a refresh of the access token
*
* @param configuration contains username and password
*/
public void setConfiguration(MyWarmupConfigurationDTO configuration) {
authToken = null;
this.configuration = configuration;
}
private void validateSession() throws MyWarmupApiException {
if (authToken == null) {
authenticate();
}
}
private void authenticate() throws MyWarmupApiException {
String body = GSON.toJson(new AuthRequestDTO(configuration.username, configuration.password,
WarmupBindingConstants.AUTH_METHOD, WarmupBindingConstants.AUTH_APP_ID));
ContentResponse response = callWarmup(WarmupBindingConstants.APP_ENDPOINT, body, false);
AuthResponseDTO ar = GSON.fromJson(response.getContentAsString(), AuthResponseDTO.class);
if (ar != null && ar.getStatus() != null && ar.getStatus().getResult().equals("success")) {
authToken = ar.getResponse().getToken();
} else {
throw new MyWarmupApiException("Authentication Failed");
}
}
/**
* Query the API to get the status of all devices connected to the Bridge.
*
* @return The {@link QueryResponseDTO} object if retrieved, else null
* @throws MyWarmupApiException API callout error
*/
public synchronized QueryResponseDTO getStatus() throws MyWarmupApiException {
return callWarmupGraphQL("query QUERY { user { locations{ id name "
+ " rooms { id roomName runMode overrideDur targetTemp currentTemp "
+ " thermostat4ies{ deviceSN lastPoll }}}}}");
}
/**
* Call the API to set a temperature override on a specific room
*
* @param locationId Id of the location
* @param roomId Id of the room
* @param temperature Temperature to set * 10
* @param duration Duration in minutes of the override
* @throws MyWarmupApiException API callout error
*/
public void setOverride(String locationId, String roomId, int temperature, Integer duration)
throws MyWarmupApiException {
callWarmupGraphQL(String.format("mutation{deviceOverride(lid:%s,rid:%s,temperature:%d,minutes:%d)}", locationId,
roomId, temperature, duration));
}
/**
* Call the API to toggle frost protection mode on a specific room
*
* @param locationId Id of the location
* @param roomId Id of the room
* @param command Temperature to set
* @throws MyWarmupApiException API callout error
*/
public void toggleFrostProtectionMode(String locationId, String roomId, OnOffType command)
throws MyWarmupApiException {
callWarmupGraphQL(String.format("mutation{turn%s(lid:%s,rid:%s){id}}", command == OnOffType.ON ? "Off" : "On",
locationId, roomId));
}
private QueryResponseDTO callWarmupGraphQL(String body) throws MyWarmupApiException {
validateSession();
ContentResponse response = callWarmup(WarmupBindingConstants.QUERY_ENDPOINT, "{\"query\": \"" + body + "\"}",
true);
QueryResponseDTO qr = GSON.fromJson(response.getContentAsString(), QueryResponseDTO.class);
if (qr != null && qr.getStatus().equals("success")) {
return qr;
} else {
throw new MyWarmupApiException("Unexpected reponse from API");
}
}
private synchronized ContentResponse callWarmup(String endpoint, String body, Boolean authenticated)
throws MyWarmupApiException {
try {
final Request request = httpClient.newRequest(endpoint);
request.method(HttpMethod.POST);
request.getHeaders().remove(HttpHeader.USER_AGENT);
request.header(HttpHeader.USER_AGENT, WarmupBindingConstants.USER_AGENT);
request.header(HttpHeader.CONTENT_TYPE, "application/json");
request.header("App-Token", WarmupBindingConstants.APP_TOKEN);
if (authenticated) {
request.header("Warmup-Authorization", authToken);
}
request.content(new StringContentProvider(body));
request.timeout(10, TimeUnit.SECONDS);
logger.trace("Sending body to My Warmup: Endpoint {}, Body {}", endpoint, body);
ContentResponse response = request.send();
logger.trace("Response from my warmup: Status {}, Body {}", response.getStatus(),
response.getContentAsString());
if (response.getStatus() == HttpStatus.OK_200) {
return response;
} else if (response.getStatus() == HttpStatus.UNAUTHORIZED_401) {
logger.debug("Authentication failure {} {}", response.getStatus(), response.getContentAsString());
authToken = null;
throw new MyWarmupApiException("Authentication failure");
} else {
logger.debug("Unexpected response {} {}", response.getStatus(), response.getContentAsString());
}
throw new MyWarmupApiException("Callout failed");
} catch (InterruptedException | TimeoutException | ExecutionException e) {
throw new MyWarmupApiException(e);
}
}
}

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.api;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Exception thrown in case of api problems.
*
* @author James Melville - Initial contribution
*/
@SuppressWarnings("serial")
@NonNullByDefault
public class MyWarmupApiException extends Exception {
public MyWarmupApiException(@Nullable String message) {
super(message);
}
public MyWarmupApiException(@Nullable String message, @Nullable Throwable cause) {
super(message, cause);
}
public MyWarmupApiException(@Nullable Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,119 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.discovery;
import static org.openhab.binding.warmup.internal.WarmupBindingConstants.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.warmup.internal.handler.MyWarmupAccountHandler;
import org.openhab.binding.warmup.internal.handler.WarmupRefreshListener;
import org.openhab.binding.warmup.internal.model.query.LocationDTO;
import org.openhab.binding.warmup.internal.model.query.QueryResponseDTO;
import org.openhab.binding.warmup.internal.model.query.RoomDTO;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
/**
* The {@link WarmupDiscoveryService} is used to discover devices that are connected to a My Warmup account.
*
* @author James Melville - Initial contribution
*/
@NonNullByDefault
public class WarmupDiscoveryService extends AbstractDiscoveryService
implements DiscoveryService, ThingHandlerService, WarmupRefreshListener {
private @Nullable MyWarmupAccountHandler bridgeHandler;
private @Nullable ThingUID bridgeUID;
public WarmupDiscoveryService() {
super(DISCOVERABLE_THING_TYPES_UIDS, 5, false);
}
@Override
public void deactivate() {
}
@Override
public void startScan() {
final MyWarmupAccountHandler handler = bridgeHandler;
if (handler != null) {
removeOlderResults(getTimestampOfLastScan());
handler.setDiscoveryService(this);
}
}
/**
* Process device list and populate discovery list with things
*
* @param domain Data model representing all devices
*/
@Override
public void refresh(@Nullable QueryResponseDTO domain) {
if (domain != null) {
HashSet<ThingUID> discoveredThings = new HashSet<ThingUID>();
for (LocationDTO location : domain.getData().getUser().getLocations()) {
for (RoomDTO room : location.getRooms()) {
discoverRoom(location, room, discoveredThings);
}
}
}
}
private void discoverRoom(LocationDTO location, RoomDTO room, HashSet<ThingUID> discoveredThings) {
if (room.getThermostat4ies() != null && !room.getThermostat4ies().isEmpty()) {
final String deviceSN = room.getThermostat4ies().get(0).getDeviceSN();
ThingUID localBridgeUID = this.bridgeUID;
if (localBridgeUID != null && deviceSN != null) {
final Map<String, Object> roomProperties = new HashMap<>();
roomProperties.put(Thing.PROPERTY_SERIAL_NUMBER, deviceSN);
roomProperties.put(PROPERTY_ROOM_ID, room.getId());
roomProperties.put(PROPERTY_ROOM_NAME, room.getName());
roomProperties.put(PROPERTY_LOCATION_ID, location.getId());
roomProperties.put(PROPERTY_LOCATION_NAME, location.getName());
ThingUID roomThingUID = new ThingUID(THING_TYPE_ROOM, localBridgeUID, deviceSN);
thingDiscovered(DiscoveryResultBuilder.create(roomThingUID).withBridge(localBridgeUID)
.withProperties(roomProperties).withLabel(location.getName() + " - " + room.getName())
.withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).build());
discoveredThings.add(roomThingUID);
}
}
}
@Override
public void setThingHandler(@Nullable final ThingHandler handler) {
if (handler instanceof MyWarmupAccountHandler) {
bridgeHandler = (MyWarmupAccountHandler) handler;
bridgeUID = handler.getThing().getUID();
} else {
bridgeHandler = null;
bridgeUID = null;
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}
}

View File

@ -0,0 +1,134 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.handler;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.warmup.internal.api.MyWarmupApi;
import org.openhab.binding.warmup.internal.api.MyWarmupApiException;
import org.openhab.binding.warmup.internal.discovery.WarmupDiscoveryService;
import org.openhab.binding.warmup.internal.model.query.QueryResponseDTO;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
/**
* @author James Melville - Initial contribution
*/
@NonNullByDefault
public class MyWarmupAccountHandler extends BaseBridgeHandler {
private final MyWarmupApi api;
private @Nullable QueryResponseDTO queryResponse = null;
private @Nullable ScheduledFuture<?> refreshJob;
private @Nullable WarmupDiscoveryService discoveryService;
public MyWarmupAccountHandler(Bridge thing, final HttpClient httpClient) {
super(thing);
api = new MyWarmupApi(httpClient, getConfigAs(MyWarmupConfigurationDTO.class));
}
@Override
public void initialize() {
MyWarmupConfigurationDTO config = getConfigAs(MyWarmupConfigurationDTO.class);
if (config.username.length() == 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Username not configured");
} else if (config.password.length() == 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Password not configured");
} else if (config.refreshInterval >= 10) {
api.setConfiguration(config);
refreshJob = scheduler.scheduleWithFixedDelay(this::refreshFromServer, 0, config.refreshInterval,
TimeUnit.SECONDS);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Refresh interval misconfigured (minimum 10s)");
}
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(WarmupDiscoveryService.class);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
@Override
public void dispose() {
cancelRefresh();
}
public void cancelRefresh() {
if (refreshJob != null) {
refreshJob.cancel(true);
refreshJob = null;
}
}
public synchronized void refreshFromServer() {
try {
queryResponse = api.getStatus();
updateStatus(ThingStatus.ONLINE);
} catch (MyWarmupApiException e) {
queryResponse = null;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
refreshFromCache();
}
/**
* Trigger updates to all devices
*/
public synchronized void refreshFromCache() {
notifyListeners(queryResponse);
}
public void setDiscoveryService(final WarmupDiscoveryService discoveryService) {
this.discoveryService = discoveryService;
refreshFromServer();
}
public void unsetDiscoveryService() {
discoveryService = null;
}
/**
*
* @return reference to the bridge's API
*/
public MyWarmupApi getApi() {
return api;
}
private void notifyListeners(@Nullable QueryResponseDTO domain) {
if (discoveryService != null && queryResponse != null) {
discoveryService.refresh(queryResponse);
}
getThing().getThings().stream().filter(thing -> thing.getHandler() instanceof WarmupRefreshListener)
.map(Thing::getHandler).map(WarmupRefreshListener.class::cast).forEach(thing -> thing.refresh(domain));
}
}

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link MyWarmupConfigurationDTO} class contains fields mapping thing configuration parameters for the MyWarmup.
*
* @author James Melville - Initial contribution
*/
@NonNullByDefault
public class MyWarmupConfigurationDTO {
public String username = "";
public String password = "";
public int refreshInterval = 300;
}

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link RoomConfigurationDTO} class contains fields mapping thing configuration parameters for the Warmup Room.
*
* @author James Melville - Initial contribution
*/
@NonNullByDefault
public class RoomConfigurationDTO {
private String serialNumber = "";
private int overrideDuration = 60;
public String getSerialNumber() {
return serialNumber;
}
public int getOverrideDuration() {
return overrideDuration;
}
}

View File

@ -0,0 +1,151 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.handler;
import static org.openhab.binding.warmup.internal.WarmupBindingConstants.*;
import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.warmup.internal.api.MyWarmupApiException;
import org.openhab.binding.warmup.internal.model.query.LocationDTO;
import org.openhab.binding.warmup.internal.model.query.QueryResponseDTO;
import org.openhab.binding.warmup.internal.model.query.RoomDTO;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author James Melville - Initial contribution
*/
@NonNullByDefault
public class RoomHandler extends WarmupThingHandler implements WarmupRefreshListener {
private final Logger logger = LoggerFactory.getLogger(RoomHandler.class);
private @Nullable RoomConfigurationDTO config;
public RoomHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
super.initialize();
config = getConfigAs(RoomConfigurationDTO.class);
if (config.getSerialNumber().length() == 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Serial Number not configured");
} else {
super.refreshFromServer();
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
if (CHANNEL_TARGET_TEMPERATURE.equals(channelUID.getId()) && command instanceof QuantityType<?>) {
setOverride((QuantityType<?>) command);
}
if (CHANNEL_FROST_PROTECTION_MODE.equals(channelUID.getId()) && command instanceof OnOffType) {
toggleFrostProtectionMode((OnOffType) command);
}
}
/**
* Process device list and populate room properties, status and state
*
* @param domain Data model representing all devices
*/
@Override
public void refresh(@Nullable QueryResponseDTO domain) {
if (domain == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "No data from bridge");
} else if (config != null) {
final String serialNumber = config.getSerialNumber();
for (LocationDTO location : domain.getData().getUser().getLocations()) {
for (RoomDTO room : location.getRooms()) {
if (room.getThermostat4ies() != null && !room.getThermostat4ies().isEmpty()
&& room.getThermostat4ies().get(0).getDeviceSN().equals(serialNumber)) {
if (room.getThermostat4ies().get(0).getLastPoll() > 10) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Thermostat has not polled for 10 minutes");
} else {
updateStatus(ThingStatus.ONLINE);
updateProperty(PROPERTY_ROOM_ID, room.getId());
updateProperty(PROPERTY_ROOM_NAME, room.getName());
updateProperty(PROPERTY_LOCATION_ID, location.getId());
updateProperty(PROPERTY_LOCATION_NAME, location.getName());
updateState(CHANNEL_CURRENT_TEMPERATURE, parseTemperature(room.getCurrentTemperature()));
updateState(CHANNEL_TARGET_TEMPERATURE, parseTemperature(room.getTargetTemperature()));
updateState(CHANNEL_OVERRIDE_DURATION, parseDuration(room.getOverrideDuration()));
updateState(CHANNEL_RUN_MODE, parseString(room.getRunMode()));
updateState(CHANNEL_FROST_PROTECTION_MODE,
OnOffType.from(room.getRunMode().equals(FROST_PROTECTION_MODE)));
}
return;
}
}
}
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Room not found");
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Room not configured");
}
}
private void setOverride(final QuantityType<?> command) {
String roomId = getThing().getProperties().get(PROPERTY_ROOM_ID);
String locationId = getThing().getProperties().get(PROPERTY_LOCATION_ID);
QuantityType<?> temp = command.toUnit(SIUnits.CELSIUS);
if (temp != null) {
final int value = temp.multiply(BigDecimal.TEN).intValue();
try {
final MyWarmupAccountHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null && config != null) {
final int overrideDuration = config.getOverrideDuration();
if (overrideDuration > 0 && locationId != null && roomId != null) {
bridgeHandler.getApi().setOverride(locationId, roomId, value, overrideDuration);
refreshFromServer();
}
}
} catch (MyWarmupApiException e) {
logger.debug("Set Override failed: {}", e.getMessage());
}
}
}
private void toggleFrostProtectionMode(OnOffType command) {
String roomId = getThing().getProperties().get(PROPERTY_ROOM_ID);
String locationId = getThing().getProperties().get(PROPERTY_LOCATION_ID);
try {
final MyWarmupAccountHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null && locationId != null && roomId != null) {
bridgeHandler.getApi().toggleFrostProtectionMode(locationId, roomId, command);
refreshFromServer();
}
} catch (MyWarmupApiException e) {
logger.debug("Toggle Frost Protection failed: {}", e.getMessage());
}
}
}

View File

@ -0,0 +1,64 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.handler;
import static org.openhab.binding.warmup.internal.WarmupBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link WarmupHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author James Melville - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.warmup", service = ThingHandlerFactory.class)
public class WarmupHandlerFactory extends BaseThingHandlerFactory {
private final HttpClient httpClient;
@Activate
public WarmupHandlerFactory(@Reference final HttpClientFactory factory) {
httpClient = factory.getCommonHttpClient();
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_BRIDGE.equals(thingTypeUID)) {
return new MyWarmupAccountHandler((Bridge) thing, httpClient);
} else if (THING_TYPE_ROOM.equals(thingTypeUID)) {
return new RoomHandler(thing);
}
return null;
}
}

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.warmup.internal.model.query.QueryResponseDTO;
/**
* The {@link WarmupRefreshListener} is an interface applied to Things related to the Bridge allowing updates to be
* processed easily.
*
* @author James Melville - Initial contribution
*/
@NonNullByDefault
public interface WarmupRefreshListener {
void refresh(@Nullable QueryResponseDTO domain);
}

View File

@ -0,0 +1,106 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* The {@link WarmupThingHandler} is a super class for Things related to the Bridge consolidating logic.
*
* @author James Melville - Initial contribution
*/
@NonNullByDefault
public class WarmupThingHandler extends BaseThingHandler {
public WarmupThingHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
final MyWarmupAccountHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) {
updateStatus(ThingStatus.UNKNOWN);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
final MyWarmupAccountHandler bridgeHandler = getBridgeHandler();
if (command instanceof RefreshType && bridgeHandler != null) {
bridgeHandler.refreshFromCache();
}
}
protected void refreshFromServer() {
final MyWarmupAccountHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) {
bridgeHandler.refreshFromServer();
}
}
protected @Nullable MyWarmupAccountHandler getBridgeHandler() {
final Bridge bridge = getBridge();
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
return null;
} else {
return (MyWarmupAccountHandler) bridge.getHandler();
}
}
/**
*
* @param temperature value returned from the API as an Integer * 10. i.e. 215 = 21.5 degrees C
* @return the temperature as a {@link QuantityType}
*/
protected State parseTemperature(@Nullable Integer temperature) {
return temperature != null ? new QuantityType<>(temperature / 10.0, SIUnits.CELSIUS) : UnDefType.UNDEF;
}
/**
*
* @param value a string to convert to {@link StringType}
* @return the string as a {@link StringType}
*/
protected State parseString(@Nullable String value) {
return value != null ? new StringType(value) : UnDefType.UNDEF;
}
/**
*
* @param value an integer to convert to {@link QuantityType} in minutes
* @return the number of minutes as a {@link QuantityType}
*/
protected State parseDuration(@Nullable Integer value) {
return value != null ? new QuantityType<>(value, Units.MINUTE) : UnDefType.UNDEF;
}
}

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.model.auth;
/**
* @author James Melville - Initial contribution
*/
@SuppressWarnings("unused")
public class AuthRequestDTO {
private AuthRequestDataDTO request;
public AuthRequestDTO(String email, String password, String method, String appId) {
setRequest(new AuthRequestDataDTO(email, password, method, appId));
}
public void setRequest(AuthRequestDataDTO request) {
this.request = request;
}
}

View File

@ -0,0 +1,47 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.model.auth;
/**
* @author James Melville - Initial contribution
*/
@SuppressWarnings("unused")
public class AuthRequestDataDTO {
private String email;
private String password;
private String method;
private String appId;
public AuthRequestDataDTO(String email, String password, String method, String appId) {
this.setEmail(email);
this.setPassword(password);
this.setMethod(method);
this.setAppId(appId);
}
public void setEmail(String email) {
this.email = email;
}
public void setPassword(String password) {
this.password = password;
}
public void setMethod(String method) {
this.method = method;
}
public void setAppId(String appId) {
this.appId = appId;
}
}

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.model.auth;
/**
* @author James Melville - Initial contribution
*/
public class AuthResponseDTO {
private AuthResponseStatusDTO status;
private AuthResponseDataDTO response;
public AuthResponseStatusDTO getStatus() {
return status;
}
public AuthResponseDataDTO getResponse() {
return response;
}
}

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.model.auth;
/**
* @author James Melville - Initial contribution
*/
public class AuthResponseDataDTO {
private String method;
private String token;
public String getToken() {
return token;
}
public String getMethod() {
return method;
}
}

View File

@ -0,0 +1,24 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.model.auth;
/**
* @author James Melville - Initial contribution
*/
public class AuthResponseStatusDTO {
private String result;
public String getResult() {
return result;
}
}

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.model.query;
/**
* @author James Melville - Initial contribution
*/
public class DeviceDTO {
private String deviceSN;
private int lastPoll;
public String getDeviceSN() {
return deviceSN;
}
public int getLastPoll() {
return lastPoll;
}
}

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.model.query;
import java.util.List;
/**
* @author James Melville - Initial contribution
*/
public class LocationDTO {
private int id;
private String name;
private List<RoomDTO> rooms;
public String getId() {
return String.valueOf(id);
}
public String getName() {
return name;
}
public List<RoomDTO> getRooms() {
return rooms;
}
}

View File

@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.model.query;
/**
* @author James Melville - Initial contribution
*/
public class QueryDataDTO {
private UserDTO user;
public UserDTO getUser() {
return user;
}
}

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.model.query;
/**
* @author James Melville - Initial contribution
*/
public class QueryResponseDTO {
private QueryDataDTO data;
private String status;
public QueryDataDTO getData() {
return data;
}
public String getStatus() {
return status;
}
}

View File

@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.model.query;
import java.util.List;
/**
* @author James Melville - Initial contribution
*/
public class RoomDTO {
private int id;
private String roomName;
private Integer currentTemp;
private Integer targetTemp;
private String runMode;
private Integer overrideDur;
private List<DeviceDTO> thermostat4ies;
public String getId() {
return String.valueOf(id);
}
public String getName() {
return roomName;
}
public Integer getCurrentTemperature() {
return currentTemp;
}
public Integer getTargetTemperature() {
return targetTemp;
}
public String getRunMode() {
return runMode;
}
public Integer getOverrideDuration() {
return overrideDur;
}
public List<DeviceDTO> getThermostat4ies() {
return thermostat4ies;
}
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.warmup.internal.model.query;
import java.util.List;
/**
* @author James Melville - Initial contribution
*/
public class UserDTO {
private List<LocationDTO> locations;
public List<LocationDTO> getLocations() {
return locations;
}
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="warmup" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Warmup Binding</name>
<description>This is the binding for a Warmup 4iE Thermostat primarily used for controlling underfloor heating.</description>
</binding:binding>

View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="warmup"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="my-warmup">
<label>My Warmup Account</label>
<description>Connection to the https://my.warmup.com site</description>
<category>WebService</category>
<config-description>
<parameter name="username" type="text" required="true">
<context>email</context>
<label>Username</label>
<description>Username for my.warmup.com</description>
</parameter>
<parameter name="password" type="text" required="true">
<context>password</context>
<label>Password</label>
<description>Password for my.warmup.com</description>
</parameter>
<parameter name="refreshInterval" type="integer" unit="s" required="true" min="10">
<label>Refresh Interval</label>
<description>Interval in seconds between automatic refreshes</description>
<default>300</default>
</parameter>
</config-description>
</bridge-type>
<thing-type id="room">
<supported-bridge-type-refs>
<bridge-type-ref id="my-warmup"/>
</supported-bridge-type-refs>
<label>Room</label>
<description>Warmup 4iE Device controlling a room</description>
<category>RadiatorControl</category>
<channels>
<channel id="currentTemperature" typeId="currentTemperature"/>
<channel id="targetTemperature" typeId="targetTemperature"/>
<channel id="overrideRemaining" typeId="overrideRemaining"/>
<channel id="runMode" typeId="runMode"/>
<channel id="frostProtectionMode" typeId="frostProtectionMode"/>
</channels>
<representation-property>serialNumber</representation-property>
<config-description>
<parameter name="serialNumber" type="text" required="true">
<label>Serial Number</label>
</parameter>
<parameter name="overrideDuration" type="integer" unit="m" required="true">
<label>Override Duration</label>
<description>Duration in minutes of override when target temperature is changed</description>
<default>60</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="currentTemperature">
<item-type>Number:Temperature</item-type>
<label>Current Temperature</label>
<description>Current temperature in room, may be air or floor dependent on Heating Target</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="targetTemperature">
<item-type>Number:Temperature</item-type>
<label>Target Temperature</label>
<description>Target temperature currently set on device</description>
<category>Heating</category>
<state min="5" max="30" step="0.5" readOnly="false" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="overrideRemaining">
<item-type>Number:Time</item-type>
<label>Override Remaining</label>
<description>How long until the override deactivates</description>
<category>Time</category>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="runMode">
<item-type>String</item-type>
<label>Run Mode</label>
<description>The heat regulation mode of the thermostat</description>
<state readOnly="true">
<options>
<option value="not_set">Not Set</option>
<option value="off">Off</option>
<option value="schedule">Schedule</option>
<option value="override">Override</option>
<option value="fixed">Fixed</option>
<option value="anti_frost">Frost Protection</option>
<option value="holiday">Holiday</option>
<option value="fil_pilote">Fil Pilote</option>
<option value="gradual">Gradual</option>
<option value="relay">Relay</option>
<option value="previous">Previous</option>
</options>
</state>
</channel-type>
<channel-type id="frostProtectionMode">
<item-type>Switch</item-type>
<label>Frost Protection Mode</label>
</channel-type>
</thing:thing-descriptions>

View File

@ -337,6 +337,7 @@
<module>org.openhab.binding.vigicrues</module>
<module>org.openhab.binding.vitotronic</module>
<module>org.openhab.binding.volvooncall</module>
<module>org.openhab.binding.warmup</module>
<module>org.openhab.binding.weathercompany</module>
<module>org.openhab.binding.weatherunderground</module>
<module>org.openhab.binding.webthing</module>