| 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 |
+| message | String (Write Only) | Used to display a number in the upper left 'price message' area of the thermostat's screen where the time is normally displayed |
## Full Example
Number Therm_Override "Override [MAP(radiotherm.map):%s_over]" { channel="radiothermostat:rtherm:mytherm1:override" }
Switch Therm_Hold "Hold" { channel="radiothermostat:rtherm:mytherm1:hold" }
-Number Therm_Day "Thermostat Day [%s]" { channel="radiothermostat:rtherm:mytherm1:day" }
-Number Therm_Hour "Thermostat Hour [%s]" { channel="radiothermostat:rtherm:mytherm1:hour" }
-Number Therm_Minute "Thermostat Minute [%s]" { channel="radiothermostat:rtherm:mytherm1:minute" }
+Number Therm_Day "Thermostat Day [%d]" { channel="radiothermostat:rtherm:mytherm1:day" }
+Number Therm_Hour "Thermostat Hour [%d]" { channel="radiothermostat:rtherm:mytherm1:hour" }
+Number Therm_Minute "Thermostat Minute [%d]" { channel="radiothermostat:rtherm:mytherm1:minute" }
String Therm_Dstmp "Thermostat DateStamp [%s]" <time> { channel="radiothermostat:rtherm:mytherm1:dt_stamp" }
Number:Time Therm_todayheat "Today's Heating Runtime [%d %unit%]" { channel="radiothermostat:rtherm:mytherm1:today_heat_runtime" }
Number:Time Therm_todaycool "Today's Cooling Runtime [%d %unit%]" { channel="radiothermostat:rtherm:mytherm1:today_cool_runtime" }
Number:Time Therm_yesterdayheat "Yesterday's Heating Runtime [%d %unit%]" { channel="radiothermostat:rtherm:mytherm1:yesterday_heat_runtime" }
Number:Time Therm_yesterdaycool "Yesterday's Cooling Runtime [%d %unit%]" { channel="radiothermostat:rtherm:mytherm1:yesterday_cool_runtime" }
+String Therm_Message "Message: [%s]" { channel="radiothermostat:rtherm:mytherm1:message" }
// 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" }
// 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":' + "68" + ', "tmode":1}')
+
+ // Also a command can be sent to a specific endpoint on the thermostat by
+ // specifying it as the second argument to sendRawCommand():
+
+ // Reboot the thermostat
+ // actions.sendRawCommand('{"command": "reboot"}', 'sys/command')
+
+ // Control the energy LED (CT80 only) [0 = off, 1 = green, 2 = yellow, 4 = red]
+ // actions.sendRawCommand('{"energy_led": 1}', 'tstat/led')
+
+ // Send a message to the User Message Area (CT80 only)
+ // actions.sendRawCommand('{"line": 0, "message": "Hello World!"}', 'tstat/uma')
+
+end
+
+rule "Display outside temp in thermostat message area"
+when
+ // An item containing the current outside temperature
+ Item OutsideTemp changed
+then
+ // Display up to 5 numbers in the thermostat's Price Message Area (PMA)
+ // A decimal point can be used. CT80 can display a negative '-' number
+ // Send null or empty string to clear the number and restore the time display
+ var Number temp = Math.round((OutsideTemp.state as DecimalType).doubleValue).intValue
+ Therm_Message.sendCommand(temp)
end
```
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;
public static final String PROPERTY_IP = "hostName";
public static final String PROPERTY_ISCT80 = "isCT80";
public static final String JSON_TIME = "{\"day\":%s,\"hour\":%s,\"minute\":%s}";
+ public static final String JSON_PMA = "{\"line\":1,\"message\":\"%s\"}";
+ public static final String BLANK = "";
public static final String KEY_ERROR = "error";
public static final String TIME_RESOURCE = "tstat/time";
public static final String HEAT_PROGRAM_RESOURCE = "tstat/program/heat";
public static final String COOL_PROGRAM_RESOURCE = "tstat/program/cool";
+ public static final String PMA_RESOURCE = "tstat/pma";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_RTHERM = new ThingTypeUID(BINDING_ID, "rtherm");
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 String MESSAGE = "message";
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, REMOTE_TEMP)
- .collect(Collectors.toSet());
+
+ public static final Set<String> SUPPORTED_CHANNEL_IDS = Set.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, REMOTE_TEMP, MESSAGE);
+
+ public static final Set<String> NO_UPDATE_CHANNEL_IDS = Set.of(REMOTE_TEMP, MESSAGE);
// Units of measurement of the data delivered by the API
public static final Unit<Temperature> API_TEMPERATURE_UNIT = ImperialUnits.FAHRENHEIT;
private @Nullable RadioThermostatHandler handler;
- @RuleAction(label = "send a raw command", description = "Send a raw command to the thermostat.")
+ @RuleAction(label = "send a raw command", description = "Send a raw command to the thermostat's 'tstat' endpoint.")
public void sendRawCommand(@ActionInput(name = "sendRawCommand") @Nullable String rawCommand) {
if (rawCommand == null) {
logger.warn("sendRawCommand called with null command, ignoring");
}
}
- /** Static alias to support the old DSL rules engine and make the action available there. */
+ @RuleAction(label = "send a raw command", description = "Send a raw command to a specific endpoint on the thermostat.")
+ public void sendRawCommand(@ActionInput(name = "sendRawCommand") @Nullable String rawCommand,
+ @Nullable String resource) {
+ if (rawCommand == null || resource == null) {
+ logger.warn("sendRawCommand called with null command, ignoring");
+ return;
+ }
+
+ RadioThermostatHandler localHandler = handler;
+ if (localHandler != null) {
+ localHandler.handleRawCommand(rawCommand, resource);
+ logger.debug("sendRawCommand called with raw command: {}, resource: {}", rawCommand, resource);
+ }
+ }
+
+ /** Static aliases to support the old DSL rules engine and make the action available there. */
public static void sendRawCommand(ThingActions actions, @Nullable String rawCommand) {
((RadioThermostatThingActions) actions).sendRawCommand(rawCommand);
}
+ public static void sendRawCommand(ThingActions actions, @Nullable String rawCommand, @Nullable String resource) {
+ ((RadioThermostatThingActions) actions).sendRawCommand(rawCommand, resource);
+ }
+
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
this.handler = (RadioThermostatHandler) handler;
public class RadioThermostatConnector {
private final Logger logger = LoggerFactory.getLogger(RadioThermostatConnector.class);
- private static final String URL = "http://%hostName%/%resource%";
+ private static final String URL = "http://%s/%s";
private final HttpClient httpClient;
private final List<RadioThermostatEventListener> listeners = new CopyOnWriteArrayList<>();
- private @Nullable String hostName;
+ private String hostName = BLANK;
public RadioThermostatConnector(HttpClient httpClient) {
this.httpClient = httpClient;
}
- public void setThermostatHostName(@Nullable String hostName) {
+ public void setThermostatHostName(String hostName) {
this.hostName = hostName;
}
* @param resouce the url of the json resource on the thermostat
*/
public void getAsyncThermostatData(String resource) {
- String urlStr = buildRequestURL(resource);
-
- httpClient.newRequest(urlStr).method(GET).timeout(30, TimeUnit.SECONDS).send(new BufferingResponseListener() {
- @Override
- public void onComplete(@Nullable Result result) {
- if (result != null && !result.isFailed()) {
- String response = getContentAsString();
- dispatchKeyValue(resource, response);
- } else {
- dispatchKeyValue(KEY_ERROR, "");
- }
- }
- });
+ httpClient.newRequest(buildRequestURL(resource)).method(GET).timeout(30, TimeUnit.SECONDS)
+ .send(new BufferingResponseListener() {
+ @Override
+ public void onComplete(@Nullable Result result) {
+ if (result != null && !result.isFailed()) {
+ dispatchKeyValue(resource, getContentAsString());
+ } else {
+ dispatchKeyValue(KEY_ERROR, BLANK);
+ }
+ }
+ });
}
/**
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(resource);
-
- String output = "";
try {
- Request request = httpClient.POST(urlStr);
+ Request request = httpClient.POST(buildRequestURL(resource));
request.header(HttpHeader.ACCEPT, "text/plain");
request.header(HttpHeader.CONTENT_TYPE, "text/plain");
request.content(new StringContentProvider(postJson), "application/json");
logger.trace("Sending POST request to '{}', data: {}", resource, postJson);
ContentResponse contentResponse = request.send();
- int httpStatus = contentResponse.getStatus();
- if (httpStatus != OK_200) {
- throw new RadioThermostatHttpException("Thermostat HTTP response code was: " + httpStatus);
+ if (contentResponse.getStatus() != OK_200) {
+ throw new RadioThermostatHttpException(
+ "Thermostat HTTP response code was: " + contentResponse.getStatus());
}
- output = contentResponse.getContentAsString();
- logger.trace("Response: {}", output);
+
+ logger.trace("Response: {}", contentResponse.getContentAsString());
+ return contentResponse.getContentAsString();
} catch (RadioThermostatHttpException | InterruptedException | TimeoutException | ExecutionException e) {
logger.debug("Error executing thermostat command: {}, {}", postJson, e.getMessage());
+ return BLANK;
}
-
- return output;
}
/**
* @return a valid URL for the thermostat's JSON interface
*/
private String buildRequestURL(String resource) {
- String hostName = this.hostName;
- if (hostName == null) {
- throw new IllegalStateException("hostname must not be null");
- }
- String urlStr = URL.replace("%hostName%", hostName);
- urlStr = urlStr.replace("%resource%", resource);
-
- return urlStr;
+ return String.format(URL, hostName, resource);
}
/**
import static org.openhab.binding.radiothermostat.internal.RadioThermostatBindingConstants.*;
-import java.math.BigDecimal;
import java.text.NumberFormat;
import java.text.ParseException;
import java.time.ZonedDateTime;
private boolean disableLogs = false;
private boolean clockSync = false;
private String setpointCmdKeyPrefix = "t_";
- private String heatProgramJson = "";
- private String coolProgramJson = "";
+ private String heatProgramJson = BLANK;
+ private String coolProgramJson = BLANK;
public RadioThermostatHandler(Thing thing, RadioThermostatStateDescriptionProvider stateDescriptionProvider,
HttpClient httpClient) {
this.disableLogs = config.disableLogs;
this.clockSync = config.clockSync;
- if (hostName == null || "".equals(hostName)) {
+ if (hostName == null || hostName.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error-hostname");
return;
Runnable runnable = () -> {
// populate the heat and cool programs on the thermostat from the user configuration,
// the commands will be sent each time the refresh job runs until a success response is seen
- if (!"".equals(heatProgramJson)) {
+ if (!heatProgramJson.isEmpty()) {
final String response = connector.sendCommand(null, null, heatProgramJson, HEAT_PROGRAM_RESOURCE);
if (response.contains("success")) {
- heatProgramJson = "";
+ heatProgramJson = BLANK;
}
}
- if (!"".equals(coolProgramJson)) {
+ if (!coolProgramJson.isEmpty()) {
final String response = connector.sendCommand(null, null, coolProgramJson, COOL_PROGRAM_RESOURCE);
if (response.contains("success")) {
- coolProgramJson = "";
+ coolProgramJson = BLANK;
}
}
connector.sendCommand(null, null, rawCommand, DEFAULT_RESOURCE);
}
+ public void handleRawCommand(@Nullable String rawCommand, String resource) {
+ connector.sendCommand(null, null, rawCommand, resource);
+ }
+
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
connector.sendCommand("rem_mode", "0", REMOTE_TEMP_RESOURCE);
}
break;
+ case MESSAGE:
+ if (!cmdStr.isEmpty()) {
+ connector.sendCommand(null, null, String.format(JSON_PMA, cmdStr), PMA_RESOURCE);
+ } else {
+ connector.sendCommand("mode", "0", PMA_RESOURCE);
+ }
+ break;
default:
logger.warn("Unsupported command: {}", command.toString());
}
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 Number) {
+ state = new DecimalType((Number) value);
} else if (value instanceof String) {
state = new StringType(value.toString());
} else if (value instanceof OnOffType) {
*/
private void updateAllChannels() {
// Update all channels from rthermData
- for (Channel channel : getThing().getChannels()) {
- updateChannel(channel.getUID().getId(), rthermData);
- }
+ getThing().getChannels().forEach(channel -> {
+ if (!NO_UPDATE_CHANNEL_IDS.contains(channel.getUID().getId())) {
+ updateChannel(channel.getUID().getId(), rthermData);
+ }
+ });
}
/**
private List<StateOption> getFanModeOptions() {
List<StateOption> fanModeOptions = new ArrayList<>();
- fanModeOptions.add(new StateOption("0", "Auto"));
+ fanModeOptions.add(new StateOption("0", "@text/options.fan-option-auto"));
if (this.isCT80) {
- fanModeOptions.add(new StateOption("1", "Auto/Circulate"));
+ fanModeOptions.add(new StateOption("1", "@text/options.fan-option-circulate"));
}
- fanModeOptions.add(new StateOption("2", "On"));
+ fanModeOptions.add(new StateOption("2", "@text/options.fan-option-on"));
return fanModeOptions;
}
channel-type.radiothermostat.hold.description = Indicates If the Current Set Point Temperature Is to Be Held Indefinitely
channel-type.radiothermostat.humidity.label = Humidity
channel-type.radiothermostat.humidity.description = The Current Humidity Reading of the Thermostat
+channel-type.radiothermostat.message.label = Message
+channel-type.radiothermostat.message.description = Use this channel to display a number in the price message area
channel-type.radiothermostat.mode.label = Mode
channel-type.radiothermostat.mode.description = The Current Operating Mode of the HVAC System
channel-type.radiothermostat.mode.state.option.0 = Off
offline.configuration-error-heating-program = Thermostat HEATING program schedule is invalid, check configuration!
offline.configuration-error-cooling-program = Thermostat COOLING program schedule is invalid, check configuration!
offline.communication-error-get-data = Error retrieving data from Thermostat
+options.fan-option-auto = Auto
+options.fan-option-circulate = Auto/Circulate
+options.fan-option-on = On
<channel id="today_cool_runtime" typeId="today_cool_runtime"/>
<channel id="yesterday_heat_runtime" typeId="yesterday_heat_runtime"/>
<channel id="yesterday_cool_runtime" typeId="yesterday_cool_runtime"/>
+ <channel id="message" typeId="message"/>
</channels>
- <config-description-ref uri="thing-type:radiothermostat:thermostatconfig"/>
+ <properties>
+ <property name="thingTypeVersion">1</property>
+ </properties>
+ <config-description-ref uri="thing-type:radiothermostat:thermostatconfig"/>
</thing-type>
<channel-type id="temp-temperature">
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
+ <channel-type id="message" advanced="true">
+ <item-type>String</item-type>
+ <label>Message</label>
+ <description>Use this channel to display a number in the price message area</description>
+ </channel-type>
+
</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
+
+ <thing-type uid="radiothermostat:rtherm">
+ <instruction-set targetVersion="1">
+ <add-channel id="message">
+ <type>radiothermostat:message</type>
+ </add-channel>
+ </instruction-set>
+ </thing-type>
+
+</update:update-descriptions>