[radiothermostat] Add Remote Temperature channel (#10194)

* Add Remote Temperature channel

Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>

* Fix spelling error

Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>

* Fix spelling error2

Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>

* review changes

Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>

* review changes

Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>

* review changes

Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>

* minor README update

Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
This commit is contained in:
mlobstein 2021-04-09 16:28:38 -05:00 committed by GitHub
parent 3561388061
commit 035556bc55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 83 additions and 48 deletions

View File

@ -1,5 +1,7 @@
# RadioThermostat Binding # RadioThermostat Binding
![RadioThermostat logo](doc/index.jpg)
This binding connects RadioThermostat/3M Filtrete models CT30, CT50/3M50, CT80, etc. with built-in Wi-Fi module to openHAB. 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 binding retrieves and periodically updates all basic system information from the thermostat.
@ -45,26 +47,27 @@ The thing has a few configuration parameters:
The thermostat information that is retrieved is available as these channels: The thermostat information that is retrieved is available as these channels:
| Channel ID | Item Type | Description | | Channel ID | Item Type | Description |
|------------------------|----------------------|---------------------------------------------------------------------------| |------------------------|----------------------|------------------------------------------------------------------------------------------------------------------------------------|
| temperature | Number:Temperature | The current temperature reading of the thermostat | | temperature | Number:Temperature | The current temperature reading of the thermostat |
| humidity | Number:Dimensionless | The current humidity reading of the thermostat (CT80 only) | | humidity | Number:Dimensionless | The current humidity reading of the thermostat (CT80 only) |
| mode | Number | The current operating mode of the HVAC system | | mode | Number | The current operating mode of the HVAC system |
| fan_mode | Number | The current operating mode of the fan | | 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) | | 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 | | set_point | Number:Temperature | The current temperature set point of the thermostat |
| status | Number | Indicates the current running status of the HVAC system | | status | Number | Indicates the current running status of the HVAC system |
| fan_status | Number | Indicates the current fan 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 | | 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 | | 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) | | remote_temp | Number:Temperature | Override the internal temperature as read by the thermostat's temperature sensor; Set to -1 to return to internal temperature mode |
| hour | Number | The current hour of the day reported by the thermostat (24 hr) | | day | Number | The current day of the week reported by the thermostat (0 = Monday) |
| minute | Number | The current minute past the hour reported by the thermostat | | hour | Number | The current hour of the day reported by the thermostat (24 hr) |
| dt_stamp | String | The current day of the week and time reported by the thermostat (E HH:mm) | | minute | Number | The current minute past the hour reported by the thermostat |
| today_heat_runtime | Number:Time | The total number of minutes of heating run-time today | | dt_stamp | String | The current day of the week and time reported by the thermostat (E HH:mm) |
| today_cool_runtime | Number:Time | The total number of minutes of cooling run-time today | | today_heat_runtime | Number:Time | The total number of minutes of heating run-time today |
| yesterday_heat_runtime | Number:Time | The total number of minutes of heating run-time yesterday | | today_cool_runtime | Number:Time | The total number of minutes of cooling run-time today |
| yesterday_cool_runtime | Number:Time | The total number of minutes of cooling run-time yesterday | | 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 ## Full Example
@ -145,6 +148,9 @@ Number:Time Therm_todaycool "Today's Cooling Runtime [%d %unit%]" { channe
Number:Time Therm_yesterdayheat "Yesterday's Heating Runtime [%d %unit%]" { channel="radiothermostat:rtherm:mytherm1:yesterday_heat_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" } Number:Time Therm_yesterdaycool "Yesterday's Cooling Runtime [%d %unit%]" { channel="radiothermostat:rtherm:mytherm1:yesterday_cool_runtime" }
// Override the thermostat's temperature reading with a value from an external sensor, set to -1 to revert to internal temperature mode
Number:Temperature Therm_Rtemp "Remote Temperature [%d]" <temperature> { channel="radiothermostat:rtherm:mytherm1:remote_temp" }
// A virtual switch used to trigger a rule to send a json command to the thermostat // A virtual switch used to trigger a rule to send a json command to the thermostat
Switch Therm_mysetting "Send my preferred setting" Switch Therm_mysetting "Send my preferred setting"
``` ```
@ -167,9 +173,12 @@ sitemap radiotherm label="My Thermostat" {
Text item=Therm_Override icon="smoke" Text item=Therm_Override icon="smoke"
Switch item=Therm_Hold icon="smoke" Switch item=Therm_Hold icon="smoke"
// Example of overriding the thermostat's temperature reading
Switch item=Therm_Rtemp label="Remote Temp" icon="temperature" mappings=[60="60", 75="75", 80="80", -1="Reset"]
// Virtual switch/button to trigger a rule to send a custom command // Virtual switch/button to trigger a rule to send a custom command
// The ON value displays in the button // The ON value displays in the button
Switch item=Therm_mysetting mappings=[ON="Heat, 58, hold"] Switch item=Therm_mysetting mappings=[ON="Heat, 68, hold"]
Text item=Therm_Day Text item=Therm_Day
Text item=Therm_Hour Text item=Therm_Hour
@ -198,6 +207,6 @@ then
} }
// JSON to send directly to the thermostat's '/tstat' endpoint // JSON to send directly to the thermostat's '/tstat' endpoint
// See RadioThermostat_CT50_Honeywell_Wifi_API_V1.3.pdf for more detail // See RadioThermostat_CT50_Honeywell_Wifi_API_V1.3.pdf for more detail
actions.sendRawCommand('{"hold":1, "t_heat":' + "58" + ', "tmode":1}') actions.sendRawCommand('{"hold":1, "t_heat":' + "68" + ', "tmode":1}')
end end
``` ```

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -47,6 +47,7 @@ public class RadioThermostatBindingConstants {
public static final String DEFAULT_RESOURCE = "tstat"; public static final String DEFAULT_RESOURCE = "tstat";
public static final String RUNTIME_RESOURCE = "tstat/datalog"; public static final String RUNTIME_RESOURCE = "tstat/datalog";
public static final String HUMIDITY_RESOURCE = "tstat/humidity"; public static final String HUMIDITY_RESOURCE = "tstat/humidity";
public static final String REMOTE_TEMP_RESOURCE = "tstat/remote_temp";
// List of all Thing Type UIDs // List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_RTHERM = new ThingTypeUID(BINDING_ID, "rtherm"); public static final ThingTypeUID THING_TYPE_RTHERM = new ThingTypeUID(BINDING_ID, "rtherm");
@ -70,11 +71,12 @@ public class RadioThermostatBindingConstants {
public static final String TODAY_COOL_RUNTIME = "today_cool_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_HEAT_RUNTIME = "yesterday_heat_runtime";
public static final String YESTERDAY_COOL_RUNTIME = "yesterday_cool_runtime"; public static final String YESTERDAY_COOL_RUNTIME = "yesterday_cool_runtime";
public static final String REMOTE_TEMP = "remote_temp";
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_RTHERM); 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, 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, 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) TODAY_HEAT_RUNTIME, TODAY_COOL_RUNTIME, YESTERDAY_HEAT_RUNTIME, YESTERDAY_COOL_RUNTIME, REMOTE_TEMP)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
// Units of measurement of the data delivered by the API // Units of measurement of the data delivered by the API

View File

@ -105,10 +105,11 @@ public class RadioThermostatConnector {
* *
* @param the JSON attribute key for the value to be updated * @param the JSON attribute key for the value to be updated
* @param the value to be updated in the thermostat * @param the value to be updated in the thermostat
* @param the end point URI to use for the command
* @return the JSON response string from the thermostat * @return the JSON response string from the thermostat
*/ */
public String sendCommand(String cmdKey, @Nullable String cmdVal) { public String sendCommand(String cmdKey, @Nullable String cmdVal, String resource) {
return sendCommand(cmdKey, cmdVal, null); return sendCommand(cmdKey, cmdVal, null, resource);
} }
/** /**
@ -117,12 +118,14 @@ public class RadioThermostatConnector {
* @param the JSON attribute key for the value to be updated * @param the JSON attribute key for the value to be updated
* @param the value to be updated in the thermostat * @param the value to be updated in the thermostat
* @param JSON string to send directly to the thermostat instead of a key/value pair * @param JSON string to send directly to the thermostat instead of a key/value pair
* @param the end point URI to use for the command
* @return the JSON response string from the thermostat * @return the JSON response string from the thermostat
*/ */
public String sendCommand(@Nullable String cmdKey, @Nullable String cmdVal, @Nullable String cmdJson) { public String sendCommand(@Nullable String cmdKey, @Nullable String cmdVal, @Nullable String cmdJson,
String resource) {
// if we got a cmdJson string send that, otherwise build the json from the key and val params // 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 postJson = cmdJson != null ? cmdJson : "{\"" + cmdKey + "\":" + cmdVal + "}";
String urlStr = buildRequestURL(DEFAULT_RESOURCE); String urlStr = buildRequestURL(resource);
String output = ""; String output = "";

View File

@ -81,11 +81,12 @@ public class RadioThermostatDiscoveryService extends AbstractDiscoveryService {
TimeUnit.SECONDS); TimeUnit.SECONDS);
} }
@SuppressWarnings("null")
@Override @Override
protected void stopBackgroundDiscovery() { protected void stopBackgroundDiscovery() {
if (scheduledFuture != null && !scheduledFuture.isCancelled()) { ScheduledFuture<?> scheduledFuture = this.scheduledFuture;
if (scheduledFuture != null) {
scheduledFuture.cancel(true); scheduledFuture.cancel(true);
this.scheduledFuture = null;
} }
} }

View File

@ -46,6 +46,7 @@ import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PointType; import org.openhab.core.library.types.PointType;
import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.thing.Channel; import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
@ -216,7 +217,7 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe
} }
public void handleRawCommand(@Nullable String rawCommand) { public void handleRawCommand(@Nullable String rawCommand) {
connector.sendCommand(null, null, rawCommand); connector.sendCommand(null, null, rawCommand, DEFAULT_RESOURCE);
} }
@Override @Override
@ -226,21 +227,19 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe
} else { } else {
Integer cmdInt = -1; Integer cmdInt = -1;
String cmdStr = command.toString(); String cmdStr = command.toString();
if (cmdStr != null) { try {
try { // parse out an Integer from the string
// parse out an Integer from the string // ie '70.5 F' becomes 70, also handles negative numbers
// ie '70.5 F' becomes 70, also handles negative numbers cmdInt = NumberFormat.getInstance().parse(cmdStr).intValue();
cmdInt = NumberFormat.getInstance().parse(cmdStr).intValue(); } catch (ParseException e) {
} catch (ParseException e) { logger.debug("Command: {} -> Not an integer", cmdStr);
logger.debug("Command: {} -> Not an integer", cmdStr);
}
} }
switch (channelUID.getId()) { switch (channelUID.getId()) {
case MODE: case MODE:
// only do if commanded mode is different than current mode // only do if commanded mode is different than current mode
if (!cmdInt.equals(rthermData.getThermostatData().getMode())) { if (!cmdInt.equals(rthermData.getThermostatData().getMode())) {
connector.sendCommand("tmode", cmdStr); connector.sendCommand("tmode", cmdStr, DEFAULT_RESOURCE);
// set the new operating mode, reset everything else, // set the new operating mode, reset everything else,
// because refreshing the tstat data below is really slow. // because refreshing the tstat data below is really slow.
@ -253,26 +252,26 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe
rthermData.getThermostatData().setProgramMode(-1); rthermData.getThermostatData().setProgramMode(-1);
updateChannel(PROGRAM_MODE, rthermData); updateChannel(PROGRAM_MODE, rthermData);
// now just trigger a refresh of the thermost to get the new active setpoint // now just trigger a refresh of the thermostat to get the new active setpoint
// this takes a while for the JSON request to complete (async). // this takes a while for the JSON request to complete (async).
connector.getAsyncThermostatData(DEFAULT_RESOURCE); connector.getAsyncThermostatData(DEFAULT_RESOURCE);
} }
break; break;
case FAN_MODE: case FAN_MODE:
rthermData.getThermostatData().setFanMode(cmdInt); rthermData.getThermostatData().setFanMode(cmdInt);
connector.sendCommand("fmode", cmdStr); connector.sendCommand("fmode", cmdStr, DEFAULT_RESOURCE);
break; break;
case PROGRAM_MODE: case PROGRAM_MODE:
rthermData.getThermostatData().setProgramMode(cmdInt); rthermData.getThermostatData().setProgramMode(cmdInt);
connector.sendCommand("program_mode", cmdStr); connector.sendCommand("program_mode", cmdStr, DEFAULT_RESOURCE);
break; break;
case HOLD: case HOLD:
if (command instanceof OnOffType && command == OnOffType.ON) { if (command instanceof OnOffType && command == OnOffType.ON) {
rthermData.getThermostatData().setHold(1); rthermData.getThermostatData().setHold(1);
connector.sendCommand("hold", "1"); connector.sendCommand("hold", "1", DEFAULT_RESOURCE);
} else if (command instanceof OnOffType && command == OnOffType.OFF) { } else if (command instanceof OnOffType && command == OnOffType.OFF) {
rthermData.getThermostatData().setHold(0); rthermData.getThermostatData().setHold(0);
connector.sendCommand("hold", "0"); connector.sendCommand("hold", "0", DEFAULT_RESOURCE);
} }
break; break;
case SET_POINT: case SET_POINT:
@ -287,7 +286,16 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe
// don't do anything if we are not in heat or cool mode // don't do anything if we are not in heat or cool mode
break; break;
} }
connector.sendCommand(cmdKey, cmdInt.toString()); connector.sendCommand(cmdKey, cmdInt.toString(), DEFAULT_RESOURCE);
break;
case REMOTE_TEMP:
if (cmdInt != -1) {
QuantityType<?> remoteTemp = ((QuantityType<Temperature>) command)
.toUnit(ImperialUnits.FAHRENHEIT);
connector.sendCommand("rem_temp", String.valueOf(remoteTemp.intValue()), REMOTE_TEMP_RESOURCE);
} else {
connector.sendCommand("rem_mode", "0", REMOTE_TEMP_RESOURCE);
}
break; break;
default: default:
logger.warn("Unsupported command: {}", command.toString()); logger.warn("Unsupported command: {}", command.toString());
@ -320,7 +328,10 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe
updateAllChannels(); updateAllChannels();
break; break;
case HUMIDITY_RESOURCE: case HUMIDITY_RESOURCE:
rthermData.setHumidity(gson.fromJson(evtVal, RadioThermostatHumidityDTO.class).getHumidity()); RadioThermostatHumidityDTO dto = gson.fromJson(evtVal, RadioThermostatHumidityDTO.class);
if (dto != null) {
rthermData.setHumidity(dto.getHumidity());
}
updateChannel(HUMIDITY, rthermData); updateChannel(HUMIDITY, rthermData);
break; break;
case RUNTIME_RESOURCE: case RUNTIME_RESOURCE:
@ -382,7 +393,7 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe
/** /**
* Update a given channelId from the thermostat data * Update a given channelId from the thermostat data
* *
* @param the channel id to be updated * @param the channel id to be updated
* @param data the RadioThermostat dto * @param data the RadioThermostat dto
* @return the value to be set in the state * @return the value to be set in the state
@ -456,7 +467,7 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe
/** /**
* Build a list of fan modes based on what model thermostat is used * Build a list of fan modes based on what model thermostat is used
* *
* @return list of state options for thermostat fan modes * @return list of state options for thermostat fan modes
*/ */
private List<StateOption> getFanModeOptions() { private List<StateOption> getFanModeOptions() {

View File

@ -22,6 +22,7 @@
<channel id="hold" typeId="hold"/> <channel id="hold" typeId="hold"/>
<channel id="status" typeId="status"/> <channel id="status" typeId="status"/>
<channel id="fan_status" typeId="fan_status"/> <channel id="fan_status" typeId="fan_status"/>
<channel id="remote_temp" typeId="remote_temp"/>
<channel id="day" typeId="t_day"/> <channel id="day" typeId="t_day"/>
<channel id="hour" typeId="t_hour"/> <channel id="hour" typeId="t_hour"/>
<channel id="minute" typeId="t_minute"/> <channel id="minute" typeId="t_minute"/>
@ -157,6 +158,14 @@
<state min="0" max="2" pattern="%d"/> <state min="0" max="2" pattern="%d"/>
</channel-type> </channel-type>
<channel-type id="remote_temp" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Remote Temperature</label>
<description>The remote temperature takes the place of the ambient temperature as read by the local thermostat
temperature sensor</description>
<state pattern="%d %unit%"/>
</channel-type>
<channel-type id="t_day" advanced="true"> <channel-type id="t_day" advanced="true">
<item-type>Number</item-type> <item-type>Number</item-type>
<label>Day</label> <label>Day</label>