Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[radiothermostat] Add Remote Temperature channel #10194

Merged
merged 7 commits into from
Apr 9, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 31 additions & 22 deletions bundles/org.openhab.binding.radiothermostat/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# 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.

The binding retrieves and periodically updates all basic system information from the thermostat.
Expand Down Expand Up @@ -45,26 +47,27 @@ The thing has a few configuration parameters:

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 |
| 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 |
| remote_temp | Number | Override the internal temperature as read by the thermostat's temperature sensor; Set to -1 to return to internal temperature mode |
| 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

Expand Down Expand Up @@ -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_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 Therm_Rtemp "Remote Temperature [%s]" { channel="radiothermostat:rtherm:mytherm1:remote_temp" }

// A virtual switch used to trigger a rule to send a json command to the thermostat
Switch Therm_mysetting "Send my preferred setting"
```
Expand All @@ -167,9 +173,12 @@ sitemap radiotherm label="My Thermostat" {
Text item=Therm_Override 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
// 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_Hour
Expand Down Expand Up @@ -198,6 +207,6 @@ then
}
// 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}')
actions.sendRawCommand('{"hold":1, "t_heat":' + "68" + ', "tmode":1}')
end
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class RadioThermostatBindingConstants {
public static final String DEFAULT_RESOURCE = "tstat";
public static final String RUNTIME_RESOURCE = "tstat/datalog";
public static final String HUMIDITY_RESOURCE = "tstat/humidity";
public static final String REMOTE_TEMP_RESOURCE = "tstat/remote_temp";

// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_RTHERM = new ThingTypeUID(BINDING_ID, "rtherm");
Expand All @@ -70,11 +71,12 @@ public class RadioThermostatBindingConstants {
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 String REMOTE_TEMP = "remote_temp";

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)
TODAY_HEAT_RUNTIME, TODAY_COOL_RUNTIME, YESTERDAY_HEAT_RUNTIME, YESTERDAY_COOL_RUNTIME, REMOTE_TEMP)
.collect(Collectors.toSet());

// Units of measurement of the data delivered by the API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,11 @@ public void onComplete(@Nullable Result result) {
*
* @param the JSON attribute key for the value to be updated
* @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
*/
public String sendCommand(String cmdKey, @Nullable String cmdVal) {
return sendCommand(cmdKey, cmdVal, null);
public String sendCommand(String cmdKey, @Nullable String cmdVal, String resource) {
return sendCommand(cmdKey, cmdVal, null, resource);
}

/**
Expand All @@ -117,12 +118,14 @@ public String sendCommand(String cmdKey, @Nullable String cmdVal) {
* @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
* @param the end point URI to use for the command
* @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
String postJson = cmdJson != null ? cmdJson : "{\"" + cmdKey + "\":" + cmdVal + "}";
String urlStr = buildRequestURL(DEFAULT_RESOURCE);
String urlStr = buildRequestURL(resource);

String output = "";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@
/**
* 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
Expand All @@ -81,11 +81,12 @@ protected void startBackgroundDiscovery() {
TimeUnit.SECONDS);
}

@SuppressWarnings("null")
@Override
protected void stopBackgroundDiscovery() {
if (scheduledFuture != null && !scheduledFuture.isCancelled()) {
ScheduledFuture<?> scheduledFuture = this.scheduledFuture;
if (scheduledFuture != null) {
scheduledFuture.cancel(true);
this.scheduledFuture = null;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ public void dispose() {
}

public void handleRawCommand(@Nullable String rawCommand) {
connector.sendCommand(null, null, rawCommand);
connector.sendCommand(null, null, rawCommand, DEFAULT_RESOURCE);
}

@Override
Expand All @@ -226,21 +226,19 @@ public void handleCommand(ChannelUID channelUID, Command command) {
} 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);
}
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);
connector.sendCommand("tmode", cmdStr, DEFAULT_RESOURCE);

// set the new operating mode, reset everything else,
// because refreshing the tstat data below is really slow.
Expand All @@ -253,26 +251,26 @@ public void handleCommand(ChannelUID channelUID, Command command) {
rthermData.getThermostatData().setProgramMode(-1);
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).
connector.getAsyncThermostatData(DEFAULT_RESOURCE);
}
break;
case FAN_MODE:
rthermData.getThermostatData().setFanMode(cmdInt);
connector.sendCommand("fmode", cmdStr);
connector.sendCommand("fmode", cmdStr, DEFAULT_RESOURCE);
break;
case PROGRAM_MODE:
rthermData.getThermostatData().setProgramMode(cmdInt);
connector.sendCommand("program_mode", cmdStr);
connector.sendCommand("program_mode", cmdStr, DEFAULT_RESOURCE);
break;
case HOLD:
if (command instanceof OnOffType && command == OnOffType.ON) {
rthermData.getThermostatData().setHold(1);
connector.sendCommand("hold", "1");
connector.sendCommand("hold", "1", DEFAULT_RESOURCE);
} else if (command instanceof OnOffType && command == OnOffType.OFF) {
rthermData.getThermostatData().setHold(0);
connector.sendCommand("hold", "0");
connector.sendCommand("hold", "0", DEFAULT_RESOURCE);
}
break;
case SET_POINT:
Expand All @@ -287,7 +285,14 @@ public void handleCommand(ChannelUID channelUID, Command command) {
// don't do anything if we are not in heat or cool mode
break;
}
connector.sendCommand(cmdKey, cmdInt.toString());
connector.sendCommand(cmdKey, cmdInt.toString(), DEFAULT_RESOURCE);
break;
case REMOTE_TEMP:
if (cmdInt != -1) {
connector.sendCommand("rem_temp", cmdInt.toString(), REMOTE_TEMP_RESOURCE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
connector.sendCommand("rem_temp", cmdInt.toString(), REMOTE_TEMP_RESOURCE);
QuantityType<?> quantity = ((QuantityType<Temperature>) command).toUnit(SIUnits.Celsius);
connector.sendCommand("rem_temp", String.valueOf(quantity.intValue()), REMOTE_TEMP_RESOURCE);

Please just make the recommended change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, done.

} else {
connector.sendCommand("rem_mode", "0", REMOTE_TEMP_RESOURCE);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should make sure that the units are what you expect before you send the command to the device.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am already parsing out the value of the command string into a whole number on line 232. This device is US centric and only supports Fahrenheit so it should already be understood by the user that they are sending a Fahrenheit temperature value to it.

Copy link
Contributor

@cpmeister cpmeister Feb 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That may be the case but the user might be using Celsius as their system default so the UI would end up displaying and sending values with that unit instead. Certain countries have easy access to products that use Fahrenheit even though they actually use celsius. E.g. Canada.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to recap this functionality a bit... This new channel is a write-only channel, it does not display anything back, It allows a user to override the internal temperature reading of this thermostat with a temperature value from an external sensor. Presumably a rule would be used to monitor an item containing a temperature sensor's value and when it changes, it would update an item linked to this channel. What ever number is received by the thermostat would then display on its screen as the current temperature reading (a whole number Fahrenheit value) and then be sent back to the main temperature channel (and displayed per whatever unit is specified by their system default settings) when the binding does its next polling update.

I am sure that there are a few of these in Canada even though it was not sold there.....
If someone in a Celsius country is using this, they will simply have to do a conversion of the Celsius temperature value they want to use in the rule that will send the value to this new channel. This will hopefully force them to ensure that a sane value is sent instead of relying on an automatic conversion that might have unpredictable results.

break;
default:
logger.warn("Unsupported command: {}", command.toString());
Expand Down Expand Up @@ -320,7 +325,10 @@ public void onNewMessageEvent(RadioThermostatEvent event) {
updateAllChannels();
break;
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);
break;
case RUNTIME_RESOURCE:
Expand Down Expand Up @@ -382,7 +390,7 @@ private void updateChannel(String channelId, RadioThermostatDTO rthermData) {

/**
* 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
Expand Down Expand Up @@ -456,7 +464,7 @@ private void updateAllChannels() {

/**
* 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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<channel id="hold" typeId="hold"/>
<channel id="status" typeId="status"/>
<channel id="fan_status" typeId="fan_status"/>
<channel id="remote_temp" typeId="remote_temp"/>
<channel id="day" typeId="t_day"/>
<channel id="hour" typeId="t_hour"/>
<channel id="minute" typeId="t_minute"/>
Expand Down Expand Up @@ -157,6 +158,14 @@
<state min="0" max="2" pattern="%d"/>
</channel-type>

<channel-type id="remote_temp" advanced="true">
<item-type>Number</item-type>
mlobstein marked this conversation as resolved.
Show resolved Hide resolved
<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"/>
mlobstein marked this conversation as resolved.
Show resolved Hide resolved
</channel-type>

<channel-type id="t_day" advanced="true">
<item-type>Number</item-type>
<label>Day</label>
Expand Down