]> git.basschouten.com Git - openhab-addons.git/commitdiff
[radiothermostat] Add message display channel and improve ThingActions (#14799)
authormlobstein <michael.lobstein@gmail.com>
Sat, 15 Apr 2023 19:20:20 +0000 (14:20 -0500)
committerGitHub <noreply@github.com>
Sat, 15 Apr 2023 19:20:20 +0000 (21:20 +0200)
* Add price message channel

Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
bundles/org.openhab.binding.radiothermostat/README.md
bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/RadioThermostatBindingConstants.java
bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/RadioThermostatThingActions.java
bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/communication/RadioThermostatConnector.java
bundles/org.openhab.binding.radiothermostat/src/main/java/org/openhab/binding/radiothermostat/internal/handler/RadioThermostatHandler.java
bundles/org.openhab.binding.radiothermostat/src/main/resources/OH-INF/i18n/radiothermostat.properties
bundles/org.openhab.binding.radiothermostat/src/main/resources/OH-INF/thing/thing-types.xml
bundles/org.openhab.binding.radiothermostat/src/main/resources/OH-INF/update/instructions.xml [new file with mode: 0644]

index dbd27203d824ef7c45a13cfc5595ed8600e65fb7..b7025c539892808aea656e9f12ce2070a86712eb 100644 (file)
@@ -88,6 +88,7 @@ The thermostat information that is retrieved is available as these channels:
 | 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
 
@@ -158,15 +159,16 @@ Number Therm_FanStatus          "Fan Status [MAP(radiotherm.map):%s_fstus]"
 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" }
@@ -228,5 +230,30 @@ 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":' + "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
 ```
index 409ea78f465f566e79f4945c74ff9e3bfa095cc6..8e043d4ade938eb45bfe99fa9c5ff023038682dd 100644 (file)
@@ -14,8 +14,6 @@ package org.openhab.binding.radiothermostat.internal;
 
 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;
@@ -41,6 +39,8 @@ public class RadioThermostatBindingConstants {
     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";
 
@@ -52,6 +52,7 @@ public class RadioThermostatBindingConstants {
     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");
@@ -76,12 +77,15 @@ public class RadioThermostatBindingConstants {
     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;
index 418f987eb21fb7c6d13a646c17f81fbf5036a81e..b7f26eb9fc29cfc34699a3da19c883b24d8f9432 100644 (file)
@@ -35,7 +35,7 @@ public class RadioThermostatThingActions implements ThingActions {
 
     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");
@@ -49,11 +49,30 @@ public class RadioThermostatThingActions implements ThingActions {
         }
     }
 
-    /** 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;
index 0e203c70c5f42ab7f4429430f51c9cd873ef6ad3..be1d016de5f3412ab972fdd973944dd9280c5eb7 100644 (file)
@@ -44,18 +44,18 @@ import org.slf4j.LoggerFactory;
 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;
     }
 
@@ -84,19 +84,17 @@ public class RadioThermostatConnector {
      * @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);
+                        }
+                    }
+                });
     }
 
     /**
@@ -124,30 +122,27 @@ public class RadioThermostatConnector {
             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;
     }
 
     /**
@@ -156,14 +151,7 @@ public class RadioThermostatConnector {
      * @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);
     }
 
     /**
index 7f06974d9c0e0f21581c789dd62d2e90337a10ee..ed8e5827f30e2870c7bec4333d7325665577539b 100644 (file)
@@ -14,7 +14,6 @@ package org.openhab.binding.radiothermostat.internal.handler;
 
 import static org.openhab.binding.radiothermostat.internal.RadioThermostatBindingConstants.*;
 
-import java.math.BigDecimal;
 import java.text.NumberFormat;
 import java.text.ParseException;
 import java.time.ZonedDateTime;
@@ -96,8 +95,8 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe
     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) {
@@ -119,7 +118,7 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe
         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;
@@ -198,17 +197,17 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe
             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;
                     }
                 }
 
@@ -307,6 +306,10 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe
         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) {
@@ -384,6 +387,13 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe
                         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());
             }
@@ -460,10 +470,8 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe
                 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) {
@@ -556,9 +564,11 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe
      */
     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);
+            }
+        });
     }
 
     /**
@@ -569,11 +579,11 @@ public class RadioThermostatHandler extends BaseThingHandler implements RadioThe
     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;
     }
index 2d563ede20a0a3bfa8133a051789f1e11d4bc91a..98aae24d6303645a6bf5c9e9a9fddc18684d5f4e 100644 (file)
@@ -267,6 +267,8 @@ channel-type.radiothermostat.hold.label = Hold
 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
@@ -311,3 +313,6 @@ offline.configuration-error-hostname = Thermostat hostname must be specified
 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
index 21b08d87fd8ecfa2d7271e7a401f7450efcf339d..a8f7d482e6b01d09c0c57ead0d181b1ef52fd150 100644 (file)
                        <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>
diff --git a/bundles/org.openhab.binding.radiothermostat/src/main/resources/OH-INF/update/instructions.xml b/bundles/org.openhab.binding.radiothermostat/src/main/resources/OH-INF/update/instructions.xml
new file mode 100644 (file)
index 0000000..a44c902
--- /dev/null
@@ -0,0 +1,14 @@
+<?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>