mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[warmup] Initial contribution (#8562)
Signed-off-by: James Melville <jamesmelville@gmail.com>
This commit is contained in:
parent
18497a9436
commit
db05079e6f
@ -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
|
||||
|
@ -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>
|
||||
|
13
bundles/org.openhab.binding.warmup/NOTICE
Normal file
13
bundles/org.openhab.binding.warmup/NOTICE
Normal 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
|
112
bundles/org.openhab.binding.warmup/README.md
Normal file
112
bundles/org.openhab.binding.warmup/README.md
Normal 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
|
||||
}
|
||||
```
|
17
bundles/org.openhab.binding.warmup/pom.xml
Normal file
17
bundles/org.openhab.binding.warmup/pom.xml
Normal 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>
|
@ -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>
|
@ -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";
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user