diff --git a/bundles/org.openhab.binding.radiothermostat/README.md b/bundles/org.openhab.binding.radiothermostat/README.md index ccef6429de..600cedf786 100644 --- a/bundles/org.openhab.binding.radiothermostat/README.md +++ b/bundles/org.openhab.binding.radiothermostat/README.md @@ -23,15 +23,16 @@ Otherwise the Thing must be manually added. The Thing has a few configuration parameters: -| Parameter | Description | -|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| hostName | The host name or IP address of the thermostat. Mandatory. | -| refresh | Overrides the refresh interval of the thermostat data. Optional, the default is 2 minutes. | -| logRefresh | Overrides the refresh interval of the run-time logs & humidity data. Optional, the default is 10 minutes. | -| isCT80 | Flag to enable additional features only available on the CT80 thermostat. Optional, the default is false. | -| disableLogs | Disable retrieval of run-time logs from the thermostat. Optional, the default is false. | -| setpointMode | Controls temporary or absolute setpoint mode. In "temporary" mode the thermostat will temporarily maintain the given setpoint until the next scheduled setpoint time period. In "absolute" mode the thermostat will ignore its program and maintain the given setpoint indefinitely. Optional, the default is "temporary". | -| clockSync | Flag to enable the binding to sync the internal clock on the thermostat to match the openHAB host's system clock. Sync occurs at binding startup and every hour thereafter. Optional, the default is **true**. | +| Parameter | Description | +|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| hostName | The host name or IP address of the thermostat. Mandatory. | +| refresh | Overrides the refresh interval of the thermostat data. Optional, the default is 2 minutes. | +| logRefresh | Overrides the refresh interval of the run-time logs & humidity data. Optional, the default is 10 minutes. | +| isCT80 | Flag to enable additional features only available on the CT80 thermostat. Optional, the default is **false**. | +| disableLogs | Disable retrieval of run-time logs from the thermostat. Optional, the default is **false**. | +| setpointMode | Controls temporary or absolute setpoint mode. In "temporary" mode the thermostat will temporarily maintain the given setpoint until the next scheduled setpoint time period. In "absolute" mode the thermostat will ignore its program and maintain the given setpoint indefinitely. Optional, the default is "temporary". | +| clockSync | Flag to enable the binding to sync the internal clock on the thermostat to match the openHAB host's system clock. Sync occurs at binding startup and every hour thereafter. Optional, the default is **true**. | +| remoteTempDeadband | Flag to enable rounding of the value received by the `remote_temp` channel, see notes and rule example below. Optional, the default is **false**. | ### Schedule Configuration @@ -68,6 +69,12 @@ curl http://$THERMOSTAT_IP/cloud -d '{"authkey":""}' - The `override` flag is not reported correctly on older thermostat versions (i.e. /tstat/model reports v1.09) - The Program Mode command is untested and according to the published API is only available on a CT80 Rev B. - Humidity information is available only when using a CT80 thermostat. +- The `remote_temp` channel can be used to send a temperature reading from a remote sensor to override the internal temperature reading of the thermostat in 1 degree increments. +- An example use case for a thermostat that is located in a hallway would be to place a sensor in a bedroom to provide more accurate temperature regulation in the bedroom. +- The temperature value from the sensor should be sent to the thermostat once per minute and with care taken to reset the thermostat to internal temperature mode in the case that the sensor stops updating. +- If the `remoteTempDeadband` configuration parameter is set to **true** the value received by the `remote_temp` channel will be rounded up or down depending on the thermostat's current state. +- This rounding allows the deadband/temperature swing to work properly for any remote temperature value (including those with decimal places) sent. +- For example, if the heat setpoint is 70°F, the heat will not energize until the temperature drops to 69.0°F and should stay on until the temperature reaches 70.0°F again. The reverse is true for cooling. - If `remote_temp` or `message` channels are used, their values in the thermostat will be cleared during binding shutdown. ## Channels @@ -185,7 +192,7 @@ String Therm_Message "Message: [%s]" Number:Temperature Therm_Rtemp "Remote Temperature [%d]" { 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" +Switch Therm_mysetting "Send my preferred setting" ``` ### `radiotherm.sitemap` Example @@ -208,8 +215,8 @@ sitemap radiotherm label="My Thermostat" { Text item=Therm_NextTemp icon="temperature" Text item=Therm_NextTime icon="time" - // 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"] + // Example of overriding the thermostat's temperature reading - TEST USE ONLY! + // 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 @@ -236,7 +243,7 @@ when Item Therm_mysetting received command then val actions = getActions("radiothermostat","radiothermostat:rtherm:mytherm1") - if(null === actions) { + if (null === actions) { logInfo("actions", "Actions not found, check thing ID") return } @@ -258,6 +265,20 @@ then end +rule "Send remote temperature sensor readings to the thermostat" +when + // An item containing the temperature from a remote sensor + // The item should be made to expire (e.g. expire="10m" in the item definition) in case the sensor should stop updating + // Must set the `remoteTempDeadband` configuration parameter to true and the `refresh` configuration parameter to 1 + Item RemoteSensorTemp received update +then + if (RemoteSensorTemp.state != NULL && RemoteSensorTemp.state != UNDEF) { + Therm_Rtemp.sendCommand(RemoteSensorTemp.state) + } else { + Therm_Rtemp.sendCommand(-1) + } +end + rule "Display outside temp in thermostat message area" when // An item containing the current outside temperature @@ -268,7 +289,7 @@ then // Sends empty string to clear the number and restore the time display if OutsideTemp is undefined var temp = "" - if (newState != null && newState != UNDEF) { + if (newState != NULL && newState != UNDEF) { temp = Math.round((newState as DecimalType).doubleValue).intValue.toString } 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 index e14e2f297a..d0fb04772c 100644 --- 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 @@ -30,6 +30,7 @@ public class RadioThermostatConfiguration { public boolean disableLogs = false; public boolean clockSync = false; public String setpointMode = "temporary"; + public boolean remoteTempDeadband = false; public @Nullable String monMorningHeatTime; public @Nullable String monDayHeatTime; 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 index 99acb547bc..16acf0b89c 100644 --- 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 @@ -94,6 +94,7 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe private boolean isCT80 = false; private boolean disableLogs = false; private boolean clockSync = false; + private boolean remoteTempDeadband = false; private String setpointCmdKeyPrefix = "t_"; private String heatProgramJson = BLANK; private String coolProgramJson = BLANK; @@ -117,6 +118,7 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe this.isCT80 = config.isCT80; this.disableLogs = config.disableLogs; this.clockSync = config.clockSync; + this.remoteTempDeadband = config.remoteTempDeadband; if (hostName == null || hostName.isBlank()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, @@ -339,7 +341,7 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe updateChannel(channelUID.getId(), rthermData); } else { Integer cmdInt = -1; - String cmdStr = command.toString(); + final String cmdStr = command.toString(); try { // parse out an Integer from the string // ie '70.5 F' becomes 70, also handles negative numbers @@ -392,7 +394,7 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe updateChannel(NEXT_TIME, rthermData); break; case SET_POINT: - String cmdKey; + final String cmdKey; if (rthermData.getThermostatData().getMode() == 1) { cmdKey = this.setpointCmdKeyPrefix + "heat"; rthermData.getThermostatData().setHeatTarget(Double.valueOf(cmdInt)); @@ -409,10 +411,10 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe break; case REMOTE_TEMP: if (cmdInt != -1) { - QuantityType remoteTemp = ((QuantityType) command) + final QuantityType remoteTemp = ((QuantityType) command) .toUnit(ImperialUnits.FAHRENHEIT); if (remoteTemp != null) { - connector.sendCommand("rem_temp", String.valueOf(remoteTemp.intValue()), + connector.sendCommand("rem_temp", handleRemoteTempDeadband(remoteTemp), REMOTE_TEMP_RESOURCE); } } else { @@ -441,8 +443,8 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe public void onNewMessageEvent(RadioThermostatEvent event) { logger.debug("onNewMessageEvent: key {} = {}", event.getKey(), event.getValue()); - String evtKey = event.getKey(); - String evtVal = event.getValue(); + final String evtKey = event.getKey(); + final String evtVal = event.getValue(); if (KEY_ERROR.equals(evtKey)) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, @@ -460,7 +462,7 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe } break; case HUMIDITY_RESOURCE: - RadioThermostatHumidityDTO dto = gson.fromJson(evtVal, RadioThermostatHumidityDTO.class); + final RadioThermostatHumidityDTO dto = gson.fromJson(evtVal, RadioThermostatHumidityDTO.class); // if thermostat returned -1 for humidity, skip this update if (dto != null && dto.getHumidity() >= 0) { rthermData.setHumidity(dto.getHumidity()); @@ -485,7 +487,7 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe */ private void updateChannel(String channelId, RadioThermostatDTO rthermData) { if (isLinked(channelId)) { - Object value; + final Object value; try { value = getValue(channelId, rthermData, thermostatSchedule); } catch (Exception e) { @@ -625,7 +627,7 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe * @return list of state options for thermostat fan modes */ private List getFanModeOptions() { - List fanModeOptions = new ArrayList<>(); + final List fanModeOptions = new ArrayList<>(this.isCT80 ? 3 : 2); fanModeOptions.add(new StateOption("0", "@text/options.fan-option-auto")); if (this.isCT80) { @@ -635,4 +637,31 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe return fanModeOptions; } + + private String handleRemoteTempDeadband(QuantityType remoteTemp) { + if (!this.remoteTempDeadband) { + return String.valueOf(remoteTemp.intValue()); + } + + final int remoteTempRounded; + final boolean isHeatModeOff = Integer.valueOf(1).equals(rthermData.getThermostatData().getMode()) + && Integer.valueOf(0).equals(rthermData.getThermostatData().getStatus()); + final boolean isCoolModeOff = Integer.valueOf(2).equals(rthermData.getThermostatData().getMode()) + && Integer.valueOf(0).equals(rthermData.getThermostatData().getStatus()); + + // hvac heating on OR hvac off in cooling mode, round temperature down, e.g. 69.5 = 69 + if (Integer.valueOf(1).equals(rthermData.getThermostatData().getStatus()) || isCoolModeOff) { + remoteTempRounded = (int) Math.floor(remoteTemp.doubleValue()); + logger.debug("remote temp: {}, rounding down to {}", remoteTemp, remoteTempRounded); + } else if (Integer.valueOf(2).equals(rthermData.getThermostatData().getStatus()) || isHeatModeOff) { + // hvac cooling on OR hvac off in heat mode, round temperature up, e.g. 69.1 = 70 + remoteTempRounded = (int) Math.ceil(remoteTemp.doubleValue()); + logger.debug("remote temp: {}, rounding up to {}", remoteTemp, remoteTempRounded); + } else { + // hvac mode off (or unknown), round temperature, e.g. 69.5 = 70 + remoteTempRounded = (int) Math.round(remoteTemp.doubleValue()); + logger.debug("remote temp: {}, rounding to {}", remoteTemp, remoteTempRounded); + } + return String.valueOf(remoteTempRounded); + } } diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.radiothermostat/src/main/resources/OH-INF/config/config.xml index 970a2548f8..40ad4f4cf4 100644 --- a/bundles/org.openhab.binding.radiothermostat/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.radiothermostat/src/main/resources/OH-INF/config/config.xml @@ -51,10 +51,16 @@ - Optional Flag to sync the thermostat's clock with the host system clock + Optional flag to sync the thermostat's clock with the host system clock true + + + Optional flag to round the value received by the Remote Temperature channel + false + + diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/resources/OH-INF/i18n/radiothermostat.properties b/bundles/org.openhab.binding.radiothermostat/src/main/resources/OH-INF/i18n/radiothermostat.properties index 51daaac631..dc9f296c93 100644 --- a/bundles/org.openhab.binding.radiothermostat/src/main/resources/OH-INF/i18n/radiothermostat.properties +++ b/bundles/org.openhab.binding.radiothermostat/src/main/resources/OH-INF/i18n/radiothermostat.properties @@ -11,7 +11,7 @@ thing-type.radiothermostat.rtherm.description = CT30, CT50/3M50 or CT80 Wi-Fi Th # thing types config thing-type.config.radiothermostat.thermostatconfig.clockSync.label = Enable Thermostat Clock Sync -thing-type.config.radiothermostat.thermostatconfig.clockSync.description = Optional Flag to sync the thermostat's clock with the host system clock +thing-type.config.radiothermostat.thermostatconfig.clockSync.description = Optional flag to sync the thermostat's clock with the host system clock thing-type.config.radiothermostat.thermostatconfig.disableLogs.label = Disable Retrieval of Run-time Data thing-type.config.radiothermostat.thermostatconfig.disableLogs.description = Optional Flag to Disable the Retrieval of Run-time Data from the Thermostat thing-type.config.radiothermostat.thermostatconfig.friDayCoolTemp.label = Friday Day Cool Setpoint @@ -90,6 +90,8 @@ thing-type.config.radiothermostat.thermostatconfig.monNightHeatTime.label = Mond thing-type.config.radiothermostat.thermostatconfig.monNightHeatTime.description = Monday Night setpoint start time (HH:mm) thing-type.config.radiothermostat.thermostatconfig.refresh.label = Refresh Interval thing-type.config.radiothermostat.thermostatconfig.refresh.description = Specifies the Refresh Interval in Minutes +thing-type.config.radiothermostat.thermostatconfig.remoteTempDeadband.label = Enable Remote Temperature Deadband +thing-type.config.radiothermostat.thermostatconfig.remoteTempDeadband.description = Optional flag to round the value received by the Remote Temperature channel thing-type.config.radiothermostat.thermostatconfig.satDayCoolTemp.label = Saturday Day Cool Setpoint thing-type.config.radiothermostat.thermostatconfig.satDayCoolTemp.description = The setpoint in Degrees Fahrenheit thing-type.config.radiothermostat.thermostatconfig.satDayCoolTime.label = Saturday Day