From 811d329a590f314c9654fafb5758d5ce839e1b88 Mon Sep 17 00:00:00 2001 From: silverfunk Date: Wed, 9 Sep 2020 09:08:55 -0500 Subject: [PATCH] [radiothermostat] Added Absolute Setpoint Mode (#8393) Signed-off-by: Ted Jordan --- .../README.md | 19 +++-- .../RadioThermostatConfiguration.java | 1 + ...dioThermostatStateDescriptionProvider.java | 44 +++------- .../handler/RadioThermostatHandler.java | 84 +++++++++++++------ .../resources/ESH-INF/thing/thing-types.xml | 9 ++ 5 files changed, 88 insertions(+), 69 deletions(-) diff --git a/bundles/org.openhab.binding.radiothermostat/README.md b/bundles/org.openhab.binding.radiothermostat/README.md index ab307b4cd4904..74987466aae42 100644 --- a/bundles/org.openhab.binding.radiothermostat/README.md +++ b/bundles/org.openhab.binding.radiothermostat/README.md @@ -32,13 +32,14 @@ The binding has no configuration options, all configuration is done at Thing lev 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. | +| 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, returning to its program after a time. In "absolute" mode the thermostat will ignore its program maintaining the given setpoint. | ## Channels @@ -113,8 +114,8 @@ NULL_over=- radiotherm.things: ```java -radiothermostat:rtherm:mytherm1 "My 1st floor thermostat" [ hostName="192.168.10.1", refresh=2, logRefresh=10, isCT80=false, disableLogs=false ] -radiothermostat:rtherm:mytherm2 "My 2nd floor thermostat" [ hostName="mythermhost2", refresh=1, logRefresh=20, isCT80=true, disableLogs=false ] +radiothermostat:rtherm:mytherm1 "My 1st floor thermostat" [ hostName="192.168.10.1", refresh=2, logRefresh=10, isCT80=false, disableLogs=false, setpointMode="temporary" ] +radiothermostat:rtherm:mytherm2 "My 2nd floor thermostat" [ hostName="mythermhost2", refresh=1, logRefresh=20, isCT80=true, disableLogs=false, setpointMode="absolute" ] ``` radiotherm.items: 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 18b8c4232f178..d509e25658244 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 @@ -28,4 +28,5 @@ public class RadioThermostatConfiguration { public @Nullable Integer logRefresh; public boolean isCT80 = false; public boolean disableLogs = false; + public String setpointMode = "temporary"; } 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 index b5a63acbac247..a135ae9d7b813 100644 --- 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 @@ -12,21 +12,12 @@ */ 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.binding.BaseDynamicStateDescriptionProvider; +import org.eclipse.smarthome.core.thing.i18n.ChannelTypeI18nLocalizationService; 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; +import org.osgi.service.component.annotations.Reference; /** * The {@link RadioThermostatStateDescriptionProvider} class is a dynamic provider of state options while leaving other @@ -37,28 +28,15 @@ */ @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(); +public class RadioThermostatStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { + @Reference + protected void setChannelTypeI18nLocalizationService( + final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { + this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService; } - @Deactivate - public void deactivate() { - channelOptionsMap.clear(); + protected void unsetChannelTypeI18nLocalizationService( + final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { + this.channelTypeI18nLocalizationService = null; } } 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 dac91d9010e0e..635b20b84fe13 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 @@ -76,8 +76,7 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe private static final int DEFAULT_REFRESH_PERIOD = 2; private static final int DEFAULT_LOG_REFRESH_PERIOD = 10; - private @Nullable final RadioThermostatStateDescriptionProvider stateDescriptionProvider; - + private final RadioThermostatStateDescriptionProvider stateDescriptionProvider; private final Logger logger = LoggerFactory.getLogger(RadioThermostatHandler.class); private final Gson gson; @@ -87,37 +86,66 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe private @Nullable ScheduledFuture refreshJob; private @Nullable ScheduledFuture logRefreshJob; - private @Nullable RadioThermostatConfiguration config; + private int refreshPeriod = DEFAULT_REFRESH_PERIOD; + private int logRefreshPeriod = DEFAULT_LOG_REFRESH_PERIOD; + private boolean isCT80 = false; + private boolean disableLogs = false; + private String setpointCmdKeyPrefix = "t_"; - public RadioThermostatHandler(Thing thing, - @Nullable RadioThermostatStateDescriptionProvider stateDescriptionProvider, HttpClient httpClient) { + public RadioThermostatHandler(Thing thing, RadioThermostatStateDescriptionProvider stateDescriptionProvider, + HttpClient httpClient) { super(thing); this.stateDescriptionProvider = stateDescriptionProvider; gson = new Gson(); connector = new RadioThermostatConnector(httpClient); } - @SuppressWarnings("null") @Override public void initialize() { logger.debug("Initializing RadioThermostat handler."); - this.config = getConfigAs(RadioThermostatConfiguration.class); - connector.setThermostatHostName(config.hostName); + RadioThermostatConfiguration config = getConfigAs(RadioThermostatConfiguration.class); + + final String hostName = config.hostName; + final Integer refresh = config.refresh; + final Integer logRefresh = config.logRefresh; + this.isCT80 = config.isCT80; + this.disableLogs = config.disableLogs; + + if (hostName == null || hostName.equals("")) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Thermostat Host Name must be specified"); + return; + } + + if (refresh != null) { + this.refreshPeriod = refresh; + } + + if (logRefresh != null) { + this.logRefreshPeriod = logRefresh; + } + + connector.setThermostatHostName(hostName); connector.addEventListener(this); + // The setpoint mode is controlled by the name of setpoint attribute sent to the thermostat. + // Temporary mode uses setpoint names prefixed with "t_" while absolute mode uses "a_" + if (config.setpointMode.equals("absolute")) { + this.setpointCmdKeyPrefix = "a_"; + } + // populate fan mode options based on thermostat model - List fanModeOptions = getFanModeOptions(config.isCT80); - stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), FAN_MODE), fanModeOptions); + stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), FAN_MODE), getFanModeOptions()); // if we are not a CT-80, remove the humidity & program mode channel - if (!config.isCT80) { + if (!this.isCT80) { List channels = new ArrayList<>(this.getThing().getChannels()); channels.removeIf(c -> (c.getUID().getId().equals(HUMIDITY))); channels.removeIf(c -> (c.getUID().getId().equals(PROGRAM_MODE))); updateThing(editThing().withChannels(channels).build()); } startAutomaticRefresh(); - if (!config.disableLogs || config.isCT80) { + if (!this.disableLogs || this.isCT80) { startAutomaticLogRefresh(); } @@ -132,56 +160,58 @@ public Collection> getServices() { /** * Start the job to periodically update data from the thermostat */ - @SuppressWarnings("null") private void startAutomaticRefresh() { + ScheduledFuture refreshJob = this.refreshJob; if (refreshJob == null || refreshJob.isCancelled()) { Runnable runnable = () -> { // send an async call to the thermostat to get the 'tstat' data connector.getAsyncThermostatData(DEFAULT_RESOURCE); }; - int delay = (config.refresh != null) ? config.refresh.intValue() : DEFAULT_REFRESH_PERIOD; - refreshJob = scheduler.scheduleWithFixedDelay(runnable, 0, delay, TimeUnit.MINUTES); + refreshJob = null; + this.refreshJob = scheduler.scheduleWithFixedDelay(runnable, 0, refreshPeriod, TimeUnit.MINUTES); } } /** * Start the job to periodically update humidity and runtime date from the thermostat */ - @SuppressWarnings("null") private void startAutomaticLogRefresh() { + ScheduledFuture logRefreshJob = this.logRefreshJob; if (logRefreshJob == null || logRefreshJob.isCancelled()) { Runnable runnable = () -> { // Request humidity data from the thermostat if we are a CT80 - if (config.isCT80) { + if (this.isCT80) { // send an async call to the thermostat to get the humidity data connector.getAsyncThermostatData(HUMIDITY_RESOURCE); } - if (!config.disableLogs) { + if (!this.disableLogs) { // send an async call to the thermostat to get the runtime data connector.getAsyncThermostatData(RUNTIME_RESOURCE); } }; - int delay = ((config.logRefresh != null) ? config.logRefresh.intValue() : DEFAULT_LOG_REFRESH_PERIOD) * 60; - logRefreshJob = scheduler.scheduleWithFixedDelay(runnable, 30, delay, TimeUnit.SECONDS); + logRefreshJob = null; + this.logRefreshJob = scheduler.scheduleWithFixedDelay(runnable, 1, logRefreshPeriod, TimeUnit.MINUTES); } } - @SuppressWarnings("null") @Override public void dispose() { logger.debug("Disposing the RadioThermostat handler."); connector.removeEventListener(this); + ScheduledFuture refreshJob = this.refreshJob; if (refreshJob != null) { refreshJob.cancel(true); - refreshJob = null; + this.refreshJob = null; } + + ScheduledFuture logRefreshJob = this.logRefreshJob; if (logRefreshJob != null) { logRefreshJob.cancel(true); - logRefreshJob = null; + this.logRefreshJob = null; } } @@ -248,10 +278,10 @@ public void handleCommand(ChannelUID channelUID, Command command) { case SET_POINT: String cmdKey = null; if (rthermData.getThermostatData().getMode() == 1) { - cmdKey = "t_heat"; + cmdKey = this.setpointCmdKeyPrefix + "heat"; rthermData.getThermostatData().setHeatTarget(cmdInt); } else if (rthermData.getThermostatData().getMode() == 2) { - cmdKey = "t_cool"; + cmdKey = this.setpointCmdKeyPrefix + "cool"; rthermData.getThermostatData().setCoolTarget(cmdInt); } else { // don't do anything if we are not in heat or cool mode @@ -429,11 +459,11 @@ private void updateAllChannels() { * * @return list of state options for thermostat fan modes */ - private List getFanModeOptions(boolean isCT80) { + private List getFanModeOptions() { List fanModeOptions = new ArrayList<>(); fanModeOptions.add(new StateOption("0", "Auto")); - if (isCT80) { + if (this.isCT80) { fanModeOptions.add(new StateOption("1", "Auto/Circulate")); } fanModeOptions.add(new StateOption("2", "On")); 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 index ecdadebcec0ad..557d36c560bbe 100644 --- 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 @@ -58,6 +58,15 @@ Optional Flag to Disable the Retrieval of Run-time Data from the Thermostat false + + + Run in absolute or temporary setpoint mode + temporary + + + + +