diff --git a/CODEOWNERS b/CODEOWNERS
index 58bf402a9a6c0..739d4e128ab48 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -147,6 +147,7 @@
/bundles/org.openhab.binding.powermax/ @lolodomo
/bundles/org.openhab.binding.pulseaudio/ @peuter
/bundles/org.openhab.binding.pushbullet/ @hakan42
+/bundles/org.openhab.binding.radiothermostat/ @mlobstein
/bundles/org.openhab.binding.regoheatpump/ @crnjan
/bundles/org.openhab.binding.rfxcom/ @martinvw @paulianttila
/bundles/org.openhab.binding.rme/ @kgoderis
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index 00421d5781da4..1784d9ca69ada 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -729,6 +729,11 @@
org.openhab.binding.pushbullet
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.radiothermostat
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.regoheatpump
diff --git a/bundles/org.openhab.binding.radiothermostat/.classpath b/bundles/org.openhab.binding.radiothermostat/.classpath
new file mode 100644
index 0000000000000..a5d95095ccaaf
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/.classpath
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.radiothermostat/.project b/bundles/org.openhab.binding.radiothermostat/.project
new file mode 100644
index 0000000000000..6efb0cb2923e5
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/.project
@@ -0,0 +1,23 @@
+
+
+ org.openhab.binding.radiothermostat
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/bundles/org.openhab.binding.radiothermostat/NOTICE b/bundles/org.openhab.binding.radiothermostat/NOTICE
new file mode 100644
index 0000000000000..38d625e349232
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/NOTICE
@@ -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
diff --git a/bundles/org.openhab.binding.radiothermostat/README.md b/bundles/org.openhab.binding.radiothermostat/README.md
new file mode 100644
index 0000000000000..1719b0445c936
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/README.md
@@ -0,0 +1,198 @@
+# 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.
+
+## 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. |
+| disableLogs | Disable retrieval of run-time logs from the thermostat. Optional, the default is 0. |
+| disableHumidity | Disable retrieval of humidity information from the thermostat. Optional, the default is 0. |
+
+## Channels
+
+The thermostat information that is retrieved is available as these channels:
+
+| Channel ID | Item Type | Description |
+|-----------------------|----------------------|---------------------------------------------------------------------------|
+| name | String | The name of the thermostat |
+| model | String | The model number and firmware version of the thermostat |
+| temperature | Number:Temperature | The current temperature reading of the thermostat |
+| humidity | Number | 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 | Number | 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) |
+| last_update | DateTime | Last successful contact with thermostat |
+| today_heat_hour | Number | The number of hours of heating run-time today |
+| today_heat_minute | Number | The number of minutes of heating run-time today |
+| today_cool_hour | Number | The number of hours of cooling run-time today |
+| today_cool_minute | Number | The number of minutes of cooling run-time today |
+| yesterday_heat_hour | Number | The number of hours of heating run-time yesterday |
+| yesterday_heat_minute | Number | The number of minutes of heating run-time yesterday |
+| yesterday_cool_hour | Number | The number of hours of cooling run-time yesterday |
+| yesterday_cool_minute | Number | The 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
+UNDEF_hold=-
+NULL_hold=-
+-_hold=-
+0_hold=Off
+1_hold=On
+
+```
+
+radiotherm.things:
+
+```java
+radiothermostat:rtherm:mytherm1 "My 1st floor thermostat" [ hostName="192.168.10.1", refresh=2, logRefresh=10, disableLogs=0, disableHumidity=0 ]
+radiothermostat:rtherm:mytherm2 "My 2nd floor thermostat" [ hostName="mythermhost2", refresh=1, logRefresh=20, disableLogs=1, disableHumidity=1 ]
+```
+
+radiotherm.items:
+
+```java
+String Therm_Name "Thermostat Name [%s]" { channel="radiothermostat:rtherm:mytherm1:name" }
+String Therm_Model "Thermostat Model [%s]" { channel="radiothermostat:rtherm:mytherm1:model" }
+
+Number:Temperature Therm_Temp "Current Temperature [%.1f °F] " { channel="radiothermostat:rtherm:mytherm1:temperature" }
+// Humidity only supported on CT80
+Number Therm_Hum "Current Humidity [%d %%]" { 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 °F]" { 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" }
+Number Therm_Hold "Hold [MAP(radiotherm.map):%s_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]" { channel="radiothermostat:rtherm:mytherm1:dt_stamp" }
+DateTime Therm_Lastupd "Thermostat Last Updated [%1$tl:%1$tM %1$tp]" { channel="radiothermostat:rtherm:mytherm1:last_update" }
+
+Number Therm_thh "Today's Heating Hours [%s]" { channel="radiothermostat:rtherm:mytherm1:today_heat_hour" }
+Number Therm_thm "Today's Heating Minutes [%s]" { channel="radiothermostat:rtherm:mytherm1:today_heat_minute" }
+Number Therm_tch "Today's Cooling Hours [%s]" { channel="radiothermostat:rtherm:mytherm1:today_cool_hour" }
+Number Therm_tcm "Today's Cooling Minutes [%s]" { channel="radiothermostat:rtherm:mytherm1:today_cool_minute" }
+Number Therm_yhh "Yesterday's Heating Hours [%s]" { channel="radiothermostat:rtherm:mytherm1:yesterday_heat_hour" }
+Number Therm_yhm "Yesterday's Heating Minutes [%s]" { channel="radiothermostat:rtherm:mytherm1:yesterday_heat_minute" }
+Number Therm_ych "Yesterday's Cooling Hours [%s]" { channel="radiothermostat:rtherm:mytherm1:yesterday_cool_hour" }
+Number Therm_ycm "Yesterday's Cooling Minutes [%s]" { channel="radiothermostat:rtherm:mytherm1:yesterday_cool_minute" }
+```
+
+radiotherm.sitemap:
+
+```perl
+sitemap radiotherm label="My Thermostat" {
+ Frame label="My 1st floor thermostat" {
+ Text item=Therm_Name
+ Text item=Therm_Model
+
+ Text item=Therm_Temp icon="temperature" valuecolor=[>76="orange",>67.5="green",<=67.5="blue"]
+ 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
+ Switch item=Therm_Mode icon="climate"
+ Switch item=Therm_Fmode icon="fan"
+ Switch 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"
+
+ Text item=Therm_Day
+ Text item=Therm_Hour
+ Text item=Therm_Minute
+ Text item=Therm_Dstmp
+ Text item=Therm_Lastupd
+
+ Text item=Therm_thh
+ Text item=Therm_thm
+ Text item=Therm_tch
+ Text item=Therm_tcm
+ Text item=Therm_yhh
+ Text item=Therm_yhm
+ Text item=Therm_ych
+ }
+}
+```
+
diff --git a/bundles/org.openhab.binding.radiothermostat/pom.xml b/bundles/org.openhab.binding.radiothermostat/pom.xml
new file mode 100644
index 0000000000000..c564413736852
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/pom.xml
@@ -0,0 +1,15 @@
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 2.5.4-SNAPSHOT
+
+
+ org.openhab.binding.radiothermostat
+
+ openHAB Add-ons :: Bundles :: RadioThermostat Binding
+
+
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/feature/feature.xml b/bundles/org.openhab.binding.radiothermostat/src/main/feature/feature.xml
new file mode 100644
index 0000000000000..f052b49b48e64
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/src/main/feature/feature.xml
@@ -0,0 +1,9 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ mvn:org.openhab.addons.bundles/org.openhab.binding.radiothermostat/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/RadioThermostatBindingConstants.java b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/RadioThermostatBindingConstants.java
new file mode 100644
index 0000000000000..d30281d7c7e8f
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/RadioThermostatBindingConstants.java
@@ -0,0 +1,82 @@
+/**
+ * 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 org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.smarthome.core.library.unit.ImperialUnits;
+import org.eclipse.smarthome.core.library.unit.SmartHomeUnits;
+import org.eclipse.smarthome.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";
+
+ // 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 NAME = "name";
+ public static final String MODEL = "model";
+ 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 LAST_UPDATE ="last_update";
+ public static final String TODAY_HEAT_HOUR ="today_heat_hour";
+ public static final String TODAY_HEAT_MINUTE ="today_heat_minute";
+ public static final String TODAY_COOL_HOUR ="today_cool_hour";
+ public static final String TODAY_COOL_MINUTE ="today_cool_minute";
+ public static final String YESTERDAY_HEAT_HOUR ="yesterday_heat_hour";
+ public static final String YESTERDAY_HEAT_MINUTE ="yesterday_heat_minute";
+ public static final String YESTERDAY_COOL_HOUR ="yesterday_cool_hour";
+ public static final String YESTERDAY_COOL_MINUTE ="yesterday_cool_minute";
+
+ public static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_RTHERM);
+ public static final Set SUPPORTED_CHANNEL_IDS = Stream.of(NAME, MODEL, TEMPERATURE, HUMIDITY, MODE, FAN_MODE, PROGRAM_MODE,
+ SET_POINT, OVERRIDE, HOLD, STATUS, FAN_STATUS, DAY, HOUR, MINUTE, DATE_STAMP, LAST_UPDATE, TODAY_HEAT_HOUR, TODAY_HEAT_MINUTE,
+ TODAY_COOL_HOUR, TODAY_COOL_MINUTE, YESTERDAY_HEAT_HOUR, YESTERDAY_HEAT_MINUTE, YESTERDAY_COOL_HOUR, YESTERDAY_COOL_MINUTE)
+ .collect(Collectors.toSet());
+
+ // Units of measurement of the data delivered by the API
+ public static final Unit API_TEMPERATURE_UNIT = ImperialUnits.FAHRENHEIT;
+ public static final Unit API_HUMIDITY_UNIT = SmartHomeUnits.PERCENT;
+
+
+}
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/RadioThermostatConfiguration.java b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/RadioThermostatConfiguration.java
new file mode 100644
index 0000000000000..8c840088c81b5
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/RadioThermostatConfiguration.java
@@ -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;
+
+/**
+ * The {@link RadioThermostatConfiguration} is the class used to match the
+ * thing configuration.
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class RadioThermostatConfiguration {
+
+ public String hostName;
+ public Integer refresh;
+ public Integer logRefresh;
+ public Integer disableLogs;
+ public Integer disableHumidity;
+
+}
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/RadioThermostatHandlerFactory.java b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/RadioThermostatHandlerFactory.java
new file mode 100644
index 0000000000000..37487f079505b
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/RadioThermostatHandlerFactory.java
@@ -0,0 +1,66 @@
+/**
+ * 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.smarthome.core.thing.Thing;
+import org.eclipse.smarthome.core.thing.ThingTypeUID;
+import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory;
+import org.eclipse.smarthome.core.thing.binding.ThingHandler;
+import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory;
+import org.openhab.binding.radiothermostat.internal.handler.RadioThermostatHandler;
+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
+ */
+@Component(service = ThingHandlerFactory.class, configurationPid = "binding.radiothermostat")
+public class RadioThermostatHandlerFactory extends BaseThingHandlerFactory {
+
+ private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_RTHERM);
+ private RadioThermostatStateDescriptionProvider stateDescriptionProvider;
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+ }
+
+ @Override
+ protected ThingHandler createHandler(Thing thing) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+ if (thingTypeUID.equals(THING_TYPE_RTHERM)) {
+ return new RadioThermostatHandler(thing, stateDescriptionProvider);
+ }
+
+ return null;
+ }
+
+ @Reference
+ protected void setDynamicStateDescriptionProvider(RadioThermostatStateDescriptionProvider provider) {
+ this.stateDescriptionProvider = provider;
+ }
+
+ protected void unsetDynamicStateDescriptionProvider(RadioThermostatStateDescriptionProvider provider) {
+ this.stateDescriptionProvider = null;
+ }
+
+}
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/RadioThermostatStateDescriptionProvider.java b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/RadioThermostatStateDescriptionProvider.java
new file mode 100644
index 0000000000000..5e8b92116fc22
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/RadioThermostatStateDescriptionProvider.java
@@ -0,0 +1,63 @@
+/**
+ * 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.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.thing.Channel;
+import org.eclipse.smarthome.core.thing.ChannelUID;
+import org.eclipse.smarthome.core.thing.type.DynamicStateDescriptionProvider;
+import org.eclipse.smarthome.core.types.StateDescription;
+import org.eclipse.smarthome.core.types.StateDescriptionFragmentBuilder;
+import org.eclipse.smarthome.core.types.StateOption;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+
+/**
+ * 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 implements DynamicStateDescriptionProvider {
+ private final Map> channelOptionsMap = new ConcurrentHashMap<>();
+
+ public void setStateOptions(ChannelUID channelUID, List options) {
+ channelOptionsMap.put(channelUID, options);
+ }
+
+ @Override
+ public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription original,
+ @Nullable Locale locale) {
+ List options = channelOptionsMap.get(channel.getUID());
+ if (options == null) {
+ return null;
+ }
+
+ StateDescriptionFragmentBuilder builder = (original == null) ? StateDescriptionFragmentBuilder.create() : StateDescriptionFragmentBuilder.create(original);
+ return builder.withOptions(options).build().toStateDescription();
+ }
+
+ @Deactivate
+ public void deactivate() {
+ channelOptionsMap.clear();
+ }
+}
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/handler/RadioThermostatHandler.java b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/handler/RadioThermostatHandler.java
new file mode 100644
index 0000000000000..816920c2f0d18
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/handler/RadioThermostatHandler.java
@@ -0,0 +1,622 @@
+/**
+ * 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.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.math.BigDecimal;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import javax.measure.quantity.Temperature;
+import javax.xml.ws.http.HTTPException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.eclipse.smarthome.core.library.types.DateTimeType;
+import org.eclipse.smarthome.core.library.types.DecimalType;
+import org.eclipse.smarthome.core.library.types.PointType;
+import org.eclipse.smarthome.core.library.types.QuantityType;
+import org.eclipse.smarthome.core.library.types.StringType;
+import org.eclipse.smarthome.core.thing.Channel;
+import org.eclipse.smarthome.core.thing.ChannelUID;
+import org.eclipse.smarthome.core.thing.Thing;
+import org.eclipse.smarthome.core.thing.ThingStatus;
+import org.eclipse.smarthome.core.thing.ThingStatusDetail;
+import org.eclipse.smarthome.core.thing.binding.BaseThingHandler;
+import org.eclipse.smarthome.core.types.Command;
+import org.eclipse.smarthome.core.types.RefreshType;
+import org.eclipse.smarthome.core.types.State;
+import org.eclipse.smarthome.core.types.StateOption;
+import org.eclipse.smarthome.core.types.UnDefType;
+import org.openhab.binding.radiothermostat.internal.RadioThermostatConfiguration;
+import org.openhab.binding.radiothermostat.internal.RadioThermostatStateDescriptionProvider;
+import org.openhab.binding.radiothermostat.internal.json.RadioThermostatData;
+import org.openhab.binding.radiothermostat.internal.json.RadioThermostatJsonHumidity;
+import org.openhab.binding.radiothermostat.internal.json.RadioThermostatJsonModel;
+import org.openhab.binding.radiothermostat.internal.json.RadioThermostatJsonName;
+import org.openhab.binding.radiothermostat.internal.json.RadioThermostatJsonResponse;
+import org.openhab.binding.radiothermostat.internal.json.RadioThermostatJsonRuntime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * 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
+ */
+public class RadioThermostatHandler extends BaseThingHandler {
+ private final RadioThermostatStateDescriptionProvider stateDescriptionProvider;
+
+ private Logger logger = LoggerFactory.getLogger(RadioThermostatHandler.class);
+
+ private static final String URL = "http://%hostName%/%resource%";
+
+ private static final String DEFAULT_RESOURCE = "tstat";
+ private static final String RUNTIME_RESOURCE = "tstat/datalog";
+ private static final String HUMIDITY_RESOURCE = "tstat/humidity";
+ private static final String MODEL_RESOURCE = "tstat/model";
+ private static final String NAME_RESOURCE = "sys/name";
+
+ private static final int DEFAULT_REFRESH_PERIOD = 2;
+ private static final int DEFAULT_LOG_REFRESH_PERIOD = 10;
+
+ private ScheduledFuture> refreshJob;
+ private ScheduledFuture> logRefreshJob;
+
+ private RadioThermostatData rthermData = new RadioThermostatData();
+
+ private Gson gson;
+
+ private int initialized = 0;
+ private int retryCounter = 0;
+
+ public RadioThermostatHandler(Thing thing, RadioThermostatStateDescriptionProvider stateDescriptionProvider) {
+ super(thing);
+ this.stateDescriptionProvider = stateDescriptionProvider;
+ gson = new Gson();
+ }
+
+ @Override
+ public void initialize() {
+ logger.debug("Initializing RadioThermostat handler.");
+
+ RadioThermostatConfiguration config = getConfigAs(RadioThermostatConfiguration.class);
+ logger.debug("config hostName = {}", config.hostName);
+ logger.debug("config refresh = {}", config.refresh);
+ logger.debug("config logRefresh = {}", config.logRefresh);
+ logger.debug("config disableLogs = {}", config.disableLogs);
+ logger.debug("config disableHumidity = {}", config.disableHumidity);
+
+ String errorMsg = null;
+
+ if (StringUtils.trimToNull(config.hostName) == null) {
+ errorMsg = "Parameter 'hostName' is mandatory and must be configured";
+ }
+ if (config.refresh != null && config.refresh < 1) {
+ errorMsg = "Parameter 'refresh' must be at least 1 minute";
+ }
+ if (config.logRefresh != null && config.logRefresh < 5) {
+ errorMsg = "Parameter 'logRefresh' must be at least 5 minutes";
+ }
+ if (config.disableLogs != null && (config.disableLogs != 0 && config.disableLogs != 1)) {
+ errorMsg = "Parameter 'disableLogs' must be either 0 or 1";
+ }
+ if (config.disableHumidity != null && (config.disableHumidity != 0 && config.disableHumidity != 1)) {
+ errorMsg = "Parameter 'disableHumidity' must be either 0 or 1";
+ }
+
+ //test the thermostat connection by getting the thermostat name and model
+ if (errorMsg == null) {
+ try {
+ Object nameResult = getRadioThermostatData(NAME_RESOURCE);
+ if (nameResult instanceof RadioThermostatJsonName) {
+ rthermData.setName(((RadioThermostatJsonName) nameResult).getName());
+ } else {
+ errorMsg = "Unable to get thermostat name";
+ }
+
+ Thread.sleep(2000);
+
+ Object modelResult = getRadioThermostatData(MODEL_RESOURCE);
+ if (modelResult instanceof RadioThermostatJsonModel) {
+ rthermData.setModel(((RadioThermostatJsonModel) modelResult).getModel());
+ } else {
+ errorMsg = "Unable to get thermostat model";
+ }
+ } catch (Exception e) {
+ logger.error("Exception occurred attempting to connect with thermostat: {}", e.getMessage(), e);
+ }
+ }
+
+ // populate fan mode options based on thermostat model
+ List fanModeOptions = getFanModeOptions();
+ stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), FAN_MODE), fanModeOptions);
+
+ if (errorMsg == null) {
+ updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, rthermData.getName() + " " + rthermData.getModel());
+ initialized = 1;
+ startAutomaticRefresh();
+ if (!(config.disableLogs == 1 && config.disableHumidity == 1)) {
+ startAutomaticLogRefresh();
+ }
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMsg);
+ }
+ }
+
+ /**
+ * Start the job to periodically update data from the thermostat
+ */
+ private void startAutomaticRefresh() {
+ if (refreshJob == null || refreshJob.isCancelled()) {
+ Runnable runnable = () -> {
+ try {
+ // Request new data from the thermostat
+ retryCounter = 0;
+ Object result = getRadioThermostatData(DEFAULT_RESOURCE);
+ if (result instanceof RadioThermostatJsonResponse) {
+ rthermData.setThermostatData((RadioThermostatJsonResponse) result);
+
+ // Update all channels with the new data
+ updateAllChannels();
+ }
+ } catch (Exception e) {
+ logger.error("Exception occurred during execution: {}", e.getMessage(), e);
+ }
+ };
+
+ RadioThermostatConfiguration config = getConfigAs(RadioThermostatConfiguration.class);
+ int delay = (config.refresh != null) ? config.refresh.intValue() : DEFAULT_REFRESH_PERIOD;
+ refreshJob = scheduler.scheduleWithFixedDelay(runnable, 0, delay, TimeUnit.MINUTES);
+ }
+ }
+
+ /**
+ * Start the job to periodically update humidity and runtime date from the thermostat
+ */
+ private void startAutomaticLogRefresh() {
+ if (logRefreshJob == null || logRefreshJob.isCancelled()) {
+ RadioThermostatConfiguration config = getConfigAs(RadioThermostatConfiguration.class);
+ Runnable runnable = () -> {
+ try {
+ // Request humidity data from the thermostat if we are a CT80
+ if (isCT80() && config.disableHumidity != 1) {
+ retryCounter = 0;
+ Object result = getRadioThermostatData(HUMIDITY_RESOURCE);
+ if (result instanceof RadioThermostatJsonHumidity) {
+ rthermData.setHumidity(((RadioThermostatJsonHumidity) result).getHumidity());
+ }
+ Thread.sleep(2000);
+ }
+
+ if (config.disableLogs != 1) {
+ // Request runtime data from the thermostat
+ retryCounter = 0;
+ Object result = getRadioThermostatData(RUNTIME_RESOURCE);
+ if (result instanceof RadioThermostatJsonRuntime) {
+ rthermData.setRuntime((RadioThermostatJsonRuntime) result);
+ }
+ }
+ } catch (Exception e) {
+ logger.error("Exception occurred during execution: {}", e.getMessage(), e);
+ }
+ };
+
+ int delay = (config.logRefresh != null) ? config.logRefresh.intValue() : DEFAULT_LOG_REFRESH_PERIOD;
+ logRefreshJob = scheduler.scheduleWithFixedDelay(runnable, 1, delay, TimeUnit.MINUTES);
+ }
+ }
+
+ @Override
+ public void dispose() {
+ logger.debug("Disposing the RadioThermostat handler.");
+
+ if (refreshJob != null && !refreshJob.isCancelled()) {
+ refreshJob.cancel(true);
+ refreshJob = null;
+ }
+ if (logRefreshJob != null && !logRefreshJob.isCancelled()) {
+ logRefreshJob.cancel(true);
+ logRefreshJob = null;
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ if (command instanceof RefreshType) {
+ updateChannel(channelUID.getId(), rthermData);
+ } else {
+ // remove all non-numeric characters except negative '-'
+ String cmdStr = command.toString().replaceAll("[^\\d-]", "");
+ Integer cmdInt = Integer.parseInt(cmdStr);
+
+ switch (channelUID.getId()) {
+ case MODE:
+ //only do if commanded mode is different than current mode
+ if (!cmdInt.equals(rthermData.getThermostatData().getMode())) {
+ 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(null);
+ rthermData.getThermostatData().setCoolTarget(null);
+ rthermData.getThermostatData().setHold(0);
+ rthermData.getThermostatData().setProgramMode(-1);
+ updateAllChannels();
+
+ //now just go ahead and refresh tstat data to update with the new active setpoint
+ // this takes a while for the JSON request to complete.
+ Object result = getRadioThermostatData(DEFAULT_RESOURCE);
+ if (result instanceof RadioThermostatJsonResponse) {
+ rthermData.setThermostatData((RadioThermostatJsonResponse) result);
+ updateAllChannels();
+ }
+ }
+ break;
+ case FAN_MODE:
+ sendCommand("fmode", cmdStr);
+ rthermData.getThermostatData().setFanMode(cmdInt);
+ break;
+ case PROGRAM_MODE:
+ sendCommand("program_mode", cmdStr);
+ rthermData.getThermostatData().setProgramMode(cmdInt);
+ break;
+ case HOLD:;
+ sendCommand("hold", cmdStr);
+ rthermData.getThermostatData().setHold(cmdInt);
+ break;
+ case SET_POINT:
+ String cmdKey = null;
+ if (rthermData.getThermostatData().getMode() == 1) {
+ cmdKey = "t_heat";
+ rthermData.getThermostatData().setHeatTarget(cmdInt);
+ } else if (rthermData.getThermostatData().getMode() == 2) {
+ cmdKey = "t_cool";
+ rthermData.getThermostatData().setCoolTarget(cmdInt);
+ } else {
+ //don't do anything if we are not in heat or cool mode
+ break;
+ }
+ sendCommand(cmdKey, cmdStr);
+ break;
+ default:
+ logger.error("Unsupported command: {}", command.toString());
+ }
+
+ // update the value in the commanded channel
+ updateChannel(channelUID.getId(), rthermData);
+ }
+ }
+
+ /**
+ * Update the channel from the last Thermostat data retrieved
+ *
+ * @param channelId the id identifying the channel to be updated
+ */
+ private void updateChannel(String channelId, RadioThermostatData 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 {
+ 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);
+ }
+ }
+ }
+
+ /**
+ * Build request URL from configuration data
+ *
+ * @return a valid URL for the thermostat's JSON interface
+ */
+ private String buildRequestURL(String resource) {
+ RadioThermostatConfiguration config = getConfigAs(RadioThermostatConfiguration.class);
+
+ String hostName = StringUtils.trimToEmpty(config.hostName);
+
+ String urlStr = URL.replace("%hostName%", hostName);
+ urlStr = urlStr.replace("%resource%", resource);
+
+ return urlStr;
+ }
+
+ /**
+ * Request new data from the thermostat
+ *
+ * @param the resource URL constant for a particular thermostat JSON resource
+ * @return an object mapping to one of the various thermostat JSON responses or null in case of error
+ */
+ private Object getRadioThermostatData(String resource) {
+ Object result = null;
+ String errorMsg = null;
+
+ String urlStr = buildRequestURL(resource);
+ logger.debug("URL = {}", urlStr);
+
+ try {
+ // Run the HTTP request and get the JSON response from the thermostat
+ URL url = new URL(urlStr);
+ URLConnection connection = url.openConnection();
+
+ try {
+ String response = IOUtils.toString(connection.getInputStream());
+ logger.debug("thermostatResponse = {}", response);
+
+ // Map the JSON response to the correct object
+ if (DEFAULT_RESOURCE.equals(resource)) {
+ result = gson.fromJson(response, RadioThermostatJsonResponse.class);
+ } else if (HUMIDITY_RESOURCE.equals(resource)) {
+ result = gson.fromJson(response, RadioThermostatJsonHumidity.class);
+ } else if (RUNTIME_RESOURCE.equals(resource)) {
+ result = gson.fromJson(response, RadioThermostatJsonRuntime.class);
+ } else if (MODEL_RESOURCE.equals(resource)) {
+ result = gson.fromJson(response, RadioThermostatJsonModel.class);
+ } else if (NAME_RESOURCE.equals(resource)) {
+ result = gson.fromJson(response, RadioThermostatJsonName.class);
+ }
+
+ } finally {
+ IOUtils.closeQuietly(connection.getInputStream());
+ }
+
+ if (result != null ) {
+ if (initialized == 1) {
+ updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, rthermData.getName() + " " + rthermData.getModel());
+ }
+ return result;
+ } else {
+ retryCounter++;
+ if (retryCounter == 1) {
+ logger.warn("Error in contacting the thermostat, retrying once");
+ return getRadioThermostatData(resource);
+ }
+ errorMsg = "missing data object";
+ logger.warn("Error in thermostat response: {}", errorMsg);
+ }
+
+ } catch (MalformedURLException e) {
+ errorMsg = e.getMessage();
+ logger.warn("Constructed url is not valid: {}", errorMsg);
+ } catch (JsonSyntaxException e) {
+ errorMsg = "Configuration is incorrect";
+ logger.warn("Error running thermostat request: {}", errorMsg);
+ } catch (IOException | IllegalStateException e) {
+ errorMsg = e.getMessage();
+ }
+
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, errorMsg);
+ return 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
+ * @return the JSON response string from the thermostat
+ */
+ private String sendCommand(String cmdKey, String cmdVal) {
+ String urlStr = buildRequestURL(DEFAULT_RESOURCE);
+ String postJson = "{\""+ cmdKey + "\":" + cmdVal + "}";
+ byte[] out = postJson.getBytes(StandardCharsets.US_ASCII);
+ String output = null;
+ String errorMsg = null;
+
+ try {
+ URL url = new URL(urlStr);
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestProperty("Content-Type", "text/plain");
+ conn.setFixedLengthStreamingMode(out.length);
+ conn.setRequestMethod("POST");
+ conn.setDoOutput(true);
+ conn.connect();
+
+ OutputStream os = conn.getOutputStream();
+ os.write(postJson.getBytes(StandardCharsets.US_ASCII));
+
+ BufferedReader br = new BufferedReader(new InputStreamReader(
+ (conn.getInputStream())));
+ try {
+ if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
+ throw new HTTPException(conn.getResponseCode());
+ }
+ output = br.readLine();
+
+ } catch (IOException | HTTPException e) {
+ logger.error("Exception occurred during execution: {}", e.getMessage(), e);
+ } finally {
+ br.close();
+ os.close();
+ conn.disconnect();
+ }
+
+ } catch (MalformedURLException e) {
+ errorMsg = e.getMessage();
+ logger.warn("Constructed url is not valid: {}", errorMsg);
+ } catch (JsonSyntaxException e) {
+ errorMsg = "Configuration is incorrect";
+ logger.warn("Error running thermostat command: {}", errorMsg);
+ } catch (IOException | IllegalStateException e) {
+ logger.error("Exception occurred during execution: {}", e.getMessage(), e);
+ }
+
+ return output;
+
+ }
+
+ /**
+ * Update a given channelId from the thermostat data
+ *
+ * @param the channel id to be updated
+ * @param data
+ * @return
+ * @throws Exception
+ */
+ public static Object getValue(String channelId, RadioThermostatData data) throws Exception {
+ String[] fields = StringUtils.split(channelId, "#");
+
+ if (data != null) {
+ switch (fields[0]) {
+ case NAME:
+ return data.getName();
+ case MODEL:
+ return data.getModel();
+ case TEMPERATURE:
+ if (data.getThermostatData().getTemperature() != null) {
+ return new QuantityType(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() != null) {
+ return new QuantityType(data.getThermostatData().getSetpoint(), API_TEMPERATURE_UNIT);
+ } else {
+ return null;
+ }
+ case OVERRIDE:
+ return data.getThermostatData().getOverride();
+ case HOLD:
+ return data.getThermostatData().getHold();
+ 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 LAST_UPDATE:
+ return ZonedDateTime.now();
+ case TODAY_HEAT_HOUR:
+ return data.getRuntime().getToday().getHeatTime().getHour();
+ case TODAY_HEAT_MINUTE:
+ return data.getRuntime().getToday().getHeatTime().getMinute();
+ case TODAY_COOL_HOUR:
+ return data.getRuntime().getToday().getCoolTime().getHour();
+ case TODAY_COOL_MINUTE:
+ return data.getRuntime().getToday().getCoolTime().getMinute();
+ case YESTERDAY_HEAT_HOUR:
+ return data.getRuntime().getYesterday().getHeatTime().getHour();
+ case YESTERDAY_HEAT_MINUTE:
+ return data.getRuntime().getYesterday().getHeatTime().getMinute();
+ case YESTERDAY_COOL_HOUR:
+ return data.getRuntime().getYesterday().getCoolTime().getHour();
+ case YESTERDAY_COOL_MINUTE:
+ return data.getRuntime().getYesterday().getCoolTime().getMinute();
+ }
+ }
+
+ 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);
+ }
+ }
+
+ /**
+ * Check if the thermostat is a CT80 model
+ *
+ * @return boolean indicating whether or not the thermostat is a CT80 model
+ */
+ private boolean isCT80() {
+ return (rthermData.getModel() != null && rthermData.getModel().contains("CT80"));
+ }
+
+ /**
+ * Build a list of fan modes based on what model thermostat is used
+ *
+ * @return list of state options for thermostat fan modes
+ */
+ private List getFanModeOptions() {
+ List fanModeOptions = new ArrayList<>();
+
+ fanModeOptions.add(new StateOption("0","Auto"));
+ if (isCT80()) {
+ fanModeOptions.add(new StateOption("1","Auto/Circulate"));
+ }
+ fanModeOptions.add(new StateOption("2","On"));
+
+ return fanModeOptions;
+ }
+
+}
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatData.java b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatData.java
new file mode 100644
index 0000000000000..c676f8bad62d9
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatData.java
@@ -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.json;
+
+/**
+ * The {@link RadioThermostatData} is responsible for storing
+ * all of the JSON data objects that are retrieved from the thermostat
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class RadioThermostatData {
+ private String name;
+ private String model;
+ private RadioThermostatJsonResponse thermostatData;
+ private Integer humidity;
+ private RadioThermostatJsonRuntime runtime;
+
+ public RadioThermostatData() {
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getModel() {
+ return model;
+ }
+
+ public void setModel(String model) {
+ this.model = model;
+ }
+
+ public RadioThermostatJsonResponse getThermostatData() {
+ return thermostatData;
+ }
+
+ public void setThermostatData(RadioThermostatJsonResponse thermostatData) {
+ this.thermostatData = thermostatData;
+ }
+
+ public Integer getHumidity() {
+ return humidity;
+ }
+
+ public void setHumidity(Integer humidity) {
+ this.humidity = humidity;
+ }
+
+ public RadioThermostatJsonRuntime getRuntime() {
+ return runtime;
+ }
+
+ public void setRuntime(RadioThermostatJsonRuntime runtime) {
+ this.runtime = runtime;
+ }
+
+}
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonHumidity.java b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonHumidity.java
new file mode 100644
index 0000000000000..b978ef045a4d4
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonHumidity.java
@@ -0,0 +1,34 @@
+/**
+ * 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.json;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link RadioThermostatJsonHumidity} is responsible for storing
+ * the data from the thermostat 'tstat/humidity' JSON response
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class RadioThermostatJsonHumidity {
+ @SerializedName("humidity")
+ private Integer humidity;
+
+ public RadioThermostatJsonHumidity() {
+ }
+
+ public Integer getHumidity() {
+ return humidity;
+ }
+
+}
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonModel.java b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonModel.java
new file mode 100644
index 0000000000000..1678ebf73350a
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonModel.java
@@ -0,0 +1,31 @@
+/**
+ * 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.json;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link RadioThermostatJsonModel} is responsible for storing
+ * the data from the thermostat 'tstat/model' JSON response
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class RadioThermostatJsonModel {
+ @SerializedName("model")
+ private String model;
+
+ public String getModel() {
+ return model;
+ }
+
+}
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonName.java b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonName.java
new file mode 100644
index 0000000000000..9bf389ab58ea0
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonName.java
@@ -0,0 +1,31 @@
+/**
+ * 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.json;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link RadioThermostatJsonName} is responsible for storing
+ * the data from the thermostat 'sys/name' JSON response
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class RadioThermostatJsonName {
+ @SerializedName("name")
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+}
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonResponse.java b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonResponse.java
new file mode 100644
index 0000000000000..6e92f1f9dee95
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonResponse.java
@@ -0,0 +1,151 @@
+/**
+ * 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.json;
+
+import java.math.BigDecimal;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link RadioThermostatJsonResponse} is responsible for storing
+ * the data from the thermostat 'tstat' JSON response
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class RadioThermostatJsonResponse {
+
+ @SerializedName("temp")
+ private BigDecimal 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 RadioThermostatJsonTime time;
+
+ public RadioThermostatJsonResponse() {
+ }
+
+ public BigDecimal 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 null;
+ }
+ }
+
+ /**
+ * Receives "time" node from the JSON response
+ *
+ * @return {RadioThermostatJsonTime}
+ */
+ public RadioThermostatJsonTime getTime() {
+ return time;
+ }
+
+}
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonRuntime.java b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonRuntime.java
new file mode 100644
index 0000000000000..21da7103454b1
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonRuntime.java
@@ -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.json;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link RadioThermostatJsonRuntime} is responsible for storing
+ * the "today" and "yesterday" node from the "tstat/datalog" JSON response
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class RadioThermostatJsonRuntime {
+
+ @SerializedName("today")
+ private RadioThermostatJsonRuntimeHeatCool today;
+
+ @SerializedName("yesterday")
+ private RadioThermostatJsonRuntimeHeatCool yesterday;
+
+ public RadioThermostatJsonRuntime() {
+ }
+
+ /**
+ * Receives "today" node from the JSON response
+ *
+ * @return {RadioThermostatRuntimeHeatCool}
+ */
+ public RadioThermostatJsonRuntimeHeatCool getToday() {
+ return today;
+ }
+
+ /**
+ * Receives "yesterday" node from the JSON response
+ *
+ * @return {RadioThermostatRuntimeHeatCool}
+ */
+ public RadioThermostatJsonRuntimeHeatCool getYesterday() {
+ return yesterday;
+ }
+
+}
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonRuntimeHeatCool.java b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonRuntimeHeatCool.java
new file mode 100644
index 0000000000000..fe8c2c9fadbc6
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonRuntimeHeatCool.java
@@ -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.json;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link RadioThermostatJsonRuntimeHeatCool} is responsible for storing
+ * the "heat_runtime" and "cool_runtime" node from the thermostat JSON response
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class RadioThermostatJsonRuntimeHeatCool {
+
+ public RadioThermostatJsonRuntimeHeatCool() {
+ }
+
+ @SerializedName("heat_runtime")
+ private RadioThermostatJsonTime heatTime;
+
+ @SerializedName("cool_runtime")
+ private RadioThermostatJsonTime coolTime;
+
+ /**
+ * Receives "heat_runtime" node from the JSON response
+ *
+ * @return {RadioThermostatJsonTime}
+ */
+ public RadioThermostatJsonTime getHeatTime() {
+ return heatTime;
+ }
+
+ /**
+ * Receives "cool_runtime" node from the JSON response
+ *
+ * @return {RadioThermostatJsonTime}
+ */
+ public RadioThermostatJsonTime getCoolTime() {
+ return coolTime;
+ }
+
+}
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonTime.java b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonTime.java
new file mode 100644
index 0000000000000..8c6a53724cc01
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/json/RadioThermostatJsonTime.java
@@ -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.json;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link RadioThermostatJsonTime} is responsible for storing
+ * the "time" node from the thermostat JSON response
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+public class RadioThermostatJsonTime {
+ @SerializedName("day")
+ private Integer dayOfWeek;
+
+ @SerializedName("hour")
+ private Integer hour;
+
+ @SerializedName("minute")
+ private Integer minute;
+
+ public RadioThermostatJsonTime() {
+ }
+
+ public Integer getDayOfWeek() {
+ return dayOfWeek;
+ }
+
+ public Integer getHour() {
+ return hour;
+ }
+
+ public Integer getMinute() {
+ return 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);
+ }
+
+}
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/resources/ESH-INF/binding/binding.xml b/bundles/org.openhab.binding.radiothermostat/src/main/resources/ESH-INF/binding/binding.xml
new file mode 100644
index 0000000000000..dd84e3ee5788b
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/src/main/resources/ESH-INF/binding/binding.xml
@@ -0,0 +1,11 @@
+
+
+
+ Radio Thermostat Binding
+ Controls the RadioThermostat model CT30, CT50 or CT80 via the built-in WIFI module
+ Michael Lobstein
+
+
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/resources/ESH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.radiothermostat/src/main/resources/ESH-INF/thing/thing-types.xml
new file mode 100644
index 0000000000000..4831c31a12de4
--- /dev/null
+++ b/bundles/org.openhab.binding.radiothermostat/src/main/resources/ESH-INF/thing/thing-types.xml
@@ -0,0 +1,268 @@
+
+
+
+
+
+ Thermostat
+
+ A Thermostat to control the house's HVAC system
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thermostat Host Name/IP Address
+ Host name or IP address of the thermostat
+
+
+ Refresh Interval
+ Specifies the refresh interval in minutes
+ 2
+
+
+ Run-time Log Refresh Interval
+ Specifies the run-time log and humidity refresh interval in minutes
+ 10
+
+
+ Disable retrieval of run-time data
+ Optional flag to disable the retrieval of run-time data from the thermostat
+ 0
+
+
+ Disable retrieval of humidity data
+ Optional flag to disable the retrieval of humidity data from the thermostat
+ 0
+
+
+
+
+
+ String
+ Thermostat Name
+ The name of the thermostat
+
+
+
+
+ String
+ Thermostat Model
+ The model number and firmware version of the thermostat
+
+
+
+
+ Number
+ Temperature
+ The current temperature reading of the thermostat
+ Temperature
+
+
+
+
+ Number
+ Humidity
+ The current humidity reading of the thermostat
+ Humidity
+
+
+
+
+ Number
+ Mode
+ The current operating mode of the HVAC system
+
+
+ Off
+ Heat
+ Cool
+ Auto
+
+
+
+
+
+ Number
+ Fan Mode
+ The current operating mode of the fan
+
+
+
+ Number
+ Program Mode
+ The program schedule that the thermostat is running
+
+
+ None
+ Program A
+ Program B
+ Vacation
+ Holiday
+
+
+
+
+
+ Number
+ Setpoint
+ The current temperature set point of the thermostat
+ Temperature
+
+
+
+
+ Number
+ Override
+ Indicates if the normal program setpoint has been manually overriden
+
+
+
+
+ Number
+ Hold
+ Indicates if the current set point temperature is to be held indefinitely
+
+
+ Off
+ On
+
+
+
+
+
+ Number
+ Status
+ Indicates the current running status of the HVAC system
+
+
+
+
+ Number
+ Fan Status
+ Indicates the current fan status of the HVAC system
+
+
+
+
+ Number
+ Day
+ The current day of the week reported by the thermostat
+
+
+
+
+ Number
+ Hour
+ The current hour of the day reported by the thermostat
+
+
+
+
+ Number
+ Minute
+ The current minute past the hour reported by the thermostat
+
+
+
+
+ String
+ Thermostat Date
+ The current day of the week and time reported by the thermostat
+
+
+
+
+ DateTime
+ Last Updated
+ Last successful contact with thermostat
+ Date
+
+
+
+
+ Number
+ Today's Heating Hours
+ The number of hours of heating run-time today
+
+
+
+
+ Number
+ Today's Heating Minutes
+ The number of minutes of heating run-time today
+
+
+
+
+ Number
+ Today's Cooling Hours
+ The number of hours of cooling run-time today
+
+
+
+
+ Number
+ Today's Cooling Minutes
+ The number of minutes of cooling run-time today
+
+
+
+
+ Number
+ Yesterday's Heating Hours
+ The number of hours of heating run-time yesterday
+
+
+
+
+ Number
+ Yesterday's Heating Minutes
+ The number of minutes of heating run-time yesterday
+
+
+
+
+ Number
+ Yesterday's Cooling Hours
+ The number of hours of cooling run-time yesterday
+
+
+
+
+ Number
+ Yesterday's Cooling Minutes
+ The number of minutes of cooling run-time yesterday
+
+
+
+
+
diff --git a/bundles/pom.xml b/bundles/pom.xml
index c87fc40e90922..d08e610e1b1a6 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -181,6 +181,7 @@
org.openhab.binding.powermax
org.openhab.binding.pulseaudio
org.openhab.binding.pushbullet
+ org.openhab.binding.radiothermostat
org.openhab.binding.regoheatpump
org.openhab.binding.rfxcom
org.openhab.binding.rme