added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.radiothermostat/.classpath
Normal file
32
bundles/org.openhab.binding.radiothermostat/.classpath
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.radiothermostat/.project
Normal file
23
bundles/org.openhab.binding.radiothermostat/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.radiothermostat</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
13
bundles/org.openhab.binding.radiothermostat/NOTICE
Normal file
13
bundles/org.openhab.binding.radiothermostat/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
|
||||
203
bundles/org.openhab.binding.radiothermostat/README.md
Normal file
203
bundles/org.openhab.binding.radiothermostat/README.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# RadioThermostat Binding
|
||||
|
||||
This binding connects RadioThermostat/3M Filtrete models CT30, CT50/3M50, CT80, etc. with built-in Wi-Fi module to openHAB.
|
||||
|
||||
The binding retrieves and periodically updates all basic system information from the thermostat.
|
||||
The main thermostat functions such as thermostat mode, fan mode, temperature set point and hold mode can be controlled.
|
||||
System run-time information and humidity readings are polled less frequently and can be disabled completely if not desired.
|
||||
Humidity information is available only when using a CT80 thermostat and I have noticed that the humidity reported is very inaccurate.
|
||||
|
||||
The main caveat for using this binding is to keep in mind that the web server in the thermostat is very slow.
|
||||
Do not over load it with excessive amounts of simultaneous commands.
|
||||
When changing the thermostat mode, the current temperature set point is cleared and a refresh of the thermostat data is done to get the new mode's set point.
|
||||
Since retrieving the thermostat's data is the slowest operation, it will take several seconds after changing the mode before the new set point is displayed.
|
||||
The 'Program Mode' command is untested and according to the published API is only available on a CT80 Rev B.
|
||||
|
||||
## Supported Things
|
||||
|
||||
There is exactly one supported thing type, which represents the thermostat.
|
||||
It has the `rtherm` id.
|
||||
Multiple Things can be added if more than one thermostat is to be controlled.
|
||||
|
||||
## Discovery
|
||||
|
||||
Auto-discovery is supported if the thermostat can be located on the local network using SSDP.
|
||||
Otherwise the thing must be manually added.
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
The binding has no configuration options, all configuration is done at Thing level.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
The thing has a few configuration parameters:
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| hostName | The host name or IP address of the thermostat. Mandatory. |
|
||||
| refresh | Overrides the refresh interval of the thermostat data. Optional, the default is 2 minutes. |
|
||||
| logRefresh | Overrides the refresh interval of the run-time logs & humidity data. Optional, the default is 10 minutes. |
|
||||
| isCT80 | Flag to enable additional features only available on the CT80 thermostat. Optional, the default is false. |
|
||||
| disableLogs | Disable retrieval of run-time logs from the thermostat. Optional, the default is false. |
|
||||
| setpointMode | Controls temporary or absolute setpoint mode. In "temporary" mode the thermostat will temporarily maintain the given setpoint, returning to its program after a time. In "absolute" mode the thermostat will ignore its program maintaining the given setpoint. |
|
||||
|
||||
## Channels
|
||||
|
||||
The thermostat information that is retrieved is available as these channels:
|
||||
|
||||
| Channel ID | Item Type | Description |
|
||||
|------------------------|----------------------|---------------------------------------------------------------------------|
|
||||
| temperature | Number:Temperature | The current temperature reading of the thermostat |
|
||||
| humidity | Number:Dimensionless | The current humidity reading of the thermostat (CT80 only) |
|
||||
| mode | Number | The current operating mode of the HVAC system |
|
||||
| fan_mode | Number | The current operating mode of the fan |
|
||||
| program_mode | Number | The program schedule that the thermostat is running (CT80 Rev B only) |
|
||||
| set_point | Number:Temperature | The current temperature set point of the thermostat |
|
||||
| status | Number | Indicates the current running status of the HVAC system |
|
||||
| fan_status | Number | Indicates the current fan status of the HVAC system |
|
||||
| override | Number | Indicates if the normal program set-point has been manually overridden |
|
||||
| hold | Switch | Indicates if the current set point temperature is to be held indefinitely |
|
||||
| day | Number | The current day of the week reported by the thermostat (0 = Monday) |
|
||||
| hour | Number | The current hour of the day reported by the thermostat (24 hr) |
|
||||
| minute | Number | The current minute past the hour reported by the thermostat |
|
||||
| dt_stamp | String | The current day of the week and time reported by the thermostat (E HH:mm) |
|
||||
| today_heat_runtime | Number:Time | The total number of minutes of heating run-time today |
|
||||
| today_cool_runtime | Number:Time | The total number of minutes of cooling run-time today |
|
||||
| yesterday_heat_runtime | Number:Time | The total number of minutes of heating run-time yesterday |
|
||||
| yesterday_cool_runtime | Number:Time | The total number of minutes of cooling run-time yesterday |
|
||||
|
||||
## Full Example
|
||||
|
||||
radiotherm.map:
|
||||
|
||||
```text
|
||||
UNDEF_stus=-
|
||||
NULL_stus=-
|
||||
-_stus=-
|
||||
0_stus=Off
|
||||
1_stus=Heating
|
||||
2_stus=Cooling
|
||||
UNDEF_fstus=-
|
||||
NULL_fstus=-
|
||||
-_fstus=-
|
||||
0_fstus=Off
|
||||
1_fstus=On
|
||||
UNDEF_mode=-
|
||||
NULL_mode=-
|
||||
-_mode=-
|
||||
0_mode=Off
|
||||
1_mode=Heat
|
||||
2_mode=Cool
|
||||
3_mode=Auto
|
||||
UNDEF_fan=-
|
||||
NULL_fan=-
|
||||
-_fan=-
|
||||
0_fan=Auto
|
||||
1_fan=Auto/Circulate
|
||||
2_fan=On
|
||||
UNDEF_pgm=-
|
||||
NULL_pgm=-
|
||||
-_pgm=-
|
||||
-1_pgm=None
|
||||
0_pgm=Program A
|
||||
1_pgm=Program B
|
||||
2_pgm=Vacation
|
||||
3_pgm=Holiday
|
||||
UNDEF_over=-
|
||||
NULL_over=-
|
||||
-_over=-
|
||||
0_over=No
|
||||
1_over=Yes
|
||||
|
||||
```
|
||||
|
||||
radiotherm.things:
|
||||
|
||||
```java
|
||||
radiothermostat:rtherm:mytherm1 "My 1st floor thermostat" [ hostName="192.168.10.1", refresh=2, logRefresh=10, isCT80=false, disableLogs=false, setpointMode="temporary" ]
|
||||
radiothermostat:rtherm:mytherm2 "My 2nd floor thermostat" [ hostName="mythermhost2", refresh=1, logRefresh=20, isCT80=true, disableLogs=false, setpointMode="absolute" ]
|
||||
```
|
||||
|
||||
radiotherm.items:
|
||||
|
||||
```java
|
||||
Number:Temperature Therm_Temp "Current Temperature [%.1f °F] " <temperature> { channel="radiothermostat:rtherm:mytherm1:temperature" }
|
||||
// Humidity only supported on CT80
|
||||
Number Therm_Hum "Current Humidity [%d %%]" <temperature> { channel="radiothermostat:rtherm:mytherm1:humidity" }
|
||||
Number Therm_Mode "Thermostat Mode [MAP(radiotherm.map):%s_mode]" { channel="radiothermostat:rtherm:mytherm1:mode" }
|
||||
// The Auto/Circulate option will only appear for CT80
|
||||
Number Therm_Fmode "Fan Mode [MAP(radiotherm.map):%s_fan]" { channel="radiothermostat:rtherm:mytherm1:fan_mode" }
|
||||
// Program Mode only supported on CT80 Rev B
|
||||
Number Therm_Pmode "Program Mode [MAP(radiotherm.map):%s_pgm]" { channel="radiothermostat:rtherm:mytherm1:program_mode" }
|
||||
Number:Temperature Therm_Setpt "Set Point [%d]" <temperature> { channel="radiothermostat:rtherm:mytherm1:set_point" }
|
||||
Number Therm_Status "Status [MAP(radiotherm.map):%s_stus]" { channel="radiothermostat:rtherm:mytherm1:status" }
|
||||
Number Therm_FanStatus "Fan Status [MAP(radiotherm.map):%s_fstus]" { channel="radiothermostat:rtherm:mytherm1:fan_status" }
|
||||
Number Therm_Override "Override [MAP(radiotherm.map):%s_over]" { channel="radiothermostat:rtherm:mytherm1:override" }
|
||||
Switch Therm_Hold "Hold" { channel="radiothermostat:rtherm:mytherm1:hold" }
|
||||
|
||||
Number Therm_Day "Thermostat Day [%s]" { channel="radiothermostat:rtherm:mytherm1:day" }
|
||||
Number Therm_Hour "Thermostat Hour [%s]" { channel="radiothermostat:rtherm:mytherm1:hour" }
|
||||
Number Therm_Minute "Thermostat Minute [%s]" { channel="radiothermostat:rtherm:mytherm1:minute" }
|
||||
String Therm_Dstmp "Thermostat DateStamp [%s]" <time> { channel="radiothermostat:rtherm:mytherm1:dt_stamp" }
|
||||
|
||||
Number:Time Therm_todayheat "Today's Heating Runtime [%d %unit%]" { channel="radiothermostat:rtherm:mytherm1:today_heat_runtime" }
|
||||
Number:Time Therm_todaycool "Today's Cooling Runtime [%d %unit%]" { channel="radiothermostat:rtherm:mytherm1:today_cool_runtime" }
|
||||
Number:Time Therm_yesterdayheat "Yesterday's Heating Runtime [%d %unit%]" { channel="radiothermostat:rtherm:mytherm1:yesterday_heat_runtime" }
|
||||
Number:Time Therm_yesterdaycool "Yesterday's Cooling Runtime [%d %unit%]" { channel="radiothermostat:rtherm:mytherm1:yesterday_cool_runtime" }
|
||||
|
||||
// A virtual switch used to trigger a rule to send a json command to the thermostat
|
||||
Switch Therm_mysetting "Send my preferred setting"
|
||||
```
|
||||
|
||||
radiotherm.sitemap:
|
||||
|
||||
```perl
|
||||
sitemap radiotherm label="My Thermostat" {
|
||||
Frame label="My 1st floor thermostat" {
|
||||
Text item=Therm_Temp icon="temperature" valuecolor=[>76="orange",>67.5="green",<=67.5="blue"]
|
||||
// Humidity only supported on CT80
|
||||
Text item=Therm_Hum icon="humidity"
|
||||
Setpoint item=Therm_Setpt label="Target temperature [%d °F]" visibility=[Therm_Mode==1,Therm_Mode==2] icon="temperature" minValue=60 maxValue=85 step=1
|
||||
Selection item=Therm_Mode icon="climate"
|
||||
Selection item=Therm_Fmode icon="fan"
|
||||
// Program Mode only supported on CT80 Rev B
|
||||
Selection item=Therm_Pmode icon="smoke"
|
||||
Text item=Therm_Status icon="climate"
|
||||
Text item=Therm_FanStatus icon="flow"
|
||||
Text item=Therm_Override icon="smoke"
|
||||
Switch item=Therm_Hold icon="smoke"
|
||||
|
||||
// Virtual switch/button to trigger a rule to send a custom command
|
||||
// The ON value displays in the button
|
||||
Switch item=Therm_mysetting mappings=[ON="Heat, 58, hold"]
|
||||
|
||||
Text item=Therm_Day
|
||||
Text item=Therm_Hour
|
||||
Text item=Therm_Minute
|
||||
Text item=Therm_Dstmp
|
||||
|
||||
Text item=Therm_todayheat
|
||||
Text item=Therm_todaycool
|
||||
Text item=Therm_yesterdayheat
|
||||
Text item=Therm_yesterdaycool
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
radiotherm.rules:
|
||||
|
||||
```java
|
||||
rule "Send my thermostat command"
|
||||
when
|
||||
Item Therm_mysetting received command
|
||||
then
|
||||
val actions = getActions("radiothermostat","radiothermostat:rtherm:mytherm1")
|
||||
if(null === actions) {
|
||||
logInfo("actions", "Actions not found, check thing ID")
|
||||
return
|
||||
}
|
||||
// JSON to send directly to the thermostat's '/tstat' endpoint
|
||||
// See RadioThermostat_CT50_Honeywell_Wifi_API_V1.3.pdf for more detail
|
||||
actions.sendRawCommand('{"hold":1, "t_heat":' + "58" + ', "tmode":1}')
|
||||
end
|
||||
```
|
||||
17
bundles/org.openhab.binding.radiothermostat/pom.xml
Normal file
17
bundles/org.openhab.binding.radiothermostat/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
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.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.radiothermostat</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: RadioThermostat Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.radiothermostat-${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-radiothermostat" description="Radio Thermostat Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.radiothermostat/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link IRadioThermostatThingActions} defines the interface for all thing actions supported by the binding.
|
||||
* These methods, parameters, and return types are explained in {@link RadioThermostatThingActions}.
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IRadioThermostatThingActions {
|
||||
|
||||
void sendRawCommand(@Nullable String rawCommand);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Dimensionless;
|
||||
import javax.measure.quantity.Temperature;
|
||||
import javax.measure.quantity.Time;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link RadioThermostatBinding} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RadioThermostatBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "radiothermostat";
|
||||
public static final String LOCAL = "local";
|
||||
public static final String PROPERTY_IP = "hostName";
|
||||
public static final String PROPERTY_ISCT80 = "isCT80";
|
||||
|
||||
public static final String KEY_ERROR = "error";
|
||||
|
||||
// List of JSON resources
|
||||
public static final String DEFAULT_RESOURCE = "tstat";
|
||||
public static final String RUNTIME_RESOURCE = "tstat/datalog";
|
||||
public static final String HUMIDITY_RESOURCE = "tstat/humidity";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_RTHERM = new ThingTypeUID(BINDING_ID, "rtherm");
|
||||
|
||||
// List of all Channel id's
|
||||
public static final String TEMPERATURE = "temperature";
|
||||
public static final String HUMIDITY = "humidity";
|
||||
public static final String MODE = "mode";
|
||||
public static final String FAN_MODE = "fan_mode";
|
||||
public static final String PROGRAM_MODE = "program_mode";
|
||||
public static final String SET_POINT = "set_point";
|
||||
public static final String OVERRIDE = "override";
|
||||
public static final String HOLD = "hold";
|
||||
public static final String STATUS = "status";
|
||||
public static final String FAN_STATUS = "fan_status";
|
||||
public static final String DAY = "day";
|
||||
public static final String HOUR = "hour";
|
||||
public static final String MINUTE = "minute";
|
||||
public static final String DATE_STAMP = "dt_stamp";
|
||||
public static final String TODAY_HEAT_RUNTIME = "today_heat_runtime";
|
||||
public static final String TODAY_COOL_RUNTIME = "today_cool_runtime";
|
||||
public static final String YESTERDAY_HEAT_RUNTIME = "yesterday_heat_runtime";
|
||||
public static final String YESTERDAY_COOL_RUNTIME = "yesterday_cool_runtime";
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_RTHERM);
|
||||
public static final Set<String> SUPPORTED_CHANNEL_IDS = Stream.of(TEMPERATURE, HUMIDITY, MODE, FAN_MODE,
|
||||
PROGRAM_MODE, SET_POINT, OVERRIDE, HOLD, STATUS, FAN_STATUS, DAY, HOUR, MINUTE, DATE_STAMP,
|
||||
TODAY_HEAT_RUNTIME, TODAY_COOL_RUNTIME, YESTERDAY_HEAT_RUNTIME, YESTERDAY_COOL_RUNTIME)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Units of measurement of the data delivered by the API
|
||||
public static final Unit<Temperature> API_TEMPERATURE_UNIT = ImperialUnits.FAHRENHEIT;
|
||||
public static final Unit<Dimensionless> API_HUMIDITY_UNIT = SmartHomeUnits.PERCENT;
|
||||
public static final Unit<Time> API_MINUTES_UNIT = SmartHomeUnits.MINUTE;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link RadioThermostatConfiguration} is the class used to match the
|
||||
* thing configuration.
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RadioThermostatConfiguration {
|
||||
public @Nullable String hostName;
|
||||
public @Nullable Integer refresh;
|
||||
public @Nullable Integer logRefresh;
|
||||
public boolean isCT80 = false;
|
||||
public boolean disableLogs = false;
|
||||
public String setpointMode = "temporary";
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal;
|
||||
|
||||
import static org.openhab.binding.radiothermostat.internal.RadioThermostatBindingConstants.THING_TYPE_RTHERM;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.radiothermostat.internal.handler.RadioThermostatHandler;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
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 RadioThermostatHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.radiothermostat")
|
||||
public class RadioThermostatHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_RTHERM);
|
||||
private final RadioThermostatStateDescriptionProvider stateDescriptionProvider;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public RadioThermostatHandlerFactory(final @Reference RadioThermostatStateDescriptionProvider provider,
|
||||
final @Reference HttpClientFactory httpClientFactory) {
|
||||
this.stateDescriptionProvider = provider;
|
||||
this.httpClient = httpClientFactory.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 (thingTypeUID.equals(THING_TYPE_RTHERM)) {
|
||||
RadioThermostatHandler handler = new RadioThermostatHandler(thing, stateDescriptionProvider, httpClient);
|
||||
return handler;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link RadioThermostatHttpException} extends Exception
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RadioThermostatHttpException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public RadioThermostatHttpException(String errorMessage) {
|
||||
super(errorMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
|
||||
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
|
||||
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link RadioThermostatStateDescriptionProvider} class is a dynamic provider of state options while leaving other
|
||||
* state description fields as original.
|
||||
*
|
||||
* @author Gregory Moyer - Initial contribution
|
||||
* @author Michael Lobstein - Adapted for RadioThermostat Binding
|
||||
*/
|
||||
@Component(service = { DynamicStateDescriptionProvider.class, RadioThermostatStateDescriptionProvider.class })
|
||||
@NonNullByDefault
|
||||
public class RadioThermostatStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
|
||||
@Reference
|
||||
protected void setChannelTypeI18nLocalizationService(
|
||||
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||
}
|
||||
|
||||
protected void unsetChannelTypeI18nLocalizationService(
|
||||
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.channelTypeI18nLocalizationService = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.radiothermostat.internal.handler.RadioThermostatHandler;
|
||||
import org.openhab.core.automation.annotation.ActionInput;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Some automation actions to be used with a {@link RadioThermostatThingActions}
|
||||
*
|
||||
* @author Michael Lobstein - initial contribution
|
||||
*
|
||||
*/
|
||||
@ThingActionsScope(name = "radiothermostat")
|
||||
@NonNullByDefault
|
||||
public class RadioThermostatThingActions implements ThingActions, IRadioThermostatThingActions {
|
||||
private final Logger logger = LoggerFactory.getLogger(RadioThermostatThingActions.class);
|
||||
|
||||
private @Nullable RadioThermostatHandler handler;
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "sendRawCommand", description = "Action that sends raw command to the thermostat")
|
||||
public void sendRawCommand(@ActionInput(name = "sendRawCommand") @Nullable String rawCommand) {
|
||||
RadioThermostatHandler localHandler = handler;
|
||||
if (rawCommand == null) {
|
||||
logger.warn("sendRawCommand called with null command, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
if (localHandler != null) {
|
||||
localHandler.handleRawCommand(rawCommand);
|
||||
logger.debug("sendRawCommand called with raw command: {}", rawCommand);
|
||||
}
|
||||
}
|
||||
|
||||
/** Static alias to support the old DSL rules engine and make the action available there. */
|
||||
public static void sendRawCommand(@Nullable ThingActions actions, @Nullable String rawCommand)
|
||||
throws IllegalArgumentException {
|
||||
invokeMethodOf(actions).sendRawCommand(rawCommand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
this.handler = (RadioThermostatHandler) handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
private static IRadioThermostatThingActions invokeMethodOf(@Nullable ThingActions actions) {
|
||||
if (actions == null) {
|
||||
throw new IllegalArgumentException("actions cannot be null");
|
||||
}
|
||||
if (actions.getClass().getName().equals(RadioThermostatThingActions.class.getName())) {
|
||||
if (actions instanceof RadioThermostatThingActions) {
|
||||
return (IRadioThermostatThingActions) actions;
|
||||
} else {
|
||||
return (IRadioThermostatThingActions) Proxy.newProxyInstance(
|
||||
IRadioThermostatThingActions.class.getClassLoader(),
|
||||
new Class[] { IRadioThermostatThingActions.class },
|
||||
(Object proxy, Method method, Object[] args) -> {
|
||||
Method m = actions.getClass().getDeclaredMethod(method.getName(),
|
||||
method.getParameterTypes());
|
||||
return m.invoke(actions, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Actions is not an instance of RadioThermostatThingActions");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal.communication;
|
||||
|
||||
import static org.eclipse.jetty.http.HttpMethod.GET;
|
||||
import static org.eclipse.jetty.http.HttpStatus.OK_200;
|
||||
import static org.openhab.binding.radiothermostat.internal.RadioThermostatBindingConstants.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
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.api.Result;
|
||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.openhab.binding.radiothermostat.internal.RadioThermostatHttpException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Class for communicating with the RadioThermostat web interface
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RadioThermostatConnector {
|
||||
private final Logger logger = LoggerFactory.getLogger(RadioThermostatConnector.class);
|
||||
|
||||
private static final String URL = "http://%hostName%/%resource%";
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final List<RadioThermostatEventListener> listeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
private @Nullable String hostName;
|
||||
|
||||
public RadioThermostatConnector(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
public void setThermostatHostName(@Nullable String hostName) {
|
||||
this.hostName = hostName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener to the list of listeners to be notified with events
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
public void addEventListener(RadioThermostatEventListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a listener from the list of listeners to be notified with events
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
public void removeEventListener(RadioThermostatEventListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an asynchronous http call to the thermostat, the response will be send to the
|
||||
* event listeners as a RadioThermostat event when it is finally received
|
||||
*
|
||||
* @param resouce the url of the json resource on the thermostat
|
||||
*/
|
||||
public void getAsyncThermostatData(String resource) {
|
||||
String urlStr = buildRequestURL(resource);
|
||||
|
||||
httpClient.newRequest(urlStr).method(GET).timeout(20, TimeUnit.SECONDS).send(new BufferingResponseListener() {
|
||||
@Override
|
||||
public void onComplete(@Nullable Result result) {
|
||||
if (result != null && !result.isFailed()) {
|
||||
String response = getContentAsString();
|
||||
logger.debug("thermostatResponse = {}", response);
|
||||
dispatchKeyValue(resource, response);
|
||||
} else {
|
||||
dispatchKeyValue(KEY_ERROR, "");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a command to the thermostat
|
||||
*
|
||||
* @param the JSON attribute key for the value to be updated
|
||||
* @param the value to be updated in the thermostat
|
||||
* @return the JSON response string from the thermostat
|
||||
*/
|
||||
public String sendCommand(String cmdKey, @Nullable String cmdVal) {
|
||||
return sendCommand(cmdKey, cmdVal, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a command to the thermostat
|
||||
*
|
||||
* @param the JSON attribute key for the value to be updated
|
||||
* @param the value to be updated in the thermostat
|
||||
* @param JSON string to send directly to the thermostat instead of a key/value pair
|
||||
* @return the JSON response string from the thermostat
|
||||
*/
|
||||
public String sendCommand(@Nullable String cmdKey, @Nullable String cmdVal, @Nullable String cmdJson) {
|
||||
// if we got a cmdJson string send that, otherwise build the json from the key and val params
|
||||
String postJson = cmdJson != null ? cmdJson : "{\"" + cmdKey + "\":" + cmdVal + "}";
|
||||
String urlStr = buildRequestURL(DEFAULT_RESOURCE);
|
||||
|
||||
String output = "";
|
||||
|
||||
try {
|
||||
Request request = httpClient.POST(urlStr);
|
||||
request.header(HttpHeader.ACCEPT, "text/plain");
|
||||
request.header(HttpHeader.CONTENT_TYPE, "text/plain");
|
||||
request.content(new StringContentProvider(postJson), "application/json");
|
||||
|
||||
ContentResponse contentResponse = request.send();
|
||||
int httpStatus = contentResponse.getStatus();
|
||||
|
||||
if (httpStatus != OK_200) {
|
||||
throw new RadioThermostatHttpException("Thermostat HTTP response code was: " + httpStatus);
|
||||
}
|
||||
output = contentResponse.getContentAsString();
|
||||
} catch (RadioThermostatHttpException | InterruptedException | TimeoutException | ExecutionException e) {
|
||||
logger.warn("Error executing thermostat command: {}, {}", postJson, e.getMessage());
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build request URL from configuration data
|
||||
*
|
||||
* @return a valid URL for the thermostat's JSON interface
|
||||
*/
|
||||
private String buildRequestURL(String resource) {
|
||||
String urlStr = URL.replace("%hostName%", hostName);
|
||||
urlStr = urlStr.replace("%resource%", resource);
|
||||
|
||||
return urlStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch an event (key, value) to the event listeners
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
*/
|
||||
private void dispatchKeyValue(String key, String value) {
|
||||
RadioThermostatEvent event = new RadioThermostatEvent(this, key, value);
|
||||
for (RadioThermostatEventListener listener : listeners) {
|
||||
listener.onNewMessageEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal.communication;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* RadioThermostatEvent used to pass json update data received from the thermostat
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RadioThermostatEvent extends EventObject {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String key;
|
||||
private String value;
|
||||
|
||||
public RadioThermostatEvent(Object source, String key, String value) {
|
||||
super(source);
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal.communication;
|
||||
|
||||
import java.util.EventListener;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* RadtioThermostat Event Listener interface. Handles incoming RadioThermostat message events
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface RadioThermostatEventListener extends EventListener {
|
||||
|
||||
/**
|
||||
* Event handler method for incoming RadioThermostat message events
|
||||
*
|
||||
* @param event the event object
|
||||
*/
|
||||
public void onNewMessageEvent(RadioThermostatEvent event);
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal.discovery;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.MulticastSocket;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.radiothermostat.internal.RadioThermostatBindingConstants;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link RadioThermostatDiscoveryService} is responsible for discovery of
|
||||
* RadioThermostats on the local network
|
||||
*
|
||||
* @author William Welliver - Initial contribution
|
||||
* @author Dan Cunningham - Refactoring and Improvements
|
||||
* @author Bill Forsyth - Modified for the RadioThermostat's peculiar discovery mode
|
||||
* @author Michael Lobstein - Cleanup for RadioThermostat
|
||||
*
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
@Component(service = DiscoveryService.class, configurationPid = "discovery.radiothermostat")
|
||||
public class RadioThermostatDiscoveryService extends AbstractDiscoveryService {
|
||||
private final Logger logger = LoggerFactory.getLogger(RadioThermostatDiscoveryService.class);
|
||||
private static final String RADIOTHERMOSTAT_DISCOVERY_MESSAGE = "TYPE: WM-DISCOVER\r\nVERSION: 1.0\r\n\r\nservices:com.marvell.wm.system*\r\n\r\n";
|
||||
|
||||
private static final String SSDP_MATCH = "WM-NOTIFY";
|
||||
private static final int BACKGROUND_SCAN_INTERVAL_SECONDS = 300;
|
||||
|
||||
private @Nullable ScheduledFuture<?> scheduledFuture = null;
|
||||
|
||||
public RadioThermostatDiscoveryService() {
|
||||
super(RadioThermostatBindingConstants.SUPPORTED_THING_TYPES_UIDS, 30, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
logger.debug("Starting Background Scan");
|
||||
stopBackgroundDiscovery();
|
||||
scheduledFuture = scheduler.scheduleWithFixedDelay(this::doRunRun, 0, BACKGROUND_SCAN_INTERVAL_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
if (scheduledFuture != null && !scheduledFuture.isCancelled()) {
|
||||
scheduledFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.debug("Starting Interactive Scan");
|
||||
doRunRun();
|
||||
}
|
||||
|
||||
protected synchronized void doRunRun() {
|
||||
logger.debug("Sending SSDP discover.");
|
||||
try {
|
||||
Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
|
||||
while (nets.hasMoreElements()) {
|
||||
NetworkInterface ni = nets.nextElement();
|
||||
if (ni.isUp() && ni.supportsMulticast() && !ni.isLoopback()) {
|
||||
sendDiscoveryBroacast(ni);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error discovering devices", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a SSDP discovery message into the network to find provided
|
||||
* services.
|
||||
*
|
||||
* @return The Socket the answers will arrive at.
|
||||
* @throws UnknownHostException
|
||||
* @throws IOException
|
||||
* @throws SocketException
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
private void sendDiscoveryBroacast(NetworkInterface ni)
|
||||
throws UnknownHostException, SocketException, UnsupportedEncodingException {
|
||||
InetAddress m = InetAddress.getByName("239.255.255.250");
|
||||
final int port = 1900;
|
||||
logger.debug("Sending discovery broadcast");
|
||||
try {
|
||||
Enumeration<InetAddress> addrs = ni.getInetAddresses();
|
||||
InetAddress a = null;
|
||||
while (addrs.hasMoreElements()) {
|
||||
a = addrs.nextElement();
|
||||
if (a instanceof Inet4Address) {
|
||||
break;
|
||||
} else {
|
||||
a = null;
|
||||
}
|
||||
}
|
||||
if (a == null) {
|
||||
logger.debug("no ipv4 address on {}", ni.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
// for whatever reason, the radio thermostat responses will not be seen
|
||||
// if we bind this socket to a particular address.
|
||||
// this seems to be okay on linux systems, but osx apparently prefers ipv6, so this
|
||||
// prevents responses from being received unless the ipv4 stack is given preference.
|
||||
MulticastSocket socket = new MulticastSocket(null);
|
||||
socket.setSoTimeout(5000);
|
||||
socket.setReuseAddress(true);
|
||||
// socket.setBroadcast(true);
|
||||
socket.setNetworkInterface(ni);
|
||||
socket.joinGroup(m);
|
||||
logger.debug("Joined UPnP Multicast group on Interface: {}", ni.getName());
|
||||
byte[] requestMessage = RADIOTHERMOSTAT_DISCOVERY_MESSAGE.getBytes("UTF-8");
|
||||
DatagramPacket datagramPacket = new DatagramPacket(requestMessage, requestMessage.length, m, port);
|
||||
socket.send(datagramPacket);
|
||||
try {
|
||||
// Try to ensure that joinGroup has taken effect. Without this delay, the query
|
||||
// packet ends up going out before the group join.
|
||||
Thread.sleep(1000);
|
||||
|
||||
socket.send(datagramPacket);
|
||||
|
||||
byte[] buf = new byte[4096];
|
||||
DatagramPacket packet = new DatagramPacket(buf, buf.length);
|
||||
|
||||
try {
|
||||
while (!Thread.interrupted()) {
|
||||
socket.receive(packet);
|
||||
String response = new String(packet.getData(), StandardCharsets.UTF_8);
|
||||
logger.debug("Response: {} ", response);
|
||||
if (response.contains(SSDP_MATCH)) {
|
||||
logger.debug("Match: {} ", response);
|
||||
parseResponse(response);
|
||||
}
|
||||
}
|
||||
logger.debug("Bridge device scan interrupted");
|
||||
} catch (SocketTimeoutException e) {
|
||||
logger.debug(
|
||||
"Timed out waiting for multicast response. Presumably all devices have already responded.");
|
||||
}
|
||||
} finally {
|
||||
socket.leaveGroup(m);
|
||||
socket.close();
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
logger.debug("got exception: {}", e.getMessage());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans all messages that arrive on the socket and scans them for the
|
||||
* search keywords. The search is not case sensitive.
|
||||
*
|
||||
* @param socket
|
||||
* The socket where the answers arrive.
|
||||
* @param keywords
|
||||
* The keywords to be searched for.
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
|
||||
protected void parseResponse(String response) {
|
||||
DiscoveryResult result;
|
||||
|
||||
String name = "unknownName";
|
||||
String uuid = "unknownThermostat";
|
||||
String ip = null;
|
||||
String url = null;
|
||||
|
||||
Scanner scanner = new Scanner(response);
|
||||
while (scanner.hasNextLine()) {
|
||||
String line = scanner.nextLine();
|
||||
String[] pair = line.split(":", 2);
|
||||
if (pair.length != 2) {
|
||||
continue;
|
||||
}
|
||||
String key = pair[0].toLowerCase();
|
||||
String value = pair[1].trim();
|
||||
logger.debug("key: {} value: {}.", key, value);
|
||||
if ("location".equals(key)) {
|
||||
try {
|
||||
url = value;
|
||||
ip = new URL(value).getHost();
|
||||
} catch (MalformedURLException e) {
|
||||
logger.debug("Malfored URL {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
scanner.close();
|
||||
|
||||
logger.debug("Found thermostat, ip: {} ", ip);
|
||||
|
||||
if (ip == null) {
|
||||
logger.debug("Bad Format from thermostat");
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject content;
|
||||
String sysinfo;
|
||||
boolean isCT80 = false;
|
||||
|
||||
try {
|
||||
// Run the HTTP request and get the JSON response from the thermostat
|
||||
sysinfo = HttpUtil.executeUrl("GET", url, 20000);
|
||||
content = new JsonParser().parse(sysinfo).getAsJsonObject();
|
||||
uuid = content.get("uuid").getAsString();
|
||||
} catch (IOException | JsonSyntaxException e) {
|
||||
logger.debug("Cannot get system info from thermostat {} {}", ip, e.getMessage());
|
||||
sysinfo = null;
|
||||
}
|
||||
|
||||
try {
|
||||
String nameinfo = HttpUtil.executeUrl("GET", url + "name", 20000);
|
||||
content = new JsonParser().parse(nameinfo).getAsJsonObject();
|
||||
name = content.get("name").getAsString();
|
||||
} catch (IOException | JsonSyntaxException e) {
|
||||
logger.debug("Cannot get name from thermostat {} {}", ip, e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
String model = HttpUtil.executeUrl("GET", "http://" + ip + "/tstat/model", 20000);
|
||||
isCT80 = (model != null && model.contains("CT80")) ? true : false;
|
||||
} catch (IOException | JsonSyntaxException e) {
|
||||
logger.debug("Cannot get model information from thermostat {} {}", ip, e.getMessage());
|
||||
}
|
||||
|
||||
logger.debug("Discovery returned: {} uuid {} name {}", sysinfo, uuid, name);
|
||||
|
||||
ThingUID thingUid = new ThingUID(RadioThermostatBindingConstants.THING_TYPE_RTHERM, uuid);
|
||||
|
||||
logger.debug("Got discovered device.");
|
||||
|
||||
String label = String.format("RadioThermostat (%s)", name);
|
||||
result = DiscoveryResultBuilder.create(thingUid).withLabel(label)
|
||||
.withRepresentationProperty(RadioThermostatBindingConstants.PROPERTY_IP)
|
||||
.withProperty(RadioThermostatBindingConstants.PROPERTY_IP, ip)
|
||||
.withProperty(RadioThermostatBindingConstants.PROPERTY_ISCT80, isCT80).build();
|
||||
logger.debug("New RadioThermostat discovered with ID=<{}>", uuid);
|
||||
this.thingDiscovered(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal.dto;
|
||||
|
||||
/**
|
||||
* The {@link RadioThermostatDTO} is responsible for storing
|
||||
* all of the JSON data objects that are retrieved from the thermostat
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
public class RadioThermostatDTO {
|
||||
private RadioThermostatTstatDTO thermostatData;
|
||||
private Integer humidity;
|
||||
private RadioThermostatRuntimeDTO runtime;
|
||||
|
||||
public RadioThermostatDTO() {
|
||||
}
|
||||
|
||||
public RadioThermostatTstatDTO getThermostatData() {
|
||||
return thermostatData;
|
||||
}
|
||||
|
||||
public void setThermostatData(RadioThermostatTstatDTO thermostatData) {
|
||||
this.thermostatData = thermostatData;
|
||||
}
|
||||
|
||||
public Integer getHumidity() {
|
||||
return humidity;
|
||||
}
|
||||
|
||||
public void setHumidity(Integer humidity) {
|
||||
this.humidity = humidity;
|
||||
}
|
||||
|
||||
public RadioThermostatRuntimeDTO getRuntime() {
|
||||
return runtime;
|
||||
}
|
||||
|
||||
public void setRuntime(RadioThermostatRuntimeDTO runtime) {
|
||||
this.runtime = runtime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link RadioThermostatHumidityDTO} is responsible for storing
|
||||
* the data from the thermostat 'tstat/humidity' JSON response
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
public class RadioThermostatHumidityDTO {
|
||||
@SerializedName("humidity")
|
||||
private Integer humidity;
|
||||
|
||||
public RadioThermostatHumidityDTO() {
|
||||
}
|
||||
|
||||
public Integer getHumidity() {
|
||||
return humidity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link RadioThermostatRuntimeDTO} is responsible for storing
|
||||
* the "today" and "yesterday" node from the "tstat/datalog" JSON response
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
public class RadioThermostatRuntimeDTO {
|
||||
|
||||
@SerializedName("today")
|
||||
private RadioThermostatRuntimeHeatCoolDTO today;
|
||||
|
||||
@SerializedName("yesterday")
|
||||
private RadioThermostatRuntimeHeatCoolDTO yesterday;
|
||||
|
||||
public RadioThermostatRuntimeDTO() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives "today" node from the JSON response
|
||||
*
|
||||
* @return {RadioThermostatRuntimeHeatCool}
|
||||
*/
|
||||
public RadioThermostatRuntimeHeatCoolDTO getToday() {
|
||||
return today;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives "yesterday" node from the JSON response
|
||||
*
|
||||
* @return {RadioThermostatRuntimeHeatCool}
|
||||
*/
|
||||
public RadioThermostatRuntimeHeatCoolDTO getYesterday() {
|
||||
return yesterday;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link RadioThermostatRuntimeHeatCoolDTO} is responsible for storing
|
||||
* the "heat_runtime" and "cool_runtime" node from the thermostat JSON response
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
public class RadioThermostatRuntimeHeatCoolDTO {
|
||||
|
||||
public RadioThermostatRuntimeHeatCoolDTO() {
|
||||
}
|
||||
|
||||
@SerializedName("heat_runtime")
|
||||
private RadioThermostatTimeDTO heatTime;
|
||||
|
||||
@SerializedName("cool_runtime")
|
||||
private RadioThermostatTimeDTO coolTime;
|
||||
|
||||
/**
|
||||
* Receives "heat_runtime" node from the JSON response
|
||||
*
|
||||
* @return {RadioThermostatJsonTime}
|
||||
*/
|
||||
public RadioThermostatTimeDTO getHeatTime() {
|
||||
return heatTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives "cool_runtime" node from the JSON response
|
||||
*
|
||||
* @return {RadioThermostatJsonTime}
|
||||
*/
|
||||
public RadioThermostatTimeDTO getCoolTime() {
|
||||
return coolTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link RadioThermostatTimeDTO} is responsible for storing
|
||||
* the "time" node from the thermostat JSON response
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
public class RadioThermostatTimeDTO {
|
||||
@SerializedName("day")
|
||||
private Integer dayOfWeek;
|
||||
|
||||
@SerializedName("hour")
|
||||
private Integer hour;
|
||||
|
||||
@SerializedName("minute")
|
||||
private Integer minute;
|
||||
|
||||
public RadioThermostatTimeDTO() {
|
||||
}
|
||||
|
||||
public Integer getDayOfWeek() {
|
||||
return dayOfWeek;
|
||||
}
|
||||
|
||||
public Integer getHour() {
|
||||
return hour;
|
||||
}
|
||||
|
||||
public Integer getMinute() {
|
||||
return minute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to return the total number of runtime minutes
|
||||
*
|
||||
* @return {runtime hours + minutes as minutes Integer}
|
||||
*/
|
||||
public Integer getRuntime() {
|
||||
return (hour * 60) + minute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted thermostat date stamp
|
||||
*
|
||||
* @return {Day of week/Time string}
|
||||
*/
|
||||
public String getThemostatDateTime() {
|
||||
String day;
|
||||
|
||||
switch (dayOfWeek.toString()) {
|
||||
case "0":
|
||||
day = "Monday ";
|
||||
break;
|
||||
case "1":
|
||||
day = "Tuesday ";
|
||||
break;
|
||||
case "2":
|
||||
day = "Wedensday ";
|
||||
break;
|
||||
case "3":
|
||||
day = "Thursday ";
|
||||
break;
|
||||
case "4":
|
||||
day = "Friday ";
|
||||
break;
|
||||
case "5":
|
||||
day = "Saturday ";
|
||||
break;
|
||||
case "6":
|
||||
day = "Sunday ";
|
||||
break;
|
||||
default:
|
||||
day = "";
|
||||
}
|
||||
return day + hour + ":" + String.format("%02d", minute);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link RadioThermostatTstatDTO} is responsible for storing
|
||||
* the data from the thermostat 'tstat' JSON response
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
public class RadioThermostatTstatDTO {
|
||||
|
||||
@SerializedName("temp")
|
||||
private Double temperature;
|
||||
|
||||
@SerializedName("tmode")
|
||||
private Integer mode;
|
||||
|
||||
@SerializedName("fmode")
|
||||
private Integer fanMode;
|
||||
|
||||
@SerializedName("program_mode")
|
||||
private Integer programMode;
|
||||
|
||||
@SerializedName("t_heat")
|
||||
private Integer heatTarget;
|
||||
|
||||
@SerializedName("t_cool")
|
||||
private Integer coolTarget;
|
||||
|
||||
@SerializedName("override")
|
||||
private Integer override;
|
||||
|
||||
@SerializedName("hold")
|
||||
private Integer hold;
|
||||
|
||||
@SerializedName("tstate")
|
||||
private Integer status;
|
||||
|
||||
@SerializedName("fstate")
|
||||
private Integer fanStatus;
|
||||
|
||||
@SerializedName("time")
|
||||
private RadioThermostatTimeDTO time;
|
||||
|
||||
public RadioThermostatTstatDTO() {
|
||||
}
|
||||
|
||||
public Double getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public Integer getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(Integer mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public Integer getFanMode() {
|
||||
return fanMode;
|
||||
}
|
||||
|
||||
public void setFanMode(Integer fanMode) {
|
||||
this.fanMode = fanMode;
|
||||
}
|
||||
|
||||
public Integer getProgramMode() {
|
||||
return programMode;
|
||||
}
|
||||
|
||||
public void setProgramMode(Integer programMode) {
|
||||
this.programMode = programMode;
|
||||
}
|
||||
|
||||
public Integer getHeatTarget() {
|
||||
return heatTarget;
|
||||
}
|
||||
|
||||
public void setHeatTarget(Integer heatTarget) {
|
||||
this.heatTarget = heatTarget;
|
||||
}
|
||||
|
||||
public Integer getCoolTarget() {
|
||||
return coolTarget;
|
||||
}
|
||||
|
||||
public void setCoolTarget(Integer coolTarget) {
|
||||
this.coolTarget = coolTarget;
|
||||
}
|
||||
|
||||
public Integer getOverride() {
|
||||
return override;
|
||||
}
|
||||
|
||||
public Integer getHold() {
|
||||
return hold;
|
||||
}
|
||||
|
||||
public void setHold(Integer hold) {
|
||||
this.hold = hold;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public Integer getFanStatus() {
|
||||
return fanStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we are in heat mode or cool mode and return that temp value
|
||||
*
|
||||
* @return {Integer}
|
||||
*/
|
||||
public Integer getSetpoint() {
|
||||
if (mode == 1) {
|
||||
return heatTarget;
|
||||
} else if (mode == 2) {
|
||||
return coolTarget;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives "time" node from the JSON response
|
||||
*
|
||||
* @return {RadioThermostatJsonTime}
|
||||
*/
|
||||
public RadioThermostatTimeDTO getTime() {
|
||||
return time;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,473 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.radiothermostat.internal.handler;
|
||||
|
||||
import static org.openhab.binding.radiothermostat.internal.RadioThermostatBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.radiothermostat.internal.RadioThermostatConfiguration;
|
||||
import org.openhab.binding.radiothermostat.internal.RadioThermostatStateDescriptionProvider;
|
||||
import org.openhab.binding.radiothermostat.internal.RadioThermostatThingActions;
|
||||
import org.openhab.binding.radiothermostat.internal.communication.RadioThermostatConnector;
|
||||
import org.openhab.binding.radiothermostat.internal.communication.RadioThermostatEvent;
|
||||
import org.openhab.binding.radiothermostat.internal.communication.RadioThermostatEventListener;
|
||||
import org.openhab.binding.radiothermostat.internal.dto.RadioThermostatDTO;
|
||||
import org.openhab.binding.radiothermostat.internal.dto.RadioThermostatHumidityDTO;
|
||||
import org.openhab.binding.radiothermostat.internal.dto.RadioThermostatRuntimeDTO;
|
||||
import org.openhab.binding.radiothermostat.internal.dto.RadioThermostatTstatDTO;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
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.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* The {@link RadioThermostatHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* Based on the 'airquality' binding by Kuba Wolanin
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RadioThermostatHandler extends BaseThingHandler implements RadioThermostatEventListener {
|
||||
private static final int DEFAULT_REFRESH_PERIOD = 2;
|
||||
private static final int DEFAULT_LOG_REFRESH_PERIOD = 10;
|
||||
|
||||
private final RadioThermostatStateDescriptionProvider stateDescriptionProvider;
|
||||
private final Logger logger = LoggerFactory.getLogger(RadioThermostatHandler.class);
|
||||
|
||||
private final Gson gson;
|
||||
private final RadioThermostatConnector connector;
|
||||
private final RadioThermostatDTO rthermData = new RadioThermostatDTO();
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
private @Nullable ScheduledFuture<?> logRefreshJob;
|
||||
|
||||
private int refreshPeriod = DEFAULT_REFRESH_PERIOD;
|
||||
private int logRefreshPeriod = DEFAULT_LOG_REFRESH_PERIOD;
|
||||
private boolean isCT80 = false;
|
||||
private boolean disableLogs = false;
|
||||
private String setpointCmdKeyPrefix = "t_";
|
||||
|
||||
public RadioThermostatHandler(Thing thing, RadioThermostatStateDescriptionProvider stateDescriptionProvider,
|
||||
HttpClient httpClient) {
|
||||
super(thing);
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
gson = new Gson();
|
||||
connector = new RadioThermostatConnector(httpClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing RadioThermostat handler.");
|
||||
RadioThermostatConfiguration config = getConfigAs(RadioThermostatConfiguration.class);
|
||||
|
||||
final String hostName = config.hostName;
|
||||
final Integer refresh = config.refresh;
|
||||
final Integer logRefresh = config.logRefresh;
|
||||
this.isCT80 = config.isCT80;
|
||||
this.disableLogs = config.disableLogs;
|
||||
|
||||
if (hostName == null || hostName.equals("")) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Thermostat Host Name must be specified");
|
||||
return;
|
||||
}
|
||||
|
||||
if (refresh != null) {
|
||||
this.refreshPeriod = refresh;
|
||||
}
|
||||
|
||||
if (logRefresh != null) {
|
||||
this.logRefreshPeriod = logRefresh;
|
||||
}
|
||||
|
||||
connector.setThermostatHostName(hostName);
|
||||
connector.addEventListener(this);
|
||||
|
||||
// The setpoint mode is controlled by the name of setpoint attribute sent to the thermostat.
|
||||
// Temporary mode uses setpoint names prefixed with "t_" while absolute mode uses "a_"
|
||||
if (config.setpointMode.equals("absolute")) {
|
||||
this.setpointCmdKeyPrefix = "a_";
|
||||
}
|
||||
|
||||
// populate fan mode options based on thermostat model
|
||||
stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), FAN_MODE), getFanModeOptions());
|
||||
|
||||
// if we are not a CT-80, remove the humidity & program mode channel
|
||||
if (!this.isCT80) {
|
||||
List<Channel> channels = new ArrayList<>(this.getThing().getChannels());
|
||||
channels.removeIf(c -> (c.getUID().getId().equals(HUMIDITY)));
|
||||
channels.removeIf(c -> (c.getUID().getId().equals(PROGRAM_MODE)));
|
||||
updateThing(editThing().withChannels(channels).build());
|
||||
}
|
||||
startAutomaticRefresh();
|
||||
if (!this.disableLogs || this.isCT80) {
|
||||
startAutomaticLogRefresh();
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singletonList(RadioThermostatThingActions.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the job to periodically update data from the thermostat
|
||||
*/
|
||||
private void startAutomaticRefresh() {
|
||||
ScheduledFuture<?> refreshJob = this.refreshJob;
|
||||
if (refreshJob == null || refreshJob.isCancelled()) {
|
||||
Runnable runnable = () -> {
|
||||
// send an async call to the thermostat to get the 'tstat' data
|
||||
connector.getAsyncThermostatData(DEFAULT_RESOURCE);
|
||||
};
|
||||
|
||||
refreshJob = null;
|
||||
this.refreshJob = scheduler.scheduleWithFixedDelay(runnable, 0, refreshPeriod, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the job to periodically update humidity and runtime date from the thermostat
|
||||
*/
|
||||
private void startAutomaticLogRefresh() {
|
||||
ScheduledFuture<?> logRefreshJob = this.logRefreshJob;
|
||||
if (logRefreshJob == null || logRefreshJob.isCancelled()) {
|
||||
Runnable runnable = () -> {
|
||||
// Request humidity data from the thermostat if we are a CT80
|
||||
if (this.isCT80) {
|
||||
// send an async call to the thermostat to get the humidity data
|
||||
connector.getAsyncThermostatData(HUMIDITY_RESOURCE);
|
||||
}
|
||||
|
||||
if (!this.disableLogs) {
|
||||
// send an async call to the thermostat to get the runtime data
|
||||
connector.getAsyncThermostatData(RUNTIME_RESOURCE);
|
||||
}
|
||||
};
|
||||
|
||||
logRefreshJob = null;
|
||||
this.logRefreshJob = scheduler.scheduleWithFixedDelay(runnable, 1, logRefreshPeriod, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Disposing the RadioThermostat handler.");
|
||||
connector.removeEventListener(this);
|
||||
|
||||
ScheduledFuture<?> refreshJob = this.refreshJob;
|
||||
if (refreshJob != null) {
|
||||
refreshJob.cancel(true);
|
||||
this.refreshJob = null;
|
||||
}
|
||||
|
||||
ScheduledFuture<?> logRefreshJob = this.logRefreshJob;
|
||||
if (logRefreshJob != null) {
|
||||
logRefreshJob.cancel(true);
|
||||
this.logRefreshJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void handleRawCommand(@Nullable String rawCommand) {
|
||||
connector.sendCommand(null, null, rawCommand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateChannel(channelUID.getId(), rthermData);
|
||||
} else {
|
||||
Integer cmdInt = -1;
|
||||
String cmdStr = command.toString();
|
||||
if (cmdStr != null) {
|
||||
try {
|
||||
// parse out an Integer from the string
|
||||
// ie '70.5 F' becomes 70, also handles negative numbers
|
||||
cmdInt = NumberFormat.getInstance().parse(cmdStr).intValue();
|
||||
} catch (ParseException e) {
|
||||
logger.debug("Command: {} -> Not an integer", cmdStr);
|
||||
}
|
||||
}
|
||||
|
||||
switch (channelUID.getId()) {
|
||||
case MODE:
|
||||
// only do if commanded mode is different than current mode
|
||||
if (!cmdInt.equals(rthermData.getThermostatData().getMode())) {
|
||||
connector.sendCommand("tmode", cmdStr);
|
||||
|
||||
// set the new operating mode, reset everything else,
|
||||
// because refreshing the tstat data below is really slow.
|
||||
rthermData.getThermostatData().setMode(cmdInt);
|
||||
rthermData.getThermostatData().setHeatTarget(0);
|
||||
rthermData.getThermostatData().setCoolTarget(0);
|
||||
updateChannel(SET_POINT, rthermData);
|
||||
rthermData.getThermostatData().setHold(0);
|
||||
updateChannel(HOLD, rthermData);
|
||||
rthermData.getThermostatData().setProgramMode(-1);
|
||||
updateChannel(PROGRAM_MODE, rthermData);
|
||||
|
||||
// now just trigger a refresh of the thermost to get the new active setpoint
|
||||
// this takes a while for the JSON request to complete (async).
|
||||
connector.getAsyncThermostatData(DEFAULT_RESOURCE);
|
||||
}
|
||||
break;
|
||||
case FAN_MODE:
|
||||
rthermData.getThermostatData().setFanMode(cmdInt);
|
||||
connector.sendCommand("fmode", cmdStr);
|
||||
break;
|
||||
case PROGRAM_MODE:
|
||||
rthermData.getThermostatData().setProgramMode(cmdInt);
|
||||
connector.sendCommand("program_mode", cmdStr);
|
||||
break;
|
||||
case HOLD:
|
||||
if (command instanceof OnOffType && command == OnOffType.ON) {
|
||||
rthermData.getThermostatData().setHold(1);
|
||||
connector.sendCommand("hold", "1");
|
||||
} else if (command instanceof OnOffType && command == OnOffType.OFF) {
|
||||
rthermData.getThermostatData().setHold(0);
|
||||
connector.sendCommand("hold", "0");
|
||||
}
|
||||
break;
|
||||
case SET_POINT:
|
||||
String cmdKey = null;
|
||||
if (rthermData.getThermostatData().getMode() == 1) {
|
||||
cmdKey = this.setpointCmdKeyPrefix + "heat";
|
||||
rthermData.getThermostatData().setHeatTarget(cmdInt);
|
||||
} else if (rthermData.getThermostatData().getMode() == 2) {
|
||||
cmdKey = this.setpointCmdKeyPrefix + "cool";
|
||||
rthermData.getThermostatData().setCoolTarget(cmdInt);
|
||||
} else {
|
||||
// don't do anything if we are not in heat or cool mode
|
||||
break;
|
||||
}
|
||||
connector.sendCommand(cmdKey, cmdInt.toString());
|
||||
break;
|
||||
default:
|
||||
logger.warn("Unsupported command: {}", command.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a RadioThermostat event received from the listeners
|
||||
*
|
||||
* @param event the event received from the listeners
|
||||
*/
|
||||
@Override
|
||||
public void onNewMessageEvent(RadioThermostatEvent event) {
|
||||
logger.debug("onNewMessageEvent: key {} = {}", event.getKey(), event.getValue());
|
||||
|
||||
String evtKey = event.getKey();
|
||||
String evtVal = event.getValue();
|
||||
|
||||
if (KEY_ERROR.equals(evtKey)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Error retrieving data from Thermostat ");
|
||||
} else {
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
|
||||
|
||||
// Map the JSON response to the correct object and update appropriate channels
|
||||
switch (evtKey) {
|
||||
case DEFAULT_RESOURCE:
|
||||
rthermData.setThermostatData(gson.fromJson(evtVal, RadioThermostatTstatDTO.class));
|
||||
updateAllChannels();
|
||||
break;
|
||||
case HUMIDITY_RESOURCE:
|
||||
rthermData.setHumidity(gson.fromJson(evtVal, RadioThermostatHumidityDTO.class).getHumidity());
|
||||
updateChannel(HUMIDITY, rthermData);
|
||||
break;
|
||||
case RUNTIME_RESOURCE:
|
||||
rthermData.setRuntime(gson.fromJson(evtVal, RadioThermostatRuntimeDTO.class));
|
||||
updateChannel(TODAY_HEAT_RUNTIME, rthermData);
|
||||
updateChannel(TODAY_COOL_RUNTIME, rthermData);
|
||||
updateChannel(YESTERDAY_HEAT_RUNTIME, rthermData);
|
||||
updateChannel(YESTERDAY_COOL_RUNTIME, rthermData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the channel from the last Thermostat data retrieved
|
||||
*
|
||||
* @param channelId the id identifying the channel to be updated
|
||||
*/
|
||||
private void updateChannel(String channelId, RadioThermostatDTO rthermData) {
|
||||
if (isLinked(channelId)) {
|
||||
Object value;
|
||||
try {
|
||||
value = getValue(channelId, rthermData);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Error setting {} value", channelId.toUpperCase());
|
||||
return;
|
||||
}
|
||||
|
||||
State state = null;
|
||||
if (value == null) {
|
||||
state = UnDefType.UNDEF;
|
||||
} else if (value instanceof PointType) {
|
||||
state = (PointType) value;
|
||||
} else if (value instanceof ZonedDateTime) {
|
||||
state = new DateTimeType((ZonedDateTime) value);
|
||||
} else if (value instanceof QuantityType<?>) {
|
||||
state = (QuantityType<?>) value;
|
||||
} else if (value instanceof BigDecimal) {
|
||||
state = new DecimalType((BigDecimal) value);
|
||||
} else if (value instanceof Integer) {
|
||||
state = new DecimalType(BigDecimal.valueOf(((Integer) value).longValue()));
|
||||
} else if (value instanceof String) {
|
||||
state = new StringType(value.toString());
|
||||
} else if (value instanceof OnOffType) {
|
||||
state = (OnOffType) value;
|
||||
} else {
|
||||
logger.warn("Update channel {}: Unsupported value type {}", channelId,
|
||||
value.getClass().getSimpleName());
|
||||
}
|
||||
logger.debug("Update channel {} with state {} ({})", channelId, (state == null) ? "null" : state.toString(),
|
||||
(value == null) ? "null" : value.getClass().getSimpleName());
|
||||
|
||||
// Update the channel
|
||||
if (state != null) {
|
||||
updateState(channelId, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a given channelId from the thermostat data
|
||||
*
|
||||
* @param the channel id to be updated
|
||||
* @param data the RadioThermostat dto
|
||||
* @return the value to be set in the state
|
||||
*/
|
||||
public static @Nullable Object getValue(String channelId, RadioThermostatDTO data) {
|
||||
switch (channelId) {
|
||||
case TEMPERATURE:
|
||||
if (data.getThermostatData().getTemperature() != null) {
|
||||
return new QuantityType<Temperature>(data.getThermostatData().getTemperature(),
|
||||
API_TEMPERATURE_UNIT);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
case HUMIDITY:
|
||||
if (data.getHumidity() != null) {
|
||||
return new QuantityType<>(data.getHumidity(), API_HUMIDITY_UNIT);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
case MODE:
|
||||
return data.getThermostatData().getMode();
|
||||
case FAN_MODE:
|
||||
return data.getThermostatData().getFanMode();
|
||||
case PROGRAM_MODE:
|
||||
return data.getThermostatData().getProgramMode();
|
||||
case SET_POINT:
|
||||
if (data.getThermostatData().getSetpoint() != 0) {
|
||||
return new QuantityType<Temperature>(data.getThermostatData().getSetpoint(), API_TEMPERATURE_UNIT);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
case OVERRIDE:
|
||||
return data.getThermostatData().getOverride();
|
||||
case HOLD:
|
||||
return OnOffType.from(data.getThermostatData().getHold() == 1);
|
||||
case STATUS:
|
||||
return data.getThermostatData().getStatus();
|
||||
case FAN_STATUS:
|
||||
return data.getThermostatData().getFanStatus();
|
||||
case DAY:
|
||||
return data.getThermostatData().getTime().getDayOfWeek();
|
||||
case HOUR:
|
||||
return data.getThermostatData().getTime().getHour();
|
||||
case MINUTE:
|
||||
return data.getThermostatData().getTime().getMinute();
|
||||
case DATE_STAMP:
|
||||
return data.getThermostatData().getTime().getThemostatDateTime();
|
||||
case TODAY_HEAT_RUNTIME:
|
||||
return new QuantityType<>(data.getRuntime().getToday().getHeatTime().getRuntime(), API_MINUTES_UNIT);
|
||||
case TODAY_COOL_RUNTIME:
|
||||
return new QuantityType<>(data.getRuntime().getToday().getCoolTime().getRuntime(), API_MINUTES_UNIT);
|
||||
case YESTERDAY_HEAT_RUNTIME:
|
||||
return new QuantityType<>(data.getRuntime().getYesterday().getHeatTime().getRuntime(),
|
||||
API_MINUTES_UNIT);
|
||||
case YESTERDAY_COOL_RUNTIME:
|
||||
return new QuantityType<>(data.getRuntime().getYesterday().getCoolTime().getRuntime(),
|
||||
API_MINUTES_UNIT);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all channels from rthermData
|
||||
*/
|
||||
private void updateAllChannels() {
|
||||
// Update all channels from rthermData
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
updateChannel(channel.getUID().getId(), rthermData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a list of fan modes based on what model thermostat is used
|
||||
*
|
||||
* @return list of state options for thermostat fan modes
|
||||
*/
|
||||
private List<StateOption> getFanModeOptions() {
|
||||
List<StateOption> fanModeOptions = new ArrayList<>();
|
||||
|
||||
fanModeOptions.add(new StateOption("0", "Auto"));
|
||||
if (this.isCT80) {
|
||||
fanModeOptions.add(new StateOption("1", "Auto/Circulate"));
|
||||
}
|
||||
fanModeOptions.add(new StateOption("2", "On"));
|
||||
|
||||
return fanModeOptions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="radiothermostat" 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>Radio Thermostat Binding</name>
|
||||
<description>Controls the RadioThermostat model CT30, CT50 or CT80 via the built-in WIFI module</description>
|
||||
<author>Michael Lobstein</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,216 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="radiothermostat"
|
||||
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">
|
||||
|
||||
<!-- RadioThemostat Thing -->
|
||||
<thing-type id="rtherm">
|
||||
<label>Thermostat</label>
|
||||
<description>
|
||||
A Thermostat to Control the House's HVAC System
|
||||
</description>
|
||||
|
||||
<channels>
|
||||
<channel id="temperature" typeId="temp-temperature"/>
|
||||
<channel id="humidity" typeId="humidity"/>
|
||||
<channel id="mode" typeId="mode"/>
|
||||
<channel id="fan_mode" typeId="fan_mode"/>
|
||||
<channel id="program_mode" typeId="program_mode"/>
|
||||
<channel id="set_point" typeId="temp-sp"/>
|
||||
<channel id="override" typeId="override"/>
|
||||
<channel id="hold" typeId="hold"/>
|
||||
<channel id="status" typeId="status"/>
|
||||
<channel id="fan_status" typeId="fan_status"/>
|
||||
<channel id="day" typeId="t_day"/>
|
||||
<channel id="hour" typeId="t_hour"/>
|
||||
<channel id="minute" typeId="t_minute"/>
|
||||
<channel id="dt_stamp" typeId="dt_stamp"/>
|
||||
<channel id="today_heat_runtime" typeId="today_heat_runtime"/>
|
||||
<channel id="today_cool_runtime" typeId="today_cool_runtime"/>
|
||||
<channel id="yesterday_heat_runtime" typeId="yesterday_heat_runtime"/>
|
||||
<channel id="yesterday_cool_runtime" typeId="yesterday_cool_runtime"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="hostName" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>Thermostat Host Name/IP Address</label>
|
||||
<description>Host Name or IP Address of the Thermostat</description>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" min="1" required="false" unit="min">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Specifies the Refresh Interval in Minutes</description>
|
||||
<default>2</default>
|
||||
</parameter>
|
||||
<parameter name="logRefresh" type="integer" min="5" required="false" unit="min">
|
||||
<label>Run-time Log Refresh Interval</label>
|
||||
<description>Specifies the Run-time Log and Humidity Refresh Interval in Minutes</description>
|
||||
<default>10</default>
|
||||
</parameter>
|
||||
<parameter name="isCT80" type="boolean">
|
||||
<label>Enable CT80 Thermostat Options</label>
|
||||
<description>Optional Flag to Enable Additional Features Only Available on the CT80 Thermostat</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="disableLogs" type="boolean">
|
||||
<label>Disable Retrieval of Run-time Data</label>
|
||||
<description>Optional Flag to Disable the Retrieval of Run-time Data from the Thermostat</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="setpointMode" type="text">
|
||||
<label>Setpoint Mode</label>
|
||||
<description>Run in absolute or temporary setpoint mode</description>
|
||||
<default>temporary</default>
|
||||
<options>
|
||||
<option value="absolute">Absolute</option>
|
||||
<option value="temporary">Temporary</option>
|
||||
</options>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="temp-temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<description>The Current Temperature Reading of the Thermostat</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="humidity">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Humidity</label>
|
||||
<description>The Current Humidity Reading of the Thermostat</description>
|
||||
<category>Humidity</category>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="mode">
|
||||
<item-type>Number</item-type>
|
||||
<label>Mode</label>
|
||||
<description>The Current Operating Mode of the HVAC System</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="0">Off</option>
|
||||
<option value="1">Heat</option>
|
||||
<option value="2">Cool</option>
|
||||
<option value="3">Auto</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="fan_mode">
|
||||
<item-type>Number</item-type>
|
||||
<label>Fan Mode</label>
|
||||
<description>The Current Operating Mode of the Fan</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="program_mode" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Program Mode</label>
|
||||
<description>The Program Schedule That the Thermostat Is Running</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="-1">None</option>
|
||||
<option value="0">Program A</option>
|
||||
<option value="1">Program B</option>
|
||||
<option value="2">Vacation</option>
|
||||
<option value="3">Holiday</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="temp-sp">
|
||||
<item-type>Number</item-type>
|
||||
<label>Setpoint</label>
|
||||
<description>The Current Temperature Set Point of the Thermostat</description>
|
||||
<category>Temperature</category>
|
||||
<state min="35" max="95" pattern="%d"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="override">
|
||||
<item-type>Number</item-type>
|
||||
<label>Override</label>
|
||||
<description>Indicates If the Normal Program Setpoint Has Been Manually Overriden</description>
|
||||
<state readOnly="true" pattern="%d"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="hold">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Hold</label>
|
||||
<description>Indicates If the Current Set Point Temperature Is to Be Held Indefinitely</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="status">
|
||||
<item-type>Number</item-type>
|
||||
<label>Status</label>
|
||||
<description>Indicates the Current Running Status of the HVAC System</description>
|
||||
<state min="0" max="2" pattern="%d"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="fan_status">
|
||||
<item-type>Number</item-type>
|
||||
<label>Fan Status</label>
|
||||
<description>Indicates the Current Fan Status of the HVAC System</description>
|
||||
<state min="0" max="2" pattern="%d"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="t_day" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Day</label>
|
||||
<description>The Current Day of the Week Reported by the Thermostat</description>
|
||||
<state readOnly="true" pattern="%d"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="t_hour" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Hour</label>
|
||||
<description>The Current Hour of the Day Reported by the Thermostat</description>
|
||||
<state readOnly="true" pattern="%d"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="t_minute" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Minute</label>
|
||||
<description>The Current Minute Past the Hour Reported by the Thermostat</description>
|
||||
<state readOnly="true" pattern="%d"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dt_stamp" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Thermostat Date</label>
|
||||
<description>The Current Day of the Week and Time Reported by the Thermostat</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="today_heat_runtime">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Today's Heating Runtime</label>
|
||||
<description>The Number of Minutes of Heating Run-time Today</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="today_cool_runtime">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Today's Cooling Runtime</label>
|
||||
<description>The Number of Minutes of Cooling Run-time Today</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="yesterday_heat_runtime">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Yesterday's Heating Runtime</label>
|
||||
<description>The Number of Minutes of Heating Run-time Yesterday</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="yesterday_cool_runtime">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Yesterday's Cooling Runtime</label>
|
||||
<description>The Number of Minutes of Cooling Run-time Yesterday</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user